Skip to content

Carry a fenced layer until the window's committed basis moves - #635

Open
tvanhens wants to merge 12 commits into
masterfrom
claude/carry-fenced-layers
Open

tvanhens wants to merge 12 commits into
masterfrom
claude/carry-fenced-layers

Conversation

@tvanhens

@tvanhens tvanhens commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Fixes #634.

What

The two-window snap-back that survived #632: retiring an optimistic layer is a durable, broadcast deletion, but layer visibility was never gated on the retiring window's own committed state. The moment fenceActivation deleted a committed layer's rows, every window — the writer included, whose fence can settle on a ResumeReady served from a stale 5s-TTL-cached basis in another isolate — recomposed committed-without-layer and rendered the pre-commit position until its committed value caught up 0–5s later. Deterministic repro: two iframes, one IndexedDB, real rendering — both tabs regress in ~250ms on master.

The fix keeps all state in ClientDatabaseHandle: a retired committed-unobserved layer is carried in the composed view, keyed to the basis (the #632 per-partition ordinal) at which it was acknowledged and the activation that retired it, and released when either moves. Newer queued layers still win by sequence; pending semantics are unchanged (durable rows only); rejections are never carried (deleted while queued); the update-required quarantine clears carries; identity resets forget them. OptimisticReconciler.view gains an optional layers parameter — no other reconciler change.

Known bound, stated deliberately: a commit producing no visible change for its own principal never advances the ordinal, so its carried layer persists until the next activation instead of vanishing at the fence. Lifting that needs the wire to carry the commit's visibility ordinal — a protocol change deferred until it matters.

Tests

test/browser/propagation.browser.test.ts (real client, two iframes over one IndexedDB, fence driven through the product's durable API):

  • "a fenced layer never returns a tab to the position the card left" — fails on master with both tabs regressed.
  • "a fence over the earlier layer keeps the later one the tabs already render" — sequence composition.
  • "a fence after the committed outcome arrived retires the layer at once" — the release path.

Negative controls: each of the three design elements reverted independently fails exactly its test. Propagation file run 5× consecutively clean.

bun run typecheck clean · test:unit 1788 pass · test:browser 233 pass · test:local 135 pass · build clean.

🤖 Generated with Claude Code

Layer retirement is a durable broadcast deletion, but a window's view
composed whatever layers remained over whatever committed value it
held, so the instant a fence deleted a committed layer's rows every
window — the writer included, whose fence can settle on a resume served
from a stale cached basis — re-rendered the pre-commit position until
its own committed value caught up seconds later. A handle now carries a
retired committed-unobserved layer in its composed view, keyed to the
basis at which the layer was acknowledged and the activation that
retired it, and releases it when either moves; newer layers still win
by sequence, rejections are never carried, and an outcome whose commit
already landed retires at once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tvanhens
tvanhens deployed to Development September 1, 2026 04:22 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 1, 2026 04:22 — with GitHub Actions Active
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T00:25:20.009250Z 75c04f2 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Reef preview: https://ramose-reef-api-pr-635-rj232vtfnjcyca6s.tvanhens.workers.dev

Deployed from 75c04f2 as stage pr-635. Torn down when this PR closes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ee006fc30

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ramose/src/client/database.ts Outdated
The acknowledgement basis was recorded when a layer was first observed
committed-unobserved, so a handle that observed it late — a fresh tab,
or the replica notice beating the receipt notice — anchored to a basis
that already contained the authoritative result and carried the stale
layer over a server-decided value. The anchor is now the basis at which
the handle first observed the layer queued; a layer never observed
queued gets no anchor and retires at its fence immediately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tvanhens
tvanhens deployed to Development September 1, 2026 04:42 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 1, 2026 04:42 — with GitHub Actions Active

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 791826a881

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ramose/src/client/database.ts Outdated
Comment thread packages/ramose/src/client/database.ts Outdated
tvanhens and others added 2 commits August 31, 2026 22:04
A mutation's receipt had no linkage to the replication state containing
its outcome, so layer retirement guessed from observation order — and
the guesses failed both ways: carried layers masked server-decided
values, and anchored layers snapped back under coalesced notices or
unrelated traffic. The transactor now assigns each principal's
committed invocations a dense settlement sequence, atomic with the
receipt; the acknowledgement and frames carry it, the manifest persists
the covered watermark, and a handle composes a retired layer exactly
until its own committed state covers the layer's settlement. Only the
principal's own commits move the watermark, so hidden activity stays
unobservable. Protocol and replica storage versions rotate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n coverage

A handle decided carries from the layer rows it happened to observe, so
a refresh that coalesced the acknowledgement and the fence saw an
unsettled row and snapped back; receipts outlive the fence, so the
overlay state now joins every receiver receipt's settlement and the
carry decision takes the durable value over the observed one. A carried
layer was also dropped when the next activation began, uncovered — the
last vestige of the observation-order heuristic; coverage is now the
only release, which confirms that durable deletion timing is irrelevant
and the activation fence needs no change. A committed acknowledgement
without a settlement classifies as malformed and retries, and an
unparseable settlement lookup fails upstream instead of degrading to a
value that can never be covered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f09b517f52

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ramose/src/internal/authorization/invocation-receipts.ts Outdated
Comment thread packages/ramose/src/internal/replication/indexeddb.ts Outdated
…-layers

# Conflicts:
#	test/local/conformance-catalog.ts
#	test/local/replication.ts
@tvanhens
tvanhens deployed to Development September 1, 2026 06:30 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 1, 2026 06:30 — with GitHub Actions Active
A completed receipt written before settlements existed made replay
throw instead of returning its recorded outcome; it now decodes as an
unsettled receipt the transactor must settle before deciding — a fresh
sequence assigned idempotently at first touch, recorded at the
receipt's original basis so coverage stays truthful. An acknowledgement
that advanced the settlement without changing the visible revision was
persisted durably but republished with only the ordinal, leaving the
handle's watermark stale and a fenced layer carried indefinitely; the
acknowledgement now returns both positions and the session publishes
each monotonically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tvanhens
tvanhens deployed to Development September 1, 2026 06:54 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 1, 2026 06:54 — with GitHub Actions Active

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3514d5fe17

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ramose/src/internal/transactor/transactor.ts
Comment thread packages/ramose/src/internal/replication/outbox.ts
Coverage asked for the highest settlement at a basis, which assumed
sequences were assigned in commit order; a backfilled receipt could
then vouch for a later commit a stale frame did not contain. Coverage
now returns the largest settlement whose entire prefix is committed at
the basis — exact when order aligns, strictly conservative when it
does not, so assignment order can no longer matter. A layer
acknowledged before settlements existed decodes as settlement-pending:
incomparable, therefore unreleasable, and the reconciler re-enqueues
its invocation so the server's idempotent replay returns the backfilled
sequence and the layer rejoins normal coverage-based retirement.
Layers carrying client references stay conservatively pending until
reload, and carried layers now lift recovered settlements from the
durable records.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tvanhens
tvanhens deployed to Development September 1, 2026 07:32 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 1, 2026 07:32 — with GitHub Actions Active

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e8d776205

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ramose/src/client/database.ts Outdated
Comment thread packages/ramose/src/internal/replication/indexeddb.ts
Comment thread packages/ramose/src/client/database.ts Outdated
The carry set was per-handle memory, so a tab opened after the fence
had nothing to compose and rendered the pre-commit value; a fenced
layer whose settlement is uncovered now becomes a durable retired row,
composed by every handle while its settlement exceeds the watermark and
deleted only at a fence that observes coverage atomically. The
in-memory carry machinery is deleted; composition is a pure function of
durable rows and the watermark. Install paths merged only the ordinal,
letting a delayed frame regress the settlement watermark a concurrent
acknowledgement had advanced; every manifest and head write now folds
the maximum stored settlement, and only committed-unobserved rows ever
retire, so a rejected legacy queued layer drops its value immediately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tvanhens
tvanhens deployed to Development September 1, 2026 08:10 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 1, 2026 08:10 — with GitHub Actions Active

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a7dac15fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ramose/src/internal/replication/outbox-storage.ts Outdated
Comment thread packages/ramose/src/internal/replication/overlay-records.ts Outdated
…ruction

The fence read one arbitrary partition head, so an advanced read view
could license deleting a layer a stale partition's handle still
composed; coverage is now the minimum settlement across every matching
head, with a malformed head pinning it at zero. A settlement-pending
layer can only decode from a record written before settlements existed,
and every such commit predates the storage rotation whose clean slate
forces re-snapshotting — so any current head covers it by construction,
client references included, and the fence deletes it outright. The
recovery machinery that existed to make legacy rows comparable is
deleted; the server-side receipt backfill remains for replayed
idempotency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tvanhens
tvanhens deployed to Development September 1, 2026 08:34 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 1, 2026 08:34 — with GitHub Actions Active
…-layers

# Conflicts:
#	packages/ramose/test/internal/replication/protocol.test.ts
#	test/browser/client-graph.browser.test.ts
#	test/browser/frames/optimistic-fence-change.ndjson
#	test/browser/frames/optimistic-fence-resume.ndjson
#	test/browser/frames/optimistic-fence.client.json
#	test/browser/frames/optimistic-fence.ndjson
#	test/browser/propagation-tab.ts
#	test/browser/propagation.browser.test.ts
#	test/local/graph-paths.ts
#	test/local/replication.ts
@tvanhens

tvanhens commented Sep 1, 2026

Copy link
Copy Markdown
Owner Author

Merge of master (#640, graphs-of-graphs removal) resolved. All 15 settlement acceptance tests survived: the propagation suite's deletion was collateral (its tab harness never used the deleted example stack), so both files are restored adapted to the graphless src; the five dropped tests were each intrinsically graph-of-graphs (receiver routes, child resolution) with no settlement assertions. Recorded frames regenerated on the resolved tree (no graphLineage, protocol 4, truthful settled values; provenance retained). Semantic audit clean: partition keys never carried lineage, the fence's min-across-heads ranges over exactly the intended set (the readView collapse removes a distinction only where it genuinely no longer exists), the IDB version chain lands coherently (MANIFEST_V6 = 15 over master's renamed slot), and settlement resolution keys on the same database name as the ordinal store. All five lanes green post-merge.

🤖 Generated with Claude Code

@tvanhens
tvanhens deployed to Development September 1, 2026 23:34 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 1, 2026 23:34 — with GitHub Actions Active

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 164b420147

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ramose/src/internal/replication/indexeddb.ts Outdated
Comment thread packages/ramose/src/internal/replication/indexeddb.ts
A change or snapshot re-delivering the committed revision discarded the
higher settlement it carried — in the state machine's dedup before
storage ever saw it, and again in the storage early return that read
without writing; both now fold the maximum, and the storage path
persists it under the full install discipline before announcing. A
settlement-only acknowledgement advanced the durable head silently, and
a follower's durable re-read refused any restore at the published
revision, so other tabs kept composing a retired layer against a stale
watermark; the advance now announces after commit and a strictly higher
durable watermark is adopted at the same revision, one-directionally so
a lagging read can never regress one. Every path that can advance
durable settlement state now merges the maximum and announces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tvanhens
tvanhens deployed to Development September 2, 2026 00:01 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 2, 2026 00:01 — with GitHub Actions Active

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2c089b4d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ramose/src/internal/replication/indexeddb.ts
A settlement-only advance announced to other tabs could not reach the
one recipient it targeted: a live session refused durable adoption
entirely, and its stream deliberately sends nothing for an unchanged
revision, so its watermark never advanced and it composed the retired
layer indefinitely. A live session now performs a watermark-only
adoption — a head read, guarded by generation, published-value
identity, matching revision, and a strictly higher settlement — that
republishes the same value with the advanced watermark and the maximum
ordinal, never touching content or retention, so the open stream's
authority over revisions is preserved while announced settlements
reach every recipient.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tvanhens
tvanhens deployed to Development September 2, 2026 00:21 — with GitHub Actions Active
@tvanhens
tvanhens deployed to Development September 2, 2026 00:21 — with GitHub Actions Active
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.

Optimistic layer retirement outruns committed adoption, snapping both windows back

1 participant