feat(navigation): promote OperationToken to the eligibility authority (#1790 PR 5)#2009
Draft
NathanDrake2406 wants to merge 2 commits into
Draft
feat(navigation): promote OperationToken to the eligibility authority (#1790 PR 5)#2009NathanDrake2406 wants to merge 2 commits into
NathanDrake2406 wants to merge 2 commits into
Conversation
The navigation planner's public and internal types were suffixed `V0` (RouteSnapshotV0, NavigationDecisionV0, FlightResultV0, and 19 others). The suffix implied a versioning scheme that never materialized — there is no V1, and the schema-version concerns it hinted at are carried explicitly elsewhere (NAVIGATION_TRACE_SCHEMA_VERSION, CACHE_PROOF_MODEL_SCHEMA_VERSION). The bare suffix was pure noise at every call site. Rename all 22 `…V0` planner types to their unsuffixed names across the planner, the browser state/entry/action modules, and the planner test suite. Pure mechanical rename: no behavior change, all 295 planner and browser-entry tests pass unchanged.
The App Router client carried an OperationToken on every navigation decision but only read its `lane` field. The authority the token was meant to own lived inline elsewhere: the commit-staleness gate was a raw boolean (startedNavigationId !== activeNavigationId or a visible-commit version mismatch) evaluated before the token was even built, and cache reuse was gated separately. The token also lacked the lifecycle navigation id, so it could not answer "does this result belong to the active navigation?" at all — its operationId is a per-render counter. Promote OperationToken to the single proof-of-eligibility object. A new operation-token.ts module owns the token type and verifyOperationToken, a pure verifier over four dimensions (active-navigation, visible-commit, graph-version, cache-variant) that returns a branded VerifiedOperationToken on success. The token verifies; ApprovedVisibleCommit still mutates — the two stay separate. - Add navigationId to the token (sourced from startedNavigationId) so it can carry the active-navigation authority. - Route the commit-staleness gate through verifyOperationTokenForCommit and delete the inline boolean. The staleOperation skip + trace are byte-identical; planPendingRootBoundaryFlightResponse now requires the VerifiedOperationToken brand, making "verify before plan" a compile-time guarantee. - Share the authority with cache reuse: planFlightResponseArrived gates an accepted cache-entry reuse decision through verifyOperationTokenForCacheReuse, hard-navigating (cacheReuseTokenRejected) when the proof's graph version no longer matches the installed route graph. Behavior-preserving today (the token's graphVersion is minted from the same manifest); a real guard once cross-document and segment reuse can diverge (PR 6/7). Absence is not permission: a checked dimension tolerates an absent authority fact, but a *required* one fails closed, and require implies evaluation — the contract segment-cache/BFCache writes need in PR 6/7. Tests: operation-token.test.ts covers every verdict dimension, the brand, the absent/mismatch split, and require⇒evaluate; navigation-planner and app-browser-entry suites stay green, proving the gate refactor preserved behavior; two new planner tests cover the cache-reuse graph-version seam.
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Promotes
OperationTokenfrom a passively-carried struct into the single proof-of-eligibility authority for App Router client navigation. The token now verifies whether a navigation result may enter commit approval or cache reuse;ApprovedVisibleCommitremains the separate proof-of-mutation object. This is PR 5 of the navigation architecture work tracked in #1790.A new
server/operation-token.tsmodule owns the token type andverifyOperationToken— a pure verifier over four dimensions (active-navigation, visible-commit, graph-version, cache-variant) returning a brandedVerifiedOperationTokenon success.Why
The token threaded through every
NavigationDecisionbut only itslanefield was ever read. The authority it was meant to own lived inline elsewhere:resolvePendingNavigationCommitDispositionDecision) was a raw boolean —startedNavigationId !== activeNavigationId || startedVisibleCommitVersion !== visibleCommitVersion— evaluated before the token was even built.operationId(a per-render counter) but no navigation id, so it literally could not answer "does this result belong to the active navigation?"So traversal, refresh, intercepted routes, redirects, and cache reuse did not share one authority model — exactly the split-brain #1790 calls out.
Approach
navigationIdon the token (sourced fromstartedNavigationId) supplies the missing active-navigation fact.verifyOperationTokenForCommit, inline boolean deleted in the same change. ThestaleOperationskip + trace are byte-identical.planPendingRootBoundaryFlightResponsenow requires theVerifiedOperationTokenbrand, making verify-before-plan a compile-time guarantee rather than a control-flow convention.planFlightResponseArrivedgates an accepted cache-entry reuse decision throughverifyOperationTokenForCacheReuse, hard-navigating (cacheReuseTokenRejected) when the proof's graph version no longer matches the installed route graph.requireimplies evaluation. This is the fail-closed contract segment-cache / BFCache writes need in PR 6/7.Scope boundaries (non-goals)
installedCacheVariantFingerprint: nullso that dimension stays dormant rather than comparing the token to itself.graphVersionis minted from the same manifest the planner verifies against). It activates once cross-document / segment reuse (PR 6/7) can carry a diverged graph version.Validation
tests/operation-token.test.ts(new, 15 tests): every verdict dimension, theVerifiedOperationTokenbrand, the absent-vs-mismatch split, deterministic check order, and therequire ⇒ evaluatefail-closed contract.tests/navigation-planner.test.ts: two new tests cover the cache-reuse graph-version seam (stale graph →cacheReuseTokenRejected; matching graph → commits unchanged).app-browser-entry.test.ts(8staleOperationassertions) and the fullnavigation-plannersuite stay green, proving the gate refactor preserved behavior.vp check(format, type-aware lint, knip) green; pre-commit full check + staged tests green.Risks / follow-ups
cacheReuseTokenRejectedis a new hard-navigation cause that cannot fire in the current single-document flow; first real activation is PR 6/7, which should add cross-document divergence coverage.cacheVariantFingerprint+ the segment-cache-write boundary (require: ["cacheVariant"]).Refs #1790.