Skip to content

Final audit, continued: the verified units landing after #580 - #581

Merged
NewWorldVenture merged 13 commits into
mainfrom
claude/roadmap-implementation-ld8bon
Sep 27, 2026
Merged

NewWorldVenture merged 13 commits into
mainfrom
claude/roadmap-implementation-ld8bon

Conversation

@NewWorldVenture

@NewWorldVenture NewWorldVenture commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

What this is

The next tranche of the final audit's verified units, landing one at a time as each finishes its re-derivation → fix → review → rework → re-review → mutation-proof cycle. Nothing here is marked done on an agent's word: every unit was reverted in a clean worktree at HEAD (test kept, source removed: red; source restored: green), its neighbouring suites and every document guard were run on the staged set, and every migration was applied twice onto a database built from HEAD's migrations with every probe replayed there twice.

Merged with main

main took #579 (another session's pass C1-K, migrations 0344–0380) and #582 while this PR was open. This branch's seven migrations collided on number and were renumbered 0381–0387 (git mv, names intact, every reference rewritten — 779 replacements, plus ten earlier probes whose comments still said the old numbers), then main at 6ff770da was merged in. Conflicts were resolved keeping both sides' intent and tests: xml() escaping in the Twilio client, normalizeFallbackPhone (write side) beside toCallableE164 (read side for legacy rows), the vote re-read-and-retry beside the changedByMe audit note, and main's social_has_permission CASE arm taught to the permission-matrix test's parser rather than a case deleted. Full replay after the merge: 398 migrations, 115 of 115 probes twice, 38 test files green.

Units in this PR

  • SRV-001 leads, each re-derived from the source first: m21 (0385: family_facts UPDATE/DELETE carry the member rule the service applies), m36 (0386: a calendar URL is subscribed once; a failed first sync leaves nothing behind), m7+m8 (0381: "two parents" is a rule the database keeps), m10+m11 (0382: a password alone does not reach the family's money), m1/m2+m3 (0383: an archived page takes its public answers with it), m13+m14 (0384: the urgent fallback number is stored as E.164 or refused with the form it needs), m32/m33+m34 (a failed grocery or vote read forks nothing and says so), m19 (a rebuilt graph is on screen before the toast), m15 (a dashboard setting that could not be read is not a yes), m16+m17 (the dining page neither hides a failed read nor guesses a total), m5 (a social-links form that could not be read is not a cleared one), m37 (a retried Add whose content changed is not told "Task added"), and m35's three review follow-ups.
  • AUTHZ-011 (0387): the settled SUSPECT tables; a child cannot lift the publish lock or link a document they cannot read.
  • SEC-008 and SEC-009 (an opt-out that cannot be read is still an opt-out).
  • AUDIT-011: all 39 re-controlled boundary probes landed, each mutation-tested three ways on private clones; the row closes with its residue recorded (three erasure scans that need the owner's (family_id) index each).
  • AQ-01 / I18N-003: money reads in the reader's locale across billing and pricing, the family modules, home and auto, the engines and digests, and the marketplace; the English around each amount moved to the catalogue (seven locales). The hand-written-currency ratchet fell 51 → 8 and names the eight that remain.

Migrations

0381–0387 — all unapplied, each with a docs/PENDING_PROD_MIGRATIONS.md entry and its own boundary probe. Agents do not apply migrations to production; the owner does. Version pin at 0388. Production's migration ledger still stops at 0177 (recorded in the Release · #579 section of finalaudit.md).

finalaudit.md

Index regenerated by docs/audit/finding-index.py: FIXED 99 · CHECKED 2 · PARTIAL 4 · OWNER'S 7 · OPEN 10 · BLOCKED 1 · SUPERSEDED 1. The release line records #580, #579 and #582 on main and in production. PRODUCTION READY stays NO: four production secrets unset, no migration past 0177 applied, owner decisions pending.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K


Generated by Claude Code

…es to a calendar URL once (0363)

Two SRV-001 medium leads, each re-derived from the source, fixed with a
failing-first test, reviewed, reworked where the review found anything,
re-reviewed, and mutation-proven at HEAD before landing.

m21. 0264 narrowed family_facts UPDATE and DELETE by category only, so
on the seven ordinary categories any member could rewrite or delete any
row — a parent's household "Emergency contact" among them — from the
browser's own RLS-bound client, and rememberConfirmed's label probe let
the Add form do the same without an id. The service now applies
mayChangeFact on the create path and the label probe refuses a row the
caller may not write; 0357 (unapplied, in the ledger) re-creates the two
policies with the same rule — a manager, a memory about me, or one I
wrote. Its probe's control runs first and it is proven three ways on
private clones. Two residuals its re-review found are recorded, not
smoothed over: ownership keys on created_by, set on INSERT only; and
INSERT stays open with no uniqueness on the label.

m36. A calendar whose first sync failed was left in the family's list
and the next press of "Add & Sync Now" inserted the same URL again, so
every school event came in two or three times. The action now looks for
the family's row first and re-syncs it, removes a row it created whose
first sync failed, refuses a link longer than the index can hold, and
catches the race loser's 23505 by syncing the winner's row; 0363
(unapplied, in the ledger) makes (family_id, url) unique. Four sentences
in seven locales.

Proofs: m36 red without its source (11 of 11), green with it; m21 red
without lib/services/memory/index.ts on outcome, green with it; 21
neighbouring suites and the catalogue, document, tally and version
guards green on the staged set; 0357 and 0363 applied twice onto a
HEAD database and every probe replayed there, 72 of 72, twice.
Ratchet pinned at 0364.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…e does not reach the money (0345), and the plan price reads in the reader's locale

Three units, each re-derived from the source, fixed with a failing-first
test, reviewed, reworked, re-reviewed and mutation-proven at HEAD. They
share two files (the billing page and the billing module), so they land
together and were proven together.

m7+m8. "Two parents" was a rule in TypeScript alone: 0251's decide policy
let any signed-in adult PATCH an approval to approved over /rest/v1, which
closed the request, denied the second parent a vote, and wrote no trust
audit row. 0344 (unapplied, in the ledger) adds two BEFORE UPDATE
triggers: a move into approved or modified must be earned by the row's
own votes under its own model and threshold, a vote is signed only as
yourself, and a pending row's rule cannot be rewritten. The concierge
panel's Approve and Dismiss now route an approval-backed run through
decide(), and a finished plan is stamped completed so it counts in the
family's week. Three residuals its re-review raised are recorded in
SRV-001 and the ledger, not closed: a rejection is not final in the
database, the executor still runs raw edited_payload with skipTrust, and
an adult can PATCH a run's metadata to drop its approval_id.

m10+m11. The step-up the money pages demanded was never a database rule,
so a password-only session could DELETE /rest/v1/budgets directly. 0345
(unapplied, in the ledger) adds session_cleared_step_up() — aal2, or no
factor enrolled — and RESTRICTIVE insert, update and delete guards on
budgets, savings_goals and bills. Every money action requires aal2, the
billing page sends an aal1 manager to step-up with a return path, and
the twelve client call sites route a refusal there instead of a bare
error.

billing-and-pricing (AQ-01). /pricing, the plan manager, the upgrade
modal, the trial gate and the Stripe service fee read the plan price and
the fee in the reader's locale; the per-month suffix, "billed monthly"
and "billed {amount}/yr · save {percent}%" are catalogue keys. Nine
sentences in seven locales. The hand-written-currency ratchet is set to
its live count, 38, and now says so.

Proofs: 32 red without the sources, green with them (124 tests); per
file, twelve source files go behaviourally red when reverted alone and
six only fail to load (their sibling imports them); 65 neighbouring
suites and every guard green on the staged set; both probes
mutation-tested three ways on clones of a HEAD template with the
migrations applied; 0344 and 0345 applied twice onto a HEAD database and
every probe replayed there, 74 of 74, twice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
@vercel

vercel Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
bubaly Ignored Ignored Sep 27, 2026 1:05am UTC

Request Review

…cery list a vote reaches, and a graph that is on screen before the toast says so

Five units, each re-derived from the source, fixed with a failing-first
test, reviewed, reworked, re-reviewed and mutation-proven at HEAD.

AUTHZ-011 → FIXED. 0365 (unapplied, in the ledger) puts three guards in
the database: RESTRICTIVE writes on social_settings behind
social_has_permission(…, 'manage_settings'), so a child can no longer
lift the family's publish lock over /rest/v1; a trigger that lets a trip
link only a document its caller can read from its own household; and a
SECURITY DEFINER trigger — found by the review of the fix — that refuses
to move a document to another household while a trip links it. 19
cases, 13 red with the migration absent.

SEC-008 → FIXED. GET/POST /api/blog/like, POST /api/blog/subscribe and
POST /api/ab/track bind the visitor to the request's own bubaly_vid
through the helper SEC-006/007 share; a body naming another visitor is
refused, no cookie is a 400 or an anonymous event, and the like route's
delete arm reads its result. 15 of 40 cases red with the routes
reverted. One orphaned catalogue key removed from all seven locales.

m13+m14. The urgent fallback number is normalized to E.164 before it is
stored and refused with a sentence that says the form it needs when it
cannot be; no country code is guessed. 0356 (unapplied, in the ledger)
normalizes the rows already there, keeps what they held, and adds the
CHECK the column's own comment claimed.

m32, m33+m34. A failed read of the family's grocery list never forks a
second one; a failed read of the vote or its winning recipe adds nothing
and says so; a bare-string ingredient no longer refuses the whole
dinner; tapping Grocery twice on a won vote lists the dinner once.

m19. A rebuilt graph is on screen before the toast says so, and a modal
cannot be dismissed while its save is in flight.

Eight sentences in seven locales. Proofs: every unit red without its
source (13/19, 15/40, 12/16, 21/35, 11/11), green with it; 76 suites and
every guard green on the staged set; 0356 and 0365 applied twice onto a
database built from HEAD's migrations and every probe replayed there, 76
of 76, twice. Ratchet pinned at 0366. finalaudit: FIXED 98 · OPEN 11.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
… rule can be corrected after it is saved

SRV-001 leads m1 and m2+m3, re-derived from the source, fixed with
failing-first tests, reviewed, reworked, re-reviewed and mutation-proven
at HEAD.

Generated FAQ answers are rows in marketing_aeo_questions, world-readable
on one predicate with no join back to their page, so when a page was
archived, deleted, renamed or a blog post unpublished, its route went
dark but its answers kept rendering — on /faq, in every live article of
the same category, and with a body citing a URL that now answers 404.
Nothing self-healed it. The take-down actions and the legacy bridge's
three paths now retire the answers themselves; 0355 (unapplied, in the
ledger) makes it a property of the tables — four AFTER triggers bound to
SECURITY DEFINER functions with a pinned search_path move a page's
published answers to 'answered' in the take-down's own transaction, and
the backfill retires what pages already dark left behind. The marketing
platform page also lets a brand rule be corrected after it is saved and
re-queues a retried job rather than a neighbour. Five keys in seven
locales.

Proofs: 19 of 22 red without the sources, green with them; each of the
five source files behaviourally red when reverted alone; 22 neighbouring
suites and every guard green on the staged set; both probes
mutation-tested three ways on clones of a HEAD template with 0355
applied; 0355 applied twice onto a database built from HEAD's migrations
and every probe replayed there, 78 of 78, twice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…them import

tests/medications-fixture-module-graph.test.ts walks each fixture's import
graph so a new value import is caught before the fixture fails to mount,
and it caught two on CI: budgets-view and savings-view now route a
refused money write through lib/auth/step-up-client (0345's application
half), so finance-read-states lists it and the lib/auth/mfa it reads its
path from; and the trial paywall gate prints the yearly price through
formatCents in the reader's locale, so signout-form-boundaries lists
lib/wallet/ledger, mocks lib/marketing/value's PLAN_CURRENCY, and gives
its locale-provider mock the useLocale the gate now calls — with the
locale injected into the page script, where the Node-side variable does
not exist (the first cut referenced it and the gate threw on mount).

Both specs pass on the pre-installed Chromium here (43 of 43), the
module-graph guard is green, and no test was weakened.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
PR #579 landed on main as 0344–0380, taking every number this branch had
used: 0344, 0345, 0355, 0356, 0357, 0363 and 0365. The seven files keep
their names and move into the block this branch holds, 0381–0387:

  0344 -> 0381 two_parents_means_two_parents_in_the_database_too
  0345 -> 0382 a_password_alone_does_not_delete_the_familys_budget
  0355 -> 0383 an_archived_page_takes_its_public_answers_with_it
  0356 -> 0384 the_urgent_fallback_number_is_stored_the_only_way_it_can_be_used
  0357 -> 0385 a_member_only_rewrites_their_own_memory
  0363 -> 0386 a_family_subscribes_to_a_calendar_url_once
  0365 -> 0387 a_child_cannot_lift_the_publish_lock_or_link_a_document_they_cannot_read

Every reference in a line this branch wrote follows: the migration headers
and their cross-references (including the file:line pointers, whose line
numbers did not move), the eight probes' notices, HOLDS lines, \ir includes
and fixture identifiers, the tests that read a migration by path, the source
comments that cite one, the seven ledger entries in
docs/PENDING_PROD_MIGRATIONS.md (now in numeric order after 0360), the
finalaudit.md rows that name them, and the version-safety paragraphs. The
pin moves to 0388. Nothing main wrote changes: the only remaining hits for
the old numbers are main's UI-ROUTE-03xx census ids.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Brings in PR #579 (34 migrations renumbered into 0344–0380) and #582's
release note. Five files conflicted; each resolution keeps both sides'
intent and both sides' tests:

- lib/guardian/twilio.ts: main's xml() escapes text and attribute values
  alike (a superset of this branch's escapeXmlText/escapeXmlAttr), so every
  builder uses it; this branch's twimlDial rationale and its
  findOwnedNumberSid/releaseNumber additions stay.
- lib/contact-center/phone.ts: both functions. normalizeFallbackPhone is the
  write-side rule (E.164, country code required, letters refused);
  toCallableE164 is main's read-side normalizer for rows written before it,
  and its header now says how the two relate.
- app/(app)/dashboard/contact-center/actions.ts: the save goes through
  normalizeFallbackPhone and refuses with actions.enterTheFallbackNumberIn,
  which this branch's test asserts by sentence; main's account of why is
  folded into the comment. main's actions.forwardingNumberNotCallable stays
  in the seven catalogues, unused.
- lib/services/approvals/index.ts: main's re-read-and-retry on a lost write
  race (attempt < VOTE_RETRIES) around this branch's changed/changedByMe
  audit note.
- tests/migration-version-safety.test.ts: main's paragraph on 0344–0380,
  then this branch's seven paragraphs for 0381–0387; the pin is 0388.

Two files main alone changed had to move for this branch's tests:

- app/api/contact-center/voice/route.ts: a stored fallback that reads as a
  number is normalized and dialled (main). One that does not is no longer
  treated as "no number": it goes into the <Dial> escaped, as this branch's
  test asserts, and the message-taking verbs follow it, so the caller is
  still asked for a message when that Dial fails. 0384 clears such rows.
- tests/a-child-cannot-lift-the-publish-lock-or-link-a-document-they-cannot-
  read.test.ts: its reading of social_has_permission learns the deny-list
  arm 0348 introduced (p_permission <> 'manage_access'); without it the
  admin role fell out of the matrix and the test reported the database
  refusing a parent the live probe shows it admits. No assertion changed.

finalaudit.md is the union of both sides plus #582's release section; the
index is regenerated (FIXED 98 · OPEN 11, unchanged) and the two clauses of
main's forwarding-number row that the merged code made untrue now describe
what the code does. Verified on a private database: 398 migrations applied,
0 failed; 115 probes pass twice; the version audit's next number is 0388.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…, and the engines; a dashboard setting the page could not read is not a yes; a dining page that neither hides a failed read nor guesses a total

Five units that share files — the family modules, home and auto, engines
and digests (AQ-01 / I18N-003), m15 and m16+m17 (SRV-001) — plus m35's
three review follow-ups, landing together because dining-module,
voting-module, consensus and the home dashboard are each touched by two
of them. Every unit was re-derived, fixed, reviewed, reworked,
re-reviewed and proven at HEAD.

AQ-01. Fifty keys move the sentences around money into the catalogue,
and every amount is formatted for the reader through formatCents with a
named currency: the closet, dining, inventory, family-signals and
relationship modules, the poll's consensus explanation and the AI
explanation's budget cap; the Needs-You page, the home page, the
briefing route and its decisions, the car's insurance and service
clients, the home service client and the assistant's tools; the
autopilot, decisions and reasoning engines, the food score, the hard
signals, the calm, kitchen, reasoning and family-signals pages, the kids'
submit page, the missions review card and the two crons. The engines
group changed more than formatting: a subscription suggestion's title
had been worded once, in en-US by whichever scan inserted it, so every
screen now re-words it for its own reader from the facts in its payload.
The dead key serviceClient.totalLogged, whose value was the defect
("Total logged: $"), is gone from all seven locales. The hand-written
currency ratchet falls from 38 to 20 and says so.

m15. A dashboard settings read that failed is no longer normalized to the
permissive default: readDashSettings closes the customization permission
and reports the settings as unknown, and the home dashboard withholds the
Family button, the "Set by a parent" caption and "Set family default".

m16+m17. The dining page's four tile figures are counted and summed by
the database over the whole family and arrive as props; null renders as
"—" and never as 0; a batched read degrades like a single one.

m35 follow-ups. The gate comment says what the forgiving read does, the
actions test's mock carries getAISettings so a reverted action no longer
passes by accident, and a panel test pins that a refused setting whose
re-read also fails leaves no control on screen and never the refused
value.

Proofs: with every other unit's files present, reverting one unit's
files alone goes red — family modules 29, home and auto 16, engines and
digests 42, m16+m17 18 of 437; m15's two action files 2 each, its
permissions module a load failure and its dashboard wiring unpinned
(recorded); 46 neighbouring suites and every guard green on the staged
set; eslint clean. Coverage gaps the re-reviews named are recorded in
finalaudit rather than smoothed over.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…t could not be read is not a cleared one

AQ-01 / I18N-003, the marketplace group: listings, auctions, negotiations,
the price coach, price history, saved searches, the assistant, the
close-auctions cron and the fifteen marketplace pages and panels that
render them read money through formatCents in the reader's locale; the
English sentences around each amount move to the catalogue (70 keys in
seven locales) and six retired keys that ended in a hand-typed "$" are
removed from all seven. A shared money-unit helper places the currency
unit on the side the reader's locale writes it and pads the input on
that side. Two outcome tests: one asserts, for every translated
catalogue, that the reader's own sentence carries the formatted amount
and no literal "$"; the other clicks through to the offer box, the
counter box, the agreed line and Quick Post's suggestion and pins the
unit's side. The hand-written-currency ceiling falls from 20 to 8, and
its header names the eight sites that remain.

SRV-001 m5: a failed read of the social links used to reach the admin
page as {} - the value that means "nothing configured" - so the page
drew six blanks and one Save replaced the whole app_settings row with {}
and emptied the homepage's Organization sameAs. The page now refuses to
render an editable form over a read that did not answer; the write is a
compare-and-set that re-reads the row itself, refuses a submission built
on anything else, and writes nothing when the re-read fails; the audit
row keeps what a save replaced and carries via: 'site_admin'. Six blank
fields remain a legitimate submission.

finalaudit.md records both landings with their mutation proofs and the
re-reviews' out-of-scope notes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
The browser kept one submission id across a failed save, and
withIdempotency answered a retry under that key with whatever row the key
had already written - so a person who fixed the date or the title after
a failed Save saw "Task added" and got the first press's row. The guard is
opt-in (rejectChangedRetry), set only by the todo, calendar-event and
chore actions whose key is a browser submission id: a retry whose title,
dates, assignee or other columns differ from the saved row is refused
with already_saved and a sentence naming the row that exists and saying
nothing was added or changed, in seven locales. An unchanged retry still
answers the same row silently; the executor's step keys and the trips'
pet-care tasks pass no option and behave as before, pinned. The three
modals re-mint their id on that refusal, and Quick Add drops its id
before it branches on the result.

Two decisions the re-review confirmed are recorded in the test's header:
reminder submissions carry fixed content per suggestion, so there is
nothing to refuse; a routine's Apply is keyed (routine, week) by design.

Proof at HEAD: 15 of 38 red with the sources reverted, green with them;
26 suites that import the changed units green; eslint clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…nd proven; index FIXED 99 · OPEN 10

The row's verdict cell now opens with what closed and what it does not
claim - the three family-scoped-index erasure scans that need the owner's
migration, the unwrapped control plant in the automation-runs probe, and
that nothing here is a statement about production - and keeps its history
unchanged below. The index is regenerated by docs/audit/finding-index.py
and the prose tally matches it.

Also: the onboarding-facts test's comment now says why 'onboarding' is the
wrong provenance value rather than merely an unavailable one, matching the
header of lib/onboarding/facts.ts it points at.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…numbers

The renumber to 0381–0387 rewrote every reference in the files this branch
added after #580, but probes landed with #580 already pointed at those
migrations by their old numbers - approval-dedupe, automation-runs,
money-write-boundary, family-facts-provenance, document-category-
classifier, document-vault-boundary, rls-isolation, privileged-rpc-grants,
family-keeps-a-manager and only-a-parent-mints-or-revokes-an-assistant-key.
After the merge those old numbers name main's own migrations, so each
comment and exception sentence now says 0381, 0382, 0385, 0386 or 0387.
Comments and message text only; no probe's SQL changes. Mentions of main's
0344–0365 in main's own probes are left as written.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…in production

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K

Copy link
Copy Markdown
Owner Author

E2E on 3e0e5fc3: 1 failed, 1295 passed — and the one is not this PR's.

Failing case: tests/e2e/phone-auth-http.spec.ts:219 › held genuine SMS verification cannot replace logout. It timed out at 120 s waiting for the phone-number textbox after "Continue with phone", and the retry could not close its durable-session browser context (helpers/durable-session.ts:130). Every other E2E case, the Database replay (398 migrations, all probes), Typecheck · Lint · Test · Build and Mobile passed on this head.

Why it is not this PR's:

  • main's own run on 7e54596d (the Final audit, continued: the verified units landing after #541 #580 merge, run 36269220238) failed with this exact case and this exact durable-session close error, and main was green on its next run with no change to that code. It is a browser-context hang in the phone-login harness, not an assertion.
  • This PR's diff against main touches nothing in the phone-login flow, the login page or the durable-session helper. Its only changes under lib/auth are additive: mfa.ts (+23) and the new step-up-client.ts, both for the money-page step-up.

Action: re-running the failed job once, per the flake rule. If it fails a second time it is real and I will root-cause it here.


Generated by Claude Code

@NewWorldVenture
NewWorldVenture marked this pull request as ready for review September 27, 2026 01:41
@NewWorldVenture
NewWorldVenture merged commit dcc0b42 into main Sep 27, 2026
11 of 12 checks passed
NewWorldVenture pushed a commit that referenced this pull request Sep 27, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
NewWorldVenture pushed a commit that referenced this pull request Sep 27, 2026
…dropped

The login form's phone button - the primary way in - rendered enabled from
the server with no handler, and once hydrated its handler refused a form
that had not mounted yet. A tap in that window did nothing, and the phone
form never opened. CI's phone-login E2E hit exactly this three times under
load: it clicked right after domcontentloaded and waited 120 s for a phone
field that never appeared. On a slow phone, a family got the same silence.

The button is now held disabled until the form mounts, as the email
fieldset already was, so the wait is visible and Playwright's click waits
for it. The signup form's three entry buttons had the same shape and get
the same treatment.

tests/e2e/login-readiness.spec.ts gains the case: the phone entry is
disabled before hydration, an early tap waits, and the phone form opens
once mounted. Red with the fix reverted ("Expected: disabled, Received:
enabled"), green with it; the four fixture-based login, signup, password
and kid-login specs pass locally, 93 of 93; the six unit suites that
render either form pass, 113 of 113; eslint clean.

finalaudit's #581 release note, which had called the failure a hang worth
one re-run, now records the root cause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
NewWorldVenture pushed a commit that referenced this pull request Sep 27, 2026
main brought PR #579 (C1-K: thirty-four member-write boundary migrations
at 0344–0380, their probes, trust fail-safes) and #582 (a finalaudit
note). Thirty-six files conflicted; finalaudit.md and the seven
catalogues auto-merged (index regenerated: 124 rows, FIXED 96 · OPEN 13,
prose tally agreeing; catalogues valid at 13,956 keys each).

Resolutions. For 34 of the 36 conflicted files this branch's side is
byte-identical to the side close-out-556 (f55f3591) resolved against the
same main content from the same base (7e54596), so its resolutions are
taken verbatim; the classes are recorded in d41ac300's message (both
sides' discarded-write bindings, wroteNoRows checks joining main's role
gates and family filters, .then().catch() rejection handlers carrying
main's log markers, toCallableE164, the shared xml() escaper, the wallet
routes' error bindings). Two were merged by hand:
- components/modules/behavior-module.tsx: main's delete handler (family
  filter, `data`/`!data?.length`, `canRemove`, which main's
  a-child-cannot-erase-their-behavior-log pins) with this branch's
  confirmed save (`data: saved` + wroteNoRows) and its manager-only Log
  control; the comment no longer cites a migration this branch dropped.
- tests/migration-version-safety.test.ts: main's paragraphs, then this
  branch's ten, pinned at 0419.

Migrations renumbered a fifth time (main's 0344–0380 collided with the
0361–0370 the author assigned in C1-S9-89) to the slots f55f3591 had
already replayed and probed against this tree:
  0361→0406 social tokens service-role only
  0362→0407 no TRUNCATE for the public roles
  0363→0408 household secrets are not child-readable
  0364→0409 marketplace parties are not editable
  0365→0410 a review belongs to whoever wrote it
  0366→0411 deleting a review is rewriting it
  0367→0414 a health record is written by a parent
  0368→0415 a paperwork stamp does not rewrite its siblings
  0369→0416 a private journal is private
  0370→0418 a public bucket serves what you put in it
0412, 0413 and 0417 stay empty: they are f55f3591's copies of the three
migrations the author dropped in C1-S9-89 (social restriction, location
trail, record-about-you; confirmed by content against c8b5c56's
0324/0325/0329), and the drop stands. The ten files take f55f3591's
content, which differs from the author's only by number citations except
0416, whose SELECT rule takes main's 0364 owner-or-manager form (a
sibling never reads another member's entry; a parent reads only one its
owner marked not private) because main's private-journal-check.sql pins
it; recorded in the header, finalaudit C1-S8-07 and
PENDING_PROD_MIGRATIONS.

Every reference rewritten: the nine probes (eight now byte-identical to
f55f3591's; health-record keeps the author's comment on the logged_by
control), sensitive-role-boundary-check (the author's own a349bd6
reconciliation of the token controls, kept, citing 0406), PENDING_PROD
rows and the 0406 header's history, finalaudit B1 / C1-S8-07 / C1-S9-89
and every backticked or path-style filename, lib/database.types.ts and
paperwork/actions.ts (0327→0415), health-visits and immunizations
(0326→0414), insurance-module (0328→0416), care-module (cites C1-S8-09
rather than the dropped 0329), and the tests that read the migrations by
name (social-tokens, household-binder, public-bucket, paperwork-stamp,
manager-gated-table).

Tests adapted (from d41ac300, assertion strength kept): a-dialled-number
reads main's shared xml(); a-forwarding-number's fake answers .select()
with the affected rows; a-voice-log-failure pins the same best-effort
properties against lib/voice/history; a-fire-and-forget's ai-settings
anchor moves to main's log wording; a-client-write-reads-what-it-changed
accepts a file whose only confirmed writes are spelt with the bare
`data` (its C1-S9-81 closet paragraph from 0cf0eb2 kept).

Verified together with the merge of dcc0b42 (#581) that follows; the
numbers are on that commit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
NewWorldVenture pushed a commit that referenced this pull request Sep 27, 2026
main brought PR #581: migrations 0381–0387 (two-parent approvals in the
database, the money step-up guard, archived-page answers, the E.164
fallback number, member memories, calendar feed uniqueness, AUTHZ-011),
their probes, the AQ-01 currency work and finalaudit rows (index FIXED
99 · OPEN 10). Twenty-two files conflicted. This branch's ten migrations
keep 0406–0418: no collision.

Resolutions:
- catalogues (7): key union; billing.serviceFeeAddedAtCheckout takes
  main's text, whose placeholder is {amount} (AQ-01, pinned by main's
  a-german-family-reads-their-plan-price test).
- concierge/actions.ts: main's decide() route for approval-gated runs
  (0381) — the branch's two direct approval_requests stamps go with
  main's change, the executed-path one having written `approved` with no
  votes; kept from the branch: legacyStatusFor on the autopilot record
  (C1-S9-72), the confirmed `stamped`/`dismissed` writes (C1-S9-48), the
  error-bound plan read; main's completed_at on every terminal run (only
  state=completed is counted).
- contact-center/actions.ts: main's normalizeFallbackPhone (country code
  required, 0384's rule) on the write path, the branch's C1-S7-05 reason.
- lib/contact-center/server.ts: main's compare-and-swap claim, which
  subsumes C1-S9-69's confirmed save; the branch's failure log kept.
- billing/page.tsx: main's aal2 guard and locale-formatted amount with
  the branch's cautious notice on a refused settings read (C1-S9-71).
- recipes/vote/actions.ts: main's grocery-service path (covers
  C1-S9-75); the branch's close/reopen confirmations kept.
- admin/marketing/platform: main's stored-status read, the branch's
  C1-S9-61 note on the unconfirmed default clear. approvals service,
  twilio, chores-module, autopilot/scan, marketplace saved/store: unions.
- tests: service-memory keeps the branch's zero-rows case and takes
  main's author-or-subject teen case; upgrade-modal uses the branch's
  at() with main's suffix match; the version pin has main's seven
  paragraphs, then the branch's ten, at 0419.

Tests adapted to the other side, assertion strength kept:
a-dialled-number pins normalizeFallbackPhone and asserts it refuses the
same payloads and clears on empty; a-write-the-user-is-told-about-is-
confirmed asserts the concierge writes no approval row and decides
through decide(), and holds the run close that moved into the approvals
service to logged-not-raised; a-bought-number's fake models .is() and
answers the claim with the number written; a-dropped-component-read-
error follows main's tr/{amount} spelling.

Found by the verification and fixed without weakening:
- catalogues: a block-level pick had dropped the branch's
  billing.serviceFeeShownAtCheckout and actions.couldNotCheckThatRefresh;
  redone as a key-level three-way union (14,104 keys in each locale).
- a-visitor-is-hearted-subscribed-and-bucketed-only-as-itself (main):
  its in-memory fake answered every write with null; it now returns the
  changed rows when a write asks for .select(), as PostgREST does, which
  is what C1-S9-62's re-activation confirmation reads.
- an-unconfirmed-write-outside-actions-ratchet: main's
  retireAeoQuestionsForPath is deliberately unconfirmed (a page with no
  published answers retires zero rows); annotated beside the write and
  the baseline moves 51 → 52, as C1-S9-89 did for main's twin edge prune.
  The approvals service's C1-S9-65 reason moved back beside its write.
- a-write-the-user-is-told-about-is-confirmed: the forward_to_phone pin
  reads fallback.value; the executed-stamp ordering is checked against
  the success return it guards, now that decide()'s path returns first.

Also: finalaudit gains C1-S9-92 (this renumbering and both merges), the
B1 row, C1-S8-07 and every filename citation carry the 04xx numbers,
and C1-S6-11's pointer to the dropped 0324 is struck through to main's
0319.

Verified on a private database bootstrapped from this tree: 408
migrations applied, 0 failed (the two best-effort marketplace seed errors
are main's; SEED_ALL.sql and pg-bootstrap.sh are byte-identical to it);
run-probes.sh twice — 128 passed, 0 skipped, 0 failed, of 128, both runs.
audit-migration-versions: 408 files, next 0419. vitest on the 580 files
that changed or import a changed source, the guard suites included:
8,990 of 8,991; the one, a-failed-vault-probe-does-not-save-a-second-
copy, reads node_modules/@supabase/postgrest-js by a checkout-relative
path this worktree lacks and passes 16/16 where it exists. finalaudit
index: 124 rows, FIXED 99 · OPEN 10, prose agreeing. eslint: 0 errors on
the 571 .ts/.tsx files that differ from main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
NewWorldVenture pushed a commit that referenced this pull request Sep 27, 2026
…ing guards use at()

CI on 6e297ca caught two things the merge of main let through, both
flagged by this branch's own whole-tree ratchets.

lib/guardian/twilio.ts: main's releaseNumber (from #581) called
fetchExternal, which this branch had renamed to fetchWithDeadline and
whose module it had removed. The merge kept the branch's import and main's
call, so releaseNumber named a function that no longer exists - a
ReferenceError at runtime, leaving a number that should be released
billing. It now calls fetchWithDeadline with the same arguments.

tests/a-page-that-could-not-read-does-not-answer.test.ts (two asserts) and
tests/the-add-form-is-not-a-way-around-the-memory-edit-gate.test.ts (one)
came from main with ordering asserted on a bare indexOf, which passes when
the statement it names is deleted. They now use at() from
tests/helpers/source-order.ts, so absence fails.

Verified: both ratchets, both edited tests and every test that imports
the Twilio client pass, 25 files and 274 tests; eslint clean on the three
files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
NewWorldVenture pushed a commit that referenced this pull request Sep 27, 2026
…uplicate with a probe

main's #581 brings 0381-0387, whose numbers the block had already left.
Nineteen files conflicted; each keeps both sides' intent:
- concierge: main's decide() routing for a queued run's approval, with this
  branch's read-back on the run's own status updates (and its test case for
  a filtered dismiss); the direct approval stamps main removed stay removed;
- recipe vote: main's route through the grocery service's addItems; 0443 is
  still reached through ensureDefaultList, and main's grocery-list test models
  0443's function over the same state its list read answers from;
- calendar feeds: main's find-or-subscribe flow, with its raw save error
  classified rather than returned (the-database-does-not-talk-to-the-browser);
- paywall, graph, inventory, relationship: main's locale money and read-back,
  this branch's catalogue sentences and safe links;
- catalogues: key union in all seven locales;
- version pin: main's paragraphs through 0387, then this branch's block,
  rewritten for 0426-0443 and the four unused numbers; pin 0444.

Each of the four duplicates dropped in the renumber is now shown by a probe
going red with the file put back on a copy of the replayed chain: 0427 by
driving-score-write-boundary-check, 0436 by access-record-write-boundary-check,
0437 by main's private-journal-check, and 0431 by a new step in
locator-write-boundary-check that pins main's 0379 rule (with 0431 a child
files a check-in naming no member, and a parent cannot file one for a child).

The guards found, and this fixes: three guardian routes that dropped write
errors, a duplicated inbound-secret block in .env.example, and main's tests
whose fixtures predate this branch's rules (an active member, a private image
answered from the network). The 32 newly gated tables' twelve silent or
unscoped call sites are recorded in KNOWN_UNFIXED, not swept here.

The earlier renumber had spliced a drop annotation into file names and ranges;
those read cleanly again. Recorded in finalaudit.md Q70, audit/status.md and
docs/PENDING_PROD_MIGRATIONS.md (0438 is coupled with app code).

Verified at this state: replay 412/412, probes 137/137 twice, none skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
NewWorldVenture added a commit that referenced this pull request Sep 27, 2026
Final audit, continued: the verified units landing after #581
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