4001 Commits
Author SHA1 Message Date
Marc-André Bombeck c11c462e57 fix: answer a duplicate measurement with 409 on both write paths
The batch path had no unique-violation handling at all, so one duplicate
anywhere in the transaction answered 500 — indistinguishable from a
server fault, and what every client retries against. The single path
narrowed with instanceof, the form prisma-errors.ts exists to replace.
2026-08-06 20:56:16 +02:00
Marc-André Bombeck bcf2322f65 feat(02-12.1): vary the record echo on the headers that produced it
A shared cache is one reverse proxy away, and without Vary the first one
to appear would key a delegated record response on URL alone and hand it
to the next request under a different context. Appends rather than
replaces, so a route that already varies keeps what it had.
2026-08-06 20:51:29 +02:00
Marc-André Bombeck dc343f434c docs(02-12.1): narrow two overstated claims
- the idempotency arm refuses only a wrongly-asserted context; an admin
  route behind unfenced requireAdmin still serves a headerless request
- the epoch trigger does not fire on a statement that names record_epoch
  without moving the selector; the source guard is what covers that
2026-08-06 20:48:50 +02:00
Marc-André Bombeck 731203bdf4 fix(02-12.1): reconcile without firing the reads it is holding
- keep the record entries until /api/auth/me has answered: clearing the
  cache under a mounted observer made the held tab refetch at once
- exclude the account cell from the cancellation, and swallow a repeat
  trigger, so a burst of 409s cannot kill the request that ends the hold
- read the account payload network-only; the default staleTime would
  have returned the very context the browser was told is wrong
2026-08-06 20:45:07 +02:00
Marc-André Bombeck 0fee005683 fix(02-12.1): give an unprovable record context a way out
A selector pointed at a record whose grant has lapsed reports the same
disagreement on every /api/auth/me, so routing it to the hydration gate
rendered a permanent spinner with no controls. Expiry reaches this state
on its own: revocation clears the selector inside its transaction and
expiry does not.

- treat an unprovable context as a refusal, which paints the door with
  the button back to one's own record
- check the refusal ahead of the hydration gate, so neither trigger can
  render behind a spinner again
2026-08-06 20:40:31 +02:00
Marc-André Bombeck ffe38c9bb6 fix(02-12.1): reach fenced document routes through the app transport
A browser-issued <img src>, <iframe src> or <a href download> cannot
carry the record-session assertion, so the fence refused all three on
any session that had been inside a shared record — permanently, since
none of them will ever send a header.

- add a structural guard: no client file may name a record-fenced route
  in a src=, href= or window.location sink
- fetch the thumbnail, the inline preview and the download through the
  app transport and hand the DOM an object URL, revoked on unmount
- make the two API probes in the managed-profiles browser spec assert
  the same context the app beside them asserts
2026-08-06 20:36:45 +02:00
Marc-André Bombeck 8fcbdcc0df test(02-12.1): prove the record-session fence end to end
- give every acceptance case a stable id and assert the mapping from a
  runnable artifact, recording which cases execute in the ordinary gate
  and which await the browser stage
- add the browser spec for the cases no cooperative layer can reach:
  peers reconciling to an exact context, an aborted switch response, a
  closed initiator, a delayed response, and the offline disk layers
2026-08-06 19:33:15 +02:00
Marc-André Bombeck 31b4857d8b feat(02-12.1): carry and verify the record context from the browser
- attach the asserted context to every same-origin request inside the
  three fetch entries, so no call site classifies itself
- discard a response whose echoed context contradicts the adopted one;
  serve one that carries no echo, which is what keeps the public poll
  and every actor surface working
- adopt from /api/auth/me and the switch response, and from nothing else
- send the epoch on every switch and retry once after reconciling
- hold and reconcile on the 409 instead of leaving the record
2026-08-06 19:10:02 +02:00
Marc-André Bombeck 3a99de312e fix(02-12.1): check record context before an idempotent replay
- evaluate the fence in the wrapper above findCached, so a stale request
  neither replays a body, learns a cell exists, nor inserts a claim
- return the refusal rather than throwing: one route composes the
  wrapper outside apiHandler, where a throw escapes the envelope
- read the claim and the session's own context on one session lookup
2026-08-06 18:54:20 +02:00
Marc-André Bombeck 45fec0fc1b feat(02-12.1): refuse a record request whose context has moved
- both record resolvers check the fence after authentication and before
  the carrier, off the one session row that also decides the scope
- POST /api/account/switch takes an optional expectedEpoch and writes
  conditionally, so concurrent switchers resolve to one outcome
- GET /api/auth/me publishes the context a browser bootstraps from
- every response that resolved a record scope echoes it back
- regenerate the API contract
2026-08-06 18:21:40 +02:00
Marc-André Bombeck 59359738c8 feat(02-12.1): decide record-session context in one place
- add an import-free contract module both bundles can name, holding the
  header names, the parse and the verdict
- add the server fence: header read, wide-event stamp, and the refusal
- add RecordSessionChangedError as a third sibling on the existing
  sharing-error arm, so the 409 needs no new serialisation branch
2026-08-06 17:59:07 +02:00
Marc-André Bombeck 5c9befc0ae feat(02-12.1): stamp a record-context epoch on every session
- add sessions.record_epoch, owned by a BEFORE UPDATE trigger so the
  ON DELETE SET NULL path and raw SQL writes cannot skip it
- normalise pre-existing rows so epoch zero means never switched
- project the epoch off the row getSession already loads
2026-08-06 17:52:10 +02:00
Marc-André Bombeck a08e82d0be Merge commit '38214e472' into work/v137-record-session-fence 2026-08-06 17:10:56 +02:00
Marc-André Bombeck 38214e4726 fix(02-12): hold peer tabs during record switches
- Clear scoped reads before the server session changes
- Reconcile every tab with a fresh access response
2026-08-06 15:06:12 +02:00
Marc-André Bombeck a636a3b4c5 fix(02-12): gate shared record navigation
- Wait for the shared-record banner before generated reads
- Keep the profile doorway in the navigation translation namespace
2026-08-06 14:35:02 +02:00
Marc-André Bombeck c42df8cbc3 fix(02-12): gate record preload scope
- Wait for a validated account scope before warming record reads
- Abort scoped preloads and discard handoffs across record transitions
- Cover malformed scope reloads and fresh own-record recovery
2026-08-06 14:32:32 +02:00
Marc-André Bombeck 2ae3a66816 fix(02-11): bound APNs provider delivery
- Time out stalled provider sends without unhandled late rejections\n- Record a transient timeout outcome for cascade fallback\n- Cover timeout fallback with unit and integration tests
2026-08-06 14:10:28 +02:00
Marc-André Bombeck c56d1ca50b fix(02-11): protect guardian egress data lifecycle
- Exclude and erase egress claims with recipient-side lookup support\n- Serialize data wipes with managed-profile Guardian authorization\n- Cover last-Guardian and queued-claim wipe races
2026-08-06 14:05:59 +02:00
Marc-André Bombeck 21e111c06c fix(02-12): refuse malformed record access
- Preserve absent-server compatibility while rejecting published invalid blocks\n- Partition refused record reads and withhold protected presentation
2026-08-06 14:03:13 +02:00
Marc-André Bombeck 4b79f83cf7 fix(02-11): release guardian lock before provider egress
- Commit a final authorization claim under the lifecycle lock\n- Keep provider calls and retry outcomes outside database transactions\n- Retain authorization claims with notification delivery diagnostics
2026-08-06 13:46:42 +02:00
Marc-André Bombeck b844d04676 fix(02-12): reject divergent shared-record state
- Verify the active record matches its canonical switcher entry\n- Skip the owner OCR probe in delegated Labs records
2026-08-06 13:39:37 +02:00
Marc-André Bombeck 1818bd874b fix(02-11): authorize guardian egress under lock
- Hold the managed-profile lock through every provider send\n- Add the notification event retention index and race coverage
2026-08-06 09:42:47 +02:00
Marc-André Bombeck 358bb39465 fix(02-12): keep scoped record polls dormant
- suppress whole-record achievements reads for narrowed grants\n- stabilize record journey response assertions
2026-08-06 08:34:02 +02:00
Marc-André Bombeck e568caa2f1 test(02-12): make record switcher journeys deterministic
- Address granted records by their published username\n- Start the profile error mock before record navigation
2026-08-06 08:25:42 +02:00
Marc-André Bombeck c21957db9f fix(02-12): keep record switcher entries reachable
- Constrain the shared-record submenu to available viewport height\n- Scroll long grant lists without hiding delegated records
2026-08-06 08:19:43 +02:00
Marc-André Bombeck 60b4845fea feat(02-11): secure guardian notification delivery
- Remove record-mutating affordances from Guardian channel payloads\n- Reject managed Telegram callbacks and verify channel attribution
2026-08-06 05:44:41 +02:00
Marc-André Bombeck accb98e223 test(02-11): cover guardian notification channel safety
- Cover non-interactive Guardian payloads across delivery channels\n- Reject Telegram callbacks tied to managed records
2026-08-06 05:37:05 +02:00
Marc-André Bombeck 123bc15f16 fix(02-12): restore shared banner contrast
- Use the foreground token for warning context copy\n- Serialize shared-session browser journeys
2026-08-06 05:34:16 +02:00
Marc-André Bombeck 59de92b62e fix: isolate guardian notification preferences 2026-08-06 05:33:02 +02:00
Marc-André Bombeck be4ee9ac6d test: cover guardian notification preferences 2026-08-06 05:30:49 +02:00
Marc-André Bombeck a78189b7f6 feat: add guardian notification fanout 2026-08-06 05:23:38 +02:00
Marc-André Bombeck 1c0a5d7777 test: cover guardian notification eligibility 2026-08-06 05:17:56 +02:00
Marc-André Bombeck b231a7892c test(02-12): retire sharing inventory anchors
- remove obsolete inventory-only journey declarations\n- select persistent shared-record journeys by Playwright project config
2026-08-06 05:16:32 +02:00
Marc-André Bombeck 5e8b90bcc1 Merge notification delivery identity 2026-08-06 05:12:42 +02:00
Marc-André Bombeck 7104578d27 fix(02-12): honor shared scope in module pages
- keep scoped record pages out of actor module redirects\n- suppress unrelated illness reads from document-only records
2026-08-06 05:12:23 +02:00
Marc-André Bombeck f6b9c8e882 fix(02-12): fail closed in record presentation
- retain elevated chrome only for internally consistent payloads\n- fall back to the caller record on malformed access entries
2026-08-06 05:07:27 +02:00
Marc-André Bombeck a33cde3034 test(02-12): cover defensive record presentation
- reject inconsistent access levels before chrome can elevate them\n- fall back to the caller record for malformed payloads
2026-08-06 05:06:36 +02:00
Marc-André Bombeck b53147559a feat(02-12): scope cycle startup reads
- resolve calendar reads against the active record\n- resolve cycle insights against the active record
2026-08-06 05:05:59 +02:00
Marc-André Bombeck e037c1b8c5 test(02-12): admit scoped cycle startup reads
- freeze calendar and insights as cycle read handlers\n- retain the exact record resolver scope matrix
2026-08-06 05:05:13 +02:00
Marc-André Bombeck acd96a80e9 fix(02-12): gate protected routes on record scope
- resolve account capabilities before protected page queries mount\n- leave public routes outside the record loading gate
2026-08-06 05:04:26 +02:00
Marc-André Bombeck b358cfa3b8 test(02-12): cover record capability hydration
- hold protected content until account capabilities resolve\n- keep public routes outside the protected loading gate
2026-08-06 05:03:22 +02:00
Marc-André Bombeck 6c4dd2d96b feat(02-12): route scoped records to granted domains
- keep shared navigation independent of actor module preferences\n- open scoped records at their first allowed destination\n- announce record access and kind in doorway names
2026-08-06 05:02:37 +02:00
Marc-André Bombeck 34073cf8dc test(02-12): cover scoped navigation presentation
- prove profile uses a read-only shared route\n- cover scoped navigation independent of actor module flags\n- require action, access, and kind in record button names
2026-08-06 05:00:33 +02:00
Marc-André Bombeck affd356677 test: retain sustained safety event claims
- exercise the shared notification claim boundary\n- preserve rolling sustained-run dedup coverage
2026-08-06 04:59:50 +02:00
Marc-André Bombeck 9eab70f032 feat(02-12): add scoped profile summary
- expose bounded read-only profile facts for shared records\n- keep profile navigation separate from Settings mutations
2026-08-06 04:59:25 +02:00
Marc-André Bombeck 9ba37342d7 fix: claim notification events atomically
- serialize rolling event claims before delivery\n- keep provider egress outside the transaction
2026-08-06 04:57:50 +02:00
Marc-André Bombeck ba7bf49fae test: cover atomic notification event claims
- race record event claims through PostgreSQL\n- require safety producers to honor the claim
2026-08-06 04:53:50 +02:00
Marc-André Bombeck e2d308ec31 test: cover read-only profile summary
- Render bounded profile details\n- Exclude profile mutation controls
2026-08-06 04:53:28 +02:00
Marc-André Bombeck 60f91d27d7 fix: validate account access payloads
- Share the closed access schema with OpenAPI\n- Fail closed on malformed or stale record metadata
2026-08-06 04:52:46 +02:00
Marc-André Bombeck 69f97b268c fix: erase notification anchors with record data
- remove record event anchors during data deletion\n- retain sustained safety dedup coverage
2026-08-06 04:48:47 +02:00