Skip to content

Fix super-admin access without requiring the DB migration - #5

Merged
NewWorldVenture merged 10 commits into
mainfrom
claude/pricing-page-updates-lwv4d9
Jun 20, 2026
Merged

NewWorldVenture merged 10 commits into
mainfrom
claude/pricing-page-updates-lwv4d9

Conversation

@NewWorldVenture

Copy link
Copy Markdown
Owner

Root cause

Daniel wasn't landing on the Admin Dashboard because super-admin detection depended entirely on the super_admins DB table (migration 0008). That migration isn't applied to production yet, so is_super_admin() returns false — and the post-login redirect sent Daniel to /dashboard (then /onboarding if he has no family), never /admin. The admin-layout gate would also have bounced him.

Fix (works on deploy, no SQL required)

Add a code/env super-admin allowlist that's checked alongside the database:

  • lib/constants/super-admins.ts — daniel.hughen@gmail.com built in, plus an optional SUPER_ADMIN_EMAILS env var (comma-separated) for adding others without a DB change. Case-insensitive.
  • isSuperAdmin() now checks the allowlist before the DB RPC, so the admin-layout gate and the dashboard "Site Admin" link work immediately.
  • The post-login redirect is resolved server-side via resolveLandingPathAction() (used by both the password login form and the OAuth callback), so the env/code allowlist is honored — not just the DB.

The DB-backed is_super_admin() RPC remains the source of truth when present; this is purely additive (never a downgrade).

Result

Across all three entry paths — password login, Google OAuth, and direct navigation to /admin — Daniel is recognized as super admin and lands on the Admin Dashboard, with no database migration needed. Applying migration 0008 later changes nothing for him; it just also seeds the DB.

tsc, ESLint, and a full next build pass.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 10 commits June 20, 2026 00:54
Drop the Free tier from the pricing page and make the Monthly/Yearly
toggle functional, defaulting to Yearly on load. Pricing cards now show
the selected billing period's price and pass it through to signup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHvvccxiTVzkDBugo4VbfD
The full app and admin were already Supabase-backed; the only remaining
static datasets were the marketing blog and the hardcoded social-proof
figures. This replaces both with real, database-backed data.

Blog:
- Add blog_posts table (migration 0010) with public-read RLS and the 9
  existing posts seeded idempotently; writes are service-role only.
- Rewrite lib/blog/posts.ts as an async Supabase data-access layer with
  the same BlogPost shape, degrading to empty on DB errors.
- Update the /blog routes to await the helpers and revalidate hourly so
  content edits surface without a redeploy.

Marketing figures:
- Add public_stats() RPC (migration 0011): anon-callable, SECURITY
  DEFINER, returns only non-identifying aggregate counts.
- Replace the hardcoded '10,000+ families' on the landing, pricing, and
  features/how-it-works pages with the real family count, via a cached
  server fetcher (lib/marketing/stats.ts) and client-safe formatters.

Add the new table + function to database.types.ts. Production build and
typecheck pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHvvccxiTVzkDBugo4VbfD
Daniel.Hughen@gmail.com already lands on /admin: he is seeded in
super_admins (0008) and both login paths route super admins there.
This rebuilds the dashboard itself and fills the one dead nav link.

Admin Dashboard (/admin):
- Stat cards (families, active users, subscriptions, monthly revenue)
  with real new-this-month context.
- System Status rail from real health probes (DB/storage) plus email/AI
  configuration readiness; System Health gauge derived from them.
- User-growth sparkline (30d new profiles), storage-usage gauge (summed
  document bytes), recent activity (audit logs), top families by members.
- Subscription donut by plan, revenue bars (new MRR, 6 months), support
  overview, and quick-action links — all from Supabase.

Supporting:
- New support_tickets table (0012) with service-role-only access; the
  contact form now persists each message as a ticket so the Support
  Overview is real.
- Add /admin/reports (Reports & Analytics) — previously linked in the
  sidebar but missing — wired to Supabase growth/revenue/activity data.
- Extract shared SVG chart primitives to components/admin/charts.tsx.
- Add checkAI() health probe.

Typecheck, lint, and a full production build pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHvvccxiTVzkDBugo4VbfD
…pdates-lwv4d9

# Conflicts:
#	app/(marketing)/pricing/page.tsx
#	app/(marketing)/pricing/pricing-content.tsx
Audit found every existing admin tab already fully Supabase-wired (real
data + mutations, no mock). This elevates them to world-class by adding
the capabilities the reference design implied:

- Support Tickets (/admin/support): manage contact-form submissions
  through their lifecycle (open/pending/resolved/closed) with search,
  status filters, and inline status control — wired to support_tickets.
- Audit Logs (/admin/audit): filterable, paginated, append-only record
  of sensitive actions across all families and the site admin.
- Security tab: new Accounts table with real ban/unban and
  password-reset actions via the Supabase Auth admin API (self-ban
  guarded, audit-logged).
- New server actions: adminSetUserBanAction, adminSendPasswordResetAction,
  adminUpdateTicketStatusAction.
- Add Support Tickets + Audit Logs to the admin sidebar; dashboard
  Support Overview and Recent Activities now link to them.

Typecheck, lint, and a full production build pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHvvccxiTVzkDBugo4VbfD
Replace the single 'Switch to Parent Role' link in the admin shell with
two clearly-labeled entries (dropdown menu and sidebar):
- Go to Parent Dashboard → /dashboard/briefing (Daily Briefing)
- Go to Family Dashboard → /dashboard (family overview)

Both target real routes gated by the dashboard layout; a super admin
without a family rides the existing /onboarding redirect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHvvccxiTVzkDBugo4VbfD
One paste-ready, idempotent file (0008 super-admin seed, 0010 blog,
0011 public_stats, 0012 support_tickets) for applying the pending
migrations via the Supabase SQL Editor or psql.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHvvccxiTVzkDBugo4VbfD
Root cause: super-admin detection relied solely on the super_admins
table (migration 0008). Until that migration is applied to the database,
is_super_admin() returns false, so Daniel was routed to /dashboard
(then /onboarding) instead of /admin.

Fix: add a code/env super-admin allowlist (lib/constants/super-admins.ts)
with daniel.hughen@gmail.com built in, plus an optional SUPER_ADMIN_EMAILS
env var. isSuperAdmin() now checks this allowlist before the DB RPC, so
site-admin access and the post-login redirect work on deploy with no SQL.

- isSuperAdmin() (admin + dashboard layouts) is now allowlist-aware.
- Post-login redirect resolves server-side via resolveLandingPathAction()
  so the env/code allowlist is honored (login form + OAuth callback).

The DB RPC remains the source of truth when present; this is additive.

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

vercel Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
familyos Ready Ready Preview, Comment Jun 20, 2026 2:19am

@NewWorldVenture
NewWorldVenture marked this pull request as ready for review June 20, 2026 02:19
@NewWorldVenture
NewWorldVenture merged commit f260a00 into main Jun 20, 2026
2 checks passed
NewWorldVenture pushed a commit that referenced this pull request Jul 4, 2026
Closes the roadmap #5 gap — "Build a persistent family knowledge graph" had no
backing table. Adds the durable facts store the family looks up again and again.

- Migration 0123_family_facts.sql: family-scoped RLS table (category, label,
  value, notes, member_id nullable = family-level, is_pinned) + updated_at
  trigger + indexes. Validated on PG16: RLS, 4 policies, category check
  constraint, idempotent.
- lib/memory/facts.ts: pure, tested (8 cases) — categories, factMatches,
  filterFacts (member/category/search, pinned-first then newest), groupByCategory.
- components/modules/knowledge-base-module.tsx at /dashboard/knowledge: search +
  member/category filter chips, grouped cards, add/edit, pin, copy value, delete.
  100% Supabase via useRealtimeQuery.
- Types for family_facts; nav entry (Family AI OS, Brain icon); todo.md +
  AGENT_HANDOFF updated. The family-knowledge-graph view can now read this store.

Verified: tsc, eslint, vitest (1637), next build (/dashboard/knowledge in manifest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qnr3AA4xBTeaJ4Lrm41o7J
NewWorldVenture pushed a commit that referenced this pull request Jul 5, 2026
…n't missed

Friction #5 (push notifications) — the web-push delivery path already existed
(lib/server/push.ts, push_devices, service worker, subscribe routes, settings
toggle) and imminentMomentNotices was wired into generateFamilyNotifications,
but pushes were only dispatched by the once-daily /api/cron/notifications run,
which also owns the email digest. Same cadence for both meant a moment or
reminder added after the daily run for later that day was missed until the
next day — after it mattered.

Add /api/cron/push-scan (every 2h) that regenerates notifications and
dispatches pending pushes only — no email, so the digest stays daily. Safe to
overlap the daily cron: generation dedups by related_id, push is gated on
pushed_at and email on sent_at, so nothing double-sends. Live delivery still
degrades honestly when VAPID/FCM keys are absent (skips, never fakes success).

Also reconcile FRICTION_BACKLOG.md: #2/#4/#7/#10 were shipped but still read
"Todo"; marked done with their landing files. Update AGENT_HANDOFF.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJs1Px4GTVtA1jmzDZ4ShU
NewWorldVenture added a commit that referenced this pull request Jul 5, 2026
…n't missed (#226)

Friction #5 (push notifications) — the web-push delivery path already existed
(lib/server/push.ts, push_devices, service worker, subscribe routes, settings
toggle) and imminentMomentNotices was wired into generateFamilyNotifications,
but pushes were only dispatched by the once-daily /api/cron/notifications run,
which also owns the email digest. Same cadence for both meant a moment or
reminder added after the daily run for later that day was missed until the
next day — after it mattered.

Add /api/cron/push-scan (every 2h) that regenerates notifications and
dispatches pending pushes only — no email, so the digest stays daily. Safe to
overlap the daily cron: generation dedups by related_id, push is gated on
pushed_at and email on sent_at, so nothing double-sends. Live delivery still
degrades honestly when VAPID/FCM keys are absent (skips, never fakes success).

Also reconcile FRICTION_BACKLOG.md: #2/#4/#7/#10 were shipped but still read
"Todo"; marked done with their landing files. Update AGENT_HANDOFF.md.


Claude-Session: https://claude.ai/code/session_01VJs1Px4GTVtA1jmzDZ4ShU

Co-authored-by: Claude <noreply@anthropic.com>
NewWorldVenture pushed a commit that referenced this pull request Jul 5, 2026
…apshot

Operating Layer slice 2 (pillar #5): a calm end-of-day "what changed" recap
that diffs today's Family Operating Index snapshot against the prior one —
reusing the state vector slice 1 established rather than re-querying.

- lib/operating-index/summary.ts (pure, 8 tests): summarizeChange(current,
  prior) -> headline + composite delta + improved/declined dimensions (>=5-pt
  moves) + resolved/emerged suggestions (by id). Leads with cleared items,
  stays silent on a flat day, handles the first-reading case.
- server: reads the prior full snapshot (dimensions + suggestions) and returns
  change: ChangeSummary alongside the index.
- FOI page: a "Since yesterday" card (headline + cleared/new/+-dimension chips).

summarizeChange is exported so the existing Command Center / Daily Briefing
surfaces can adopt it. Zero contended files touched.

Verified: tsc, eslint, 1669 vitest, next build (route registered).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJs1Px4GTVtA1jmzDZ4ShU
NewWorldVenture added a commit that referenced this pull request Jul 5, 2026
…apshot (#228)

Operating Layer slice 2 (pillar #5): a calm end-of-day "what changed" recap
that diffs today's Family Operating Index snapshot against the prior one —
reusing the state vector slice 1 established rather than re-querying.

- lib/operating-index/summary.ts (pure, 8 tests): summarizeChange(current,
  prior) -> headline + composite delta + improved/declined dimensions (>=5-pt
  moves) + resolved/emerged suggestions (by id). Leads with cleared items,
  stays silent on a flat day, handles the first-reading case.
- server: reads the prior full snapshot (dimensions + suggestions) and returns
  change: ChangeSummary alongside the index.
- FOI page: a "Since yesterday" card (headline + cleared/new/+-dimension chips).

summarizeChange is exported so the existing Command Center / Daily Briefing
surfaces can adopt it. Zero contended files touched.

Verified: tsc, eslint, 1669 vitest, next build (route registered).


Claude-Session: https://claude.ai/code/session_01VJs1Px4GTVtA1jmzDZ4ShU

Co-authored-by: Claude <noreply@anthropic.com>
NewWorldVenture pushed a commit that referenced this pull request Jul 5, 2026
Closes tracker #5 — summarizeChange was rendered on the Operating Index page
but not in the Command Center. The Command Center now loads the same FOI
change summary (loadOperatingIndex returns `change`) and shows a "Since
yesterday" recap card (headline + cleared/new/±dimension chips), so the evening
"what changed" view lives where families check their week.

Additive, null-safe (hidden on first reading). Verified: tsc, eslint,
vitest (1712), next build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qnr3AA4xBTeaJ4Lrm41o7J
NewWorldVenture pushed a commit that referenced this pull request Jul 5, 2026
One paste-ready, idempotent seed per pillar (targets a family by email; each
inserts >=500 records so every surface can be fully tested). Validated on PG16
(counts stable across re-runs):

- seed_pillar1_orchestrator.sql  — 500 calendar events (conflicts, overload,
  missing-info) + 12 open polls → the five orchestrator questions.
- seed_pillar2_twin.sql          — 500 events + teams/classes/routines/budgets
  → the Digital Twin's linked model + simulation.
- seed_pillar3_playbook.sql      — 500 suggested playbook facts (Save/Dismiss).
- seed_pillar4_outcomes.sql      — 200 events + 150 overdue to-dos + 150 open
  grocery items + a birthday → lights up every Outcomes badge.
- seed_pillar5_command_center.sql— 500 daily Operating Index snapshots; yesterday
  carries open suggestions so the "Since yesterday" recap shows cleared items.

Idempotent via per-seed sentinels / ON CONFLICT / upsert — never clobbers real
data, re-run is stable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qnr3AA4xBTeaJ4Lrm41o7J
NewWorldVenture pushed a commit that referenced this pull request Jul 6, 2026
Finish north-star pillar #5 (Family Command Center): the Operating Index
evening "what changed since yesterday" narrative now appears wherever a
family checks in, not just on the Operating Index page.

- NEW shared components/operating-index/change-recap.tsx — a server-safe
  presentational card that renders a ChangeSummary (headline, composite
  move, cleared/new suggestions, dimension shifts). Calm by default:
  silent on the first reading.
- The Family Operating Index page now reuses it (removed the duplicated
  inline card + its now-unused icon imports).
- /dashboard/command-center: new section diffing the two latest snapshots
  (summarizeChange) and rendering the recap under the header.
- /dashboard/briefing: the Evening tab shows the recap via a new optional
  `recap` prop threaded into EveningContent (server page reads the two
  latest snapshots and passes the rendered card in).
- supabase/seed_operating_index_all_families.sql: seeds yesterday+today
  snapshots across ALL families (deliberately different) so the recap
  renders a real narrative everywhere; idempotent upsert on
  (family_id, as_of_date).

Verified: tsc, eslint, vitest (1699), next build; migration 0125 + seed
validated on PG16 (6 rows across 3 families, second run idempotent).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mz4QW1Ti45CpqKabFHYmPb
NewWorldVenture pushed a commit that referenced this pull request Jul 12, 2026
Deepens industry-first #5 (household relationship CRM) into a real surface at
/dashboard/contacts/[id], linked from every contact card.

- lib/contacts/timeline.ts: pure timeline merge (logged visits/calls/messages/
  gifts/favors/notes + inbox communications + birthdays) and relationship
  health — last touch vs. the relationship's natural cadence (median gap) →
  fresh/due/overdue with a plain-language reconnect nudge. 8 tests.
- 0170_contact_interactions: family-scoped touches (kind CHECK, occurred_on,
  gift amounts). RLS via is_family_member, updated_at trigger.
- Mobile-first module: health card, log-a-touch composer (kind chips, date,
  amount, note), railed timeline with per-entry delete.
- seed_contact_interactions.sql: 500 rows across <=25 contacts (auto-creates 8
  starter contacts if none), varied cadences; in SEED_ALL.sql.
- Docs: PENDING_PROD_MIGRATIONS (0170) + AGENT_HANDOFF session state.

PG16-verified: migration idempotent x2, seed 500 x2, 4 RLS policies, trigger.
tsc/eslint/build green; full vitest suite 2390 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJBpzPzPrBbMzYFSVCQaTM
NewWorldVenture pushed a commit that referenced this pull request Jul 12, 2026
Deepens industry-first #5 (household relationship CRM) into a real surface at
/dashboard/contacts/[id], linked from every contact card.

- lib/contacts/timeline.ts: pure timeline merge (logged visits/calls/messages/
  gifts/favors/notes + inbox communications + birthdays) and relationship
  health — last touch vs. the relationship's natural cadence (median gap) →
  fresh/due/overdue with a plain-language reconnect nudge. 8 tests.
- 0170_contact_interactions: family-scoped touches (kind CHECK, occurred_on,
  gift amounts). RLS via is_family_member, updated_at trigger.
- Mobile-first module: health card, log-a-touch composer (kind chips, date,
  amount, note), railed timeline with per-entry delete.
- seed_contact_interactions.sql: 500 rows across <=25 contacts (auto-creates 8
  starter contacts if none), varied cadences; in SEED_ALL.sql.
- Docs: PENDING_PROD_MIGRATIONS (0170) + AGENT_HANDOFF session state.

PG16-verified: migration idempotent x2, seed 500 x2, 4 RLS policies, trigger.
tsc/eslint/build green; full vitest suite 2390 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJBpzPzPrBbMzYFSVCQaTM
NewWorldVenture added a commit that referenced this pull request Jul 12, 2026
Deepens industry-first #5 (household relationship CRM) into a real surface at
/dashboard/contacts/[id], linked from every contact card.

- lib/contacts/timeline.ts: pure timeline merge (logged visits/calls/messages/
  gifts/favors/notes + inbox communications + birthdays) and relationship
  health — last touch vs. the relationship's natural cadence (median gap) →
  fresh/due/overdue with a plain-language reconnect nudge. 8 tests.
- 0170_contact_interactions: family-scoped touches (kind CHECK, occurred_on,
  gift amounts). RLS via is_family_member, updated_at trigger.
- Mobile-first module: health card, log-a-touch composer (kind chips, date,
  amount, note), railed timeline with per-entry delete.
- seed_contact_interactions.sql: 500 rows across <=25 contacts (auto-creates 8
  starter contacts if none), varied cadences; in SEED_ALL.sql.
- Docs: PENDING_PROD_MIGRATIONS (0170) + AGENT_HANDOFF session state.

PG16-verified: migration idempotent x2, seed 500 x2, 4 RLS policies, trigger.
tsc/eslint/build green; full vitest suite 2390 passing.


Claude-Session: https://claude.ai/code/session_01VJBpzPzPrBbMzYFSVCQaTM

Co-authored-by: Claude <noreply@anthropic.com>
NewWorldVenture pushed a commit that referenced this pull request Jul 12, 2026
…andoff

- SEED_ALL.sql: the owner's re-upload (979deb8, which usefully guarded
  family_apps with to_regclass) was generated from a pre-#308 copy and dropped
  the seed_paperwork section — re-append it (47 seeds; PG16-verified x2, 500
  rows, idempotent against the shipped 0169_paperwork_items schema).
- AI Front Desk: add a "Paperwork Inbox — Bubaly reads it for you" card beside
  the outbound-calls card (the paper side of the front desk; complements the
  Communications Hub entry #308 added). No global-nav change.
- AGENT_HANDOFF.md: coordination note — the Fable lane independently built the
  same Paperwork Inbox and lost the push race; #308 is canonical, the duplicate
  is parked on local branch paperwork-fable-build. Next: #5 CRM timelines,
  #1/#3 autonomous execution loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018G7EyLjpFGBb9xJiGovim5
NewWorldVenture pushed a commit that referenced this pull request Jul 12, 2026
Deep audit: the Issuing pipeline was already sound (real-time authorization
with atomic reserve against the child's Spend bucket, capture/release,
idempotent money webhook, capability resolver, honest 3-mode Cards UI).
This pass fixes what was genuinely missing:

- tests/stripe-issuing-auth.test.ts: the auth decision was documented as
  "unit-tested separately" but no test existed. 7 tests: approve within
  balance, decline on inactive/frozen/blocked-category/insufficient,
  exact-balance edge, status-outranks-balance, unknown-category safety,
  precheck parity.
- Secure PAN reveal (was promised in issuing.ts, never built):
  prepareCardRevealAction + createCardRevealAction (manager-gated,
  family-scoped, capability-gated, audited to wallet_audit_logs) and
  CardRevealModal using Stripe.js v9 Issuing display Elements — nonce →
  ephemeral key → number/expiry/CVC rendered in Stripe-hosted iframes;
  the PAN never touches our servers. Reveal button on every card row.
- child-detail-view: stale "Coming soon · real-time balance check" on the
  virtual card art → links to /wallet/cards with honest live-gate copy.
- .env.example: STRIPE_MONEY_WEBHOOK_SECRET documented (the money webhook
  reads it; was entirely undocumented).
- Docs: todo.md build-out record + AGENT_HANDOFF block with the owner
  flip-live checklist (enable Issuing, feature flags, second webhook).

Verified: 2411 tests green (7 new), tsc/eslint clean.
NewWorldVenture pushed a commit that referenced this pull request Sep 20, 2026
…r half

Merge #6 with main (13 conflicts). The governing rule, as in merge #5: where
the two sessions fixed different halves of one defect class, keep both halves;
where one is strictly better, take it and say why.

CONFLICTS

- contact-center sms + voice/transcription: took main's urgent-delivery
  RECEIPT wholesale. It closes C1-S7-04 more strongly than my `filed.inserted`
  boolean did — the receipt claims the dispatch (compare-and-set into phase
  `dispatching`) before it can send, so it has no read-then-act race.
- kid-login-form: kept main's `disabled` guard AND this branch's `focus-ring`
  class. `.focus-ring` is now self-scoped to `:focus-visible` (C2-B01), so
  main's `focus-visible:` prefix was a redundant leftover.
- voice-module: kept main's staleness re-checks and `if (sb)` guard, this
  branch's catch-block ordering (report to the user BEFORE the history write,
  never downstream of a call that fails for the same reason), and the
  extracted `lib/voice/history.ts` over main's duplicate local helper.
- concierge: restored main's prompt line order (which its test pins) and kept
  this branch's untrusted-content fences. The inner `safeContactText` is
  load-bearing: `fenceUntrustedBlock` bounds with a plain `.slice()`, which
  would cut a surrogate pair; bounding scalar-safely first makes it a no-op.
- guardian/marketing tests + status.md + finalaudit.md: unioned. finalaudit.md
  now carries both registers, verified mechanically at 1,572 IDs with nothing
  lost from either side.

TWO THINGS `git checkout --theirs` SILENTLY COST, AND ONE LESSON

Taking main's `lib/server/push.ts` after verifying that ONE of my fixes to it
survived was not the same as verifying the FILE survived. It dropped:

- the SSRF re-check before each web-push send (C3-S5-03). The helper still
  existed; nothing called it. Restored, and main's two push tests now stub it
  the way this branch's already did.
- the retry window. main retried on any failure with no age bound at all, so a
  permanently dead endpoint held the row pending and re-buzzed the healthy
  devices on every scan, for ever, with nothing in the logs saying so.
  Restored as a union: main's broader retry CONDITION, bounded by age — and
  bounded for genuine failures only, since a `skipped` row is missing provider
  configuration, which gets repaired, unlike a dead endpoint.

GUARDS RE-POINTED AT BEHAVIOUR, NOT SHAPE

Nine of this branch's guards went red against main's rewrites. Each was
re-pointed at what it actually protects, and each re-pointing was proved red by
mutation before being trusted:

- native-push: main replaced legacy FCM with v1 + provider-specific APNs. The
  guard pinned the legacy literals. Now asserts the behaviours, including that
  a bare 404 must never prune a registration.
- every-server-action-reaches-auth: main added five pre-auth actions. Rather
  than widen a blanket allow-list past a password-change endpoint, each is
  listed WITH the credential check it must still reach (CREDENTIAL_GATED), so
  "pre-auth" cannot quietly become "unchecked".
- social-tokens: main's X token store is the service-role gateway this guard
  asks for, not a breach of it — exempted, but pinned to `createServiceClient`
  plus its own `requireUserContext`/`requireSocialPermission`.

Two of this branch's vacuity guards (C4-S5-01, C4-S5-02) caught eleven real
instances in main's tests — helper names asserted without a call site, and
ordering assertions on bare `indexOf`, which `-1` satisfies most convincingly
when the statement is gone. All fixed; none went red on conversion, so no
needle was actually missing.

One of my own assertions failed to kill its mutation because it matched a
different `else result.failed++` further down, and another passed vacuously on
an EMPTY slice because `at()` searches from the start of the file. Both
tightened.

GATE

tsc clean. 16,904/16,907 tests pass across 1,343 files. Lint 0 errors, 3
pre-existing warnings. The 3 remaining failures are two files BLOCKED on this
container running Node 22.22.2 against the repo's `.nvmrc` 24.21.0, which main
bumped; CI resolves Node from `.nvmrc`, and nvm cannot fetch 24 here. Not
claimed as passing.

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

This branch was successfully deployed

1 active deployment
Preview — 80a00c99 Deployed Jun 20, 2026 by vercel[bot]
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