mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
5b3ea11153
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.
196 lines
7.1 KiB
TypeScript
196 lines
7.1 KiB
TypeScript
import { describe, expect, it } from "vite-plus/test";
|
|
import {
|
|
verifyOperationToken,
|
|
verifyOperationTokenForCacheReuse,
|
|
verifyOperationTokenForCommit,
|
|
type OperationToken,
|
|
type OperationTokenAuthority,
|
|
type VerifiedOperationToken,
|
|
} from "../packages/vinext/src/server/operation-token.js";
|
|
|
|
function createToken(overrides: Partial<OperationToken> = {}): OperationToken {
|
|
return {
|
|
operationId: 7,
|
|
lane: "navigation",
|
|
navigationId: 3,
|
|
baseVisibleCommitVersion: 2,
|
|
graphVersion: "graph:test",
|
|
deploymentVersion: null,
|
|
targetSnapshotFingerprint: "route:/dashboard|root:/",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createAuthority(
|
|
overrides: Partial<OperationTokenAuthority> = {},
|
|
): OperationTokenAuthority {
|
|
return {
|
|
activeNavigationId: 3,
|
|
visibleCommitVersion: 2,
|
|
graphVersion: "graph:test",
|
|
installedCacheVariantFingerprint: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
// Compile-time assertion that the verified token narrows to the branded type.
|
|
function consumeVerified(_token: VerifiedOperationToken): void {}
|
|
|
|
describe("verifyOperationTokenForCommit", () => {
|
|
it("authorizes when the navigation and visible-commit dimensions match", () => {
|
|
const token = createToken();
|
|
const verdict = verifyOperationTokenForCommit(token, createAuthority());
|
|
|
|
expect(verdict.authorized).toBe(true);
|
|
if (verdict.authorized) {
|
|
// Same evidence object, now carrying the verified brand.
|
|
expect(verdict.token).toBe(token);
|
|
consumeVerified(verdict.token);
|
|
}
|
|
});
|
|
|
|
it("rejects staleNavigation when the token started under a superseded navigation", () => {
|
|
const verdict = verifyOperationTokenForCommit(
|
|
createToken({ navigationId: 3 }),
|
|
createAuthority({ activeNavigationId: 4 }),
|
|
);
|
|
|
|
expect(verdict).toEqual({ authorized: false, reason: "staleNavigation" });
|
|
});
|
|
|
|
it("rejects staleVisibleCommit when visible state advanced after the operation started", () => {
|
|
const verdict = verifyOperationTokenForCommit(
|
|
createToken({ baseVisibleCommitVersion: 2 }),
|
|
createAuthority({ visibleCommitVersion: 3 }),
|
|
);
|
|
|
|
expect(verdict).toEqual({ authorized: false, reason: "staleVisibleCommit" });
|
|
});
|
|
|
|
it("reports staleNavigation first when both navigation and visible commit diverge", () => {
|
|
const verdict = verifyOperationTokenForCommit(
|
|
createToken({ navigationId: 3, baseVisibleCommitVersion: 2 }),
|
|
createAuthority({ activeNavigationId: 4, visibleCommitVersion: 3 }),
|
|
);
|
|
|
|
expect(verdict).toEqual({ authorized: false, reason: "staleNavigation" });
|
|
});
|
|
|
|
it("does not gate commits on the cache-variant or graph-version dimensions", () => {
|
|
// A commit eligibility check must not reject on cache/graph divergence; that
|
|
// authority belongs to the cache-reuse boundary and the RSC compatibility path.
|
|
const verdict = verifyOperationTokenForCommit(
|
|
createToken({ graphVersion: "graph:stale", cacheVariantFingerprint: "cv:stale" }),
|
|
createAuthority({
|
|
graphVersion: "graph:fresh",
|
|
installedCacheVariantFingerprint: "cv:fresh",
|
|
}),
|
|
);
|
|
|
|
expect(verdict.authorized).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("verifyOperationTokenForCacheReuse", () => {
|
|
it("authorizes reuse when the graph version matches the installed graph", () => {
|
|
const verdict = verifyOperationTokenForCacheReuse(createToken({ graphVersion: "graph:a" }), {
|
|
graphVersion: "graph:a",
|
|
installedCacheVariantFingerprint: null,
|
|
});
|
|
|
|
expect(verdict.authorized).toBe(true);
|
|
});
|
|
|
|
it("rejects graphVersionMismatch when the proof was produced under a different graph", () => {
|
|
const verdict = verifyOperationTokenForCacheReuse(createToken({ graphVersion: "graph:a" }), {
|
|
graphVersion: "graph:b",
|
|
installedCacheVariantFingerprint: null,
|
|
});
|
|
|
|
expect(verdict).toEqual({ authorized: false, reason: "graphVersionMismatch" });
|
|
});
|
|
|
|
it("tolerates an absent graph version rather than rejecting low-context reuse", () => {
|
|
const verdict = verifyOperationTokenForCacheReuse(createToken({ graphVersion: null }), {
|
|
graphVersion: null,
|
|
installedCacheVariantFingerprint: null,
|
|
});
|
|
|
|
expect(verdict.authorized).toBe(true);
|
|
});
|
|
|
|
it("rejects cacheVariantMismatch when the installed variant differs from the proof", () => {
|
|
const verdict = verifyOperationTokenForCacheReuse(
|
|
createToken({ graphVersion: "graph:a", cacheVariantFingerprint: "cv:a" }),
|
|
{ graphVersion: "graph:a", installedCacheVariantFingerprint: "cv:b" },
|
|
);
|
|
|
|
expect(verdict).toEqual({ authorized: false, reason: "cacheVariantMismatch" });
|
|
});
|
|
|
|
it("tolerates an absent cache-variant fingerprint until segment cache supplies it", () => {
|
|
const verdict = verifyOperationTokenForCacheReuse(
|
|
createToken({ graphVersion: "graph:a", cacheVariantFingerprint: undefined }),
|
|
{ graphVersion: "graph:a", installedCacheVariantFingerprint: "cv:b" },
|
|
);
|
|
|
|
expect(verdict.authorized).toBe(true);
|
|
});
|
|
|
|
it("reports graphVersionMismatch before cacheVariantMismatch when both diverge", () => {
|
|
const verdict = verifyOperationTokenForCacheReuse(
|
|
createToken({ graphVersion: "graph:a", cacheVariantFingerprint: "cv:a" }),
|
|
{ graphVersion: "graph:b", installedCacheVariantFingerprint: "cv:b" },
|
|
);
|
|
|
|
expect(verdict).toEqual({ authorized: false, reason: "graphVersionMismatch" });
|
|
});
|
|
});
|
|
|
|
describe("verifyOperationToken required dimensions", () => {
|
|
it("fails closed when a required dimension's authority fact is absent", () => {
|
|
// Future segment-cache writes will require the cache-variant dimension. A
|
|
// forgotten fingerprint must reject, not silently pass: absence is not
|
|
// permission.
|
|
const verdict = verifyOperationToken(
|
|
createToken({ cacheVariantFingerprint: undefined }),
|
|
createAuthority({ installedCacheVariantFingerprint: null }),
|
|
{ check: ["cacheVariant"], require: ["cacheVariant"] },
|
|
);
|
|
|
|
expect(verdict).toEqual({ authorized: false, reason: "cacheVariantMissing" });
|
|
});
|
|
|
|
it("rejects a required-but-absent graph version with graphVersionMissing", () => {
|
|
const verdict = verifyOperationToken(
|
|
createToken({ graphVersion: null }),
|
|
createAuthority({ graphVersion: "graph:test" }),
|
|
{ check: ["graphVersion"], require: ["graphVersion"] },
|
|
);
|
|
|
|
expect(verdict).toEqual({ authorized: false, reason: "graphVersionMissing" });
|
|
});
|
|
|
|
it("skips an unchecked dimension entirely even when it diverges", () => {
|
|
const verdict = verifyOperationToken(
|
|
createToken({ graphVersion: "graph:a" }),
|
|
createAuthority({ graphVersion: "graph:b" }),
|
|
{ check: ["navigation"], require: ["navigation"] },
|
|
);
|
|
|
|
expect(verdict.authorized).toBe(true);
|
|
});
|
|
|
|
it("evaluates a required dimension even when it is omitted from check", () => {
|
|
// require implies evaluation: a dimension you require but forget to list in
|
|
// check must still be verified, never silently waved through.
|
|
const verdict = verifyOperationToken(
|
|
createToken({ graphVersion: "graph:a" }),
|
|
createAuthority({ graphVersion: "graph:b" }),
|
|
{ check: ["navigation"], require: ["graphVersion"] },
|
|
);
|
|
|
|
expect(verdict).toEqual({ authorized: false, reason: "graphVersionMismatch" });
|
|
});
|
|
});
|