feat(db): persist editability classification + user override per paragraph - #205
Conversation
…graph Stores the machine's editability `classification` (verdict + confidence + evidence) and the human's `editability_override` side by side in two nullable JSONB columns (migration 026), never merged (ADR-022 D2). Each write touches exactly one column, so re-classification rewrites only the machine field and can never silently undo a human override — the never-merged contract is enforced physically, not by convention. Effective value = override ?? classification.editability, surfaced on `SpecNode.meta.editability` via `getSpecTree` (mirroring the #56 conflict / #131 sourceFacts omit-when-empty pattern), so MCP/REST tree readers inherit it for free. The machine's why-chain stays readable even under an override (the O-15 machine-vs-human badge). The DB-boundary schemas are CLOSED (.strict()): the payloads are our own engine output, not captured external data, so malformed input is rejected as engine drift rather than preserved. A corrupt row is a loud DatabaseError at the read boundary, never a silent drop. DB write-path + read-surface only; HTTP endpoints and reclassify orchestration are O-9 (#136). Closes #134 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Caution Review failedAn error occurred during the review process. Please try again later. 📝 WalkthroughWalkthroughAdds paragraph editability persistence per Issue ChangesParagraph Editability Persistence
Sequence Diagram(s)sequenceDiagram
participant ClassificationEngine
participant storeClassifications
participant ParagraphsTable
participant getSpecTree
participant deriveEditability
rect rgba(70, 130, 180, 0.5)
note over ClassificationEngine, ParagraphsTable: Write path
ClassificationEngine->>storeClassifications: specId, ClassifyResult
storeClassifications->>ParagraphsTable: BEGIN transaction
storeClassifications->>ParagraphsTable: UPDATE paragraphs SET classification WHERE id AND spec_id
storeClassifications->>ParagraphsTable: COMMIT
end
rect rgba(60, 179, 113, 0.5)
note over ParagraphsTable, deriveEditability: Read path
getSpecTree->>ParagraphsTable: SELECT paragraphs with classification and editability_override
ParagraphsTable-->>getSpecTree: ParagraphTreeRow[]
getSpecTree->>deriveEditability: classification, editability_override
deriveEditability->>deriveEditability: resolve override ?? classification.editability
deriveEditability-->>getSpecTree: SpecNodeEditability or undefined
getSpecTree-->>ClassificationEngine: SpecNode with meta.editability
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/specs/2026-06-16-issue-134-design.md`:
- Around line 128-134: The test name 'override survives reclassify' in the
editability.integration.test.ts file is too brief and lacks context for
documentation. Rename this test to be more descriptive and explicitly reference
the editability context and the machine verdict change it verifies, such as
'editability: override survives reclassification with new machine verdict'. This
expanded name will make it immediately clear that the test verifies the override
persists when a different classification is applied, improving documentation
clarity without changing the test logic or assertions.
In `@src/db/queries/specs.ts`:
- Around line 134-139: The issue is that the early return at line 134 happens
before the override validation (OverrideSchema.parse), which allows malformed
override payloads on unclassified rows to be silently ignored. Move the override
validation that extracts editability to occur BEFORE the null/undefined check on
classification, so that the override payload is validated regardless of the
classification value. Additionally, add a regression test case that verifies a
corrupt override payload combined with a null classification properly fails
validation instead of being silently ignored.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6a17b21a-4ff1-4068-9442-39e504ffbcbc
📒 Files selected for processing (12)
docs/superpowers/specs/2026-06-16-issue-134-design.mdsrc/ast/index.tssrc/ast/schemas.tssrc/ast/types.tssrc/conventions/types.tssrc/db/index.tssrc/db/migrations/026_paragraph_editability.tssrc/db/queries/editability.integration.test.tssrc/db/queries/editability.test.tssrc/db/queries/editability.tssrc/db/queries/revisions.tssrc/db/queries/specs.ts
deriveEditability returned undefined for unclassified rows before parsing editability_override, so a corrupt override payload on an unclassified row was silently dropped instead of failing loud at the DB boundary. Reorder so the override is validated first — fulfilling the docstring's promise that both JSONB columns are schema-checked. Pin with a buildNodeTree unit test for "corrupt override + null classification" and rename the override-survives-reclassify integration test to the house-style 'editability: ...' form. Addresses CodeRabbit review on #205. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Why
ADR-022 D2: a paragraph's machine
classification(verdict + confidence + evidence) and a human'soverridemust be stored side by side and never merged. Effective value =override ?? classification.editability. Re-classification must rewrite only the machine field — the machine never silently undoes a human. This is the Wave-2 (O-7) DB substrate the onboarding/editability UI and reclassify orchestration build on.What
paragraphs—classification+editability_override. NULL = "not yet classified" / "no override". The never-merged contract is enforced physically: every write touches exactly one column.src/db/queries/editability.ts):storeClassifications(specId, ClassifyResult)— one transaction;UPDATE … SET classification … WHERE id = nodeId AND spec_id = specId. Spec-scoped (a cross-spec nodeId writes nothing) and reclassify-safe by construction (never touches the override).setEditabilityOverride(paragraphId, editability)/clearEditabilityOverride(paragraphId).getSpecTreeonly: surfaces effectivemeta.editability = { value, confidence, evidence, override? }, omitted entirely when unclassified (mirrors the feat(parser): surface DOCX inference conflicts via get_paragraph MCP tool #56 conflict / feat(db): persist paragraphs.source_facts JSONB + AST meta round-trip #131 sourceFacts omit-when-empty pattern). MCP/REST tree readers inherit it for free. Revision snapshots carry it too..strict()) Zod schemas at the DB boundary — these payloads are our own engine output, not captured external data, so malformed input is rejected as engine drift. A corrupt row is a loudDatabaseErrorat the read boundary, never a silent drop. (Deliberate, code-commented deviation from the parent design's "all JSONB open" rule, which exists to preserve unknown external clues.)ClassificationEvidenceSchema/SpecNodeEditabilityin the AST foundational layer;conventions/types.tsnow re-uses the single ASTClassificationEvidencetype rather than duplicating it.DB write-path + read-surface only. Out of scope: HTTP endpoints + reclassify orchestration (O-9 / #136).
Testing
pnpm test— 948 passed) — closed-schema rejections (confidence out of range, empty evidence, unknown keys/values).pnpm test:integration— 421 passed) against real PostgreSQL, incl. the decisiveoverride survives reclassify, round-trip, clear-restores-machine, spec-scope, and corrupt-row-rejects tests.pnpm lintclean (eslint + tsc + prettier).🤖 Co-authored by Claude Opus 4.8. Closes #134.
Summary by CodeRabbit
New Features
Database
Documentation