Skip to content

Messaging lands at its reserved 0475/0476, and calendar dates, DST and paging are checked (feed claim held as reserved 0490) - #971

Closed
NewWorldVenture wants to merge 6 commits into
mainfrom
claude/roadmap-implementation-ld8bon
Closed

NewWorldVenture wants to merge 6 commits into
mainfrom
claude/roadmap-implementation-ld8bon

Conversation

@NewWorldVenture

@NewWorldVenture NewWorldVenture commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Status (2026-10-07): blocked, do not merge. Review 6048246219 found composition blockers. They are verified in 6048365063:

  • 0475 narrows existing history to rosters;
  • roster rewrites are allowed at runtime;
  • reaction and message locks invert the family-delete order;
  • the 0490 fallback is check-then-write;
  • counted-read and strict ICS date/TZID rules are missing.

The repaired combined candidate is being composed in #969. That comment lists the fixes unique to this branch for it to carry.

Summary

Private family messaging, landed at its reserved numbers 0475 and 0476. This is the messaging item of the goal: the history decision plus the unmerged repair branches.

History decision (from #854). The canonical family chat is created new and adopts no legacy group. Existing conversations keep their history and their roster. It is recorded in docs/chat-messaging-buildout.md.

Sources composed onto main. Each is a closed, unmerged PR; their branches are untouched.

Source Branch and head What it brings
#854 codex/chat-canonical-history-20261003 at 26c05e754 Base content: the canonical-history decision. It already contains #853's replay repair (4f15aa982).
#834 codex/chat-messaging-goal-20261002 Merged at its tip 0135f81bd: a chat turn the person stopped writes nothing more.
#853 later merges (b6650d824, 729d81f27) Not taken. They only re-add the legacy-group adoption that #854's decision replaced.

Added here

  • Migration 0293 is main's again, byte for byte. All three sources had rewritten it.
    • 0476 now checks notifications.related_id itself: it converts a uuid, keeps text and refuses anything else.
    • Its notice policies read the key through a security-definer lookup by notice id, so no policy depends on the column type. 0293 therefore re-applies after 0476.
    • scripts/verify-notification-key-migration.mjs proves this, and fails against the old 0476.
  • Production's schema without 0475/0476. Production's ledger ends at 0176. lib/messages/schema-compat.ts recognises a missing object only when the error names exactly that object (PGRST202/42883, PGRST205/42P01, PGRST204/42703). It warns once and names the migration, and lib/messages/legacy-schema.ts keeps main's behaviour for each path.
    • Messages page:
      • create, family chat, previews, read receipts and reactions fall back to main's behaviour;
      • Leave is hidden;
      • typing is off, and presence uses main's public channel;
      • mute stays device-local.
    • Messages service: main's family chat and access, and main's ten-minute duplicate check.
    • A browser-fixture test runs the real messages page on the old schema; it fails without the fallbacks.
  • Database probes. Each messaging probe clears the JWT claims it sets, so later probes run clean.
  • Code-structure tests. ordering-guards-fail-on-absence and query-builders-have-a-rejection-path pass.

Migrations (nothing is applied by this PR)

  • supabase/migrations/0475_messaging_conversation_privacy_and_delivery.sql
  • supabase/migrations/0476_messaging_notifications_preferences.sql

These are #834's reserved numbers and original names, under the owner's preserved map (recorded on #771 and in docs/PENDING_PROD_MIGRATIONS.md). After this PR the next free number is 0477, which is reserved for Surge (#892). The guard is unchanged.

Development databases that ran #834 recorded 0475/0476 under these same names. The CLI will not re-run them there, but their SQL is older, so re-run both files on those databases, 0475 then 0476.

Risks the owner should know before applying 0475/0476

  1. Each household's existing "Family Chat" becomes an ordinary private group, visible only to its roster.
    • A new, empty family chat appears beside it.
    • Members added after the old chat was made lose access to it.
  2. /api/ai/chat also writes ai_messages.structured_content, a column from 0250; main's assistant engine already writes it. Without 0250, saving a chat turn fails. This was not introduced here.
  3. The family-media storage bucket: 0475 no longer changes whether it is public (39f958f). That stays 0459's decision, which the owner has deferred in production (SEC-001, kept public on 2026-09-30). 0475 only adds the voice-note audio types to the bucket's existing allow-list. A guard in tests/a11-family-media-bucket.test.ts lets only 0459 write the flag.
    • Storage checks a list only when it has entries, so a NULL or empty list allows every type. Such a list is left as it is, so uploads are never narrowed to audio only (05ab40b).

    • verify-messaging-database.mjs runs the real 0475 against four bucket states, each in a rolled-back transaction:

      • public with a NULL list, and public with an empty list: both left as found;
      • private with a list: stays private and gains the six audio types once;
      • no bucket: none is created.

      The check fails against both earlier versions of 0475.

  4. On the old schema, the fallbacks are main's own weaker behaviour, and each logs one warning:
    • presence on a public channel;
    • family-wide access in the service;
    • reactions that can lose a concurrent update;
    • per-device mute.

Verification

  • tsc --noEmit is clean, and eslint on all changed files is clean. npm run lint gives 0 errors and 14 warnings against a limit of 15.
  • The i18n gate and the query audit pass. db:audit:migrations passes with the next version 0477.
  • Focused tests: 123 files (messaging, notification, chat, migration and document guards) pass in UTC, America/Los_Angeles and Asia/Tokyo. The only failure is stream-cancellation-runtime, which is environmental on Node 22 and also fails on main.
  • Full suite (UTC): everything passes except the known environmental files. One regression this lane caused, an-effect-knows-when-it-is-gone, is fixed in c967c4fb1 and passes in all three zones.
  • Browser fixtures: 22 of 22 pass, including the new old-schema test.
  • Database (local PG16, CI's steps):
    • pg-bootstrap.sh: 451 applied, 0 failed;
    • run-probes.sh: 183 passed, 0 skipped;
    • rehearse-ledger-repair.sh: 448 re-applied, 0 failed;
    • check-conflict-targets passes;
    • verify-notification-key-migration.mjs: 4 of 4;
    • verify-messaging-database.mjs: all checks pass.
  • Not run locally: the authenticated messaging E2E, which needs the disposable Supabase stack; CI runs it. No hosted Supabase or device smoke test.

Calendar: dates and DST, and the newer pagination fixes, checked

This is the calendar item of the goal. The feed-claim migration stays held as reserved 0490 in supabase/reserved/, and its probe in docs/audit/reserved/. The code works without it: on PGRST202/42883 it logs once and takes the existing check-then-write path.

Sources. Each is a closed, unmerged PR; their branches are untouched.

Source Branch and head What it brings
#923 claude/planner-sees-series-20261003 at 4b2534770 The planner, reminders, free slots and readiness see every week of a series through readCalendarOccurrences, with exact-count paging.
#908 claude/calendar-feed-fidelity-20261003 at e507246d4 Feeds keep the TZID zone, attach RECURRENCE-ID exceptions to the series and drop STATUS:CANCELLED. Only one sync runs per feed: a syncing claim with a fence and a 10-minute takeover. Brings the 0490 function (held).
#913 Nothing taken. Everything it adds beyond #908 needs its unlanded migration 0482.

Added here, each with a regression test that fails without the fix

  1. Paging is checked.

    • A page that repeats a row an earlier page returned now fails the read, naming the row; it was silently skipped.
    • The cases also cover a stuck server, an off-by-one offset, empty pages, more rows than the count, and the request shape (exact count, unbounded first request, ordered by starts_at then id).
  2. DST nights follow RFC 5545 §3.3.5. A time the clock shows twice is read as the first instant; a skipped time takes the offset in force before the gap.

    • The rule is a new instantForIcsLocalTime in lib/time/zoned.ts.
    • It applies to the feed sync, the switching importer, weekend discovery and the recurrence expander, which used to walk a skipped 02:30 forward to 03:00.
  3. An all-day event is drawn on its own date on every clock. These used to show it a day early west of Greenwich:

    • the free-slot finder and busy evenings, and the scheduling API;
    • the calendar page;
    • the briefing and its kitchen view;
    • the event modal and the heat map;
    • the reminder body (timeLabel in lib/server/notifications.ts). In New York at 08:00 on 21 September, the all-day 22nd read "today" (e540cc2);
    • the "Get ready" moment label (momentWhen in lib/moments/prep.ts), which disagreed with its own dedupe key (e540cc2).

    The shared helpers are in lib/calendar/day.ts and expandForFamily. The tests cover Los Angeles, Tokyo and UTC, and both 2026 DST Sundays.

  4. Reminder keys carry the family's day, not Greenwich's. Before, two days of a daily series shared one key on spring-forward night, and one day's reminder never fired.

  5. The assistants' calendars use the shared occurrence read. This covers list_upcoming_events, the conflict read, find_free_time and the speaker's agenda. find_free_time now also refuses impossible dates.

  6. A documented limit: an imported series is stepped on the family's clock, not its publisher's. No column carries the source zone.

Behaviour changes to know about

  • The detail modal's all-day label is now "All Day" (from calendar.allDay).
  • The heat strip counts the whole of today.
  • The speaker's agenda lists all-day rows first.
  • The moment dedupe keys changed for one-off events too. A one-off whose UTC date differs from the family's date could be pinged once more around the deploy.

Still open (outside this scope)

  • The kitchen view's read still doesn't include recurring events.
  • A multi-day all-day row still appears only on its start date in the grid.

Combined verification (this branch)

  • Messaging lane alone: see the verification above.
  • Calendar lane alone:
    • tsc --noEmit and eslint on its 65 changed files are clean.
    • The migration, query and i18n audits pass.
    • 304 calendar, briefing, planner, notification, assistant, feed and DST files pass in UTC, America/Los_Angeles and Asia/Tokyo: 4,920 tests each.
    • The full suite in America/Los_Angeles has only the known environmental failures.
  • Combined branch:
    • db:audit:migrations passes with next version 0477; 0488 and 0490 are held.
    • The query audit and i18n gate pass.
    • The two files that conflicted (tests/assistant-engine.test.ts, whose mock now keeps both messaging's ordering and calendar's count, and the version pin) pass with the migration and document guards and the code-structure tests: 662/662 in UTC and Asia/Tokyo.
    • Full suite on the combined tree (UTC): 26,722 passed. Only 3 tests failed, all because this container runs Node 22 while the repo pins 24.21.0, which CI uses: node-version-is-pinned and stream-cancellation-runtime.
    • After the family-media change, verify-messaging-database.mjs passes again on a disposable PG16 cluster.
    • The all-day label fixes pass in UTC, America/Los_Angeles and Asia/Tokyo: 185 moments and label tests, and 1,526 notification, calendar and reminder tests.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K


Generated by Claude Code

…ging are checked, with the feed claim held as reserved 0490

Messaging (#834, #853, #854, composed onto main):
- The canonical family chat is created new and adopts no legacy group
  (#854's decision); #834's tip (a stopped chat turn writes nothing
  more) is included; #853's later re-adoption merges are not.
- 0475_messaging_conversation_privacy_and_delivery and
  0476_messaging_notifications_preferences keep #834's reserved numbers
  and names under the owner's preserved map; next free is 0477.
- 0293 stays main's byte for byte; 0476 checks notifications.related_id
  itself and its notice policies read the key through a lookup, so 0293
  still re-applies after it.
- On production's schema without 0475/0476 the messages page and service
  fall back to main's behaviour, recognising only the exact missing
  object (lib/messages/schema-compat.ts, legacy-schema.ts).
- Messaging probes clear the JWT claims they set.

Calendar (#923, #908, plus the date and DST fixes):
- Planner, reminders, free slots, readiness and the assistants read every
  week of a series through readCalendarOccurrences, paged against an
  exact count; a page that repeats a row is refused.
- Feeds keep the TZID zone, attach RECURRENCE-ID exceptions to their
  series and drop cancellations; one sync runs per feed under a claim.
  calendar_feed_apply_sync is reserved as 0490 and held in
  supabase/reserved/; without it the sync takes its check-then-write path.
- DST nights follow RFC 5545 3.3.5 in the ICS readers and the expander.
- An all-day event is drawn on its own date on every clock; reminder keys
  carry the family's day.

No migration is applied by this change.

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

vercel Bot commented Oct 5, 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 Oct 5, 2026 2:22am UTC

Request Review

Copy link
Copy Markdown
Owner Author

Audit coordination at007d4c00940492120bdf21951f83aec1f8dd5608: the restored messaging0475/0476 and held feed0490 align with the confirmed preserve-existing numbering direction, subject to exact composition review. That direction does not approve the disclosed family-media public→private change, any old-audience change, or applying migrations.

Keep the family-media policy conflict explicitly blocked and identify/isolate that behavior before release; earlier voice-history/link-log consent is unrelated. The direct Family Chat choice is also still pending in this coordination channel, despite reported private-session intent elsewhere. Preserve existing user data/access evidence and make no existing-data conversion or storage-setting change on the basis of this candidate.

Please report exact source/test ownership, retained parent heads and composition evidence for the published calendar/messaging candidate. No new implementation writer, production application, merge or deployment is assigned by this note.

Copy link
Copy Markdown
Owner Author

Bounded calendar recheck at007d4c00940492120bdf21951f83aec1f8dd5608: the known DST occurrence-key collision and missing/changing-count readiness cases are corrected in source, with targeted regressions. All-day handling is corrected in free slots, busy evenings and scheduling, but one label case remains.

Next bounded source/test task for the current calendar owner: lib/server/notifications.ts timeLabel still calls dayKeyInTz on the stored UTC-midnight value before handling allDay. New York, now September21 at08:00, all-day September22 stored as2026-09-22T00:00Z produces “today” rather than “tomorrow.” Preserve the stored all-day date in that label path and add a notification-body assertion; the existing all-day test checks only the dedupe key. Keep timed-event formatting and the newly corrected keys/pagination unchanged.

This review used source inspection and synthetic date arithmetic, not test execution. It covers only those three calendar findings, not messaging/storage policy, migration application, full-PR readiness, merge or deployment. No second writer or broader edit scope is assigned.

Making family-media private is 0459's decision, which the owner has
deferred in production (SEC-001, kept public on 2026-09-30). 0475 also set
public = false, so applying messaging would have applied that deferred
decision. It now only adds the voice-note audio types, and only where the
bucket already has an allow-list (0418 sets one); a NULL list allows every
type and appending to it would narrow uploads to audio.

A new guard allows only 0459 to write the bucket's public flag; it fails
on the previous 0475. The messaging database verifier passes on a
disposable local cluster, including 0459's family-media probe.

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

Copy link
Copy Markdown
Owner Author

Bounded source verification at39f958fa76d84cbb258a033ff3461ebfe33c985b confirms0475 no longer assigns the family-media public flag. Existing visibility is preserved, an absent bucket is not created, NULL MIME lists remain untouched, and non-null lists keep the previous union of existing entries plus the six audio types.

Keep the scope of that claim narrow: unchanged0459 still assigns public=false, and this correction does not undo an older0475 already executed. The new tests are textual guards, not execution of null/empty/absent-bucket cases or a general SQL safety proof. No additional unintended configuration change was found in this diff, but no SQL/tests/live storage were exercised in the independent review. Existing production/privacy approval holds remain.

An all-day row is stored at its date's Greenwich midnight
(lib/calendar/day.ts allDayDate). Two labels still read that instant on
the family's clock, so west of Greenwich it fell on the previous day:

- the reminder body (lib/server/notifications.ts timeLabel). In New York at
  08:00 on 21 September, the all-day 22nd read "today" and the 23rd
  "tomorrow";
- the "Get ready" moment label (lib/moments/prep.ts momentWhen). In Los
  Angeles, 5 July read "Today", while the moment's own dedupe key already
  said the 5th.

Both now use the stored date. Timed labels are unchanged, and a regression
asserts one beside the all-day cases. The birthday-label test that pinned
the instant reading now expects the stored date.

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

Copy link
Copy Markdown
Owner Author

Reply to 5986486509 and 5986515122.

Family-media is isolated (39f958f).

  • 0475 no longer writes storage.buckets.public. Whether family-media is public is still decided only by 0459, which the owner deferred in production (SEC-001, kept public on 2026-09-30).
  • 0475 still adds the voice-note audio types, but only where the bucket already has an allow-list (0418 sets one). A NULL list is left alone, so uploads are never narrowed to audio.
  • A new guard in tests/a11-family-media-bucket.test.ts lets only 0459_family_media_is_read_by_the_family.sql write that flag. I checked that it fails against the previous 0475.
  • scripts/verify-messaging-database.mjs passes on a disposable local PG16 cluster, including 0459's family-media-answers-to-the-family-check.sql.

The all-day label is fixed (e540cc2).

  • timeLabel in lib/server/notifications.ts now reads an all-day row's stored date (allDayDate).
  • Regression in tests/a-reminder-is-keyed-by-the-familys-day.test.ts. It uses New York at 08:00 on 21 September and asserts the bodies:
    • the all-day 22nd reads tomorrow;
    • the all-day 23rd reads Wednesday;
    • a timed 20:30 EDT event reads today at 8:30 PM, unchanged.
  • Before the fix it failed with today. It passes in TZ=UTC, America/Los_Angeles and Asia/Tokyo.
  • The same defect was also in momentWhen in lib/moments/prep.ts, the "Get ready" label. There, 5 July in Los Angeles read "Today" while the moment's own dedupe key said the 5th, so it is fixed in the same commit.
    • tests/a-birthday-label-reads-the-familys-calendar-date.test.ts had pinned the instant reading. It now expects the stored date.
    • Timed labels are unchanged.

Family Chat is still pending. Applying 0475 turns an existing "Family Chat" into a private group, and a new empty canonical chat appears. That is #854's design, and it is disclosed as risk 1 in the PR body. Nothing here decides it, and nothing here applies a migration. If the owner chooses to keep the legacy audience, 0475's conversion block is what changes.

Composition evidence. The base is main 82f2db1, which has #970. Both lanes were built on 7dfa47d plus #970's bill commits, and those bill commits are now on main.

Lane Retained heads Excluded
Messaging (lane/messaging-rebuild 9ad138c8e) #854 26c05e754 (canonical history); #834 tip 0135f81bd (a stopped chat turn writes nothing more); #853 4f15aa982 b6650d824, 729d81f27
Calendar (lane/calendar-rebuild ee0fab556) #923 tip 4b2534770, including c8d5a3cb9; #908 e507246d4 #913 contributes nothing

The messaging heads are ancestors of the lane: they were merged, and git merge-base --is-ancestor confirms it. The calendar heads were ported by content, not merged. Comparing each source head's files with this PR's head:

Changes made while composing:

  • 0293 is restored to main byte for byte.
  • 0476 checks related_id itself.
  • The feed claim is held as supabase/reserved/0490_….
  • The bill anchor stays held as 0488.

Which lane owns each file. Compared against each lane's diff from main:

  • Messaging only: 73 files.
    • Migrations: 0475 and 0476.
    • Code: lib/messages/* (schema-compat, legacy-schema, thread-state, overview), lib/services/messages, components/modules/messages-module.tsx, app/api/ai/chat, lib/ai/*, push and notification-email.
    • Probes: docs/audit/messaging-*, plus the sender, owner, read-receipt and removed-member checks.
    • scripts/verify-messaging-database.mjs, the seeds, and the mobile assistant history.
    • 29 test files, including 3 e2e specs.
  • Calendar only: 66 files (plus the two label fixes above).
    • lib/calendar/* (day, event-range, feeds, occurrences, recurrence, scheduling, heatmap) and lib/time/zoned.ts.
    • lib/sync/ics.ts, lib/server/calendar-feeds.ts and lib/server/notifications.ts.
    • lib/briefing/*, lib/assistant/* and lib/moments/notify.ts.
    • The calendar, briefing and readiness UIs, the held 0490 and its reserved probe.
    • 35 test files.
  • Both lanes: 10 files, resolved by hand:
    • docs/PENDING_PROD_MIGRATIONS.md;
    • the seven lib/i18n/messages/*.json files (messaging keys plus briefingView.allDayEvent);
    • tests/assistant-engine.test.ts: the mock keeps messaging's sort and limit and adds calendar's count;
    • tests/migration-version-safety.test.ts: the pin is 0477, with 0488 and 0490 named as held.
  • Neither lane: tests/a11-family-media-bucket.test.ts, the isolation guard above.

No migration has been applied, nothing has been merged, and nothing has been deployed from this PR.


Generated by Claude Code

claude added 2 commits October 5, 2026 01:37
…wered

Production serves main 82f2db1 (#970), and the smoke routes return 200.
The checkpoint states what #970 and #971 carry, the held 0488/0490, and
the owner-only items still open: the rejected service-role key and
migrations from 0177, allowance_rules, the Family Chat choice, and
re-running 0475/0476 on development databases. No audit rows are closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Storage checks a bucket's allowed_mime_types only when the list has
entries, so an empty list, like NULL, allows every type. 0475 appended
the voice-note audio types to any non-NULL list, which would have
narrowed an empty one to audio only. It now updates only a list with
entries (cardinality > 0).

verify-messaging-database.mjs now runs the real 0475 against four bucket
states, each in a transaction it rolls back:
- public with a NULL list, and public with an empty list: both left as
  found;
- private with a list: stays private, and gains the six audio types once;
- no bucket at all: none is created.
The check fails against the previous condition (empty list) and against
the original 0475 (public flag).

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

Copy link
Copy Markdown
Owner Author

Re 5986580096: the bucket cases are now executed, and one of them found a gap (05ab40b).

  • Empty allow-list. Supabase Storage validates allowed_mime_types only when the list has entries (allowedMimeTypes.length > 0 in storage's uploader.ts). So '{}' allows every type, just as NULL does. The previous is not null condition would have narrowed an empty list to audio only. 0475 now updates only a list with entries: cardinality(allowed_mime_types) > 0.
  • The cases are run, not only matched as text. scripts/verify-messaging-database.mjs runs the real 0475 against four bucket states, each in a transaction it rolls back:
    • public with a NULL list: left as found;
    • public with an empty list: left as found;
    • private with {image/png, audio/webm}: stays private, and ends with exactly the six audio types plus image/png;
    • no bucket: none is created.
  • Negative controls on a disposable PG16 cluster:
    • the is not null version fails on the empty-list case;
    • the 007d4c0 version fails on the public NULL-list case;
    • the current version passes, along with every other messaging check.

Agreed on scope: 0459 still sets public = false when it is applied, and a database that already ran an older 0475 is not reverted by this change.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Independent bounded recheck confirms all three previously reported calendar findings are corrected in inspected source at05ab40bae56a57ad43da077313aad86a5368d7a6. Reviewed files are byte-identical to9da0736: all-day notification bodies now use the stored date, timed labels are unchanged, family-day DST keys remain fixed, and count/pagination/readiness fail-closed handling is preserved. The analogous prep label fix and direct notification-body/timed-control regressions are also present.

This closes those three source-review findings only. No tests or CI were executed in this review, and the newest messaging verification/test delta was not reviewed. Combined-PR readiness, Family Chat/privacy choices, exact-head CI, held migration release and production application remain separate gates. No audit-row status change, merge or deployment is performed by this note.

Copy link
Copy Markdown
Owner Author

Bounded05ab40b recheck confirms the empty-array preservation correction:0475 changes only populated MIME lists, leaving NULL/empty unrestricted representations and the public flag unchanged; an absent bucket remains absent. Official Supabase Storage uploader source treats a nonempty allowlist as the MIME-validation condition, supporting this premise without establishing the deployed server version.

The disposable verifier contains four actual migration-in-transaction cases (public/NULL, public/empty, private/populated, absent) and assertions that would catch the two earlier regressions. Execution and negative-control outcomes remain author receipts, not independently run here. No new blocker found within this specific delta; unchanged0459 and previously executed migrations still need separate handling. This is not overall privacy-policy or production-application approval.

The calendar module and the recurrence expander now import
lib/calendar/day.ts (familyFetchRange, allDayDate, compareOccurrences). The
browser fixture in calendar-navigation.spec.ts loads only the modules it
lists and stubs every other import with functions that return null, so
familyFetchRange returned null and the module threw "Cannot read
properties of null (reading 'from')" in all ten cases. The fixture now
loads day.ts too. All ten pass locally in Chromium, and the unfixed spec
fails locally with CI's error.

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

Copy link
Copy Markdown
Owner Author

Independent review of exact draft head 7ee2e4a found composition blockers with #969/main: recorded participant unions and existing-history access must survive; the oldest group must not be adopted as Family Chat; authenticated audience rewrites would expose history; preliminary reaction row locks can invert family-delete lock ordering; missing feed-claim RPC must refuse writes rather than use check-then-write fallback; counted calendar reads and strict date/TZID rejection must survive.

Repairs are being composed and tested in the isolated #969 branch without editing this author branch. The owner explicitly confirmed preservation of main's map: messaging0475/0476, bill0488 held and feed0490 held. Existing history remains unchanged and Family Chat starts empty. Full source-zone/finite recurrence/exception fidelity and production catalog acceptance remain open. Prior green draft/cfa checks do not prove the new combined source; fresh checks will follow publication. No production configuration, hosted migration, provider execution or money movement performed.

Copy link
Copy Markdown
Owner Author

Re 6048246219: I checked each finding against this head (7ee2e4a), main (82f2db1) and #969 (cfa133b). The repaired composition is being built in #969, so I am not pushing parallel edits to the same migrations here. #971 stays a draft and will not be merged as it stands. Below is the evidence, plus the items unique to this branch that the composed candidate should carry.

Messaging

  1. Existing-history access: confirmed. On main, every active member reads every conversation and message in the family (0368:23-24, 0367:61-62). 0475 replaces those policies with a roster check (0475:20-30, 119-126, 387-388), and the 0476 notice query uses the same rule (0476:135-138). That check is exclusive: participant_ids wins whenever it is non-empty, and member_ids and created_by are then ignored. These people would lose history:

    • members missing from a roster, including everyone who joined after the legacy Family Chat was created;
    • seed DMs' creators, which store participant_ids=[other] and member_ids=[self, other].

    There is also a runtime loss. The settings dialog seeds its selection from participant_ids only (messages-module.tsx:1628,1635-1641). Renaming a legacy group with an empty roster therefore writes participant_ids=[self], and the guard (0475:72-96) rebuilds member_ids from that.

  2. Oldest group adopted: not confirmed once 0475 is applied. ensure_family_conversation inserts a new flagged row (0475:142-145), and nothing sets the flag on an existing row. Before 0475 is applied, the fallbacks keep main's behaviour of showing the oldest group as the family chat (services/messages/index.ts:64-78, legacy-schema.ts:44-47, messages-module.tsx:255). They never persist a flag. If Family Chat must start empty on the old schema too, these fallbacks have to change.

  3. Audience rewrites: partly confirmed. There is no migration-time rewrite. At runtime, though:

    • UPDATE on member_ids and participant_ids is granted (0475:111);
    • the update policy lets a creator or manager add a member, who then reads all prior history, since there is no join-time cutoff (0475:72-77, 387-388);
    • a roster edit on a legacy row also adds created_by (0475:83-84).

    Preserve household access and complete calendar, meal, finance and messaging reads #969's model (UPDATE only on name, avatar, description and archived; the guard rejects audience changes) closes this.

  4. Lock order: confirmed. The reaction RPC locks the message (0475:403), then the reactor's membership (message_guard, 0475:252-254). A family delete locks the family, then the members it cascades, then messages. That is an inversion, and either side can be aborted with 40P01. Removing line 403 is not enough: a BEFORE ROW guard runs after its row is locked, so the read-through and delete guards (0475:424, 311-313) and sends (0475:270-272) invert the same way. Preserve household access and complete calendar, meal, finance and messaging reads #969's BEFORE STATEMENT parents-first triggers are the fix.

Calendar

  1. Missing claim function: confirmed. Without 0490, applyChunk (lib/server/calendar-feeds.ts:288-325) checks the fence (holdsClaim) and then writes in separate statements. A stalled sync can therefore land after a takeover. The fallback also uses the caller's feed.family_id rather than the locked feed's.
    • The fix is to drop the fallback and fail the sync as "needs migration 0490", releasing the claim.
    • Product effect: 0490 is held, so this turns feed sync off everywhere until 0490 lands. While that is so, adding a calendar always fails, because the add action rolls back a feed whose first sync fails (app/(app)/dashboard/sync/feeds/actions.ts:141).
    • Tests that assert the fallback: a-subscribed-calendar-keeps-its-zone-its-series-and-its-cancellations.test.ts (the without-0490 describe and the "sync against the family calendar" describe) and a-calendar-that-failed-to-add-is-not-a-subscription.test.ts.
  2. Counted reads and strict parsing.

Unique to #971, for the composed candidate to carry

  • Family-media: 0475 must not write storage.buckets.public (0459 owns it), and must append audio types only where cardinality(allowed_mime_types) > 0. The executed cases are in verify-messaging-database.mjs (39f958f, 05ab40b).
  • All-day labels: timeLabel (lib/server/notifications.ts) and momentWhen (lib/moments/prep.ts) read the stored date (e540cc2).
  • E2E fixture: calendar-navigation.spec.ts must load lib/calendar/day.ts once the module imports it (7ee2e4a).
  • Numbering: 0475/0476 messaging, with 0488 and 0490 held.
  • RFC 5545 §3.3.5 DST reading (instantForIcsLocalTime), and family-day reminder and moment keys.

No migration, production setting or merge was touched.


Generated by Claude Code

@NewWorldVenture

Copy link
Copy Markdown
Owner Author

The allocation-preserving composition is now pushed to #969 at 2fd2e4f; this author branch is unchanged. Review caught and preserved the unique assistant owner/thread/mobile-history fixes, notification conversion verifier, pre-limit in-app chat delivery exclusion, conversation links, meaningful regression guards and fixture registration. All 154 changed paths are present in the composed inventory; 75 differ because the composition retains separately reviewed stronger guards or truthful release qualifications. This inventory is not a claim of complete behavior parity.

Current focused acceptance: 898 assertions in 42 suites, full nonincremental TypeScript, scoped lint, 29 actual PostgreSQL concurrency checks, seven assistant and twelve calendar browser cases, and a network-blocked production-mode build with 246 pages. The messaging runner now drains stderr before checking the unchanged SQLSTATE assertions. The prior candidate's 25 failed Core suites have targeted repairs; fresh exact-head hosted CI is required before a superseding closeout.

Main's allocation remains messaging0475/0476 with0488/0490/0492–0494 held. The new held RPC dependencies would reduce feature availability if deployed without their contracts; the SQL apply preflight does not gate Vercel. Production catalog verification still returns401. Keep this PR open until fresh composition acceptance and final superseding review; do not treat the current source publication as production acceptance. finalaudit.md contains dated receipts and the unresolved release gates.

@NewWorldVenture

Copy link
Copy Markdown
Owner Author

Superseded by #969 at b7a986e after independent source reconciliation and exact combined CI acceptance. All154 author-changed paths exist in the composition:83 exact blobs and71 reviewed differences. Assistant web/native ownership, cancellation/history, messaging delivery, notification conversion, calendar dates and counted-read repairs are retained or strengthened.

This is qualified supersession, not literal behavior parity. Experimental Leave chat and historical audience editing, unsafe pre0475 compatibility paths, lossy recurring-feed admission and check-then-write feed fallback are deliberately omitted under the selected privacy and atomicity contracts. Existing conversations retain their original recorded audiences and Family Chat starts empty.

Exact CI37702448315 passes:27,076 unit assertions in each of three timezones, build/typecheck, Database/Mobile and2,462 browser tests (four skips). Full publisher-clock/recurrence/exception fidelity and production compatibility remain open. Main and production are not changed by this closeout. Reserved migration allocations remain held. The new #972–#976 draft stack is additional unmerged work and is not closed or claimed superseded here. Author branch is preserved.

NewWorldVenture pushed a commit that referenced this pull request Oct 9, 2026
…osed, carried by #969)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
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