Skip to content

feat(domain): fold contract v0.3 — context envelope, operation outputs, lineage - #14

Merged
obvious-autobuild[bot] merged 2 commits into
masterfrom
feat/contract-v03-fold
Sep 17, 2026
Merged

obvious-autobuild[bot] merged 2 commits into
masterfrom
feat/contract-v03-fold

Conversation

@obvious-autobuild

Copy link
Copy Markdown
Contributor

Acceptance criteria

  • v0.3 delta published FIRST as the governance artifact (art_bKoYFzs7) — ContextEnvelope + operation outputs (art_lyBemdV9 §2–4) and lineage additions (art_rBKvvzIa §3–4) proposed, not landed by side door.
  • ContextEnvelope schema: verbatim utterance, actor, household access root, provenance-tagged current child / view / timezone, attachments, prior references with visibleToActor.
  • Closed tagged operation-output union (fact-candidates | question-intent | write-proposal | unresolved-reference) + Answered + inert-until-Authorization execute input; question-intent carries no write payload.
  • Lineage: append-only ExtractionAttempt record (triggeredBy, inputHash, outcome/failure on the v0.2 pipeline taxonomy), Event.producedBy, deriveExtractionStatus (no attempt → pending; latest attempt → structured/failed).
  • Entry.attachments (optionalKey, shared Attachment shape); legacy photoId scalar kept until readers migrate.
  • CaptureId branded at the type level; Convex adapter still maps it to v.string() (brand-safe, not v.id).
  • Wire pins hold: unix-ms dates (no Date objects), no explicit nulls or undefined on the wire, no underscore-prefixed stored fields, outcome literal discrimination instead of _tag.
  • All pre-existing tests stay green + new v0.3 tests in packages/domain; local verification green on the exact tested SHA.

Why

Three contract additions have accumulated as separate proposals: the context envelope (what the app already knows when a capture happens), the operation-output contracts (extraction may not guess or write without authorization), and extraction lineage (reruns must supersede, not mutate, and stay auditable). None of them were canonical, so downstream slots were about to hardcode three different partial shapes. This PR folds the approved rule-2 proposals into the canonical contract as v0.3, in packages/domain — the single source of truth — so every consumer derives from one set of schemas.

What

Two commits behind the published delta (art_bKoYFzs7):

  1. c5886f0 — CaptureId branded (Schema.brand, type-level only), shared Attachment shape extracted to its own module (one shape, two consumers), Event.producedBy (optionalKey: attemptId + extractorVersion + schemaVersion).
  2. 233a91d — contextEnvelope.ts, operations.ts, lineage.ts modules; Entry.attachments; barrel wiring; 25 new tests + one widened assertion in the existing roundtrip test.

Adaptations where the proposals met the merged scaffold's reality (all recorded in the delta): outcome literal discrimination instead of _tag (Convex forbids _-prefixed stored fields); the monotonic attempt counter renamed AttemptNumber because ExtractionAttempt now names the lineage record; Entry.extractionStatus stays as the materialized read-model of deriveExtractionStatus (no destructive removal); unix-ms Schema.Number wire dates instead of DateFromMillis.

Intentionally NOT here: no Convex table or function changes (lineage table lands with the extraction pipeline work), no LLM call, no mobile UI. The contract moves; consumers follow.

How to Review

  • Start with the delta document (art_bKoYFzs7) — this PR is its direct implementation; anything un-adapted matches it literally.
  • packages/domain/src/contextEnvelope.ts — provenance vocabulary and the required capturedAtTimezone decision.
  • packages/domain/src/operations.ts — closed union: check that question-intent structurally cannot carry a write payload, and that Authorization is the only mutating input.
  • packages/domain/src/lineage.ts — attempt record + the status derivation pure function.
  • The test file (contract-v03.test.ts) encodes the wire pins as executable assertions.

Note on process: the shared repo sandbox working tree was carrying another worker's in-flight thin-path changes; this PR's diff contains only the two fold commits above (verified commit-by-commit; implemented and verified in an isolated worktree at the pushed SHAs).

Test Evidence

Local verification in an isolated worktree at 233a91d (exact pushed HEAD):

  • pnpm turbo run typecheck build test — 9/9 tasks green
  • bun test packages/domain — 36/36 (11 pre-existing + 25 new)
  • bun test ./security — 17/17 fail-closed access cases
  • evaluation/bun src/run.ts — 6/6 fixtures; broken-adapter negative control fails loudly as required

CI runs the same gates on this SHA.

Human author: Gilbert Polanco (gilbertpolanco42@gmail.com)

🔗 Obvious Project · 🧵 Obvious Thread

ObviousApp and others added 2 commits September 17, 2026 18:16
…edBy

Contract v0.3 fold (art_bKoYFzs7), part 1 of 3:
- CaptureId is branded at the type level (Schema.brand); wire format and
  Convex mapping unchanged (adapter maps brands to v.string()).
- The monotonic attempt counter renames ExtractionAttempt -> AttemptNumber;
  ExtractionAttempt now names the lineage record (landing next).
- Event carries optionalKey producedBy { attemptId, extractorVersion,
  schemaVersion } per the operator-surface lineage proposal.
- New shared Attachment shape (one shape, two consumers: Entry.attachments
  and ContextEnvelope.attachments).

Delta: art_bKoYFzs7. Sources: art_lyBemdV9 SS2-4, art_rBKvvzIa SS3-4.

Human author: Gilbert Polanco (gilbertpolanco42@gmail.com)
Co-authored-by: Gilbert Polanco <gilbertpolanco42@gmail.com>
…neage

Contract v0.3 fold (art_bKoYFzs7), part 2 of 3:
- contextEnvelope.ts: the schema-defined record traveling with every
  captured utterance — verbatim utterance, actor, household root, explicit
  child/view/timezone context, attachments, prior references. Every
  contextual value carries provenance (user-asserted | app-known |
  inferred); missing context stays absent, never null.
- operations.ts: closed tagged extraction-outcome union (fact-candidates |
  question-intent | write-proposal | unresolved-reference) plus Answered
  and the inert-until-authorized Authorization execute input; tagged via an
  explicit `outcome` literal (Convex forbids _-prefixed stored fields).
- lineage.ts: append-only ExtractionAttempt record (triggeredBy, inputHash,
  outcome/failure with the v0.2 pipeline taxonomy) + deriveExtractionStatus
  — attempt history is the source of truth; Entry.extractionStatus is its
  materialized read-model.
- entry.ts: optionalKey attachments (shared Attachment shape) alongside
  the legacy photoId scalar.
- barrel exports for all of the above; ExtractionAttempt now names the
  lineage record (the counter is AttemptNumber).
- contract-v03.test.ts: 25 new tests pinning envelope provenance, closed
  union discrimination and exhaustivity, derived status, producedBy
  round-trip, brand-safe captureId mapping (v.string, not v.id), and
  attachment shape sharing. roundtrip.test.ts widens one branded
  assertion.

Verified locally (worktree at this commit): turbo typecheck+build+test
9/9 tasks, 36/36 domain tests, 17/17 security cases, 6/6 evaluation
fixtures, negative control fails loudly.

Delta: art_bKoYFzs7. Sources: art_lyBemdV9 SS2-4, art_rBKvvzIa SS3-4.

Human author: Gilbert Polanco (gilbertpolanco42@gmail.com)
Co-authored-by: Gilbert Polanco <gilbertpolanco42@gmail.com>
@obvious-autobuild
obvious-autobuild Bot marked this pull request as ready for review September 17, 2026 18:25
@obvious-autobuild
obvious-autobuild Bot merged commit 00581f5 into master Sep 17, 2026
1 check passed
@obvious-autobuild

Copy link
Copy Markdown
Contributor Author

Evidence receipt (workflow rule)

PR:               https://github.com/OCPdev25/obv-hackaton/pull/14
Tested head SHA:  233a91dd1d39faed9e3af848c3fe7f3480b19182
Review result:    pass — full-diff review against the stated acceptance criteria; implemented and verified in an isolated worktree at the pushed SHA (worker self-review, no required human approvals per .obvious/config.yml policy), 2026-09-17
Checks:           all green — "Typecheck, test, build" pass (run 35258755766) on the same SHA
Merge commit:     00581f5962a494aa597a004679c4f66431f8ec97
Post-merge smoke: on 00581f5 — pnpm turbo typecheck/test/build 9/9 tasks; 41/41 tests across 3 domain/app test files; bun test ./security 17/17 fail-closed cases; evaluation harness 6/6 fixtures
Unlocked tasks:   backend four-table port (PR #13 lane) now consumes the v0.3 contracts; extraction pipeline work can append ExtractionAttempt lineage; context-envelope consumers (embedded agent experience) unblocked

Delta document (published before implementation, governance rule-2): art_bKoYFzs7.

@obvious-autobuild

Copy link
Copy Markdown
Contributor Author

Independent post-merge verification receipt (todo_HF7TF08w)

Verdict: pass-with-notes. Independent review thread (reviewer for todo_HF7TF08w), 2026-09-17. This review ran against both the PR head and the merge commit; the pre-merge pass landed post-merge — timing deviation is recorded by the coordinator.

PR:               https://github.com/OCPdev25/obv-hackaton/pull/14
Tested head SHA:  233a91dd1d39faed9e3af848c3fe7f3480b19182 (PR head) and 00581f5962a494aa597a004679c4f66431f8ec97 (merge commit — post-merge verification)
Review result:    pass-with-notes — independent full-diff review, isolated detached worktrees (shared-sandbox dirty tree NOT trusted), 2026-09-17
Checks:           "Typecheck, test, build" pass on exact PR head (run 35258755766, head_sha verified via API)
Merge commit:     00581f5962a494aa597a004679c4f66431f8ec97 (squash)
Post-merge smoke: at 00581f5 — pnpm turbo run typecheck test build --force → 9/9 tasks, 0 cached, 4.2s; domain tests 41/41 across 3 files (11 pre-existing + 25 v0.3 + 5 from PR #11 research tests); bun test ./security → 17/17; evaluation harness → 6/6 fixtures; broken-adapter negative control → fails loudly as required (--expect-failure honored)
Unlocked tasks:   contract consumers may build against v0.3 shapes (context capture, embedded agent, lineage consumers)

Criterion results: (1) merge diff 00581f5^..00581f5 = exactly the 10 fold files under packages/domain — no thin-path strays in either direction (no contracts.ts, no backend/convex, no schema indexes; entry.ts/roundtrip.test.ts hunks are fold-only); (2) Event.authorId remains OPEN — untouched and unclaimed; (3) no Confect — zero dependency changes, effect + sibling imports only; (4) all 7 delta claims (art_bKoYFzs7) have code + passing tests; adaptations A1–A11 honored.

Notes (documentation-level, no action required): one compile-only widened assertion in roundtrip.test.ts (as string — disclosed consequence of the branded CaptureId); ProducedBy is defined in event.ts rather than lineage.ts as the delta sketched (documented DAG-acyclicity reason, same shape); AttemptTrigger is a named export rather than an inline literal (same vocabulary).

obvious-autobuild Bot pushed a commit that referenced this pull request Sep 17, 2026
PR #14 branded CaptureId (NonEmptyString + brand). Compare decoded values
against a branded captureId instead of a plain string literal.

Co-authored-by: Gilbert Polanco <gilbertpolanco42@gmail.com>
obvious-autobuild Bot added a commit that referenced this pull request Sep 17, 2026
…tract (#13)

Ports the retired thin-path functions (PR #5) onto backend/convex with validators derived from the packages/domain contracts via the tested Effect->Convex adapter:

- contracts: CreateEntryInput gains authorId + optional captureId (capture session id, required for PR #5's idempotency semantic); new CreateEntryOutput, CreateChildInput/Output, minimal CreateHouseholdInput/Output; EntryFields gains optional captureId
- schema: entries gains by_capture (idempotency lookup) and by_child_createdAt (chronological timeline) indexes
- functions: households:create, children:create (trim + non-empty name, household existence check), entries:createEntry (raw-first capture, extractionStatus pending, idempotent on captureId - original capture wins, retried payload changes absorbed, same entryId returned), timeline:list (by_child_createdAt asc, optional limit, rows decode through EntrySchema so output is validated contract shape)
- convex codegen (_generated) committed per repo policy (CI has no deployment)
- deploy/port-evidence.md: synthetic smoke on reliable-panther-823 - idempotency, contract-shaped timeline, fail-closed negatives - re-run at the rebased head after the v0.3 fold (PR #14) merged mid-flight

Divergence from the thin path: createEntry no longer accepts inline events - the canonical model captures raw-first and events belong to the extractor via the AppendEventsInput contract.

Review: pass-with-notes at c5dc6fb (independent reviewer, th_9CCFeh6U); CI run 35259935719 SUCCESS on c5dc6fb.

Co-authored-by: Gilbert Polanco <gilbertpolanco42@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants