Files
tech-log-frontend/docs/reviews/adapters/01-network-and-state.md
T
DongHyeonkaandClaude Opus 5 4bff9ca151 chore: sync the frontend template from 4dc033c to 8157ad4
The product was materialized from the template at `4dc033c` and has stayed
on it through 43 template commits, so it was missing all three rounds of
adapter remediation — including files it never had, such as the shared
`abortable-operation` primitive and the `exact-snapshot` decoder that
later fixes are written against. Taking only the newest round was not
possible for that reason: the delta is coherent only as a whole.

The product had not touched `src/adapters` at all since materialization,
so the 140-file delta applied with a three-way merge and no conflicts.
`package.json` was the single overlap and merged cleanly: the product owns
`name`, the template contributed `check:adapter-inventory`,
`check:remediation-ledger` and the image-resolve-signal type fixture.
All 24 product-owned files — README, index.html, CI workflow, i18n
catalog, home page, generated schemas, evidence scripts, component and
visual snapshots — are byte-identical to `main`.

`template.lock.json` now pins the synced revision and tree.

Verified in this repository, not inherited from the template: six type
projects, lint, nine gates (adapter inventory, remediation ledger,
registries, diagnostics, realtime boundaries, architecture, browser
file/storage boundaries, optional recipes, documentation), the production
build, and 2,054 of 2,073 tests. The 19 failures are all in
`tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing
sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template
records; four suites that failed once under parallel load pass in
isolation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 12:04:58 +09:00

991 lines
60 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Network and state adapters implementation review
- Review date: 2026-08-13 (Asia/Seoul)
- Reviewed revision: 4dc033cf33a5b6173bbf960d5eb464a406dc4c92
- Mode: code review only; no implementation source was changed
- Primary scope: src/adapters/http, auth, query-cache, cross-context-invalidation, platform, diagnostics, telemetry
- Traced boundaries: corresponding contracts, application ports, bootstrap composition, reference feature adapters, tests, architecture decisions, and operating documentation
## 1. Outcome and priority
No Critical issue was found. Five High findings are implementation blockers or near-term correctness/security work:
1. N-01: the production V3 HTTP observation is always rejected by the diagnostics projector, and the V3 path never emits terminal-failure telemetry.
2. N-02: V3 declares authProfileId but neither composes nor enforces a profile; a credential collaborator can change transport-owned Accept and credentials, while a declared bearer profile can send no Authorization header.
3. N-03: after a command attempt has been dispatched, a retry-time final-invariant fence can downgrade MAYBE_APPLIED to NOT_STARTED.
4. N-04: telemetry dispose only removes pagehide; queued callbacks, future emit calls, and in-flight delivery survive runtime teardown.
5. N-05: the conditional-validator key codec is delimiter-ambiguous and lets two valid bindings overwrite each other. It is not composed into HTTP yet, so this is a rollout blocker rather than a current request-path incident.
The current reference feature uses V3. The older createHttpClient path remains exported and is still the documented rollback/compatibility seam, so its replay and cancellation defects cannot be dismissed as dead code.
## 2. Method, severity, and confidence
Severity:
| Level | Meaning |
| --- | --- |
| Critical | immediate broad confidentiality/integrity loss, arbitrary execution, or unrecoverable state corruption |
| High | security boundary bypass, wrong command-effect verdict, silent loss of required production evidence, or unsafe replay/rollout blocker |
| Medium | bounded correctness, cancellation, cleanup, or cross-context isolation defect with a narrower activation condition |
| Low | hardening or contract/documentation mismatch without a demonstrated material product failure |
Confidence:
| Level | Meaning |
| --- | --- |
| Very high | direct control/data-flow proof and a minimal failing reproduction |
| High | direct code proof and aligned contract/documentation evidence |
| Medium | implementation evidence exists but product requirement or browser/provider behavior must be selected |
| Low | hypothesis requiring characterization before acceptance |
Validation performed without retaining test changes:
- A temporary five-case Vitest characterization was added, run, and removed. All five expected-correct assertions failed: V3 diagnostic projection, telemetry-after-dispose, conditional-validator collision, credential transport ownership, and retry-time effect preservation.
- Related baseline: 21 test files, 144 tests passed.
- Static producer check: corepack pnpm check:diagnostics passed with “8 diagnostics and 5 telemetry producers”.
- The temporary test was deleted and the implementation worktree was clean before this report was written. Other agents later created unrelated docs/reviews entries; this review does not modify them.
This distinction matters: the green suite proves current intended behaviors, while the five failures identify missing assertions rather than contradicting existing passing tests.
## 3. Complete primary-scope inventory
All 24 files below were read in full.
| File | Responsibility | Direct dependencies / consumers | Review disposition |
| --- | --- | --- | --- |
| src/adapters/auth/external-session-adapter.ts | Adapts the external session owner to AuthSessionPort; validates credential header names and values; supplies demo, anonymous, and unavailable variants | application/ports/auth-session-port.ts; bootstrap/runtime-adapters.ts; HTTP V2/V3 | Preserve token opacity and allowlist. Modify for cooperative cancellation and required-profile header enforcement. |
| src/adapters/cross-context-invalidation/browser-cross-context-host.ts | Safely captures browser BroadcastChannel, localStorage, storage events, and secure random capabilities | contracts/cache-invalidation.ts; browser-cross-context-invalidation.ts; runtime-adapters.ts | Preserve fail-closed capability capture. Modify to capture one localStorage identity and validate native StorageEvent.storageArea. |
| src/adapters/cross-context-invalidation/browser-cross-context-invalidation.ts | Versioned invalidate-only wire protocol, BroadcastChannel/storage fallback, TTL, dedupe, per-source sequence/gap detection, bounded tracking, subscription and close | contracts/cache-invalidation.ts; host adapter; TanStack coordinator | Preserve closed envelope, bounded maps, and idempotent close. Modify exact storage-area admission; dual-transport fan-out is a separate product choice. |
| src/adapters/cross-context-invalidation/index.ts | Public barrel for host/runtime types and constructors | bootstrap and query-cache coordinator | Modify exports only if the storage event facade type changes. |
| src/adapters/diagnostics/bounded-diagnostics.ts | Bounded in-memory diagnostics projection/sink and safe pre-mount boot evidence | DiagnosticsPort; contracts/diagnostics.ts and telemetry.ts; bootstrap | Preserve fail-isolated projection and cloning. Harden non-finite capacity. V3 producer fix belongs primarily in bootstrap. |
| src/adapters/http/bounded-body-reader.ts | Declared-length and streamed byte ceilings, stream cancellation/release isolation, forbidden-body probing, strict UTF-8/JSON decode | V3 executor; bounded-body-reader tests | Keep as the single bounded response primitive. It already contains the cleanup behavior missing from bounded-json.ts. |
| src/adapters/http/bounded-json.ts | Legacy response stream reader and JSON decoder | legacy client.ts | Replace internals with bounded-body-reader delegation, then remove with V2 retirement. Current cancel/release failures can reject. |
| src/adapters/http/client.ts | Legacy/V2 operation lookup, profiles, auth recovery, retries, total deadline, response validation/mapping, diagnostics and telemetry | legacy contracts and ports; runtime-adapters createRuntimeHttpClient | Compatibility-only but exported. Fix empty keyed replay, cooperative auth cancellation, and common body-reader use before relying on it for rollback. Deprecate after callers are migrated. |
| src/adapters/http/http-contract-bridge.ts | V3 request projection, URL/body bounds, credential patch type, final request invariant | external-contract-runtime.ts; V3 executor | Preserve descriptor-owned request projection. Change credential authority: patch cannot own credentials or transport headers; final invariant must compare exact resolved profile. |
| src/adapters/http/http-effect-certainty.ts | Converts physical-attempt state and problem descriptors into mutation certainty/UI projection | V3 executor; contracts | Preserve explicit certainty vocabulary. Add a monotonic logical-execution certainty join used across retries. |
| src/adapters/http/http-execution-v3.ts | Descriptor-driven V3 lifetime: validation, projection, credentials, deadline, retry, fetch, response admission, effect verdict and observation | external contracts, scope, mutation intent, bridge, bounded reader, certainty, retry policy | Main correction site for N-01, N-02, N-03 and cooperative cancellation. Keep one retry authority and closed result union. |
| src/adapters/http/request-builder.ts | Legacy path/query construction and origin/base-prefix checks | legacy client; ApiOperation | Keep while V2 remains. Do not reuse it to weaken V3 descriptor projection. |
| src/adapters/http/resource-mapper.ts | Thin legacy operation-payload mapper delegation | boundary-mapper; legacy client | No standalone defect. Remove only with V2, not as part of the correctness patch. |
| src/adapters/http/retry-policy.ts | Legacy retry decision/backoff plus parseRetryAfter reused by V3 | legacy client and V3 executor | Keep deterministic parse/backoff seam. V2 must additionally prove a valid key before keyed replay. |
| src/adapters/http/schema-registry.ts | Legacy Zod envelope/request/payload validation and clone | legacy client/tests | No standalone defect. Remains V2-only and should not be merged with installed external validators. |
| src/adapters/platform/browser-lifecycle.ts | Single owner of visibility/network/focus/page/beforeunload listeners and lifecycle snapshots | optional-runtime-host.ts | Preserve centralized listener ownership and idempotent dispose. Clarify or redesign dirty-source attachment semantics; selection item O-02. |
| src/adapters/platform/browser-mutation-intent-factory.ts | Secure intent/idempotency UUIDs plus monotonic creation time, normalized by defineMutationIntent | MutationIntentFactory port; bootstrap | Keep. Share one idempotency-key validator so external inputs and generated values have identical bounds. |
| src/adapters/platform/system-clock.ts | Wall clock and abortable sleep with listener/timer cleanup | ClockPort; legacy HTTP | Keep. Existing unit test covers abort cleanup behavior. |
| src/adapters/query-cache/conditional-validator-store.ts | In-memory ETag CAS sidecar keyed by scope, definition, identity, representation and cache revision | bootstrap scope reset; future conditional HTTP/query join | Fix tuple codec before composition. Preserve validator grammar, generation/revision checks and bounded capacity. |
| src/adapters/query-cache/cursor-pagination-runtime.ts | Bounded cursor chain, page/snapshot/loop/item/byte validation | cursor pagination contract; currently available but not composed | Add post-await abort admission or an abort race. Current pre-await-only check can admit a late page. |
| src/adapters/query-cache/server-state-scope-runtime.ts | Synchronous session-generation fence, ordered reset participants, cache reset, identity replacement and lifecycle notifications | AuthSessionPort, query invalidation, scope contract; bootstrap | Keep synchronous FENCED-before-await design. Consider async shutdown only as O-03; no current stale-generation admission was found. |
| src/adapters/query-cache/tanstack-cache-coordinator.ts | Maps registry topics to query namespace invalidation, coalesces remote hints, defers under mutation leases, resets/disposes | TanStack Query, invalidation contracts, cross-context runtime | Keep invalidate-only remote authority, generation guard, reset serialization and bounded registry validation. Add teardown characterization if dispose becomes async. |
| src/adapters/query-cache/tanstack-query-cache.ts | Creates QueryClient defaults and QueryCachePort read/write/invalidate adapter with diagnostics | TanStack Query, QueryCachePort, errors/diagnostics | Keep retry disabled and clone-on-write. Clone-on-read is an optional port-semantics decision, not a confirmed production defect. |
| src/adapters/telemetry/best-effort-telemetry.ts | Allowlisted bounded oldest-drop telemetry queue, scheduled/pagehide flush, sink isolation and evidence | TelemetryPort, telemetry/diagnostic contracts, bootstrap | Add terminal lifecycle state, joined flush promise and in-flight abort; ensure runtime composition disposes it. |
## 4. Traced boundary inventory
### Application ports
| File | Relevant contract |
| --- | --- |
| src/application/ports/auth-session-port.ts | Session state, credential patch, recovery; currently has no AbortSignal/deadline context. |
| src/application/ports/query-cache-port.ts | Closed read/write/invalidate result; value is unknown and read mutability is unspecified. |
| src/application/ports/clock-port.ts | Time and abortable sleep. |
| src/application/ports/diagnostics-port.ts | Non-throwing logical diagnostics producer boundary. |
| src/application/ports/telemetry-port.ts | Fire-and-forget semantic event emission. |
| src/application/ports/mutation-intent-factory.ts | Intent identity and keyed-command creation. |
| src/application/result.ts | Closed application result used by pagination and feature projection. |
### Contracts
Reviewed: server-state-scope.ts, cursor-pagination.ts, diagnostic-buckets.ts, mutation-intent.ts, boundary-mapper.ts, rest-profiles.ts, api-operations.ts, errors.ts, diagnostics.ts, telemetry.ts, query-invalidation.ts, query-keys.ts, cache-invalidation.ts, and external-contract-runtime.ts.
Key joins:
- external-contract-runtime.ts:117-148 declares authProfileId but validates only non-empty text at 300-344.
- rest-profiles.ts:11-37 already provides the profile/strategy shape and exact credentials mode used by V2.
- diagnostics.ts:22-42 is a closed context allowlist and 83-99 rejects the whole record on an unknown key.
- telemetry.ts defines api.request.failed required attributes: error_kind, http_status_group, attempt_count_bucket, and route_id.
- server-state-scope.ts makes synchronous signal abortion/isCurrent the generation admission boundary.
- cache-invalidation.ts supplies the closed wire grammar used by the cross-context runtime.
### Bootstrap and feature path
Reviewed: runtime-adapters.ts, server-state-generation-store.ts, create-runtime-composition.ts, composition-root.ts, runtime-application.tsx, main.tsx, optional-runtime-host.ts, installed-contract-contributions.ts, installed-feature-adapters.ts, reference create-reference-feature-input.ts, reference-http-gateway.ts, reference feature contract contribution, reference feature API, application-query.ts, and server-state-generation-provider.tsx.
Production request flow:
reference-http-gateway (has routeId)
-> createReferenceFeatureInstalledInput (drops routeId)
-> runtime-adapters contractOperations
-> createContractHttpExecutor (V3)
-> runtime-adapters observe
-> bounded diagnostics projector
The legacy createRuntimeHttpClient is still exported at runtime-adapters.ts:142-163 but is not the installed reference feature request path.
## 5. Confirmed defects
### N-01 — V3 HTTP diagnostics are silently dropped and terminal telemetry is absent
- Severity: High
- Confidence: Very high
- Activation: current production reference-feature V3 path
Evidence:
- http-execution-v3.ts:150-155 defines an observation with diagnosticsOperation, outcome, attempts and certainty.
- http-execution-v3.ts:354-365 emits that shape exactly once.
- runtime-adapters.ts:331-343 maps attempts and certainty as literal context keys.
- diagnostics.ts:22-42 allows attempt_count_bucket but not attempts or certainty.
- diagnostics.ts:83-99 rejects the complete diagnostic on the first unknown context key.
- reference-http-gateway.ts:14-42 and 69-103 constructs a low-cardinality routeId.
- create-reference-feature-input.ts:41-54 forwards signal and intent but discards routeId.
- runtime-adapters.ts:331-347 has no V3 telemetry emit at all.
- VD-07 lines 36-39 requires exactly one logical HTTP diagnostic and exactly one terminal non-abort failure telemetry event.
Minimal reproduction:
Input was the exact runtime-adapters V3 record context:
{
operation_id: "reference.list",
outcome: "TRANSPORT_FAILURE",
attempts: 2,
certainty: "TIMEOUT"
}
Expected projectDiagnosticRecord(...).success true; actual false.
Impact:
- Success, retry recovery, failure and cancellation on the installed V3 feature leave no HTTP diagnostic record.
- V3 terminal failures leave no api.request.failed event even when telemetry is enabled.
- check:diagnostics remains green because it checks producer presence/source policy, not whether the concrete producer output passes the projector.
Required decision:
- Observation is a safe typed internal record, not an arbitrary context map.
- routeId is required at the installed operation-executor boundary.
- Raw attempt count/duration/status stay internal; only buckets reach diagnostics/telemetry.
- Cancellation and scope-fence outcomes produce diagnostics once but never api.request.failed.
- Diagnostics/telemetry failures remain unable to affect the HTTP outcome.
Proposed signature:
export type HttpExecutionObservation = Readonly<{
routeId: string;
operationId: string;
diagnosticsOperation: string;
outcome: HttpExecutionOutcome<unknown, unknown>["kind"];
errorKind: string;
status?: number;
attemptCount: number;
durationMs: number;
effect: HttpEffectCertainty;
cancellationOwner?: CancellationOwner;
}>;
export interface HttpExecutionContext {
readonly routeId: string;
readonly signal?: AbortSignal;
readonly scope: CacheScopeSnapshot;
readonly intent?: MutationIntent;
}
Projection in runtime-adapters:
diagnostics.record({
eventId: "http.request.completed",
context: {
route_id: observation.routeId,
operation_id: observation.operationId,
operation: observation.diagnosticsOperation,
outcome: observation.outcome,
error_kind: observation.errorKind,
http_status_group: statusGroup(observation.status),
attempt_count_bucket: attemptBucket(observation.attemptCount),
duration_bucket: durationBucket(observation.durationMs)
}
});
For terminal non-abort failures, emit api.request.failed using the same safe route/operation/status/attempt/duration fields. Do not add attempts or certainty as unregistered context. If product operators need effect certainty, add the explicit effect_certainty key and a closed value policy to both contracts and ADR; do not pass the current free string.
### N-02 — V3 auth profile is declarative only; credential code can alter transport policy
- Severity: High
- Confidence: Very high for generic V3 authority violation; High for current missing-bearer behavior
- Activation: current bootstrap permits a bearer-declared demo request with an empty patch; arbitrary Accept/credentials requires a custom/defective credential collaborator
Evidence:
- external-contract-runtime.ts:117-125 contains authProfileId.
- external-contract-runtime.ts:300-344 checks only non-empty identity, not registry existence or coherence.
- runtime-adapters.ts:302-326 receives operation.authProfileId but ignores it, always returns credentials: omit, and accepts an authenticated empty patch.
- http-contract-bridge.ts:14-22 lets the credential patch select any RequestCredentials.
- http-execution-v3.ts:475-483 rejects only idempotency-key.
- http-execution-v3.ts:486-489 spreads credential headers after transport-owned Accept, so patch Accept wins.
- http-contract-bridge.ts:253-273 only checks that credentials is one of three valid Fetch values and generally allows Accept/Content-Type; it does not prove profile equality or header ownership.
- reference contribution lines 133-141, 177-185, 224-236 declares REFERENCE_EXTERNAL_BEARER for all operations.
- demo-session credentialPatch is empty at external-session-adapter.ts:109, yet authenticated demo requests are sent.
- VD-23 lines 211-259 says transport owns Accept/Content-Type and an auth profile exact-fixes Fetch credentials.
- The existing V2 profile registry at rest-profiles.ts:11-37 and 77-106 is a working local pattern.
Minimal reproduction:
A credential collaborator returned:
{
kind: "READY",
headers: { Accept: "text/plain" },
credentials: "include"
}
The request completed successfully and fetch observed Accept text/plain and credentials include. Expected fetch count was zero or transport-owned application/json/omit.
Current mitigation and residual issue:
- external-session-adapter.ts:18-46 currently restricts the real owner to authorization and x-csrf-token, so the Accept injection is blocked in this bootstrap path.
- That does not restore generic executor authority, and required Authorization is not checked. Empty bearer remains possible in the current demo path.
- Therefore do not characterize this as arbitrary external-owner header injection in the current bootstrap; characterize it as an executor contract violation plus a current profile-completeness failure.
Required decision:
- Reuse the existing Profile/Strategy registry; do not introduce a general interceptor chain.
- The resolved profile, not the credential patch, owns credentials.
- Credential patch contains only a typed, runtime-validated subset of credential headers.
- A bearer profile requires authorization; an anonymous profile permits none.
- Unknown/incoherent profiles fail during composition. Missing required headers or extra headers return AUTH_INTEGRATION_FAILURE with effect NOT_STARTED and fetch count zero.
- UNAUTHENTICATED remains a user/session state, not an integration/configuration error.
Proposed signatures:
export type CredentialHeaderName =
| "authorization"
| "x-csrf-token"
| "x-tenant-context";
export type RestAuthProfile = Readonly<{
authProfileId: string;
transport: "ANONYMOUS" | "BEARER_HEADER" | "SAME_ORIGIN_COOKIE";
credentials: "omit" | "same-origin" | "include";
allowedCredentialHeaders: readonly CredentialHeaderName[];
requiredCredentialHeaders: readonly CredentialHeaderName[];
}>;
export type CredentialPatchOutcome =
| Readonly<{
kind: "READY";
headers: Readonly<Partial<Record<CredentialHeaderName, string>>>;
}>
| Readonly<{ kind: "UNAUTHENTICATED" }>
| Readonly<{ kind: "UNAVAILABLE" }>
| Readonly<{ kind: "SCOPE_FENCED" }>;
attachCredentials(
operation: Readonly<{
operationId: string;
authProfileId: string;
method: string;
}>,
context: Readonly<{ signal: AbortSignal }>
): Promise<CredentialPatchOutcome> | CredentialPatchOutcome;
The executor dependency receives a validated ReadonlyMap<string, RestAuthProfile>. Final invariant compares init.credentials to the selected profile and rejects missing/extra credential headers.
Demo migration must be explicit. Recommended repository choice: allow createDemoSessionAdapter to receive a demo credential patch from bootstrap and supply a fixed non-secret Authorization marker only in AUTH_MODE=demo; keep REFERENCE_EXTERNAL_BEARER strict. Do not silently weaken the bearer profile to make tests pass. If a product backend wants anonymous demo calls, it needs a distinct anonymous contract/profile selected at composition.
### N-03 — retry-time scope fence downgrades a previously dispatched command to NOT_STARTED
- Severity: High
- Confidence: Very high
- Activation: latent for a future IDEMPOTENT command with retryBudget greater than zero; the current reference create is KEYED with retryBudget zero
Evidence:
- After response admission, attemptState becomes SETTLED at http-execution-v3.ts:648-656.
- A retry continues at 657-692.
- The next iteration checks scope at 511-516 and uses current attemptState, which still yields MAYBE_APPLIED.
- There is a second scope check inside final invariants at 554-563.
- If the scope changes between those two checks, lines 568-575 call preDispatchEffect(isCommand), returning NOT_STARTED and forgetting the prior attempt.
- Deep design lines 1683-1690 says dispatch followed by timeout/network/abort/body loss is MAYBE_APPLIED.
Minimal reproduction:
- Contract: commandEffect non-null, retrySemantics IDEMPOTENT, retryBudget 1.
- Attempt 1: fetch returns 429.
- Sleep resolves.
- Scope is current at retry-loop entry and false at final invariant.
- Expected SCOPE_FENCED with MAYBE_APPLIED.
- Actual SCOPE_FENCED with NOT_STARTED.
Root cause:
PhysicalAttemptState is being used as both current-attempt state and logical-execution history. Reset/final-invariant code reasons only about “this retry has not sent” and loses “a previous physical attempt was sent”.
Required decision:
Maintain a monotonic logical certainty accumulator for the whole execution. A new unsent retry cannot lower prior MAYBE_APPLIED. Final-invariant failures use the joined logical certainty; the first attempt can still return NOT_STARTED.
Proposed helper:
export function joinMutationEffectCertainty(
current: MutationEffectCertainty,
observed: MutationEffectCertainty
): MutationEffectCertainty;
Join rules:
- MAYBE_APPLIED dominates NOT_STARTED and NOT_APPLIED.
- APPLIED_CONFIRMED is terminal and cannot enter an automatic retry.
- NOT_APPLIED dominates NOT_STARTED for internal history.
- Query operations remain NOT_APPLICABLE and do not use the mutation lattice.
Also check caller/scope/deadline ownership after every awaited admission and before returning success. A separately named characterization should decide the response-completed-versus-caller-abort race; do not fold an unverified race rule into this patch without a test.
### N-04 — telemetry continues work after dispose and composition never disposes it
- Severity: High
- Confidence: Very high
- Activation: current when telemetry is enabled; no-op telemetry is unaffected
Evidence:
- best-effort-telemetry.ts:95-101 schedules a callback that always calls flush.
- emit at 104-125 has no disposed check.
- flush at 127-147 has no disposed check or in-flight AbortController.
- dispose at 154-156 removes only pagehide.
- runtime-adapters.ts:412-416 clears validators/scope/generation but omits telemetry.dispose.
- create-runtime-composition.ts:60-66 calls infrastructure.dispose after optional shutdown, so the omission reaches application teardown.
- flush at 127-130 returns an already-resolved promise when another flush is active, so await adapter.flush does not mean “the active delivery has settled”.
Minimal reproduction:
- Queue one valid api.request.failed with a captured scheduler callback.
- Call dispose.
- Run the captured callback and call emit again.
- Expected no fetch and pendingCount zero.
- Actual one fetch; later emit is also accepted.
Impact:
- HMR/test/runtime teardown can send queued or future events after the owning composition is gone.
- In-flight work has no cancellation owner.
- This is a lifecycle/privacy contract defect even though delivery is best-effort.
Required decision:
Use a small terminal lifecycle state, not a durable queue:
type TelemetryLifecycle = "ACTIVE" | "DISPOSED";
flush(): Promise<void>;
dispose(): void;
Semantics:
- emit after dispose is a no-op.
- dispose removes pagehide, clears queued events, invalidates scheduled callbacks, and aborts the current sink request.
- flush joins and returns the active flush promise.
- completion of a sink that ignored abort cannot reschedule or update post-dispose delivery state.
- disposal drops data silently; it must not recursively emit a drop event while shutting down.
- runtime infrastructure.dispose calls telemetry.dispose before destroying diagnostics/state dependencies.
A separate async shutdown or persistent retry queue is unnecessary for current best-effort policy.
### N-05 — conditional-validator composite key is collision-prone
- Severity: High when composed; current effective risk Medium / activation blocker
- Confidence: Very high
- Activation: docs classify sidecar AVAILABLE_NOT_COMPOSED; bootstrap creates/clears it but HTTP does not use it
Evidence:
- conditional-validator-store.ts:43-58 joins four unescaped components with colon.
- identityToken permits colon at 47.
- scope fingerprint permits colon in server-state-scope-runtime.ts:196-200.
- definitionId is only checked for truthiness at line 46.
- architecture status at api-contract-schema-mapper-and-server-state.md:108 explicitly says AVAILABLE_NOT_COMPOSED.
Collision using valid values and the same scope/version:
A: definitionId = "resource:detail"
identityToken = "identity-token-00000001"
B: definitionId = "resource"
identityToken = "detail:identity-token-00000001"
Both encode to the same string. Installing B overwrites A; prepare(A) returns Bs ETag.
Impact after composition:
A validator from one definition could be sent for another and a 304 could admit the wrong cached representation/revision relationship.
Required decision:
Use an injective deterministic tuple codec, not a repository abstraction. JSON.stringify of a validated fixed tuple is sufficient:
type ConditionalValidatorKeyTuple = readonly [
scopeFingerprint: string,
definitionId: string,
identityToken: string,
representationVersion: number
];
Validate and byte-bound definitionId and fingerprint at this trust boundary. No public store API change or persisted-data migration is needed because the store is in-memory and not composed into HTTP yet.
### N-06 — legacy keyed commands can automatically retry with no Idempotency-Key
- Severity: High on the compatibility/rollback path
- Confidence: High
- Activation: exported createRuntimeHttpClient/createHttpClient; not the current installed reference path
Evidence:
- client.ts:241-246 uses nullish coalescing, so caller value "" is retained rather than replaced.
- client.ts:534 sets Idempotency-Key only if the value is truthy.
- retry-policy.ts:45-68 allows both safe and keyed retries.
- Therefore a keyed command with explicit empty key can replay after a retryable response while sending no key.
- The existing keyed integration test uses a non-empty logical-command value; there is no invalid-key case.
- VD-23 lines 691-703 permits V1 fallback only if hardening remains.
Required decision:
Export one defineIdempotencyKey validator from mutation-intent.ts and use it in both V2 and V3. Reject empty, whitespace-only, control-character, and over-byte-budget keys before credentials, timers, or fetch. Do not trim or silently regenerate a caller-supplied invalid value. Return VALIDATION_REJECTED / IDEMPOTENCY_KEY_INVALID, attempt count zero.
### N-07 — legacy total deadline does not bound or cancel credential attachment
- Severity: High on the compatibility/rollback path
- Confidence: High
- Activation: auth-required V2 operation with a non-cooperative external owner
Evidence:
- client.ts:517-526 creates the attempt controller/timer.
- client.ts:570-586 awaits authSession.credentialPatch directly.
- auth-session-port.ts:15-27 exposes no signal/deadline to credentialPatch.
- If the owner never settles, aborting the attempt controller does not settle the await, so execute can exceed its total deadline indefinitely.
- Recovery is raced at client.ts:297-324 and 666-697, but authSession.recover itself receives no signal; late owner work can continue.
- V3 has the better local waiting pattern at http-execution-v3.ts:427-466, although its underlying credential work is not cooperatively signaled either.
- VD-23 lines 339-349 explicitly includes credential/recovery in total deadline and requires auth waiter cleanup.
Proposed compatible port extension:
export type AuthOperationContext = Readonly<{
signal: AbortSignal;
deadlineAtMonotonicMs: number;
}>;
credentialPatch(
binding: CredentialRequestBinding,
context?: AuthOperationContext
): Promise<CredentialPatch>;
recover(context?: AuthOperationContext):
Promise<"restored" | "no-session">;
Make context optional for one release to preserve existing owner implementations, but both clients must race owner promises against the lifetime signal immediately. Extend ExternalSessionOwner attachCredential/recoverSession the same way, pass the context through, and ignore all late completions. In the following breaking release, require the context from external owners.
Error semantics:
- deadline owner: REQUEST_TIMEOUT / TIMEOUT.
- caller owner: REQUEST_ABORTED / CANCELLED.
- scope owner in V3: ABORTED_BY_SCOPE or SCOPE_FENCED, preserving logical effect.
- ordinary owner rejection: AUTH_INTEGRATION_FAILURE.
- none of these paths may fetch.
### N-08 — legacy bounded JSON can reject and leave response cleanup inconsistent
- Severity: Medium
- Confidence: High
- Activation: V2 response path
Evidence:
- bounded-json.ts:9-12 awaits response.body.cancel outside a catch.
- lines 24-26 awaits reader.cancel; a rejection escapes the closed result.
- lines 30-33 does not cancel after reader failure and releaseLock can throw.
- client.ts:714-723 returns immediately on content-type mismatch without cancelling the response body.
- bounded-body-reader.ts:31-76 and 148-153 already isolates cancellation/release errors and is well tested.
Required decision:
Make bounded-body-reader the common primitive. Keep readBoundedJsons public return codes temporarily by delegating and mapping:
- RESPONSE_TOO_LARGE -> RESPONSE_BODY_LIMIT.
- UTF8_INVALID / JSON_INVALID / RESPONSE_STREAM_FAILURE -> MALFORMED_JSON for legacy compatibility.
Cancel on V2 content-type mismatch. Do not maintain two stream-reader strategies.
### N-09 — localStorage fallback cannot prove the event came from localStorage
- Severity: Medium
- Confidence: Very high
- Activation: storage fallback; invalidation is hint-only, so effect is stale/refetch pressure rather than data/authorization corruption
Evidence:
- StoragePulseEvent at browser-cross-context-invalidation.ts:98-101 contains only key and newValue.
- receiveStorage at 179-193 checks exact key/value but cannot check area.
- browser-cross-context-host.ts:206-248 discards native storageArea.
- client-cache-and-storage.md:82-83, 939-948 and checklist 1638 explicitly documents this missing check.
- Existing native browser tests cover homogeneous BroadcastChannel and homogeneous storage fallback, not a foreign storage area.
Required decision:
Capture localStorage once and derive both the write facade and event validator from the same object identity. Do not call a hostile getter twice.
Proposed facade:
export type StoragePulseEvent = Readonly<{
key: string | null;
newValue: string | null;
storageArea: "EXPECTED_LOCAL_STORAGE" | "OTHER_OR_UNKNOWN";
}>;
Core receiveStorage admits only EXPECTED_LOCAL_STORAGE. Register the pulse key in storage-keys.ts at the same time:
CACHE_INVALIDATION_PULSE:
backend localStorage
classification opaque-cache
valueCodec opaque-string-v1
ttl null
migration discard
quotaFallback no-persist
The storage adapter need not own pulse I/O; the registry owns its physical-key policy.
### N-10 — cursor pagination can admit a page after cancellation
- Severity: Medium
- Confidence: High
- Activation: AVAILABLE_NOT_COMPOSED pagination runtime
Evidence:
- cursor-pagination-runtime.ts:29-33 checks signal only before await loadPage.
- There is no post-await signal check before page validation/accumulation and success at lines 34-59.
- A non-cooperative loadPage that resolves after abort can make the last page return success.
- The architecture status claims an abort test, but cursor-pagination-runtime.test.ts currently covers finite chain, invalid invariants, loop and snapshot drift only.
Required decision:
Race loadPage with the signal or check immediately after await and before observing the page. Prefer an awaitWithAbort helper so a never-settling loader cannot hold loadAll forever. Late page completion is ignored. Return REQUEST_ABORTED / PAGINATION_ABORTED without partial items.
### N-11 — non-finite queue capacities bypass boundedness
- Severity: Low
- Confidence: High
- Activation: custom adapter construction only; bootstrap uses defaults
Evidence:
- bounded-diagnostics.ts:21 uses Math.max(1, maxEntries). NaN remains NaN and Infinity remains Infinity.
- best-effort-telemetry.ts:49 has the same issue.
- Comparisons against NaN/Infinity can disable intended eviction.
Fix: require Number.isSafeInteger and a documented upper ceiling, throwing TypeError at construction. This is configuration validation, not a runtime drop.
## 6. Selection-dependent improvements and explicitly separated hypotheses
These are not confirmed defects at the same level as N-01 through N-11.
### O-01 — mixed BroadcastChannel/storage-only tabs
- Confidence: Medium
- Evidence: browser-cross-context-invalidation.ts:318-342 returns immediately after a successful BroadcastChannel post and does not pulse storage. A second tab whose BroadcastChannel constructor failed but whose storage works listens only to storage.
- Existing tests at cross-tab-invalidation.test.ts:447-488 cover a sender whose BroadcastChannel post fails, then storage fallback. Browser capability tests cover BroadcastChannel/BroadcastChannel and storage/storage, not BroadcastChannel sender/storage-only receiver.
- Product decision: if per-tab capability asymmetry must be supported, mirror every accepted BroadcastChannel event to storage and rely on existing eventId dedupe. If “priority fallback” assumes partition-homogeneous capability, document that assumption and keep single-write behavior.
- Trade-off: mirroring increases synchronous localStorage writes and storage-event fan-out. This protocol is a best-effort hint with focus/stale revalidation, so do not build a durable/exactly-once bus.
### O-02 — beforeunload attachment comment does not match implementation
- Confidence: High for mismatch; Low material impact
- browser-lifecycle.ts:39-43 says the listener exists only while a source reports dirty.
- syncBeforeUnload at 135-143 attaches whenever any source is registered; it cannot observe a callback changing from false to true.
- onBeforeUnload rechecks actual dirtiness, so users are not incorrectly prompted.
- Preferred minimal action: document “while at least one dirty reporter is registered”. Add an observable update handle only if listener-count optimization is a real requirement.
### O-03 — async scope/coordinator teardown
- Confidence: Medium
- ServerStateScopeRuntime.dispose and QueryInvalidationCoordinator.dispose are void while reset/flush promises may exist.
- Current generation checks and disposed flags prevent reactivation; no stale cache admission was demonstrated.
- If runtime shutdown needs a “all background state work settled” guarantee, introduce async close and await it in composition. Otherwise characterize late work and retain the simpler void API.
### O-04 — QueryCachePort clone-on-read
- Confidence: Medium
- tanstack-query-cache.ts clones writes but returns TanStacks object reference on read.
- Docs say cached mapped values are immutable, but QueryCachePort returns unknown rather than a readonly type.
- Decide whether the port guarantees immutable values or isolation. If isolation is required, clone on read and return QUERY_CACHE_FAILURE on clone failure. Do not add cost to production TanStack hooks based only on this legacy port.
### O-05 — caller-abort versus already-buffered successful response
- Confidence: Medium; not included in confirmed findings
- V3 aborts the fetch signal, but admitResponse does not directly inspect terminalCancellation after a custom/buffered reader resolves.
- Before changing semantics, add a deterministic test where readBoundedResponseBytes aborts the caller and then returns valid bytes. Product must choose first-terminal-owner-wins versus completed-response-wins. The designs CancellationOwner wording suggests first-owner-wins, but this report does not claim it without characterization.
## 7. Patterns to apply, and patterns to reject
Apply:
1. Profile/Strategy registry for auth. The operation selects an immutable profile; the credential owner supplies only proof material.
2. Stable tuple codec for validator keys. It directly solves injectivity and keeps storage private.
3. Monotonic certainty lattice for logical command execution. Physical attempts cannot downgrade already-observed uncertainty.
4. Structured cancellation context. One lifetime signal/deadline is passed through credential, recovery, retry sleep, fetch and response admission.
5. Small terminal lifecycle state for telemetry. ACTIVE/DISPOSED plus one joined flush promise is sufficient.
6. Adapter-boundary predicate for StorageEvent.storageArea. Browser identity checks belong at native capability capture.
7. Characterization-first consolidation for legacy body reading. Delegate to the proven bounded reader while preserving old error codes.
Reject:
- A generic HTTP interceptor/middleware pipeline: it obscures authority/order and recreates the transport-header bug.
- A generic repository abstraction for query cache, ETag store and storage pulse: their consistency and lifecycle semantics differ.
- Durable/exactly-once cross-tab messaging: invalidation is a bounded hint; server revalidation remains authoritative.
- Persistent telemetry retry/offline queue: current contract is best-effort and has no consent/retention decision.
- A new retry library or circuit breaker: current retry bounds are explicit and adequate once replay proof/certainty is corrected.
- Event sourcing for scope/reset: synchronous generation fencing plus ordered participants is simpler and already correct.
## 8. Exact implementation manifest
Implement as small reviewable changes. “Delete: none” and “Move: none” applies to the immediate remediation; legacy removals occur only after the compatibility window.
### Change set A — V3 observability and monotonic effect
Modify:
- src/adapters/http/http-execution-v3.ts
- src/adapters/http/http-effect-certainty.ts
- src/bootstrap/runtime-adapters.ts
- src/features/reference-feature/adapters/create-reference-feature-input.ts
- src/contracts/diagnostics.ts only if effect_certainty is approved; otherwise do not modify its allowlist
- tests/unit/http-execution-v3.test.ts
- tests/unit/runtime-adapters.test.ts
- tests/features/reference-feature/reference-runtime-composition.test.ts
- docs/architecture/decisions/VD-07-diagnostics-and-telemetry-exporter.md
- docs/architecture/2026-07-30-frontend-runtime-capability-repository-aligned-implementation-closed-deep-design.md
Create:
- tests/integration/http-execution-v3-observability.test.ts
Delete: none.
Move: none.
### Change set B — profile-authoritative credentials and auth cancellation
Modify:
- src/contracts/rest-profiles.ts
- src/contracts/external-contract-runtime.ts for strict authProfileId grammar only
- src/adapters/http/http-contract-bridge.ts
- src/adapters/http/http-execution-v3.ts
- src/application/ports/auth-session-port.ts
- src/adapters/auth/external-session-adapter.ts
- src/bootstrap/runtime-adapters.ts
- src/features/reference-feature/adapters/create-reference-feature-input.ts to map AUTH_INTEGRATION_FAILURE
- tests/unit/rest-profile-contract.test.ts
- tests/unit/auth-session-adapter.test.ts
- tests/unit/http-execution-v3.test.ts
- tests/unit/runtime-adapters.test.ts
- tests/features/reference-feature/reference-runtime-composition.test.ts
- docs/architecture/decisions/VD-23-api-transport-selection-and-rest-execution.md
Create:
- tests/integration/http-execution-v3-auth-profile.test.ts
Delete: none.
Move: none.
### Change set C — telemetry lifecycle
Modify:
- src/adapters/telemetry/best-effort-telemetry.ts
- src/bootstrap/runtime-adapters.ts
- tests/unit/telemetry.test.ts
- tests/unit/runtime-adapters.test.ts
- docs/architecture/decisions/VD-07-diagnostics-and-telemetry-exporter.md
Create: none.
Delete: none.
Move: none.
### Change set D — state sidecars and cancellation
Modify:
- src/adapters/query-cache/conditional-validator-store.ts
- src/adapters/query-cache/cursor-pagination-runtime.ts
- tests/unit/conditional-validator-store.test.ts
- tests/unit/cursor-pagination-runtime.test.ts
- docs/architecture/api-contract-schema-mapper-and-server-state.md
Create: none.
Delete: none.
Move: none.
### Change set E — legacy rollback hardening and reader consolidation
Modify:
- src/contracts/mutation-intent.ts
- src/application/ports/auth-session-port.ts
- src/adapters/auth/external-session-adapter.ts
- src/adapters/http/client.ts
- src/adapters/http/http-execution-v3.ts to reuse the common key validator/context
- src/adapters/http/bounded-json.ts to delegate to bounded-body-reader
- tests/integration/http-client.test.ts
- tests/integration/auth-recovery.test.ts
- tests/integration/http-execution-contract.test.ts
- tests/unit/bounded-body-reader.test.ts
- docs/architecture/decisions/VD-23-api-transport-selection-and-rest-execution.md
Create:
- tests/unit/bounded-json-compatibility.test.ts
Delete immediately: none.
Move: none.
Later removal after zero runtime callers and an expired rollback window:
- Delete src/adapters/http/client.ts
- Delete src/adapters/http/bounded-json.ts
- Delete src/adapters/http/request-builder.ts
- Delete src/adapters/http/resource-mapper.ts
- Delete src/adapters/http/schema-registry.ts
- Remove createRuntimeHttpClient from src/bootstrap/runtime-adapters.ts
- Remove V2-only tests/fixtures after V3 equivalents exist
Do not delete retry-policy.ts while V3 imports parseRetryAfter.
### Change set F — exact storage fallback admission
Modify:
- src/contracts/storage-keys.ts
- src/adapters/cross-context-invalidation/browser-cross-context-host.ts
- src/adapters/cross-context-invalidation/browser-cross-context-invalidation.ts
- src/adapters/cross-context-invalidation/index.ts
- tests/unit/cross-tab-invalidation.test.ts
- tests/browser-capabilities/cross-context-invalidation.spec.ts
- docs/architecture/client-cache-and-storage.md
- docs/architecture/decisions/VD-13-client-cache-scope-and-persistence.md
Create:
- tests/unit/browser-cross-context-host.test.ts
Delete: none.
Move: none.
Optional O-01 mirroring must be a separate change set and must not be bundled with the exact storageArea security check.
### Change set G — capacity and lifecycle documentation hardening
Modify:
- src/adapters/diagnostics/bounded-diagnostics.ts
- src/adapters/telemetry/best-effort-telemetry.ts
- tests/unit/diagnostics.test.ts
- tests/unit/telemetry.test.ts
- src/adapters/platform/browser-lifecycle.ts comment only, unless observable dirty state is selected
Create a browser-lifecycle unit test only if behavior changes.
Delete: none.
Move: none.
## 9. TDD matrix
Write each test red first.
| Test name | Input/setup | Expected result |
| --- | --- | --- |
| records_v3_terminal_outcome_with_allowlisted_context_once | V3 success and terminal 503; concrete diagnostics adapter | one record per logical execution; route/operation/status/attempt/duration are safe bucket keys; dropped map empty |
| emits_v3_terminal_non_abort_failure_once | V3 retry exhaustion | one api.request.failed after final attempt, none per attempt |
| does_not_emit_v3_failure_telemetry_for_caller_or_scope_abort | caller abort and scope fence | diagnostic once, telemetry zero |
| forwards_reference_route_id_to_v3_observation | list/detail/create gateway requests | exact registry route IDs reach observation; no raw URL/intent |
| rejects_unknown_auth_profile_during_runtime_composition | installed contract refers to missing profile | composition throws before any feature can execute |
| rejects_bearer_ready_patch_without_authorization | authenticated session, empty READY patch | AUTH_INTEGRATION_FAILURE, NOT_STARTED, fetch zero |
| rejects_credential_patch_that_owns_accept_content_type_or_credentials | hostile credential adapter | integration/contract failure, fetch zero |
| sends_exact_profile_credentials_and_required_headers | valid bearer and cookie profiles | exact init.credentials and header subset |
| forwards_lifetime_abort_to_external_credential_owner | hanging owner, deadline/caller abort | owner signal aborts; execution settles with correct closed error |
| preserves_prior_maybe_applied_when_retry_is_fenced_before_dispatch | IDEMPOTENT command, first 429, scope false only at retry final invariant | SCOPE_FENCED and MAYBE_APPLIED; fetch called once |
| never_decreases_logical_command_certainty_across_attempts | table of NOT_STARTED/NOT_APPLIED/MAYBE combinations | join follows lattice |
| dispose_prevents_queued_and_future_telemetry_delivery | captured schedule, emit, dispose, callback, emit | fetch zero, queue zero |
| dispose_aborts_active_telemetry_delivery_without_reschedule | fetch waits on signal, dispose | signal aborted, no reschedule |
| concurrent_flush_joins_active_delivery | call flush twice while sink pending | both promises settle only after same fetch settles; fetch once |
| runtime_dispose_disposes_telemetry_before_state_dependencies | spied telemetry/lifecycle | pagehide removed and sink aborted during composition dispose |
| keeps_delimiter_ambiguous_validator_bindings_distinct | the A/B binding pair from N-05 | prepare(A)=etag A, prepare(B)=etag B |
| rejects_unbounded_or_invalid_validator_definition_identity | empty/oversize/invalid fingerprint | install false, no row |
| rejects_empty_control_and_oversize_legacy_idempotency_keys_before_send | "", whitespace, control, 257-byte key | VALIDATION_REJECTED/IDEMPOTENCY_KEY_INVALID; auth/fetch/timer zero |
| reuses_one_valid_legacy_key_on_every_retry | keyed 503 then success | identical non-empty header on each physical attempt |
| settles_legacy_hanging_credential_at_total_deadline | owner never resolves | REQUEST_TIMEOUT; fetch zero; listeners/timer removed |
| ignores_late_legacy_recovery_completion | recovery resolves after abort/scope transition | terminal result unchanged; no replay/notification from late completion |
| bounded_json_never_rejects_when_cancel_or_release_fails | hostile stream methods | closed legacy error, no rejection |
| content_type_mismatch_cancels_legacy_response_body_once | non-JSON response with cancellable stream | CONTENT_TYPE_MISMATCH and cancel called once |
| ignores_storage_event_from_non_local_storage_area | exact pulse key/value but OTHER_OR_UNKNOWN area | delivery zero |
| accepts_storage_event_only_from_captured_local_storage | native-like event with captured identity | delivery once |
| captures_local_storage_getter_once | getter returns different objects per access | getter called once; event/write identity coherent |
| ignores_late_cursor_page_after_abort | loader aborts signal then resolves final page | REQUEST_ABORTED/PAGINATION_ABORTED |
| settles_never_resolving_cursor_loader_on_abort | loader never settles | loadAll settles promptly with abort |
| rejects_non_finite_adapter_capacities | NaN, Infinity, fractional, excessive values | TypeError at construction |
Targeted commands:
corepack pnpm exec vitest run \
tests/unit/http-execution-v3.test.ts \
tests/integration/http-execution-v3-observability.test.ts \
tests/integration/http-execution-v3-auth-profile.test.ts \
tests/features/reference-feature/reference-runtime-composition.test.ts
corepack pnpm exec vitest run \
tests/unit/telemetry.test.ts \
tests/unit/runtime-adapters.test.ts \
tests/unit/diagnostics.test.ts
corepack pnpm exec vitest run \
tests/unit/conditional-validator-store.test.ts \
tests/unit/cursor-pagination-runtime.test.ts \
tests/unit/cross-tab-invalidation.test.ts \
tests/unit/browser-cross-context-host.test.ts
corepack pnpm exec vitest run \
tests/integration/http-client.test.ts \
tests/integration/auth-recovery.test.ts \
tests/integration/http-execution-contract.test.ts \
tests/unit/bounded-json-compatibility.test.ts
Browser evidence:
corepack pnpm test:browser-capabilities
Required final gates:
corepack pnpm check:types
corepack pnpm lint
corepack pnpm check:architecture
corepack pnpm check:diagnostics
corepack pnpm test:unit
corepack pnpm test:integration
corepack pnpm test:reference-feature
If storage registry changes, also run:
corepack pnpm check:registries
corepack pnpm verify:compatibility
## 10. Compatibility and migration order
1. Land characterization tests only. They must fail for N-01 through N-05 and remain isolated from implementation.
2. Add routeId to InstalledContractOperationExecutor and HttpExecutionContext. Update every compile-time call site in reference gateway/tests in one commit. This is source-breaking but has no wire change.
3. Add typed V3 observation fields and runtime projection. Keep diagnostic/telemetry registries closed; use existing buckets. Deploy read operations first and verify non-empty, non-dropped V3 records.
4. Add logical certainty accumulation. No wire/API change outside the exported outcome values; downstream code must already handle MAYBE_APPLIED.
5. Extend RestAuthProfile with requiredCredentialHeaders and build the profile index at composition. Deploy fail-closed validation before changing credential owners.
6. Extend auth cancellation context as optional. Update internal/demo/external adapters and both clients. After one compatibility release, make it required for external owners.
7. Make demo authentication explicit. Do not relax REFERENCE_EXTERNAL_BEARER. Validate the selected demo behavior only against loopback/test provider evidence before enabling.
8. Fix telemetry lifecycle and call dispose from infrastructure teardown. This changes only post-dispose behavior and flush-await semantics.
9. Replace validator key codec before the first conditional HTTP composition. It is memory-only, so no data migration is required.
10. Harden V2 idempotency/cancellation/body reading before documenting it as a rollback target. Add deprecation notices and audit createRuntimeHttpClient callers.
11. Add storage registry entry and exact storageArea check without changing the invalidation wire envelope/version.
12. Run native browser capability evidence. Decide mixed-transport mirroring separately.
13. Only after zero V2 callers, V3/provider evidence, and expiration of the rollback window, delete legacy files.
Compatibility notes:
| Change | Compatibility |
| --- | --- |
| Required routeId | TypeScript source break; no network wire break. Update all executor callers atomically. |
| Observation shape | Internal dependency seam but exported type; tests/custom composition must update. |
| AUTH_INTEGRATION_FAILURE outcome | Exhaustive switch source break; add mapping to existing ApiFailure AUTH_INTEGRATION_FAILURE. |
| RestAuthProfile required headers | Source break for custom profiles; provide migration error naming profile ID only. |
| Optional AuthOperationContext phase | Backward compatible for owner implementation types; behavior improves immediately for updated owners. |
| Telemetry dispose | Intentional behavioral change only after ownership ends. |
| Validator key codec | No persisted state and no HTTP composition; safe replacement. |
| StoragePulseEvent area enum | Test/host facade source break; wire envelope unchanged. |
| Invalid legacy idempotency key | Intentional fail-fast behavior; callers relying on empty keys must be fixed, not grandfathered. |
## 11. Rollback sequence
Rollback must preserve security/correctness invariants.
1. Disable affected command operations first; do not route a command to legacy V2 unless V2 idempotency, auth deadline, final invariant and provider evidence are already fixed.
2. If observability sink causes incidents, set telemetry config off or wire noOpTelemetry. Keep V3 diagnostic projection, redaction registries and producer tests.
3. If strict auth composition rejects a bad deployment, fail the operation/provider as unavailable and repair the profile/owner. Do not restore broad credential headers or patch-owned credentials.
4. Read-only V3 operations may fall back only to a hardened V2 path with unexpired provider/security evidence, matching VD-23 lines 700-703.
5. The logical-effect accumulator must not be rolled back independently; downstream reconciliation depends on conservative MAYBE_APPLIED.
6. Conditional-validator codec rollback is simply disabling conditional request composition and clearing the in-memory store.
7. Storage-area hardening rollback should degrade to BroadcastChannel/local-only revalidation, not accept unverified storage events.
8. Cross-context wire version remains unchanged, so no coordinated tab upgrade is needed.
9. Roll back contract artifact, frontend and backend as one coherent set where operation/profile semantics changed.
10. Keep new regression tests during rollback; change only routing/configuration.
## 12. Existing tests/docs cross-check and false-positive controls
### What the passing tests genuinely prove
- http-execution-v3.test.ts proves descriptor projection, keyed intent validation, one-key reuse, no query key, schema containment, post-dispatch command uncertainty, scope fencing after a response, credential-wait deadline, deadline retry suppression, retry-sleep cancellation and forbidden-body stream failure.
- bounded-body-reader.test.ts has strong hostile stream/cancellation/release coverage; this is why consolidation is preferred.
- server-state-scope-runtime.test.ts proves synchronous fencing, participant order, fail-closed reset/activation and identity close.
- tanstack-cache-coordinator.test.ts proves topic mapping, lease deferral, reset ordering and disposal behavior under current contract.
- cross-tab-invalidation.test.ts proves invalid/stale/self/duplicate/gap handling, bounded fallback and cleanup.
- browser capability spec proves actual BroadcastChannel/BroadcastChannel and storage/storage delivery in supported browsers.
- diagnostics.test.ts and telemetry.test.ts prove projector allowlists, bounded queues, hostile context containment and pagehide listener removal.
- integration/http-diagnostics.test.ts proves exactly-once diagnostics/telemetry for legacy createHttpClient.
- reference runtime composition proves V3 URLs/headers and that private intent values do not appear in collected evidence.
- 21 selected files / 144 tests pass, so findings do not rely on a generally broken baseline.
### Why those tests do not invalidate the findings
- Legacy HTTP diagnostics tests import createHttpClient, not createContractHttpExecutor. They cannot validate V3 runtime-adapters projection.
- reference runtime composition only asserts private values are absent; an empty diagnostics array also satisfies it.
- check:diagnostics counts/inspects source producers but does not execute their concrete context through projectDiagnosticRecord.
- auth-session tests validate the external owners current header allowlist, but V3s exported CredentialPatchOutcome and final invariant still grant broader authority; they also do not require Authorization for the declared bearer profile.
- current V3 command is KEYED with retryBudget zero. It does not exercise an IDEMPOTENT retry followed by a final-invariant fence.
- telemetrys disposal test calls dispose after pagehide has already flushed and checks only listener removal.
- conditional-validator tests use delimiter-unambiguous values and docs explicitly mark the sidecar not composed.
- storage tests check exact key and envelope but the facade has no storageArea field to assert.
- browser docs explicitly list pulse registration and storageArea as unfinished, confirming N-09 rather than contradicting it.
- mixed-transport asymmetry is left as O-01 because the documented priority fallback can reasonably be read as a deliberate single-transport policy.
- beforeunload does not prompt falsely because the event callback rechecks dirty state; only the attachment comment is mismatched.
- no claim is made that conditional validators or cursor pagination currently corrupt the installed reference HTTP path; both are activation blockers for future composition.
## 13. Design worth preserving
- V3 keeps operation semantics in installed descriptors and re-verifies a bounded final request rather than accepting arbitrary URLs/headers from features.
- Bounded response admission avoids Response.json, enforces byte ceilings, uses strict UTF-8, and isolates stream cleanup failures.
- Mutation intent and command effect are explicit public concepts; post-dispatch uncertainty is represented instead of guessed from HTTP status.
- Server-state scope fences synchronously before any reset await, aborts the old signal, closes identity registries and creates a new QueryClient generation.
- Query invalidation sends only registry topic/version/epoch, never query keys, cached data, account IDs or mutation payloads. Remote authority is invalidate-only.
- Cross-context event parsing is closed and bounded with TTL, event dedupe, source epoch/sequence and gap escalation.
- TanStack retry is disabled so the HTTP layer remains the single retry authority.
- Diagnostics/telemetry have closed registries, low-cardinality value policies, redaction and failure isolation.
- External auth owner never returns raw tokens to application code; it returns a constrained header patch.
- Composition tears down optional capabilities before base state, which is the right dependency order.
- No unnecessary persistence/offline mutation queue/exactly-once protocol is claimed.
## 14. Recommended delivery order
P0:
1. N-01 V3 observability.
2. N-02 profile-authoritative auth.
3. N-03 monotonic command certainty.
4. N-04 telemetry terminal lifecycle.
P1 before enabling currently available capabilities or trusting rollback:
5. N-05 conditional-validator key codec.
6. N-06/N-07 V2 replay and auth deadline.
7. N-09 exact storageArea and registered pulse.
8. N-10 pagination cancellation.
P2 cleanup:
9. N-08 body-reader consolidation.
10. N-11 capacity validation.
11. O-02 documentation alignment.
12. Decide O-01/O-03/O-04/O-05 with explicit product requirements and characterization tests.
This ordering closes silent current-path failures and security/effect authority first, then makes latent capabilities safe to compose, and only then removes duplication.