feat(domain): fold contract v0.4 — retraction, CareProfile, raw-access-by-grant - #34
obvious-autobuild[bot] wants to merge 1 commit into
Conversation
…s-by-grant Contract v0.4 fold (Gil settlements 1-3, 2026-09-17), following the v0.3 fold precedent (00581f5): - retraction.ts: append-only RetractionReceipt (the household-visible receipt: entry, retractedBy, retractedAt, optional detail) — the entry row is retained at the storage layer; no "unretract" mutation exists. Entry gains retractedAt as its materialized read-model (the lineage pattern v0.3 established for extractionStatus). RetractionFilter + excludesRetracted are the query-path exclusion hook: ABSENT filter or household scope EXCLUDE retracted entries (the settled privacy posture is the default, not an opt-in); operator scope is the only include path. ListEntriesByChildInput embeds the hook as the pattern for household-facing query inputs (agent queries slots 15/17/23/24, timeline reads). - careProfile.ts: first-class CareProfile area — standing care context per child (allergy | nap-schedule | emergency), scoped to a child, household-visible per existing membership rules (audience never stored). Reuses the family-knowledge structures: source attribution (statedBy/recordedBy, KnowledgeSourceType/Span, sourceQuote), append-only supersession (supersedes/status/validFrom/validUntil), and the confidence partition. Caregiver-confirmed only binds at the SCHEMA level: CareProfileProvenance omits model-extracted (a model-proposed item cannot even encode) and the invariants forbid the extraction model as recorder — no automatic inference of medical facts. The area union is closed to the settled three. - rawAccess.ts: RawAccessGrant record (grantee, grantedBy, granted / revoked + revokedAt) with default-off semantics — a cross-household viewer sees STRUCTURED ENTRIES ONLY; the absence of an active grant IS the denial (rawTranscriptVisible is the rule slot 20 binds to). StructuredEntryView is the entry projection without rawTranscript, derived from EntryFields and stripping the verbatim field by construction (decode through the view schema). The grant is a THIRD dimension: it carries no visibility, no audience, and no retraction fields — publication state, audience, retraction, and raw-access never fuse into one enum. - backend/convex/schema.ts derives the three new tables (retractions, care_profiles, raw_access_grants) via the tested adapter — no hand-written validators (PR #13 precedent). - contract-v04.test.ts: 33 tests — round-trips, receipt state on Entry, filter semantics, closed unions, invariants (supersession lifecycle, confidence partition, source attribution, grant revocation pairing and ordering), default-off matrix, projection-leak proof, adapter mapping for every new table, and JSON Schema (draft 2020-12) derivation for every new type. - .obvious/obvious.md: layout + contract references updated (v0.4 areas, security-suite count unpinned to current reality). Verified locally (worktree at this commit): turbo typecheck 10/10, test 7/7, build green; domain suite 94/94 (33 new); security 29/0; evaluation corpus 6/6 + negative control exits 0; month-history journeys 28/28. Human author: Gilbert Polanco (gilbertpolanco42@gmail.com) Co-authored-by: Gilbert Polanco <gilbertpolanco42@gmail.com>
|
CI failure diagnosis — pre-existing on master, fix inbound via #35 (not this diff) The failing Verified independently of this PR: the same typecheck fails on pristine master Fix ownership: #35 (same lane that owns capture-recovery) carries the domain adaptation with green CI. To avoid duplicating the fix and generating conflict churn for the sweep owner, this PR carries no capture-recovery changes. Plan: once #35 merges, rebase this branch onto the new master, re-run the full local verification table at the new head, refresh the verification-manifest (testedHeadSha + evidence), and let CI re-run green. Domain gates re-verified at the current rebased head |
Human author: Gilbert Polanco (gilbertpolanco42@gmail.com)
Acceptance criteria
.obvious/obvious.mdupdated.Why
Gil's three settled product decisions (2026-09-17, pinned in project context) are product law but not yet schema: nothing in the contract distinguishes a retracted entry from a published one, standing care context (allergies, nap schedules, emergency info) has no first-class home for the handoff/digest slots to read, and the raw-transcript boundary for cross-household viewers lives in no record a query can check. Until the contract carries them, slots 20/31 and the agent-query slots would each re-invent these rules as convention. This fold follows the v0.3 precedent (PR #14, merge
00581f5): additive schema areas inpackages/domain, backend tables derived, fold pinned by tests.What
retraction.ts(new) —RetractionReceiptis the append-only, household-visible receipt (entryId,retractedBy,retractedAt, optionaldetail); there is no unretract mutation — reversal is a new contract decision.Entry.retractedAtis its materialized read-model (the same lineage pattern v0.3 established forextractionStatus).RetractionFilter+excludesRetractedare the query-path hook: ABSENT filter orhouseholdscope excludes retracted entries — the privacy posture is the default, not an opt-in;operatorscope is the only include path.ListEntriesByChildInputembeds the hook as the pattern for the agent-query inputs (slots 15/17/23/24) and timeline reads.careProfile.ts(new) —CareProfileitems reuse the family-knowledge structures (source attribution withstatedBy/recordedBy/sourceQuote, append-only supersession, confidence partition) with the provenance union NARROWED tocaregiver-stated | caregiver-confirmed:model-extractedcannot even encode, and the invariants forbid the extraction model as recorder. The area union is closed to the settled three — no diagnosis/medical area exists. Audience is never stored on the item.rawAccess.ts(new) —RawAccessGrant(grantee, grantedBy,granted/revoked+revokedAt) is a THIRD dimension: it carries no visibility, no audience, and no retraction fields. Default-off is a rule, not a convention:rawTranscriptVisibledenies a cross-household viewer unless an active grant exists (absence IS the denial), andStructuredEntryView— derived fromEntryFields— is the projection that provably dropsrawTranscript(decode through the view schema strips it as an excess key; pinned by a leak-proof test).backend/convex/schema.ts— derives the three new tables (retractions,care_profiles,raw_access_grants) through the tested adapter; no hand-written validators..obvious/obvious.md— layout + contract references brought to v0.4 reality (security-suite count was stale since PR feat(backend): family-knowledge Convex functions — create, supersede, bounded reads #31 added cases).Trade-offs / rejected alternatives: an in-place
visibility: "retracted"enum value was rejected — it would fuse retraction into publication state (criterion 4) and mutate rather than append. A single-profile-per-child document was rejected — per-item append-only records keep provenance and supersession honest, matching howKnowledgealready works.How to Review
Key files:
packages/domain/src/retraction.ts,careProfile.ts,rawAccess.ts(the whole fold),entry.ts(+retractedAt),contracts.ts(+filter hook),backend/convex/convex/schema.ts(derived tables). Tests:packages/domain/test/contract-v04.test.ts(33 tests). Intentionally excluded: no backend function bodies / no UI — those are the slot owners' work (20, 31, 15/17/23/24); noKnowledgechanges (its taxonomy stays as merged in PR #16); no entry-row deletion or soft-delete flag — retention is the storage layer's job and the receipt is the only record.Verification-manifest
{ "manifestVersion": 1, "pr": 34, "prUrl": "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/OCPdev25/obv-hackaton/pull/34", "testedHeadSha": "ccc1f0cce1b9356af21b27892d3f14b3899a47d4", "classification": "backend-only", "classificationJustification": "packages/domain contract fold + derived backend schema; no rendered UI changed — visual evidence not applicable", "review": { "result": "pass", "reviewer": "independent-review", "reviewedHeadSha": "PENDING", "date": "2026-09-17", "url": "PENDING" }, "checks": [{ "name": "ci", "status": "green", "headSha": "PENDING", "url": "PENDING" }], "evidence": [ { "kind": "test-run", "name": "domain round-trip + v0.4 fold suite (94 tests, 33 new)", "headSha": "ccc1f0c", "result": "pass", "url": "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/OCPdev25/obv-hackaton/pull/34" }, { "kind": "suite-run", "name": "pnpm typecheck + test + build (turbo, uncached)", "headSha": "ccc1f0c", "result": "pass", "url": "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/OCPdev25/obv-hackaton/pull/34" } ], "suites": { "security": { "ran": true, "result": "pass", "headSha": "ccc1f0c", "url": "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/OCPdev25/obv-hackaton/pull/34" }, "evaluation": { "ran": true, "result": "pass", "headSha": "ccc1f0c", "negativeControl": "fail-as-expected", "url": "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/OCPdev25/obv-hackaton/pull/34" } }, "notes": "month-history journeys 28/28 at ccc1f0c; local verification run in the assigned repo sandbox worktree" }🔗 Obvious Project · 🧵 Obvious Thread