87 lines
6.2 KiB
Markdown
87 lines
6.2 KiB
Markdown
# Phase 0 Research: Product Knowledge Management & Retrieval
|
|||
|
|
|
||
|
|
## Decision: Versioning mechanism — new row per version, not in-place overwrite
|
||
|
|
|
||
|
|
- **Decision**: `docs/06-database-schema.md`'s `KnowledgeEntry`/`Runbook` models are explicitly
|
||
|
|
"conceptual/pseudo-Prisma... refine field types... during Phase 1 modeling" — their flat
|
||
|
|
`version Int` field alone doesn't satisfy this feature's FR-004/FR-009 ("prior versions MUST
|
||
|
|
remain retrievable"), since a plain in-place `UPDATE` overwrites history. This feature refines
|
||
|
|
the schema: each edit inserts a **new row** sharing the same logical identifier (`code` for
|
||
|
|
`KnowledgeEntry`, `key`+`productId` for `Runbook`) with `version` incremented, and exactly one
|
||
|
|
row per logical identifier has `isCurrentVersion: true` at a time. The unique constraint moves
|
||
|
|
from a bare `code`/`key` to `(code, version)` / `(key, productId, version)`; a partial-unique-
|
||
|
|
style application check (see next decision) keeps only one current version.
|
||
|
|
- **Rationale**: This is the standard "immutable version history" pattern and directly satisfies
|
||
|
|
"prior versions remain retrievable by their own identity" (FR-004) without a separate audit
|
||
|
|
table — the versions themselves ARE the history, consistent with Constitution Principle VI.
|
||
|
|
- **Alternatives considered**: A separate `KnowledgeEntryVersion` history table with the main row
|
||
|
|
only ever holding "current" — rejected as more schema surface for the same guarantee; querying
|
||
|
|
"give me version 3 of KB-DQ-102" is equally simple either way, and one-table-per-entity-type
|
||
|
|
keeps retrieval queries (which only ever care about the current version) simpler.
|
||
|
|
|
||
|
|
## Decision: Concurrency on edit — conditional update + insert, same class as 003-ticketing
|
||
|
|
|
||
|
|
- **Decision**: Creating a new version is two steps inside one transaction: (1)
|
||
|
|
`UPDATE ... WHERE code = ? AND version = ? AND isCurrentVersion = true SET isCurrentVersion =
|
||
|
|
false` (the caller's `expectedVersion` must match the current row) — zero rows affected means a
|
||
|
|
concurrent edit already won, and this edit is rejected with `409 CONFLICT`; (2) only if step 1
|
||
|
|
affected exactly one row, insert the new current-version row.
|
||
|
|
- **Rationale**: Directly reuses the optimistic-concurrency pattern already established in
|
||
|
|
003-ticketing's `Ticket.version` handling — same shape of problem (two admins editing the same
|
||
|
|
entry), same solution, no new concurrency-control concept introduced into the codebase.
|
||
|
|
- **Alternatives considered**: Last-write-wins (no `expectedVersion` check) — rejected, would let
|
||
|
|
one admin's edit silently clobber another's without either of them knowing, which is exactly
|
||
|
|
what Constitution Principle VII's concurrency requirement exists to prevent.
|
||
|
|
|
||
|
|
## Decision: Retrieval — structured filtering, no vector/embedding search
|
||
|
|
|
||
|
|
- **Decision**: A retrieval query is `WHERE productId = ? AND isCurrentVersion = true AND status
|
||
|
|
= 'published' AND (effectiveDate IS NULL OR effectiveDate <= now()) AND (feature filter if
|
||
|
|
given) AND (categoryScope filter if given)`, ordered by `validationStatus = 'validated'` first,
|
||
|
|
then by `effectiveDate DESC` (most recently published first) as a simple, defensible tiebreak.
|
||
|
|
- **Rationale**: Per spec.md's Assumptions and doc 11 §B1, full semantic retrieval is explicitly
|
||
|
|
a future decision (embedding model, chunking, re-ranking) — this feature's job is a correct,
|
||
|
|
real, *filtered* retrieval contract that a semantic layer can be added in front of later
|
||
|
|
without changing what "correct" means (doc 11 §B1's "filters apply before the vector search"
|
||
|
|
requirement is satisfied by construction, since there's no vector search yet to apply them
|
||
|
|
before).
|
||
|
|
- **Alternatives considered**: Postgres full-text search (`tsvector`/`tsquery`) on
|
||
|
|
problem/symptoms text — considered as a nearer-term relevance improvement, but deferred: it
|
||
|
|
would still not be "the RAG layer" doc 03 describes, adds index/query complexity beyond what
|
||
|
|
this phase's requirements (FR-011 through FR-013) actually ask for, and can be added later as
|
||
|
|
a ranking refinement without a breaking contract change.
|
||
|
|
|
||
|
|
## Decision: Known issue lookup by error code
|
||
|
|
|
||
|
|
- **Decision**: `KnownIssue.errorCodeId` is a nullable FK to `ErrorCode`; lookup is a direct
|
||
|
|
`WHERE errorCodeId = ?` query (via the `ErrorCode`'s own id, resolved from its `code` string
|
||
|
|
first if the caller only has the string).
|
||
|
|
- **Rationale**: Matches doc 06's shape exactly (`KnownIssue.errorCodeId String?`) and FR-007's
|
||
|
|
"retrieve a known issue directly by its error code" — a simple indexed FK lookup, no special
|
||
|
|
design needed.
|
||
|
|
|
||
|
|
## Decision: Module placement — new `ai-support/knowledge` module group
|
||
|
|
|
||
|
|
- **Decision**: Create `src/modules/ai-support/knowledge/` now, following the standard module
|
||
|
|
shape (controller/routes/schema/repository/service/types/mapper/constants/index.ts) used by
|
||
|
|
every other module in this codebase. No other `ai-support` submodule
|
||
|
|
(agents/sessions/diagnosis/troubleshooting/tools/tool-execution/verification/escalation) is
|
||
|
|
created — those remain nonexistent until the Phase 4 feature that needs them, matching how
|
||
|
|
`identity/auth` and most of `orchestration`/`platform` remain untouched placeholder stubs from
|
||
|
|
the original scaffold rather than being pre-built speculatively.
|
||
|
|
- **Rationale**: Doc 07 explicitly places `knowledge` inside the `ai-support` group — this is
|
||
|
|
the documented, correct location, not an open design choice.
|
||
|
|
- **Alternatives considered**: Placing it under `catalog` (since it's product-scoped content,
|
||
|
|
similar to `catalog/products`/`catalog/categories`) — rejected; doc 07 already answers this
|
||
|
|
question, and following it keeps the module layout matching the architecture doc exactly.
|
||
|
|
|
||
|
|
## Decision: Admin endpoint authentication
|
||
|
|
|
||
|
|
- **Decision**: All admin CRUD endpoints (create/publish/unpublish/version-edit for knowledge
|
||
|
|
entries and runbooks; create for error codes/known issues) are gated by the existing
|
||
|
|
`fastify.authenticate` decorator — same known-limitation pattern as 002/003's admin routes (it
|
||
|
|
doesn't perform real JWT verification yet).
|
||
|
|
- **Rationale**: Consistency with every other admin surface built so far; introducing a different
|
||
|
|
auth mechanism just for this feature would be inconsistent without a reason to be.
|
||
|
|
- **Alternatives considered**: None — this follows established precedent directly.
|