Commit Graph

6 Commits

Author SHA1 Message Date
Nathan Nguyen 5748be3ba5 feat(cache): gate planner commits on explicit reuse proof (#1327)
* feat(cache): gate planner commits on explicit reuse proof

Runtime cache entries could enter browser commit planning as ordinary payloads even when the entry had no planner-visible reuse proof. That violated the #726 invariant that a cache hit is not semantic authority by itself.

The planner now receives a compact cache-entry proof decision through AppElements metadata and rejects missing or incompatible cache entries before route-topology commit approval. Cache-restored browser payloads synthesize the missing-proof rejection when metadata is absent, while fresh payloads keep the existing path.

Targeted tests cover proof projection, metadata transport, planner decisions, and the cached-payload lifecycle path.

* fix(cache): skip unproofed visited RSC cache restores

Visited App Router RSC snapshots were still eligible for replay even though production payloads do not yet carry cache-entry reuse proof. That let the new planner gate reject the cache path and turn soft restores into hard navigations.

Require decoded proof metadata before a payload is stored as a visited-response restore candidate, and update the real app-router navigation test to prove unproofed responses refetch without reloading. Also make cache-proof rejection-code parsing exhaustive at the type boundary.

* fix(cache): require explicit App Router payload origins

App Router navigation commits still carried cache-restore semantics as an optional boolean. A fresh default could hide a future restore path that forgot to request reuse proof, reopening the cache authority bug for visited responses.

The violated invariant is that cache origin is part of the payload contract, not a caller memory check.

Replace the boolean with a discriminated AppNavigationPayloadOrigin and thread it through browser entry, navigation controller, visible commit classification, and tests. Fresh payloads remain proof-optional; visited-cache payloads synthesize a missing-proof rejection and fail closed.

* docs(cache): document static layout proof parsing boundary

Runtime cache proof parsing currently accepts one proven reuse class: static layout. Future proof classes must update the parser before visited-cache responses can safely restore as commit-capable payloads.

The review also noted that the cache-restorable metadata predicate behaves as a narrowing boundary. Make that contract explicit as a TypeScript type guard without changing runtime behavior.

* fix(cache): keep accepted proof in rejection traces

Cache-proofed payloads that failed a later interception or root-boundary gate produced correct hard-navigation decisions, but the rejection trace lost the accepted proof fields. That made cache-proof diagnostics incomplete for payloads that passed proof validation and failed a later topology gate.

The root cause was that later rejection branches still used the pre-cache-proof trace fields after commitTraceFields had been computed.

Thread the enriched trace fields into those rejection decisions, document cache proof parser three-way semantics, narrow the cache-restorable metadata type guard precisely, and read the pending commit proof from the canonical top-level field.
2026-05-20 14:15:28 +01:00
Nathan Nguyen 705da9a52f feat(cache): introduce static layout artifact reuse proof primitive (#1307)
* Enable static layout artifact reuse proof

* fix(cache): reject unproven static layout variants

Static layout reuse could previously accept public or internal candidate variant dimensions without proving that the current request had the same canonical dimensions. Public visibility only means the values are safe to expose, not that the layout artifact is invariant across routes, params, search state, or other dimensions.

Reject dimensioned static layout candidates until the API can compare candidate and current variants by canonical dimension source, name, privacy, and value hashes. Make cache outcome recording best-effort so telemetry failures cannot turn a cache hit into a miss or escape the read path.

* fix(cache): require route-budgeted layout reuse candidates

Static layout artifact reuse could still accept a raw buildCacheVariant result at the authority boundary. That allowed callers to bypass route-budget admission before receiving a canReuse true decision.

Require BuildCacheVariantWithRouteBudgetResult for createStaticLayoutArtifactReuseDecision and add a type-level regression that raw variants do not satisfy the authority input.

* chore(cache): clarify static layout proof traces

The approved review called out non-blocking ambiguity in trace fields and telemetry documentation. The proof behavior is unchanged, but the trace output and type regression are easier to reason about.

Sort unproven dimension sources defensively, rename artifact compatibility trace fallback fields, document raw app-page cache keys as internal-only, and make the raw-variant type regression explicit without a dead branch.

* chore(cache): dedupe static layout dimension sources

The unproven-dimension trace already sorted sources, but duplicate source labels could still make traces noisier than necessary. Use the existing sortedUnique helper so the source summary is deterministic and compact while dimensionCount remains exact.
2026-05-20 10:36:20 +01:00
Nathan Nguyen 6790e3431f feat(cache): add static layout proof and variant budget guardrails (#1288)
* feat(cache): add disabled static layout reuse proof

Static layout cache artifacts can already carry render observations, but the cache proof model could not prove or reject static layout reuse independently of cache serving. That left private, dynamic, and incomplete observations covered only by downgrade classification, not by the reusable artifact boundary.

The missing invariant was that a reusable layout needs positive layout/root/boundary proof and negative request API proof before any future cache authority can consume it.

Add a disabled static layout proof evaluator with rejection trace codes for mismatched layout output, unknown root boundaries, private variant dimensions, private/dynamic observations, and missing or observed request APIs. Keep runtime cache reuse disabled by carrying the proof only on the disabled decision.

* feat(cache): enforce route variant budget fallback

Cache variant construction previously had a lossy scalar route count, so callers could not distinguish a duplicate admissible variant from a genuinely new variant that would exceed the route ceiling.

That violated the #726 cache proof invariant that variant cardinality must have an owned breaker path. Route budget admission now tracks canonical variant keys per route, allows duplicate variants without consuming budget, and returns structured breaker fallbacks for route mismatches or over-ceiling variants.

Tests cover duplicate admission, second distinct admission, and the over-budget breaker fallback for #726-CACHE-10.

* fix(cache): derive static layout proof authority

* docs(cache): clarify static proof evidence copy

* perf(cache): binary-search route variant keys

* refactor(cache): move sorted key search to utils

* test(cache): cover static proof boundary mismatch
2026-05-18 11:09:36 +01:00
Nathan Nguyen 4e558621df feat(cache): classify private and dynamic render downgrades (#1247)
* feat(cache): classify private and dynamic render downgrades

Render observations recorded dynamic request usage but did not carry an owned cacheability downgrade decision. That left future cache proof consumers to infer privacy from raw request API observations and cacheability strings.

Add pure downgrade classification for public variants, private request state, auth/session dimensions, uncacheable draft state, dynamic fetches, and incomplete observations. Bump the cache proof schema for the serialized observation shape and keep the disabled proof model as the only reuse decision.

* chore(cache): rename public cache candidate flag
2026-05-16 17:27:07 +01:00
Nathan Nguyen ebbf72cc8d feat(router): expose route graph manifest read model (#1089)
* feat(router): expose route graph manifest read model

The App Router graph now exposes root-boundary identity and a stable RouteManifest read model instead of leaving these facts implicit inside per-route arrays. Future #726 planner and compatibility work needs a semantic lookup surface for routes, layouts, pages, templates, slots, and root boundaries before it can stop reading meaning from transport keys.

This adds RootBoundaryId, graphVersion, StaticSegmentGraph maps, and fail-fast invariant checks for mismatched layout/template positions while preserving the existing routes array for current callers.

* chore: retrigger ci
2026-05-06 17:44:20 +01:00
Nathan Nguyen 475a7d8286 feat(server): add disabled cache proof model (#1093)
* feat(server): add disabled cache proof model

Route-scoped cache work currently lacks a typed proof boundary for representing render observations, variants, and breaker fallback decisions. That makes future cache reuse work easy to wire before proof completeness is explicit.

The missing invariant is that cache proof data must be canonical, redacted, bounded, and incapable of authorizing runtime reuse while the model is disabled.

Add a v0 cache proof model with RenderObservation, CacheVariant, BoundaryOutcomeCompatibility, variant budgets, and breaker fallbacks. The disabled decision always returns canReuse false, and tests cover canonical redacted dimensions, budget breaker paths, negative request API proof completeness, exact boundary outcome matching, and the disabled reuse gate.

Refs #726

* fix(server): use distinct invalid cache budget code

Invalid cache proof budget fields currently report the route variant ceiling reason code, even when the failing field is unrelated to route cardinality.

That conflates malformed configuration with an actual per-route variant ceiling breach.

Add CP_INVALID_VARIANT_BUDGET for invalid budget fields while leaving CP_ROUTE_VARIANT_CEILING_EXCEEDED for real route variant count failures. Cover maxEncodedLength as the regression case.

Refs #726

* fix(server): harden disabled cache proof invariants

Disabled cache proof variants currently collapse null and empty-string output scope fields into the same encoded value. That makes a future empty-string field addition able to collide silently with absent values.

The matcher also leaves two defensive contracts implicit: breaker fallback detection depends on the generic kind field, and the private boundary matcher would treat unknown as a match if called directly.

Preserve null through JSON output encoding, use the fallback-specific code field for the internal breaker guard, and make unknown outcomes non-matching at the matcher level. Add coverage for null versus empty-string scope keys and for missing request API observations not counting as negative proof.

Refs #726
2026-05-06 10:05:06 +01:00