Skip to content

feat(month-history): deterministic month-history view model with executable journeys + RN prototype - #27

Merged
obvious-autobuild[bot] merged 2 commits into
masterfrom
feat/month-history-journeys
Sep 17, 2026
Merged

obvious-autobuild[bot] merged 2 commits into
masterfrom
feat/month-history-journeys

Conversation

@obvious-autobuild

@obvious-autobuild obvious-autobuild Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Acceptance criteria

  1. Care-day placement with capture attribution. A late entry lands on the day the care happened (Sep 12), with an explicit capture label ('Captured September 20, 2026 at 8:14 AM') and a late label ('Recorded Sep 20 — about Sep 12').
  2. Household-timezone month bucketing, both directions at boundaries. The 23:50 EDT Sep 30 entry/event land on Sep 30 in the NY September view, are absent from the NY October view (31 cells, exactly 1 event), and the same instant falls in Kolkata's October view (2 events).
  3. DST correctness. November 2026 in New York renders both 1:30 AM fall-back events on 2026-11-01 in order; the month spans 721 hours; no day duplicated or lost.
  4. Failed extraction never blocks. The Sep 20 failed note renders with its raw transcript preserved and an explicit failure label; zero structured events invented.
  5. Honest gap disclosure. Sparse month renders all 30 cells; gap days read 'No entries recorded' and the month states 'Gaps in the journal are not evidence about care.' Never implies no care.
  6. One shared fail-closed gate. monthAccess/correctionAccess delegate to the existing security policy: household members read, non-members denied at the timeline gate (DENY_NO_HOUSEHOLD_PATH / DENY_ANONYMOUS), drafts author-only, attribution server-bound (claiming another author's id → DENY_ATTRIBUTION_MISMATCH).
  7. Append-only correction lineage. Chain integrity enforced (supersedes must reference an earlier correction of the same original — construction throws otherwise); corrected entries show corrected text + who/when/reason + 'The original note is preserved verbatim'; corrections disclosed to screen readers on the entry and every event of it.
  8. Receiver-authorized handoff digest. Built from the RECEIVER's view (published-only for Dad), source-linked lines, corrections attributed, excluded drafts disclosed as a COUNT only, gap statement matches the receiver's view (26/30), audience + preparer metadata + source disclaimer carried.
  9. Projection purity. Views, access checks, and digests never mutate the dataset (JSON snapshot byte-identical).
  10. Rubric is machine-checked, both directions. Every rubric row has an executing check and every executed check has a rubric row; the runner fails on either gap.

What's in the box

  • packages/month-history — zone calendar, fail-closed access adapters, view builder, corrections lineage, handoff digest, a11y contracts, deterministic fixtures, 28-check journey harness + rubric, 13 bun tests
  • apps/mobile — MonthHistoryScreen renders the same authorized view the journeys assert (grid, day panel, lineage, extraction-failure label), App.tsx wired
  • README + .obvious/obvious.md layout and verification table updated

Design note: source-facing exports

Unlike @journal/domain and @journal/ui (dist-based), this package exports types/default → ./src/index.ts: it imports the repo-root security/access/policy by relative path (outside any buildable rootDir), it is consumed by Metro (which compiles TS natively) and bun, and there is no LLM/runtime dependency on a dist build. TS ~6.0.3 resolves the types condition fine (verified).

Review & repair history

  • Round 1 (independent reviewer, head 890c15d): pass-with-notes. Findings: orphan mislabeling (scopeEntryIds dead code — events of in-scope-but-invisible entries counted as orphans, contradicting the documented semantics), stale rubric J1-A2 measure text, evidence-count typos, stale base. Verdict comment 5719665013.
  • Repair (head a893b2c, rebased onto master 90b76d9): orphan classification rebuilt on a full claim universe (scopeEntries via structuredEventIds — three-way classification: visible-claimed rendered / in-scope-invisible hidden+excluded-from-totals / unclaimed orphan); rubric measure fixed to events=6; new package test pins the hidden-vs-orphan divergence both directions.
  • Round 2 (rule-5 delta re-review, head a893b2c): pass-with-notes. All repairs verified in code and behavior (Dad's view orphanEventCount 1→0, ev_sep25_mood correctly hidden, totals consistent); 28 rubric rows ↔ 28 checks intact; no new defects. Verdict comment 5719831240.

Verification (head a893b2c, post-rebase onto 90b76d9)

pnpm install --frozen-lockfile   ✓
pnpm typecheck                   ✓ (10/10 turbo tasks)
pnpm test                        ✓ (domain 61 across 4 files + month-history 13)
pnpm build                       ✓ (5/5)
bun test ./security              ✓ 17 fail-closed cases
evaluation: bun src/run.ts       ✓ 6/6 fixtures (+ negative control exits nonzero)
month-history journeys           ✓ 28/28 checks (rubric coverage bidirectional)
CI                               ✓ both checks green on this head (runs 35262906355, 35262906350)

Full evidence receipt (round history, criterion→check map, design decisions): project artifact "PR #27 Month-History Journeys — Verification Evidence".

@obvious-autobuild

Copy link
Copy Markdown
Contributor Author

Independent review — verdict: pass-with-notes

Tested head SHA: 890c15dfe1276beb63957d81cdf1b61a05861f23 (base master @ 3caaa08; full diff inspected, all 21 files). Review performed in a clean sandbox checkout at this exact SHA; no branch edits, no merge.

Verification gates (re-run independently at the tested SHA)

Gate Result
pnpm install --frozen-lockfile ✓ (pnpm 10.34.5 via corepack pin)
pnpm typecheck ✓ 10/10 turbo tasks
pnpm test ✓ 6/6 tasks — month-history 12 pass, domain 61 pass (4 files), retrieval 22 pass
pnpm build ✓ 5/5
bun test ./security ✓ 17/17 fail-closed cases
cd evaluation && bun src/run.ts ✓ 6/6 fixtures
negative control --adapter=broken-adapter --expect-failure ✓ exit 0 with adapter failing 1/6 (convention holds)
pnpm --filter @journal/month-history journeys ✓ 28/28 checks, exit 0
CI on this head ✓ pass — run 35261149958

Acceptance criteria (all ten verified in code + executed checks)

1–5 (placement/boundaries/DST/failed-extraction/gaps): verified in view.ts/zone.ts/a11y.ts and pinned by J1-A2…A8, A14. 6 (shared fail-closed gate): access.ts is a pure adapter over security/access/policy; J2-B4/B5/B6 pin DENY_DRAFT_AUTHOR_ONLY / DENY_ANONYMOUS / DENY_ATTRIBUTION_MISMATCH / DENY_NO_HOUSEHOLD_PATH. 7 (append-only lineage): buildLineage throws on unknown/out-of-order supersedesCorrectionId (unit test pins the throw). 8 (receiver-authorized digest): built from DAD_SEP.view (Dad's own access result), 26/30 gap statement, drafts as count-only. 9 (projection purity): dataset JSON snapshot byte-identical (J1-A10 + unit test). 10 (bidirectional rubric): run.ts fails on uncovered OR unlisted; empirically confirmed — in a throwaway git archive copy of this head, deleting rubric row J3-C7 makes the runner exit 1 with EXECUTED CHECKS MISSING FROM RUBRIC: J3-C7 (nothing was pushed).

Focused checks: (a) draft-content cannot reach any rendered structure — entryIdByEventId is built only from monthAccess-filtered entries, so events of invisible entries are counted as orphans and never rendered; the screen renders only via buildAuthorizedView. (b) orphan accounting — no counterexample found that leaks content; see Note 1 for the accounting-semantics gap. (c) DST math — 721h November assert is correct (25h fall-back day), 12h stepping cannot jump a ≥23h local day, and the day-count completeness assert would throw on enumeration drift.

Notes (non-blocking)

  1. scopeEntryIds is a no-op, contradicting its own contract doc. view.ts:110 builds linkableEntryIds from input.scopeEntryIds and never uses it; orphan classification uses entryIdByEventId (visible entries only). types.ts documents that events of in-scope-but-invisible entries are "excluded WITHOUT counting as orphans" — not implemented. Observable today: Dad's September receiver view carries orphanEventCount === 1 for Mom's draft event (ev_sep25_mood), mislabeling a draft-linked event as an unclaimed/orphan event (count-granularity disclosure, consistent with the design's draft-count policy, but the label is wrong). No fixture exercises the divergence (Ana's draft has no events), so no check can catch it. One-line fix (use linkableEntryIds at the orphan-count site) or drop the parameter — suggest aligning code or docs in a repair.
  2. Rubric J1-A2 measure text is stale: says "events=5"; the executing check asserts totalEvents === 6, which the fixture arithmetic confirms (3 on Sep 2 + nap + mood + Sep 30 potty). Id-level coverage is machine-enforced so this passes; the human-readable measure contradicts its own check.
  3. Evidence-table accuracy at this head: the PR's verification table says "domain 11" tests; the domain suite runs 61 tests across 4 files at 890c15d (verified by running it). month-history "12" is accurate.
  4. Base movement: origin/master advanced to 69e7bd3 (PR feat(evaluation): conversational correction challenge corpus (CC-01..CC-15) #19 merged) after this branch was cut from 3caaa08 — the PR is one commit BEHIND base. Per workflow rule 5, a rebase invalidates prior results and requires fresh CI before merge (merge-owner action; not a code defect).

Scope hygiene

Diff confined to packages/month-history/**, apps/mobile (App.tsx, package.json, src/month/MonthHistoryScreen.tsx), pnpm-lock.yaml, README.md, .obvious/obvious.md — nothing outside the declared scope.

Verdict for the evidence receipt: pass-with-notes — reviewer: independent review lane, 2026-09-17. Notes 1–2 are repair-suggestions for the merge owner; none blocks merge under the repo workflow.

Obvious Agent added 2 commits September 17, 2026 19:05
…utable journeys + RN prototype

New @journal/month-history package (proposal tier per the 30-slot delivery map):

- zone.ts: household-timezone calendar (DST-safe month bounds, 721h Nov,
  deterministic en-US labels), isInstantInMonth membership
- access.ts: monthAccess/correctionAccess adapters over the existing
  fail-closed security policy (timeline gate first, author-only drafts,
  excluded-draft counts, attribution-mismatch denial)
- view.ts: pure month-history view builder — care-day event placement,
  capture-day fallback, late-entry disclosure, failed-extraction
  preservation, correction lineage display, explicit gap cells with
  honest copy, a11y metadata, month-scoped totals
- corrections.ts: append-only lineage (chain integrity enforced),
  latest-winner resolution
- handoff.ts: receiver-authorized source-linked digest with count-only
  draft disclosure
- a11y.ts: quiet-input + font-scaling + layout contracts as data
- fixtures: deterministic synthetic family (8 entries, 6 events, Mom/Dad/
  Ana/outsider principals, month-boundary + DST pairs)
- journeys + rubric.json + run.ts: 28 executable checks across three
  journeys (parent-retrieval, parent-correction, caregiver-handoff) with
  bidirectional rubric/execution coverage enforcement
- 12 package tests (bun); exports map source-facing (types+default ->
  src/index.ts) since the package imports the repo-root security policy
  by relative path and runs under bun/Metro without a dist build
- mobile: MonthHistoryScreen renders the same authorized view the
  journeys assert (grid, day panel, honest gap copy, lineage,
  extraction-failure label); App.tsx wires it
- README + .obvious/obvious.md layout and verification table updated
…stale rubric text

Round-1 review (pass-with-notes) findings:

1. view.ts orphan classification never consulted scopeEntryIds (dead
   linkableEntryIds), so an event claimed by an in-scope-but-invisible
   entry (Mom's draft, from Dad's perspective) was mislabeled as an
   orphan — contradicting the BuildMonthHistoryInput docs. scopeEntryIds
   (entry-id list) could not actually distinguish 'claimed by hidden
   entry' from 'unclaimed'; replaced with scopeEntries (full entry views,
   the claim universe via structuredEventIds). Events now classify three
   ways: visible-claimed (rendered), in-scope-invisible (excluded, not
   orphan, excluded from totalEvents), unclaimed (orphan). journeys.ts
   passes SCOPE_ENTRIES (full fixture entry views).
2. rubric.json J1-A2 measure text said events=5; the executed assertion
   and fixture arithmetic give events=6. Text now matches the check.
3. New package test pins the hidden-vs-orphan divergence from both the
   scoped and default-claim-universe directions (no fixture exercised it
   before, which is how the dead-code bug survived 28/28).
@obvious-autobuild
obvious-autobuild Bot force-pushed the feat/month-history-journeys branch from 890c15d to a893b2c Compare September 17, 2026 19:06
@obvious-autobuild

Copy link
Copy Markdown
Contributor Author

Independent review — round 2 (rule-5 delta re-review) — verdict: pass-with-notes

Tested head SHA: a893b2c3dd9960944369fb826c818e71393f8664 (branch force-updated 890c15d → a893b2c, rebased onto master @ 90b76d9). Repair push invalidates round-1 results per workflow rule 5; this re-review covers the delta and re-runs verification at the new HEAD. No branch edits, no merge.

Delta scope (rebase artifact clarified)

The two-dot range 890c15d..a893b2c spans 43 files, but 38 of them are PR #26 content arriving via the rebase (master advanced 69e7bd3 → 90b76d9; verified merge-base(a893b2c, origin/master) = 90b76d9 = origin/master tip). The branch's own diff against its base (90b76d9..a893b2c) is the same 21-file scope as round 1 — scope hygiene holds — and the repair delta within it is exactly the five claimed files (view.ts, types.ts, journeys.ts, rubric.json, month-history.test.ts). The .obvious/obvious.md branch delta remains the round-1 two lines only.

Round-1 Note 1 (orphan accounting) — REPAIRED, verified in code and behavior

  • scopeEntryIds (entry-id list, dead) replaced by scopeEntries (full EntryViewInput claim universe via structuredEventIds). view.ts now classifies three ways: visible-claimed (rendered), in-scope-invisible (hiddenEventCount, excluded from totalEvents, NOT an orphan), unclaimed (orphan, surfaced). types.ts docs (BuildMonthHistoryInput + orphanEventCount) now describe exactly this; default claim universe = authorized entries. Coherent.
  • My round-1 counterexample re-verified against the new code: Dad's September view — Mom's draft event ev_sep25_mood is claimed by an in-scope invisible entry → hiddenEventCount 1, orphanEventCount 0 (was mislabeled 1), totalEvents = 6 − 0 − 1 = 5 = rendered count. Mom's J1-A2 numbers are unchanged by construction (her draft event is visible-claimed to her: 30/25/5/6/1/1/0) and confirmed by the passing journeys.
  • Rendering guards are outside the repair's diff hunks and reconfirmed at the new head: day.events excludes both orphan-classified and hidden events. The new orphan accounting unit test pins the divergence in BOTH directions: scoped (orphan=1, totalEvents=0, hidden and orphan events render nowhere across all 30 days) and default claim universe (orphan=2). This is exactly the fixture-gap scenario from round 1 — now behaviorally pinned.
  • No new defects found in the repair: scopeEntries affects counts only, never rendered content (rendering is driven exclusively by input.entries), and the trusted buildAuthorizedView path computes the claim universe from the full in-scope fixture entries.

Round-1 Note 2 (rubric measure) — REPAIRED

J1-A2 measure events=5 → events=6, now matching the executing check. Rubric row set unchanged (28 rows ↔ 28 checks; bidirectional coverage re-exercised by the green journey run; the round-1 mutation test of run.ts remains valid — the runner is unchanged).

Round-1 Note 4 (rebase) — RESOLVED

Branch is exactly on the master tip (90b76d9, PR #26 squash); no longer BEHIND base.

Gates re-run at a893b2c (uncached where inputs changed)

Gate Result
pnpm install --frozen-lockfile ✓ (lockfile unchanged by the repair)
pnpm typecheck ✓ 10/10 turbo tasks
bun test packages/month-history ✓ 13/13 (12 + new orphan-accounting test)
bun test packages/domain ✓ 61/61
pnpm --filter @journal/month-history journeys ✓ 28/28, exit 0
bun test ./security ✓ 17/17
evaluation corpus / negative control ✓ 6/6; negative-control exit 0
pnpm build ✓ 5/5 (turbo cache valid — buildable package inputs byte-identical to the round-1 run; the repair touched only month-history)
CI on this head ✓ both checks green: Typecheck/test/build (run 35262906355) + suites (run 35262906350)

Remaining note (non-blocking, non-code)

Round-1 Note 3 — evidence-table correction claimed but not verifiable from the reviewer lane. The PR body's verification table still reads "domain 11 + month-history 12 tests" (actual at a893b2c: domain 61, month-history 13); no correction comment exists on the PR; art_IYBAPF9R still returns NOT_FOUND from this thread (re-verified this round). The claimed correction may live in the artifact (owned by the merge-owner thread, inaccessible to this lane) — but as of this review it is not visible anywhere inspectable. Merge owner: land the corrected counts in the PR body or evidence artifact so the evidence receipt's fields are accurate at merge time (the receipt policy requires every field fillable and accurate).

Verdict: pass-with-notes — code repairs for Notes 1, 2 and the rebase (Note 4) are complete and verified; the sole remaining note is the evidence-table accuracy item above. Merge ownership remains with the PR worker thread.

@obvious-autobuild

Copy link
Copy Markdown
Contributor Author

Round-2 review landed (pass-with-notes at a893b2c, comment 5719831240) — all round-1 findings repaired and verified. Corrections landed per the reviewer's remaining note: the verification block above now reads the true counts (domain 61 across 4 files, month-history 13 — an earlier draft's 'domain 11 + month-history 12' was a single-file run misread as the suite total). Full evidence receipt: project artifact PR #27 Month-History Journeys — Verification Evidence (round history, criterion→check map, design decisions, correction-of-record on the earlier un-published artifact id).

@obvious-autobuild
obvious-autobuild Bot merged commit b4e38e9 into master Sep 17, 2026
2 checks passed
@obvious-autobuild

Copy link
Copy Markdown
Contributor Author

PR #27 evidence receipt (complete):

PR: #27
Tested head SHA: a893b2c (round-2 review + all local gates; branch rebased onto master 90b76d9)
Review result: pass-with-notes — round 1 at 890c15d (comment 5719665013), round-2 rule-5 delta re-review at a893b2c (comment 5719831240); all findings repaired and verified, no open blocking findings
Checks: All green on a893b2c — Typecheck/test/build run 35262906355 (pass, 47s) + suites run 35262906350 (pass, 5s); CI run 35261149958 was green on the earlier round-1 head 890c15d
Merge commit: b4e38e9 (squash on master: "feat(month-history): deterministic month-history view model with executable journeys + RN prototype (#27)")
Post-merge smoke: Full local verification table re-run on merge commit b4e38e9 (detached worktree, frozen lockfile): pnpm typecheck ✓ 10/10 turbo tasks · pnpm test ✓ 6/6 tasks · pnpm build ✓ 5/5 · bun test ./security ✓ 17/17 fail-closed cases · month-history journeys ✓ 28/28 checks. All green.
Unlocked tasks: Month-history delivery-map slot closure (proposal tier → merged); month-history consumers in family-history retrieval/digest lanes can now import @journal/month-history; mobile month-history surface (MonthHistoryScreen) is wired on master for the next review round.

Notes: Round-1 review found the orphan-accounting defect (dead scopeEntryIds path mislabeling Mom's draft event as an orphan from Dad's view), the stale J1-A2 rubric measure, and evidence-count typos; all repaired at a893b2c and verified in the round-2 delta re-review. Full round history, criterion→check map, and design decisions: project artifact "PR #27 Month-History Journeys — Verification Evidence" (art_WzdyFYRC).

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.

1 participant