Strata architecture and SDDMenu
On this page

Current implementation audit

Server Components client reference identity for 0.12.0 candidate

From synchronized main SHA 02b06a17dbd5e9221bf02367beadabfbf84e54ee, on feat/server-components-client-reference-integrity-v012. The Vite analyzer already rejected ambiguous selectors at build time; the candidate closes the remaining runtime gap. Generated surrogates now pass { type, expectedSelector } to provideClientReferences, and the host checks the real Angular component selector (public reflectComponentType), rejects two distinct components claiming one selector (previously the last silently won) and rejects non-components, as a reference failure inside the existing all-or-nothing preflight. Evidence levels are recorded in the candidate report. This is not M4 or 1.0 certification.

Browser reference refusal integration (0.13.0; test-only)

From origin/main SHA 7c6942f5b458335d57265c527b3195362814d300. The existing failure harness now mutates one SSR boundary selector before Chromium scripts execute, exercising the real StrataIslandHost unknown-reference preflight. On Node and local workerd it reports one classified reference error and one terminal observation, leaves all boundaries in that host uncreated with the original DOM nodes, and preserves healthy sibling interaction. The packed 0.13.0 consumer positive matrix and its existing budget refusal pass. Malformed generated descriptors remain unit-only; installed-package selector-negative cases, the Vite optimizer cache matrix, performance series, the full local CI matrix and hosted CI remain unqualified. This is test-only: an empty Changeset records no package bump, and there is no 0.14.0 version claim. See the qualification report.

Server Components bounded hydration preflight for 0.11.0 candidate

From synchronized main SHA b69e2eec5bf0ba39f322e03143978ab892fba8a2, on feat/server-components-hydration-budgets-v011. Candidate browser-side budgets are 128 owned client boundaries and 1 MiB aggregate serialized payload UTF-8 bytes per Server Component host, alongside the existing 64 KiB individual payload limit. The preflight retains all-or-nothing hydration and uses the resource-limit error category. Package unit tests, Node production/Chromium, local workerd/Chromium and the packed consumer pass for the candidate. The regular Analog dev suite passes but does not yet exercise an over-budget host; performance series and hosted CI are pending. This is not M4 or 1.0 certification. See the candidate evidence report.

Analog route registration integrity for 0.11.0 candidate

From synchronized main 7ac01d44f7c4f20f0817dca661cad135ed956f44 (@strata-sc/analog 0.10.0), branch feat/analog-route-registration-integrity-v011. Against the locked H3 1.15.11 / Nitro 2.13.4 router, real parameter-shape collisions are order-dependent: same-method registrations with /products/:id and /products/:slug both succeed, and the first registered template supplies the handler and parameter name. Nitro also shares the parameter-name metadata across methods; GET /:id plus POST /:slug delivers whichever parameter name was registered first to both handlers. Analog registration planning now rejects differing templates with an equivalent supported shape across the router's Strata-owned routes, independent of method, batch and apiBoundary. Identical templates remain valid across methods. Unsupported Nitro syntax outside the boundary remains unchecked except for exact method/path duplicates; native registrations remain invisible to Strata. The Analog suite passes (247 tests); full local build/lint/type/test/format and the runtime, packed consumer, Server Components regression and E2E matrices passed. Startup registration timings and unrun/hosted evidence are recorded in the report. PR #145's hosted Verify passed; deploy remained skipped and no deployed qualification was performed. See the candidate report.

Server Components hydration observations (0.10.0 candidate)

From synchronized main efd14e2d1375e1762846c3ee6ab6bb49d5440609 (@strata-sc/server-components 0.9.0). The browser runtime now supports an optional, application-injector provideStrataHydrationObserver() callback. It emits one immutable terminal host event after a successful all-boundary commit, a preflight refusal, or a commit failure after rollback. It does not replace Angular ErrorHandler, alter skew recovery, include boundary values or observe later Angular behavior. Unit coverage and the local qualification matrix are recorded in the evidence report; unrun and hosted evidence must remain explicitly marked there. M4 and Strata 1.0 remain open.

Analog validated URL-encoded forms for 0.10.0 (feature branch)

On branch feat/analog-validated-forms-v010, work began from synchronized main 68e4a5135964e3ea6f8f86ef216baa969fe4e429 (@strata-sc/analog 0.9.0). The additive request.readValidatedForm(schema) API validates application/x-www-form-urlencoded with Standard Schema v1, inferred outputs and transformations. It reads at most 1 MiB, caps nonempty form fields at 10,000, rejects malformed percent/UTF-8 encoding and unsupported media/charset with safe existing 400/413/415 envelopes, and uses request-local reader ownership so raw-first reads cannot bypass bounds or JSON/form readers reinterpret one another. Repeated values preserve order; nested path syntax is literal. Unit tests pass (241), release-gate decision tests pass (17), and the real fixture passes Node production, Vite dev and local workerd with 24-request overlap. The packed consumer and full local CI matrix pass. Hosted CI, live workflow-run transition and deployed qualification remain open. One Analog-only 0.10.0 Changeset is present. See the evidence report.

The same branch fixes issue #117 in a separate release workflow change: publishing now waits for the exact main push's successful Verify, checks that main still points to that SHA, and rechecks immediately before Changesets. The unit decision matrix passes, but hosted workflow-run transitions are not yet verified; issue #117 stays open until merge and live-transition inspection. At the starting SHA, GitHub Release and npm both confirm Analog 0.9.0 was published (release run 40120642476).

Analog header validation for 0.9.0 (feature branch)

On branch feat/analog-header-validation-v09, work began from synchronized main (769899c). request.validateHeaders(schema) validates a frozen, lowercase, string-valued, request-local header snapshot with Standard Schema v1 through the existing validation path: inferred output, safe 400 with source: "headers", cleanup-first precedence and input-rejected observation. Repeated fields are the native Headers combination (", "); nothing is split, and no credential is verified. Implemented, unit tested, locally integrated (pnpm test:analog-headers on Node, dev, local workerd) and packed-consumer type-qualified; not hosted-CI qualified, deployed or adopted. See the evidence report.

Analog request observability for 0.8.0 (feature branch)

On branch feat/analog-request-observability-v08, work began from synchronized main ea9b47a1bb49491ee92b840ae75f3209ce4384ea (@strata-sc/analog 0.7.0). registerControllers() gains the opt-in onRequestSettled observer: one frozen, structured observation per Strata-owned invocation (registered route template, a finite outcome, the phase, a status only where Strata produced it, streaming, cleanup, and the monotonic invocationMs / elapsedMs), delivered once after cleanup has settled, and for an owned streamResponse() when its body completes, fails or is cancelled, not when the handler returns. It carries no request values, headers, bodies, error details or response content. It is synchronous and isolated: a throwing or rejecting observer changes neither the response nor the original error. Client disconnect is not observable on the pinned adapters, so a stream never read or cancelled is never reported. Native routes, serverFn and the API boundary's own 404/405 are not observed. Qualified by 26 unit tests on a real h3 router, pnpm test:analog-observability on the real fixture (Node, dev, local workerd) and the packed consumer. Not hosted-CI qualified when written, not deployed, no production adoption, observation overhead not measured, and the publish-before-CI issue #117 is untouched. See the evidence report.

Server Components deployment skew for 0.8.0 (feature branch)

On branch feat/server-components-deployment-skew-v08, work began from synchronized main ef31c0d0d73ef57f3228856bc70e5b7833d5df0f (@strata-sc/server-components 0.7.0). The 0.7.0 and Analog 0.7.0 work described below is merged (#118, #119, #120); the Analog 0.7.0 version PR (#121) was still open and is untouched. strataServerComponents({ buildId }) compiles a consumer-chosen label into every environment of one build; server-rendered boundaries carry data-strata-build and the browser compares it before creating any island. StrataBoundaryError gains a stable category, a confirmed mismatch of two well-formed identities is StrataBuildMismatchError, and provideSkewRecovery() adds an opt-in, bounded (sessionStorage counter) reload of the current document for that case only. Protocol numbers are unchanged; without the option nothing changes. Qualified with three independently built releases of an external consumer installed from the packed tarball, a document of one served with the script of another, on Nitro Node and local workerd (125 checks). Not deployed, not hosted-CI qualified when written, no production adoption; scripts built before the feature cannot detect anything. See the evidence report.

Analog streaming lifecycle for 0.7.0 (merged in #120; version PR #121 open when written)

On branch feat/analog-stream-lifecycle-v07, work began from synchronized main 279275eafe7519725b8413046335fc91330795d6 (@strata-sc/analog 0.6.0). The old behaviour is reproduced on the real fixture: an ordinary Response over a ReadableStream finds its request-scoped resource already closed when Nitro reads the body. @strata-sc/analog gains streamResponse(body, init?), an explicit opt-in that holds the invocation's cleanup scope until the body completes, fails or is cancelled, then closes it once (LIFO, awaited, AggregateError rules unchanged). Ownership is claimed after every interceptor settles, so a replaced response closes eagerly; an owned body read before the claim fails with a configuration error. Every other handler keeps the eager contract. Qualified on Analog 2.7.2 / Nitro 2.13.4 / H3 1.15.11: Node (production and dev) delivers incrementally; local workerd (Cloudflare Pages) buffers the body but still releases once. Client disconnect does not reach the body on either runtime (NO-GO; bodies must be finite), and what a client sees after a mid-body failure is up to the transport (Node: connection left open; workerd: empty 200). A real compiled serverFn (GET and POST, validated input) runs in the same application, undisturbed by the API boundary; POST needed the @analogjs/router h3 dependency declared (workspace packageExtensions), and on workerd every serverFn answers 500 (Angular JIT). Packed consumers (TypeScript 6 and 5.9.2) stream from tarballs, including a request Angular injector that outlives the handler. See the evidence report. Experimental fixture evidence: not hosted-CI qualified when written, not deployed, not production adoption, and not M3 or Strata 1.0 certification.

Server-only firewall vs the dependency optimizer (0.9 candidate)

From main 481976818675519015edddd209b7bd3c4fc3c83d. A bypass was reproduced against the installed Vite 8.3.0 (Rolldown 1.2.8) dev server: package modules asserting server-only, barrels re-exporting them and dynamic imports of them were served pre-bundled. strataServerComponents now adds a plugin to optimizeDeps.rolldownOptions.plugins that fails the optimizer run (no chunk) and a Connect error middleware that strips frame/source/stack from [strata] dev errors. No new public option. Locally integrated and packed-consumer qualified through pnpm test:server-component-optimized-deps; unit tested; not hosted-CI qualified, not deployed, not production adopted. The merged release authorizer was found rejecting its own genuine CI run (path spelling) and received a minimal fix with tests, issue #117 stays open until a hosted run proves it. See the report.

Server Components SSR integrity for 0.7.0 (merged in #118, released as 0.7.0 in #119)

On branch feat/server-components-ssr-integrity-v07, work began from synchronized main a54948b614368d8d0246f11fca34e72ea02838aa (@strata-sc/server-components 0.6.0, @strata-sc/analog 0.6.0). The intermittent Server Component SSR failure (HTTP 200, empty island, JIT compiler unavailable) has a reproduced mechanism: Analog builds the client and ssr environments concurrently with one shared Angular plugin that discards its compilation output when the first environment ends, so the other environment's files are skipped and ship with their decorators uncompiled. The plugin now holds each environment at buildEnd until every environment has finished transforming, and a post-transform gate fails a production build that still contains an uncompiled source-owned Angular class or an unlinked partial declaration. The qualification suites now classify every SSR answer (an HTTP 200 alone is never a pass) and the packed consumer adds two failing-build controls, an adversarial environment schedule, and cold-start overlapping first requests. The mechanism was reproduced by fault injection, not observed naturally (0 of 18 un-injected local builds), and the two CI failures are consistent with it but were never captured with their build output; the failure is therefore mitigated and made detectable, not proven eliminated. See the evidence report. Experimental fixture evidence, not a deployment and not Strata 1.0 certification.

External Angular package islands (2026-10-21; merged in #107, released as 0.4.0)

Starting main SHA: 88f68832361694caf683d529adc15d5e628559ea. The clientComponents registry, import matching, alias-aware package references, package surrogate imports and Vite package prebundling/SSR inclusion are implemented in the feature branch. The Server Components unit suite passes (257 tests); package typecheck/build and repository lint/format/test gates pass. The isolated packed consumer passed npm installation, TypeScript 5.9/6 declaration checks, Angular AOT, Node SSR, Chromium hydration/interaction, installed-plugin server-only tests, Vite dev/HMR, Cloudflare Pages/workerd SSR and hydration, and graph-confidentiality checks. The consumer library is independently partial-compiled and packed in Angular Package Format. Hosted CI, measured bundle cost and production adoption were pending when this was written. See package-island evidence. Superseded: the feature was merged in #107 and published as @strata-sc/server-components 0.4.0 (see its CHANGELOG); the packed consumer still gates it on every pull request. Production adoption remains unestablished.

Analog HTTP routing and API boundary for 0.6.0 (merged in #113, released as 0.6.0 in #116)

On branch feat/analog-http-routing-v06, work began from synchronized main b7ab2d6173fc3710ffb9fed92b64a612ba8e8063. registerControllers() gains the opt-in apiBoundary option: explicitly owned prefixes answer a safe JSON 404 for unknown paths, 405 with Allow for unsupported methods, a derived HEAD and an opt-in plain OPTIONS (never a CORS preflight), without shadowing native Nitro routes, pages or Analog server-function dispatch. Package unit tests (173), real Analog dev and Nitro production HTTP checks, the packed external consumer on Node production and local workerd (including a hydrated island that observes JSON answers), and the full local CI matrix pass. Server functions are covered at route-table level only; no real serverFn module was exercised. The intermittent Server Component SSR failure seen on PR #112 was reproduced once on main and was unresolved when this was written; it has since been traced to a reproduced mechanism (see the 0.7.0 section above); see the original report. Experimental fixture evidence, not a deployment and not Strata 1.0 certification.

Analog HTTP application contract for 0.5.0 (feature branch)

On branch feat/analog-http-contract-v05, work began from synchronized main 0031b654058dc0658cab65eb2fd360b7dcad4b15. After main advanced with the Angular CDK patch update in PR #92, the branch was rebased onto ff1034def709abe2d92e13e2fcbb4b39e7fd1b77. @strata-sc/analog adds StrataAnalogHttpError, jsonResponse() and noContent(). The route boundary translates expected errors only after successful request cleanup; cleanup failures and unexpected exceptions retain Nitro's failure behavior. The fixture's protected inventory API exercises Standard Schema body validation, 401/403 guards, 404/409/422 application errors, 201 JSON and empty 204.

Focused package tests, real Analog Node production/dev HTTP qualification, local Pages/workerd and packed-consumer behavior pass. Repository build, lint/typecheck, unit/release/performance tests, integration matrix and Entwine check/build pass. Website E2E passes against a fresh server on a dedicated port; the initial default-port run reused an unrelated process on port 3000. Cancellation remains unavailable: pinned Nitro/H3 does not expose Cloudflare's native incoming Request signal to the Strata event; the public StrataAnalogRequest has no signal. See the qualification report. This is experimental fixture evidence, not production deployment or Strata 1.0 certification.

Packed Angular DI consumer (2026-10-20)

Starting from synchronized main 8272ce98e3908c40d788ce00363c675917c85a34, test/analog-packed-angular-di-consumer adds a Node-only profile that installs the current @strata-sc/{core,analog,server-components} tarballs into an external Analog app. Strict TypeScript 6, a real Nitro Node production server, consumer-owned Angular root/request injectors, synthetic guard decisions, 24 concurrent requests, request cleanup and the Nitro close hook pass. This is a partial qualification: the complete two-interceptor and validation matrix, failure precedence, bundle/confidentiality comparison, and broader CI/Entwine reproduction remain open. The Worker JIT NO-GO is unchanged. See the external consumer report.

Angular DI with Analog HTTP composition (2026-10-20)

On feature branch test/analog-di-http-composition, starting from synchronized main eb1d17a81283d57569e9dc1b4712dea2623f6fd6, the existing SPEC-003 CatalogController is composed with the existing guard, interceptor, Standard Schema and cleanup APIs. The separate public SPEC-003 routes retain their prior behavior. Node dev and Nitro production evidence, provider overrides, failure precedence, a 24-request matrix and remaining limitations are recorded in the composition report and regenerated integration baseline. The known local workerd Angular JIT NO-GO is preserved. The minimal packed Node DI profile is partially qualified; shared SSR/controller DI, real application adoption and Strata 1.0 remain unqualified.

Analog request-scoped HTTP interceptors (2026-10-20)

Starting from 8417d60e040190a6a68c91a96f8380db934908bf, the feature branch feat/analog-http-interceptors adds opt-in onion-style wrappers to @strata-sc/analog. Guards precede interceptors; interceptors wrap the one factory/handler execution and share its cleanup scope. Focused package tests pass (109 tests), as do the package typecheck, Node dev/production integration, packed TypeScript/Node consumers, and local workerd qualification. The full local CI-equivalent matrix and Entwine validation pass except for the standalone www E2E data-app-ready assertion (no apps/www changes; www Server Component dogfood passes). Hosted Actions are pending after push. This does not qualify M3, production cancellation or Strata 1.0. See the interceptor report.

Analog request-scoped guards (2026-10-19)

The feature branch feat/analog-request-guards adds opt-in registration-level guards to @strata-sc/analog. Guards run sequentially before controller construction, receive immutable route identifiers and the existing request cleanup hook, and return an explicit allow or 401/403 denial. Identity and authorization remain consumer-owned. Analog unit, real production/dev Node, packed TypeScript 5.9/Node, and full-stack packed Node/local workerd fixture checks pass. The local CI-equivalent matrix and Entwine checks pass, while hosted Actions remain pending. This is not an M3 or 1.0 qualification. See the guard report.

Opt-in Analog request validation (2026-10-17)

At baseline 41079a32e84f5c8d652fee04d212801571cb97f4, the feature branch feat/analog-standard-schema-validation adds validateParams, validateQuery and readValidatedJson using Standard Schema v1 and @standard-schema/spec. The API is opt-in and preserves raw readers. Validated JSON is bounded to 1 MiB; safe 400/413/415 JSON responses are created only after successful cleanup. Package-level tests, Zod/Valibot interoperability, transformed output inference, and 24 overlapping mixed requests pass. Real Analog Node production/dev, Cloudflare Pages/workerd, packed TypeScript 5.9.2 and full-stack TypeScript 6.0.3 and 5.9.2 consumers pass. External full-stack workerd rehearsal also passes; no public deployment was run. Evidence and explicit remaining gaps: research report. This does not qualify authentication, authorization, guards/interceptors, production adoption, M3 or Strata 1.0.

Full-stack performance scaling: one island versus three distinct islands (2026-10-16)

At 546b35ccb708d784ef46bea2eb35a90736a3da79, STRATA_PERF_ISLANDS=1|3 pnpm test:full-stack-performance runs the same four arms with one or with three distinct interactive islands (default 1; no other count is accepted). One island reproduces the PR #86 numbers; with three, the Server Components browser overhead did not grow (+4.3 → +3.6 KiB raw, +1.7 → +1.5 KiB gzip; the net is confounded with server-only code exclusion), controllers still add 0 browser bytes, and the full-stack server/Worker overhead stayed at about 10 to 11.5 KiB raw / 3.4 to 4.4 KiB gzip. A hosted run of the existing extended.yml (islands=both, same runner, run 38904588640) agrees with local to a few bytes. Hydration, one-request-per-click interaction and confidentiality hold in every arm; 11 negative controls are rejected. The PR #86 candidate budgets stay PROPOSED and unenforced (browser: supported on this fixture; server: insufficient evidence). R13 stays open; cold start, Worker memory, cancellation/cleanup, production budgets and real-application performance remain NOT MEASURED/NOT QUALIFIED. Details: report.

Full-stack performance and bundle-cost baseline: first controlled measurement (2026-10-16)

At 1297271669ed9c419caf6f9997a3e58bffb06bf2, pnpm test:full-stack-performance builds one small Analog application in four arms (native, controllers, Server Components, both) from the packed tarballs outside the workspace and measures Nitro node-server and local workerd. On this tuple the incremental cost is small and deterministic: controllers add 0 browser bytes and +4.2 KiB (Node) / +7.5 KiB (Worker) server graph; Server Components add +1.7 KiB gzip browser and about +4 to +7 KiB server graph; both together +11.1 KiB (Node) / +11.5 KiB raw, +4.4 KiB gzip (Worker), with no interaction. No latency or RSS difference is detectable above run-to-run noise. Confidentiality holds in every arm and six negative controls are rejected. Candidate budgets are proposals only: nothing is enforced, R13 stays open, and cold start, Worker memory, cancellation/cleanup and real-application performance are NOT MEASURED/NOT QUALIFIED. Main's own CI was already red at this SHA (npm 10 peer-resolution crash with vitest 5), unrelated to this work. Details: report.

Cloudflare controller concurrency anomaly: not reproduced, cause unconfirmed (2026-10-15)

A diagnostic increment classified and attempted to reproduce run 38727456938's single anomalous controller answer. Local Node, local workerd and the existing public deployment each ran a bounded matrix (829, 829 and 478 requests) with 0 anomalies; the adapter audit found no cross-request state. One new dispatch (38741663534, deployment 0a77889b) passed every check, so that candidate is GO on Cloudflare Pages; the two earlier runs stay NO-GO and the root cause is UNCONFIRMED. Details: report.

Full-stack deployed Cloudflare qualification: two real runs, both NO-GO (2026-10-15)

The workflow was dispatched twice (runs 38722004014 and 38727456938). Run 1 deployed but the runner could not reach the new project's URL (fetch failed ×40). Run 2 passed every functional verifier on the public URL (controller, Server Component SSR, island, confidentiality: GO) but failed the bounded concurrency smoke (1 anomalous answer in 25 concurrent controller requests), so the real deployment is NO-GO, not GO. Cause of the anomaly is unknown; a third run is pending a decision. Details: report. The paragraph below is the earlier, pre-dispatch state.

Earlier state: machinery added, deployment NOT RUN

At 32a174707b356c300ce91dae51eedf92cf45f043, pnpm test:full-stack-deployed-cloudflare takes the SAME external consumer as below, built from the three source-packed tarballs (SOURCE-PACKED CANDIDATE, not a registry release), builds it with Nitro's cloudflare-pages preset (no nodejs_compat), and runs the black-box verifier on local workerd (142 checks, passing; a rehearsal, not a deployment). The manual workflow full-stack-deployed-cloudflare.yml deploys to the isolated strata-fullstack-qualification project and verifies the public URL. The real deployment has not run: REAL DEPLOYMENT: NOT RUN, and the deployed controller, SSR, island and confidentiality verdicts are NOT RUN. A GO would still describe a fixture, not authentication, tenant caching, shared DI or a real application. See the report.

Full-stack external consumer: core + analog + server-components (2026-10-15)

At 2f4f37d37c26690b2b7790835a38ecaf2f5ee056, pnpm test:full-stack-package-consumer packs @strata-sc/core@0.2.1, @strata-sc/analog@0.2.1 and @strata-sc/server-components@0.2.0, installs the three tarballs with npm into one Analog app outside the workspace and proves, on Nitro node-server and local workerd (no nodejs_compat), a Strata controller API called by a hydrated island next to a server-only @ServerComponent(): tarball/lock/single-core identity, declarations under TypeScript 6.0.3 and 5.9.2 (skipLibCheck: false), controller routing, request-scoped lifecycle, SSR, SSR DOM reuse, one click = one request = one state update, document navigation with the opt-in guard, graph/canary scans and the server-only firewall. Controller and Angular SSR DI stay independent and shared DI is NOT QUALIFIED. A fixture is not production adoption. See the report.

Server Component request-bound authorization isolation (2026-10-14)

At 4ffee83d874c86d425d5c6ea21191bbb290e5e2c, the fixture adds a narrow, fixture-owned authorization scenario at /server-component-authorized/:tenant. An independent per-render provider resolves a principal only from exactly one synthetic opaque HttpOnly cookie. Route, query and arbitrary identity-header values remain untrusted resource or override inputs. The protected service checks the verified principal against the requested resource before calling its server-only repository; a request-local read trace proves denied requests never read the target record.

pnpm test:server-component-authorization passes on Nitro node-server and local Wrangler/workerd with 200/no-store own-tenant reads, 401/no-store missing or invalid credentials, 403/no-store cross-tenant denials, 404/no-store unknown resources, duplicate-cookie rejection, 24-request overlap in both orders, expected-failure recovery, graph scans and browser document navigation with HttpOnly cookies. This is synthetic fixture evidence, not production authentication, a Strata authorization API, tenant-aware caching or a 1.0 qualification. See the authorization report.

Server Component SSR response and expected service recovery (2026-10-14)

At 7738b475fd82e1e6f4db8dcb642e4092c42f9356, the public Angular/Analog API investigation found no general bridge from an unexpected buffered Server Component render error to an HTTP status or fallback response. Angular's public RESPONSE_INIT is not provided by Analog; Analog's public RESPONSE token is a mutable consumer-owned response passed through the Nitro renderer. The existing constructor, injected-service and template failure controls therefore remain HTTP 200 with Angular's empty or half-rendered output.

The fixture now demonstrates a narrower supported pattern at /server-component-ssr-recovery: a typed application service catches only an expected repository failure, sets Analog RESPONSE to 503/no-store and renders generic HTML; missing or malformed request context is 400/no-store. Unexpected errors are rethrown. The new pnpm test:server-component-ssr-response gate passes on Nitro node-server and local Wrangler/ workerd, including concurrent isolation and document navigation/Back/re-entry. This is a narrow consumer-owned GO, not a Strata package API or a general render-failure fix. See the response and recovery report.

The full failure criterion, streaming, true SPA Server Component navigation and Strata 1.0 remain unqualified.

Server Component document navigation APIs (2026-10-21)

The optional @strata-sc/server-components/navigation entry now exports StrataDocumentLink, StrataDocumentNavigator and the hardened strataDocumentNavigationGuard, all on one same-origin resolver built on public Angular APIs (starting main 21baee81f644b46b2142ca3dfd789a691417f16c). Links stay native anchors; navigation is always a real document request; the guard cancels Router activation in canActivate. SPA subtree hydration, streaming and an SSR-error status redesign remain out of scope and unsupported. See the report.

Server Component document-navigation fallback (2026-10-14)

At f62722b3e0ba23c80e61669bca7f4f056081979c, the fixture qualified a consumer-owned experimental fallback for Angular Router transitions into designated Server Component routes. This increment promotes that policy to the optional @strata-sc/server-components/navigation entry as strataDocumentNavigationGuard. A public Analog defineRouteMeta/Angular canActivate guard allows SSR and initial bootstrap, then uses public Router state and same-origin browser Location.assign to cancel later SPA activation and request a fresh document. The fixture now consumes the package API; its separately identified unprotected route retains the historical empty surrogate control without a URL-controllable bypass. Node Nitro qualification continues to cover RouterLink, navigateByUrl, Back/Forward, query/fragment preservation, SSR HTML, DOM-reusing island hydration, exactly-once interaction and graph confidentiality.

This is CONDITIONAL GO. Explicit document navigation is SUPPORTED; the opt-in package fallback is EXPERIMENTAL and a full reload; SPA hydration of a newly fetched Server Component subtree, streaming, automatic route discovery, generic router integration and Strata 1.0 remain UNSUPPORTED/unqualified. See the fallback report.

Request-scoped Server Component context and service isolation (2026-10-14)

At baseline e06fae5, Angular 22.1.7 publicly exposes the SSR-only REQUEST_CONTEXT token and Analog 2.7.2 publicly forwards per-render platformProviders to renderApplication. The fixture provides an immutable synthetic value through that seam and consumes it from a parent Server Component, a nested server-owned child and a server-only service/repository. The fixture now allow-lists alpha/beta, rejects unknown or mismatched identities into an empty safe state for rejected identities, consumes distinct private values, and proves the parent and child share the component-provided service instance within one render. pnpm test:server-component-request-context covers overlapping and sequential requests, failure/recovery, Node and local Cloudflare workerd, graph controls, and browser-output absence of both private values.

This is a narrow experimental GO for buffered document SSR and synthetic fixture data isolation. It does not qualify controller/SSR shared DI, Angular Router navigation, streaming, authentication, authorization, tenant/cache isolation or Strata 1.0.

Reconciliation 2026-09-24

Inspected at c671ba4 (main, merge of PR #19) plus the SPEC-003 branch. The sections after this one are the 2026-09-17 audit at f258fc3. They are kept as recorded, and this section supersedes their current-state claims where the two differ. Work now runs on two tracks with independent gates:

TrackScopeState
H3 consumer qualification@strata-sc/h3 (H3 v2) packed consumers, strict declarations; M1SPEC-001 and SPEC-002 executed. Upstream-blocked: H3's HTTPError.isError lib assumption and its crossws import graph fail strict TS 6.0.3 consumers; no published closure qualifies. pnpm test:consumer stays failing on AC2 by design. Unchanged by later work.
Analog integration / Server Component feasibility@strata-sc/analog (Nitro 2 / H3 v1) inside a real Analog 2.7.2 app; M2Node integration baseline passes in dev and production; the ordinary packed and full-stack consumers also pass, but neither exercises Angular DI in an external consumer. SPEC-003 controller DI is NO-GO on local workerd because Angular JIT is refused. Guard/interceptor composition with the DI-backed controller remains unqualified (cross-runtime report). @strata-sc/analog exposes no H3 v2 (or v1) declarations, so the H3 type blocker does not apply.

Analog track increments since the 2026-09-17 audit:

PR / specIncrement
#7Analog integration baseline fixture and pnpm test:analog
#8@strata-sc/analog: registerControllers(nitroApp.router, …) from a Nitro plugin; depends only on @strata-sc/core
#17Provisional StrataAnalogRequest (method, path, URL, headers, params, query, context snapshot, lazy body readers); Nitro/H3 event not exposed
#18Controllers are request-scoped: no instance at registration, one per request through experimental controllerFactory (default new Controller())
#19Experimental onCleanup on the factory context: LIFO, awaited, exactly once, on success and every failure path, before the route handler settles
SPEC-003Angular DI feasibility: CONDITIONAL GO through consumer-owned Angular injectors on the existing seam; no Strata API change (report)
SC PoCServer-component graph PoC: CONDITIONAL GO. Server component + transitive server-only deps absent from browser output; child hydrates onto SSR nodes and is interactive; fixture-only, no package change (report)
SC navServer-component navigation: CONDITIONAL GO. Explicit document navigation GO; the optional @strata-sc/server-components/navigation entry provides an experimental Angular canActivate fallback for designated RouterLink/navigateByUrl transitions; unprotected SPA navigation remains NO-GO with the empty surrogate and is retained as a separately identified control (fallback report, baseline report)
SC CFServer-component PoC on Cloudflare Pages (local Wrangler/workerd): CONDITIONAL GO. SC direct load, document navigation, controllers GO; SPEC-003 Angular DI NO-GO (JIT refused) (report)
SC pkgServer-component mechanism extracted into the public @strata-sc/server-components package (0.1.0 on npm, next channel, experimental, document-navigation-only); the fixture consumes it. Client references are explicit [strataClient] boundaries. PoC verdicts unchanged (README)
SC composeServer Component composition (public experimental package): ordinary server-only child and grandchild components, nested Server Components, and transitive client-boundary discovery through local imports; interactive (event, two-way, host-listener) bindings in server-owned templates fail the build. Dogfooded by Relay (pnpm test:relay:server-components) (README)
SC protocolClient boundary protocol v1 (public experimental package, protocol not stable): [strataClient] props are validated at runtime on the server (flat plain object; string, finite number, boolean, null; __proto__/prototype/constructor rejected; 64 props; 64 KiB UTF-8) and written beside data-strata-protocol="1". The browser preflights every boundary of a Server Component host (protocol, JSON, values, limits, selector, public input names) before creating any island, and rolls back a failed commit. Fixture /server-component-boundaries; pnpm test:server-components (Node, fail-closed matrix) and pnpm test:server-components:cloudflare (positive path) (README)
SC deferServer Components + Angular @defer (public experimental package): @defer inside a [strataClient] component is Angular's (incremental hydration on interaction and viewport, lazy chunk fetched only on the trigger, event replay, SSR DOM reused); @defer in any server-owned template fails the build with a shape-specific diagnostic. Analog 2.7.2 apps need environments.ssr.define.ngServerMode = "true" for SSR to honour hydrate triggers. Fixture /server-component-defer, Relay rollout island; pnpm test:server-component-defer (Node), pnpm test:server-components:cloudflare (workerd) (report)
SC firewallServer-only modules (public experimental package): import "@strata-sc/server-components/server-only"; is a build-time assertion that the module must never enter the browser graph; barrels re-exporting a marked module inherit it; ordinary imports do not propagate. Illegal direct, dynamic, @defer-lazy, barrel, chained-barrel, ?raw and ?url browser imports fail real production builds with a Strata diagnostic. Fixture canary, www and Relay dogfood; pnpm test:server-component-server-only (README)
SC protocol v2Structured boundary props: readonly authored view models of primitives, dense arrays and plain objects; v1 remains selected for flat primitive props. The shared server/browser validator snapshots values and enforces depth 8, 512 values, 128 array items, 64 nested object keys, 64 root props and 65 536 UTF-8 bytes. Relay Node production dogfood, packed-consumer Node/workerd, security, and the requested local CI matrix pass. Protocol remains experimental; hosted checks are pending after PR push (research).
SC securityServer Component data confidentiality (public experimental package, no runtime change): a server-only repository uses a synthetic DATA canary; only a safe verdict and a PUBLIC control cross. The DATA canary is absent from raw SSR HTML, headers, protocol v1 payload, hydrated DOM, public network, browser output, source maps, production errors and build diagnostics on Node and local workerd; present in the server/Worker graphs. Fixture /server-component-security*; pnpm test:server-component-security (report)
SC failureServer Component failure and recovery (public experimental package, buffered SSR + document navigation, runtime change: one diagnostic only): six separate cases (server render, serialization, preflight, commit rollback, error after hydration, navigation to a failing document). One Server Component host is one hydration transaction: a failing host is inert with its SSR DOM while sibling hosts hydrate; Angular's per-callback afterNextRender handling delivers the one error to ErrorHandler, no rethrow, no retry. Server render errors answer HTTP 200 (empty outlet, or a half-rendered component for a template error): the exact current Angular/Analog behaviour, not a success; no supported Strata seam to change it. Streaming unsupported. Fixture /server-component-failures, /server-component-failure/:mode; pnpm test:server-component-failures (Node + workerd) (report)
SC release@strata-sc/server-components@0.1.0 is published: PUBLIC / EXPERIMENTAL, on npm under the next channel with npm provenance (Release run 37791376843; git tag @strata-sc/server-components@0.1.0 and a GitHub Release exist). npm also set latest → 0.1.0 itself, as the package's first real release (a 0.0.0-stage placeholder preceded it); next is the supported channel and no dist-tag was edited. The exact-version registry consumer passes against 0.1.0 (fresh npm install outside the workspace, real non-symlink install, no workspace resolution, production build, graph isolation, SSR, protocol v1, hydration, interaction, server-only firewall; npm audit signatures clean) and is rerunnable through the read-only "Verify published Server Components" workflow. The original post-publish step never ran (a release.yml trigger bug, not a publication failure; PR #56 changed it to a registry pre-check, implemented but not yet exercised by a real next publish). Experimental, document-navigation-only, streaming unsupported (report).

Current lifecycle facts:

  • @strata-sc/h3 still creates one controller instance per controller at registration and shares it across requests. That instance is never a safe place for request state.
  • @strata-sc/analog creates a controller per request and provides no injector. With the consumer glue measured in SPEC-003, controllers resolve application- and request-scoped Angular services with inject() at construction. Overlapping requests are isolated (barrier-proven), and request injectors are destroyed on success, throw and rejection. The application injector is separate from Analog's SSR and server-function injectors (no supported seam reaches those), and the Nitro bundle then needs @angular/compiler declared in nitro.moduleSideEffects.
  • Server components (experimental, document requests only): the Vite plugin and runtime live in the experimental, public @strata-sc/server-components package (0.1.0, next channel), consumed by the fixture. The plugin sends @ServerComponent() modules to generated empty-template surrogates in the client environment. Only children marked [strataClient] are client references; they are hydrated as roots from their ngh annotations. The plugin finds them transitively: it walks the Server Component's template and, through each component's own imports, the templates of the unmarked local components it renders (ordinary children, grandchildren and nested Server Components, all server-owned), stopping at each [strataClient] boundary. The outer surrogate owns every client reference found. An event, two-way or host-listener binding in a server-owned template, a composition cycle, or an ambiguous selector fails the build. Package components are opaque (never crawled). This relies on undocumented Angular 22.1.7 hydration behaviour and on Analog's environment names. Direct load and document navigation work; an Angular Router navigation to the route renders the empty surrogate without error. Leaving through the router destroys the islands. Checked by pnpm test:server-components and pnpm test:server-component-navigation (in CI).
  • Client boundary protocol v1 (preview, not stable), evidence from the Analog fixture's BoundaryProtocolServerComponent (/server-component-boundaries) and unit tests in packages/server-components/src/runtime/*.test.ts:
    • Runtime value validation: unsupported values (undefined, NaN, ±Infinity, BigInt, functions and signals, symbols, arrays, nested objects, Date, Map, Set, RegExp, class instances), reserved keys, accessors and symbol keys fail with a Strata error naming the boundary, prop, received type and allowed values, before JSON.stringify (unit).
    • Limits: 64 props pass and 65 fail; 65 536 UTF-8 bytes pass and 65 537 fail, measured with TextEncoder and a multibyte case that UTF-16 length would pass (unit).
    • Escaping: " ' < > &, </script><script>…, U+2028/U+2029, emoji and non-Latin text stay inside the Angular-escaped attribute; the island receives the exact strings and globalThis.__STRATA_XSS__ is never set (Node and workerd).
    • Boundary identity is the SSR host element: two islands with the same selector and props are two ComponentRefs on two hosts with independent state (Node and workerd).
    • Version skew fails closed: a data-strata-protocol="999" boundary (also invalid JSON, an unknown input, an aliased input under its property name) hydrates nothing in its Server Component host, leaves the SSR DOM and interaction inert, logs one Strata error and does not reload (Node, Playwright response interception).
    • A commit failure after three islands destroys all three, restores their SSR hosts in place and leaves ApplicationRef.viewCount at its zero-island baseline (Node).
    • An invalid value at SSR logs the Strata error and never reaches the HTML (no data-strata-props is written), so the browser refuses that host. Angular reports the binding error to its ErrorHandler and completes the render: the HTTP status stays 200, owned by Angular/Analog.
  • Structured client boundary protocol v2 is implemented in the 0.3.0 candidate. Flat primitive markup stays v1; v2 carries recursively validated snapshots with finite depth/count/key/array and UTF-8 byte caps. Unit tests cover boundary and above-boundary cases, no-getter/custom-serializer behavior, cycles, dangerous keys, sparse arrays, proxies, repeated references and v1/v2 shape mismatches. Relay proves a nested public incident model hydrates in Chromium and preserves the SSR DOM. Packed-consumer Node/workerd verification passes. Hosted CI is pending; release qualification and protocol stability are not claimed.
  • Server Components and Angular @defer (fixture /server-component-defer, Relay's rollout island; report):
    • Two layers: StrataIslandHost hydrates each [strataClient] root on load (unchanged, not lazy); Angular hydrates each @defer block inside that root's own template on its trigger.
    • hydrate on interaction: the lazy chunk is requested only by the click, the widget is constructed once, one click produces one effect (event replay), and the SSR host, button and text nodes are kept (Node, workerd, Relay). hydrate on viewport: the lazy chunk is requested only when scrolled into view (Node, workerd).
    • Bundle: each deferred dependency is a chunk reached only by dynamic import() from the island's chunk, never in the static closure of the entry or the island; server parent and server-owned child stay absent from the browser.
    • Protocol v1 still gates the island: a tampered version refuses the host, and the deferred chunk is never requested even on interaction (Angular then logs a TypeError from its replay contract, recorded).
    • @defer in a server-owned template (root, ordinary child, nested Server Component; any trigger, hydrate never included, with or without a [strataClient] inside) fails the build. Measured before the rule: the placeholder is never replaceable, hydrate triggers throw or never complete, an island inside is hydrated on load regardless of its trigger, and under hydrate never it is rendered twice.
    • Analog 2.7.2 defines ngServerMode as false for the SSR graph of a production build (and rewrites only core.mjs), so SSR renders hydrate-trigger placeholders until the app sets environments: { ssr: { define: { ngServerMode: "true" } } } (fixture and Relay do).
  • Server-only modules (fixture src/app/server-component/, www, Relay; README):
    • Assertion: import "@strata-sc/server-components/server-only";, an empty package entry (no imports, no code). The plugin pre-scans sourceDir with the TypeScript AST at config(); the specifier inside a comment, string or template literal does not mark a module (unit).
    • Re-export taint: export * from, export { X } from and export * as X from propagate the restriction upward to a fixpoint (chains and cycles, unit); ordinary imports and export type { … } from do not. shared-format.ts, imported by the marked repository and by the AddToCart island, is bundled for both graphs.
    • Direct browser import rejected: a temporary [strataClient] island importing the marked ProductRepository fails vite build with [strata] Server-only module entered the browser graph: src/app/server-component/product-repository.ts / Imported from: the island / Reason: / fix.
    • Dynamic import rejected: await import("…/product-repository") in the island fails the build with the island as importer; no lazy browser chunk is emitted. The same holds for a dynamic import inside a component loaded by the island's own @defer (importer: the lazy component).
    • Re-export/barrel rejected: a barrel re-exporting the repository, and a barrel B → barrel A → repository chain, fail the build naming the barrel the browser reached and the full chain.
    • Raw-source import rejected: server-secret.ts?raw and ?url fail the build (query stripped, underlying module checked). A query import of a @ServerComponent() module now fails the same way instead of resolving to its surrogate.
    • No browser output exists after any of these failures; a legal control (Server Component → marked repository, island → shared module) builds.
    • Node graph proof: the canary STRATA_SERVER_ONLY_CANARY_7F3D9A41C2E5 (in the marked server-secret.ts) is in dist/ssr and the Nitro server, absent from dist/client, dist/analog/public and every browser source map; the marked modules are in SSR source maps only; the assertion entry is bundled nowhere in the browser; the page renders through the marked repository on Nitro node-server. The plugin-off control leaks the canary and both modules.
    • workerd graph proof: pnpm test:server-components:cloudflare finds the canary in the Worker graph reachable from _worker.js and not in the public assets; www's marked repository and proof module are in its Worker graph and absent from its Pages assets.
    • Dogfood: Relay marks server-operations-intelligence.ts and incident-digest.source.ts; www marks server-component-facts.repository.ts and server-component-proof.ts. Their gates check the assertion in source (AST) and the modules' absence from browser source maps.
    • Superseded for aliases (0.6 candidate, see the report): aliased re-exports (Vite resolve.alias, tsconfig paths, index files) are now followed by the pre-scan. Still not covered: package re-exports in the pre-scan (packages are enforced through their assertion import, not crawled), marked modules outside sourceDir (rejected only when their assertion import is resolved), data disclosure through HTML, errors or caches (R11: covered for synthetic canaries by the SC security entry below). Security as a whole is not complete.
  • Server Component dev server (vite; fixture /server-component-dev, report, pnpm test:server-component-dev, real Analog dev server and Chromium, Vite hotUpdate):
    • Initial dev graph: derived at config() before Analog reads its tsconfig, as in a build; the page SSRs, its island hydrates once and a click has one effect; the server-only repository runs on the server and its canary is in no browser response or HMR frame (positive control: a public module's canary is).
    • Hot regeneration: after every .ts/.html edit under sourceDir the graph is analyzed whole in memory and then committed (generated surrogates synchronized: unchanged ones not rewritten, stale ones deleted); an analysis error keeps the committed graph and files. 128+ refreshes in the gate: median 6 ms on the fixture.
    • Server-owned reload: an edit to the Server Component, an ordinary server child, a templateUrl grandchild each replaces the JS realm (sentinel gone) with exactly one document request and one Strata reload; SSR HTML has the new text and not the old; the island hydrates once; one click is one effect.
    • Boundary lifecycle: add, swap (B → C) and remove a [strataClient] boundary regenerate the surrogate (imports exactly the current client reference), reload once and leave only the current islands; repeated server/boundary cycles leave one island and one ComponentRef per realm.
    • Client coexistence: a client-island edit leaves the graph unchanged and Strata sends no reload. Analog's default (liveReload: false) then has Vite reload the page itself; with liveReload: true Angular's component HMR updates the island (same realm, no document request, state kept). Server-owned edits under liveReload: true are not qualified: Analog leaves SSR stale for any edited component there (reproduced with the plugin off).
    • Live server-only: adding the assertion to a public module, or making a barrel re-export a marked module, while Vite runs terminates the old realm and fails the new one closed (HTTP 500 with the Strata diagnostic; dynamic import(), ?raw and ?url rejected; no canary in any response or HMR frame after the edit); removing it recovers without a restart. Bytes already delivered to the old realm cannot be revoked; the reload ends that realm.
    • Invalid edits: @defer, an event binding, an unresolved [strataClient] each give the analyzer's own diagnostic in the server log and Vite's overlay, the client graph refuses every app module (including ones Vite had cached) and the fix recovers with a reload, no restart. A later CI failure was traced to the harness taking its log mark before restoring files while child-process output arrived asynchronously. The assertion now starts after observable browser recovery and a bounded quiet-log boundary; scenario K re-invalidates after recovery to prove a new failure still closes the graph (evidence).
    • Create/delete: a new @ServerComponent() module gets a surrogate while Vite runs and renders once wired into the page; deleting it removes the stale surrogate.
    • Dogfood smoke: Relay (/release: SSR, rollout island hydrates, interaction, one server-owned text edit reloads the document, restored) and www (/: demo island hydrates, one click).
    • Not covered: Analog liveReload: true for server-owned edits, plain helper modules shared with client code (not tracked as server-owned), an app invalid at startup (Vite fails to start), streaming, Router navigation, a deployed environment.
  • Server Component packed-package consumer (tarball qualified before publication, outside the workspace; report, pnpm test:server-component-package-consumer):
    • Packed tarball: pnpm pack of the built private package, 33 files, 72,222 bytes, with a package-local MIT LICENSE (added; pnpm pack does not inherit the root one). Every export target exists; no source, test or config shipped; no workspace ranges, no absolute paths; emitted imports are @angular/core, @angular/compiler, typescript, vite (types), node:fs, node:path, all declared peers; no tslib import. At that baseline the package was 0.0.0, private (published later as 0.1.0).
    • External install: npm install of the tarball into a real Analog app in an OS temp directory, empty npm config; a real directory (no symlink), one copy, no other @strata-sc package; ., /vite, /server-only resolve under consumer/node_modules and nothing resolves into the repository; one Angular/TypeScript universe; no UNMET or Strata-related npm ls problem.
    • Declaration portability: TypeScript 6.0.3 and 5.9.2, strict, skipLibCheck: false, Bundler resolution, 0 diagnostics, every Strata declaration from node_modules/.../dist.
    • External production build: graph split (three server markers only in the Node server output, the client marker in the browser), 708 browser source-map sources with none of the consumer's server files, no compiler/plugin tooling in the browser; SSR, protocol v1, DOM-identity hydration, one click one effect, document navigation and Back re-entry.
    • External firewall: direct, dynamic and ?raw imports of a server-only repository fail the production build with the installed plugin's diagnostic; sources restored, next build clean.
    • External dev: vite from the consumer's node_modules hydrates and, after one server-owned edit, reloads the document with fresh SSR and a new realm.
    • External workerd: Cloudflare Pages build, server graph in the Worker only, SSR/protocol/hydration and one click on the consumer's own Wrangler 4.135.0.
    • Defects found and fixed in the package: enabled omitted silently shipped the implementation to the browser (now defaults to true); the installed runtime fell back to the JIT compiler in Node SSR (the plugin now sets ssr.noExternal for it); source maps pointed at unshipped src/.
    • Not covered by this gate (the registry install is covered separately by pnpm test:server-component-registry-consumer): Angular/Vite versions other than the tested tuple, a deployed environment. Router navigation stays NO-GO.
  • Server Component confidentiality, in three independent layers (fixture /server-component-security*, report):
    • Module confidentiality (PR #49, unchanged): the server-only assertion keeps marked modules out of the browser graph. It says nothing about the values those modules return.
    • Data confidentiality: a value that stays server-owned does not cross merely because a Server Component reads or uses it. A synthetic DATA canary used through a server-only repository is absent, in raw and encoded forms, from the raw SSR HTML and its comments, inline scripts, ng-state, ngh and data-strata-* attributes, every response header, the parsed protocol v1 payload, the hydrated DOM and text before and after an interaction, cookies and storage, every public text/script response, dist/client, dist/analog/public and every browser source map (Node and workerd); it is present in dist/ssr, dist/analog/server and the Worker graph, and changing it in the server output changes the render (it is consumed, not merely present). A PUBLIC control passed through [strataClient] is present in the SSR HTML, the payload, the island's input and the hydrated DOM. A repository instance or a nested object cast into [strataClient] fails with StrataBoundaryError before serialization, the diagnostic naming only prop and type. Everything passed through [strataClient] is browser-public by definition.
    • Error-response confidentiality: a plain Error, an Error with a secret cause and an AggregateError thrown by the server-only repository leave the public response (status, headers, body, browser console and rendered document) without the secret, a stack or an absolute path, on Node and workerd. The response is HTTP 200 with an empty outlet (owned by Angular/Analog; pinned by the failure gate). The original error is only in the server's log, which is the operator surface, not a public one. PR #49's illegal-import build diagnostic prints module, importer and reason and never a source line or the canary.
    • Request isolation: Angular's public REQUEST_CONTEXT seam is now qualified narrowly for buffered Server Component SSR. The fixture allow-lists synthetic alpha/beta identities, consumes distinct private repository values, proves parent/child service-instance agreement, and passes 24 overlapping requests in both orders plus sequential and failure/recovery checks on Node and workerd (isolation report). This remains fixture-only evidence, not authentication, authorization, tenant/cache isolation or a Strata-owned request API. The runtime holds no global payload registry, process-wide boundary store or secret cache; module-level state shared between Angular SSR and Nitro (Relay's processSingleton) is a domain-state workaround, not request or tenant isolation.
    • Not covered: authorization, tenant/cache isolation, origin/CSRF, dev-server confidentiality beyond the live firewall below, a real deployed environment, log redaction, third-party error reporters.
  • Server Component failure and recovery (fixture /server-component-failures and /server-component-failure/:mode, report); buffered SSR and document navigation only, Node and local workerd:
    • Server render error (constructor, injected provider, template): the error reaches the server ErrorHandler once; the response is HTTP 200 with an empty router outlet (construction) or a half-rendered component whose boundary host has no data-strata-* attribute (template). This is the exact current Angular/Analog buffered behaviour, not an ideal or a Strata success response. No public seam (renderApplication and BEFORE_APP_SERIALIZED route errors to ErrorHandler; only a global rethrowing handler would change the status) exists, so Strata ships no recovery.
    • Serialization error: HTTP 200, one StrataBoundaryError on the server, no data-strata-props and no value in the HTML; the boundary host keeps data-strata-client and data-strata-protocol. The browser refuses it at preflight and hydrates nothing.
    • Preflight and commit failures are per host: B inert with its SSR nodes, A and C hydrated and working, exactly one ErrorHandler call and one console.error, no pageerror. Commit failure destroys every island that commit created (constructed = destroyed, views = baseline − B's), restores the same DOM nodes in place and reports the component's own Error. No retry or reload. Stale assets (protocol 999 on a host) behave the same; automatic recovery is not implemented.
    • After hydration, a throwing handler is Angular's: reported per click, no Strata intervention.
    • Document navigation into a failing route, Back (a new back_forward document, not bfcache) and re-entry are fresh lifecycles, one server report each, no stale client state.
    • Runtime: no ErrorHandler code was needed (Angular already catches per afterNextRender callback and does not rethrow). The preflight "invalid JSON" error no longer repeats the JS engine's message, which some engines build from the payload.
    • Streaming (Analog experimental.streaming) is unsupported and not qualified.
  • Cloudflare Workers (local workerd only, nothing deployed): the same fixture built with BUILD_PRESET=cloudflare-pages passes the server-component, navigation and controller assertions under wrangler pages dev. The SPEC-003 Angular DI route fails there, because workerd refuses Angular's JIT code generation. Checked by pnpm test:server-components:cloudflare (in CI).
  • Published package on Cloudflare Pages (@strata-sc/server-components@0.1.0 from registry.npmjs.org, fresh external app, cloudflare-pages preset, compatibility date 2026-09-21, no flags): the local build qualification passes (Worker graph holds the three server markers, the browser graph and its source maps none) and the verifier passes against local workerd. The real deployed Server Components qualification is GO (narrow scope): Actions run 38243892864 (main at 2c3f495c, 2026-10-12, deployment https://9b341445.strata-sc-qualification.pages.dev) passed the whole verifier from a GitHub runner with 0 failed checks: Node readiness (5 consecutive 200s on attempt 6, 10.9 s; attempt 1 was Cloudflare's 404), fresh Chromium readiness (3 consecutive on attempt 3, 0 misses), SSR and protocol v1, hydration with SSR DOM reuse, Count: 0 → Count: 1, document navigation /product → /about → back, reload, confidentiality (HTML, headers, scripts, assets, source maps, Worker bundle not served, props payload) and the 5/10/25 request smoke (all 200; p50 35.1/137.4/210.3 ms). It is not a tenant-isolation result, not Angular Router navigation, not streaming. History: four earlier deployments (runs 38055222217, 38055684868, 38133853890, 38137809896) failed on readiness or Chromium receiving Cloudflare's "Deployment Not Found" 404; the cause of that edge disagreement was never proven, and PR #61's fresh-Chromium readiness gate is what the passing run adds. One passing run is one data point. Runner pnpm test:server-components:deployed-cloudflare, workflow server-component-deployed-cloudflare.yml (report). The complete Analog deployment criterion (controllers, services, bindings) is still not qualified.
  • Not yet verified on the Analog track: a deployed Cloudflare Pages project with controllers, services and bindings, abort/timeout cleanup, streamed-body lifetimes. (Packed @strata-sc/analog consumers and non-GET methods are now covered; see the gaps section.)

The first gap bullet of the 2026-09-17 audit below ("No Angular/Analog/Nitro dependency, DI integration … request API") is superseded by the table above. Its other bullets still stand.

Audit of 2026-09-17

Inspected on 2026-09-17 at f258fc3 (main, merge of SPEC-001 / PR #4). Initial working tree was clean. Scope: all tracked source, tests, manifests, build/test/compiler configuration, CI, Changesets, root documentation and relevant history; dependency resolution inspected in pnpm-lock.yaml. Documentation, packed-consumer fixtures and qualification tooling now exist. No production package files changed between 9d2635e and this checkout.

History

CommitActual increment
fb5038cOSS workspace/tooling foundation
80341ea, merged by 61d0959Core controller/GET metadata; TypeScript decorator pre-transform
b899c59, merged by 9d2635eReal H3 v2 adapter and integration tests
1929957, merged by a7fe88cArchitecture/SDD documentation and SPEC-001
f865fca, merged by f258fc3SPEC-001 tooling, executed evidence and consumer CI invocation

The next slice must not recreate H3 execution or packed-consumer tooling: both already exist.

Implemented contracts

AreaEvidence and limitations
Core exportsPublic barrel: Controller, Get, Post, Put, Patch, Delete, getControllerDefinition, definition types, HttpMethod and STRATA_VERSION
MetadataStandard decorator context.metadata, private symbol keys, guarded Symbol.metadata initialization; frozen route records, definition and route array. Own-property access only: no implicit controller/route inheritance. No H3 or Angular dependency.
PathsMissing/empty paths become /; leading slash added, repeated slashes collapsed, trailing slash removed. HttpMethod was only "GET" at this audit; see the gaps section for the current set.
Adapter exportsPublic barrel: registerControllers, ControllerClass, StrataH3ConfigurationError
ExecutionReads core's public definition, joins paths, calls app.on, returns the original H3 instance. Native H3 routes coexist. Sync/async results pass to H3.
LifecycleOne no-argument constructor invocation per controller entry per registration call; instance reused by its routes and requests. Methods receive no request argument. No DI or request scope.
Configuration errorsMissing controller metadata and non-callable handlers throw during registration. This is not an atomic registration transaction.
Runtime dependencyManifest h3: ^2.0.1-rc.1; lockfile resolves 2.0.1-rc.32. H3 and core are externalized in adapter build.
DistributionESM-only, dist exports, declarations/maps. Changesets versions: core/analog/h3 0.1.0. Only core and analog are published, by the manual publish-packages.yml under next, first publish pending (ADR-004); h3 is excluded. pnpm test:package-consumer qualifies the packed tarballs with TypeScript 5.9.2.

The dynamic-route test proves matching /users/123; its handler returns a fixed object. It does not prove parameter extraction. No request, body, query or header API is implemented.

Toolchain and checks

Repository pins pnpm 10.30.1, TypeScript 6.0.3, Vite 8.3.0, Vitest 5.0.1; lockfile resolves Rolldown 1.2.8. Manifests declare Node >=22; CI selects Node 22 and runs lint, typecheck, tests and build, then pnpm test:consumer. That configuration does not certify all Node versions. The merge CI run failed at lint (23 unresolved-type errors in adapter source/tests); later steps, including consumer qualification, were skipped. Public core exports target dist, which a fresh checkout has not built before lint. This is a separate workspace bootstrap issue, not remote reproduction of AC2. Local pnpm lint passes with existing build artifacts. SPEC-002 must make the qualification reachable from a clean checkout without disabling rules or aliasing package source.

Both packages contain a private vite-plugin-standard-decorators.ts. It invokes TypeScript transpileModule with ES2023/ESNext before Vite, excludes dependencies and declaration files, and only matches IDs ending in .ts. It is used by their Vite/Vitest configurations and is not exported or shipped as a consumer tool. transpileModule does not replace type checking.

Earlier pre-SPEC-001 local check: pnpm exec vitest run on Node 22.23.0 passed 34 tests in 6 files (22 core tests, 12 adapter tests). Existing installed dependencies and built core output were used; this was not a clean build, tarball installation or deployment test. No production source was changed to obtain this result.

SPEC-001: executed, runtime proven, strict type qualification blocked

The recorded run used baseline a7fe88c, Node 22.23.0, pnpm 10.30.1, TypeScript 6.0.3, Vite 8.3.0 and H3 2.0.1-rc.32. Its implementation was merged in PR #4. Review confirms:

  • tests/consumer/fixture and tools/consumer build/pack both packages, install them outside the workspace and use consumer-local tools and public imports. workspace:* becomes 0.0.0; the fixture's local-tarball override yields one core copy. This does not prove registry publication.
  • Consumer-authored standard decorators compile through tsc; emitted JS runs in plain Node. Vite SSR bundling of that JS also runs. Both exercise real in-process H3 requests: sync/async controller routes, instance binding and a native route. Public metadata and preservation of an existing Symbol.metadata pass. No listening server or deployment was tested.
  • Strict consumer typecheck exits 2: H3's HTTPError.isError uses static override without the ambient Error.isError in the fixed lib set (TS4113); H3's root declarations import optional crossws types even for HTTP-only consumers (TS2307). No diagnostic in this run originates in Strata declarations. Strata nevertheless owns qualification of its exposed dependency contract.
  • JS was emitted because noEmitOnError defaults to false. Runtime success does not make the typecheck clean. The consumer does not inherit the workspace's skipLibCheck: true.
  • Direct stock Vite builds but retains standard decorator syntax; Node rejects it. The reference tsc → JS → Vite → Node path works. This proves a consumer compilation requirement on the measured tuple, not a requirement to publish or reuse Strata's private plugin.
  • The report records AC1/AC3–AC8 Pass and AC2 Blocked. Those are stage-level observations, not acceptance of the complete spec. Remote execution and exact transitive pinning need correction.

Review found the runner reports workspace Rolldown 1.2.8 but does not pin/measure the external consumer's Rolldown. The retained installation and its npm lock resolve 1.2.9. Runtime results remain useful; an exact 1.2.8 consumer claim is unproven. Version assertions and failure ownership must come from the measured graph/diagnostics, not the runner's hard-coded explanations.

Read-only rechecks in this audit reproduced both diagnostics with consumer-local TS 6.0.3 (--noEmit --skipLibCheck false). Adding only esnext.error removed TS4113 and left TS2307. Node 22.23.0 reports typeof Error.isError === "undefined": that lib supplies types, not a runtime polyfill. Registry/source research and alternatives are in H3 consumer type compatibility.

M1: Blocked, partially proven. SPEC-001 remains Blocked. SPEC-002 has now been implemented and executed (below) with a measured no-go; a clean type contract and reproducible remote qualification are still required. No M2 work is authorized.

SPEC-002: executed, no eligible candidate found (upstream-blocked)

Implemented and executed against 497a23b (main, TypeScript-first tooling cleanup; no packages/ drift from f258fc3). Full measured evidence, regenerated by every pnpm test:consumer:types run: docs/research/consumer-type-closure.md. Detailed AC-level evidence is recorded in SPEC-002's Implementation result.

  • A reproduces both historical diagnostics (TS4113 on HTTPError.isError, TS2307 on crossws) exactly, on the H3-only reproduction and the packed Strata consumer alike.
  • B (esnext.error) removes TS4113 only; TS2307 remains. Confirms the lib correction alone is insufficient, as predicted.
  • C (crossws@0.4.12 declared explicitly) does not close the graph: crossws's own root declarations unconditionally import bun and cloudflare:workers/@cloudflare/workers-types from internal chunk files, producing new TS2307/TS2552 diagnostics. Declaring the peer makes the measured diagnostic count worse, not better.
  • D-node-only (Bun/Workers type packages installed but not activated in compilerOptions.types) still fails: crossws's own module resolution for the bare "bun" import pulls in bun-types regardless of types filtering, conflicting with Node's own declarations; cloudflare:workers remains unresolved since that ambient module needs explicit types activation. This isolates the actual mechanism (ordinary module resolution, not types inclusion) behind the leak.
  • D-full-providers (types: ["node","bun","@cloudflare/workers-types"]) is disqualified by SPEC-002's stop rule regardless of outcome, and independently fails with 125 diagnostics — mostly @cloudflare/workers-types redeclaring DOM/Node globals that conflict with the existing libs. A genuine double no-go, not merely a policy rejection of an otherwise-clean configuration.
  • Every diagnostic across all cases traces to h3, crossws, bun-types/@types/bun, or @cloudflare/workers-types declaration files. Zero diagnostics originated in @strata-sc/core or @strata-sc/h3 in any case — reconfirming SPEC-001's finding that Strata's own declarations are clean.
  • Negative ambient controls confirm Bun/Workers globals are correctly unresolved under Node-only types and correctly resolved once activated, independent of any H3 diagnostic.
  • The CI clean-checkout lint failure recorded above is fixed by build ordering alone: removing packages/*/dist and running pnpm lint reproduces the exact 23 unresolved-type errors from the merge CI run; running pnpm build first resolves it. .github/workflows/ci.yml now builds before lint/typecheck and runs pnpm test:consumer:types before the existing pnpm test:consumer gate.

Verdict: upstream-blocked. No candidate satisfies SPEC-002's promotion rules. pnpm test:consumer (SPEC-001's required gate) is unmodified and remains failing on AC2, as required; no promotion was applied to its fixture. SPEC-001/M1 remain Blocked. Astra owns acceptance of this disposition and any further M1 direction (upstream issue, alternate closure hypothesis, or continued block).

Gaps and review findings

  • No Angular/Analog/Nitro dependency, DI integration, request API, validation, guards/interceptors, logging contract, component compiler, navigation protocol or deployment fixture.
  • Packed-consumer runtime tests exist; strict installed-package types are blocked. No browser/e2e tests, browser bundle assertions or Cloudflare execution.
  • Resolved: metadata is class-local (own-property reads/writes). An undecorated subclass has no definition; a decorated subclass gets only its own routes; siblings, parents and overrides stay isolated. Every HTTP route decorator rejects static, private and symbol-named methods at class definition. Covered by core, Analog and packed-consumer (TypeScript 5.9.2) tests. Explicit controller inheritance is not supported.
  • Resolved (Analog): registerControllers() preflights the whole batch before any router.add(), so a configuration error registers nothing and reserves nothing. A second Strata route with the same method + final path on one router fails, within a call or across calls; non-overlapping repeated calls and separate routers are fine. Ownership lives in an adapter-local WeakMap per router.
  • Open: a router.add() failure mid-commit propagates and cannot be rolled back (earlier routes stay); duplicates against native Nitro routes are not detected; @strata-sc/h3 keeps the old per-controller registration; constructor failures are not specified.
  • Resolved (Core + Analog): HttpMethod is "GET" | "POST" | "PUT" | "PATCH" | "DELETE", with public Get, Post, Put, Patch and Delete decorators built on one private core primitive (same path normalization, class-local metadata, declaration order, frozen records and member restrictions; errors name the decorator used). @strata-sc/analog maps every method exhaustively to Nitro's router and runs all of them through the same planning, duplicate check (method + final path) and request invocation path. Verified on H3 v1 unit tests, the Analog fixture (dev and production), Wrangler/workerd (pnpm test:server-components:cloudflare) and the packed TypeScript 5.9.2 consumer. The private @strata-sc/h3 routes all five methods on H3 v2 (internal tests only). HEAD, OPTIONS and other methods have no decorator.
  • Resolved (Analog, workerd): StrataAnalogRequest body readers failed under Nitro's Cloudflare presets, which attach the payload to a non-iterable mock Node request (req.body). They now read an attached body first, like H3 v1's readRawBody; POST/PATCH JSON bodies are asserted under Wrangler.
  • Handler exceptions and Response passthrough are tested (later releases); the stream lifetime has dedicated tests since 0.7.0, and client-disconnect cancellation remains NO-GO on the pinned adapters.

These remain inputs to M1/M3, not permission for Astra to implement fixes. SPEC-002 addresses the consumer type-contract blocker; it does not declare the remaining primitive contracts stable.

Source: docs/STATE.md

Generated by Entwine · Markdown remains the source of truth.