/speckit-plan output for 004-product-knowledge: technical context and constitution gate check (all PASS), Phase 0 research (6 decisions: new-row-per-version instead of in-place overwrite to satisfy history retention, conditional-update-then-insert concurrency reusing 003-ticketing's optimistic-locking pattern, structured (non-semantic) filtered retrieval per doc 11 gap B1, known-issue lookup by error code, creating the ai-support module group for the first time with only its knowledge submodule populated, and admin auth consistent with prior features), Phase 1 data model (KnowledgeEntry/ErrorCode/ KnownIssue/Runbook, refining doc 06's conceptual schema with an explicit version-history mechanism), the admin CRUD + retrieval contract, and a 6-scenario quickstart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.2 KiB
6.2 KiB
Phase 0 Research: Product Knowledge Management & Retrieval
Decision: Versioning mechanism — new row per version, not in-place overwrite
- Decision:
docs/06-database-schema.md'sKnowledgeEntry/Runbookmodels are explicitly "conceptual/pseudo-Prisma... refine field types... during Phase 1 modeling" — their flatversion Intfield alone doesn't satisfy this feature's FR-004/FR-009 ("prior versions MUST remain retrievable"), since a plain in-placeUPDATEoverwrites history. This feature refines the schema: each edit inserts a new row sharing the same logical identifier (codeforKnowledgeEntry,key+productIdforRunbook) withversionincremented, and exactly one row per logical identifier hasisCurrentVersion: trueat a time. The unique constraint moves from a barecode/keyto(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
KnowledgeEntryVersionhistory 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'sexpectedVersionmust match the current row) — zero rows affected means a concurrent edit already won, and this edit is rejected with409 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.versionhandling — 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
expectedVersioncheck) — 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 byvalidationStatus = 'validated'first, then byeffectiveDate 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.errorCodeIdis a nullable FK toErrorCode; lookup is a directWHERE errorCodeId = ?query (via theErrorCode's own id, resolved from itscodestring 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 otherai-supportsubmodule (agents/sessions/diagnosis/troubleshooting/tools/tool-execution/verification/escalation) is created — those remain nonexistent until the Phase 4 feature that needs them, matching howidentity/authand most oforchestration/platformremain untouched placeholder stubs from the original scaffold rather than being pre-built speculatively. - Rationale: Doc 07 explicitly places
knowledgeinside theai-supportgroup — this is the documented, correct location, not an open design choice. - Alternatives considered: Placing it under
catalog(since it's product-scoped content, similar tocatalog/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.authenticatedecorator — 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.