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>
991 lines
60 KiB
Markdown
991 lines
60 KiB
Markdown
# 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 B’s 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 readBoundedJson’s 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 TanStack’s 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 design’s 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 owner’s current header allowlist, but V3’s 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.
|
||
- telemetry’s 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.
|
||
|