Skip to main content
Nova and Inventory use the shared Start bridge for request context, Next adapters, function guards, route dispatch, cookies and base paths. App code must not copy those implementations. The canonical codemod supplies the RSC page adapter, router and Worker entry. Source registration alone does not establish an accepted native build, staging behavior or production cutover. Retained action imports use the shared React server-action transport in the native build.

Route registration

Both apps declare start-bridge.routes.json. Regenerate and check from the root:
Registries live under each app’s src/start-bridge/. Nova includes both OG route.tsx handlers, not only its route.ts files. Nova declares the existing portable @cloudflare/pages-plugin-vercel-og renderer. The generated preset resolves next/og to its API; do not remove routes to satisfy the registry. Changes to public assets must also refresh public-assets.bridge.gen.ts before sealing. Generated manifests prove source coverage, not page or API behavior. The generated Worker entries use the shared createBridgeHandler and read the matcher from each app’s proxy configuration. Inventory excludes _next and dotted paths. Its existing proxy retains public storefront/webhook exceptions, workspace session requirements and inventory-specific rate limits. The bridge resolves ownership per request; current app options admit only the declared root hosts. Inventory keeps Web-owned paths ahead of local endpoints using the existing Next configuration’s reserved API paths. Server functions compile at /_serverFn; the shared fetch helper and Worker apply the resolved mount. Cookies remain unchanged and shared, as defined by the owning bridge/auth contract. Login pages derive native callback URLs from the active request context and base, while retained Next requests use the configured app URL. Inventory builds local login and locale redirects from request.nextUrl.clone() so the Next base path and query survive. The login-mount and proxy regressions cover both root and apex callbacks; central Web login return-app classification must also recognize mounted callbacks before acceptance. The exact GET and HEAD /api/build-info endpoint is public deployment metadata. Both app proxies follow CMS: remove ambient cookies and authorization, skip app-session refresh for that read, and retain the API guard. Mutation methods and path lookalikes keep their existing session and guard boundaries. The app-owned proxy-build-info.test.ts suites cover these boundaries; the generated emitted-runtime assertion must still verify the compiled source SHA through an anonymous request.

Root hosts and response policy

Both native apps are root-only while apex cutover is held. Their app-owned start-bridge.config.ts sets mountHosts: [] and unknownHost: 'reject': Nova’s nova.tuturuuu.com alias is a routing-layer 307 redirect to nova.ai.vn, not a Worker root. Preserve that redirect at cutover, including the requested path and query; the native Worker rejects the alias host. Apex paths, suffix lookalikes and unlisted hosts, including Workers preview hosts, return the bridge’s 404 before app dispatch. The generated runtime gate uses raw Host headers to test each allowed root and the rejected hosts. Inventory declares its anonymous root outcome as 307; Nova declares 200. Every response receives the app’s robots policy; ordinary paths also receive anti-framing CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosniff and Referrer-Policy: strict-origin-when-cross-origin. The CSP retains the existing Next frame-ancestors 'none' policy from packages/utils/src/next-config.ts. A stricter script policy also requires nonce support for app-owned and library inline scripts; verify that compatibility in the real browser before adopting it. App configs leave handler-level csp unset: it would overwrite an upstream policy before the response decorator runs. The Web-owned /api/v1/workspaces/:wsId/external-projects/assets/:assetId/webgl subtree is the same intentional framing exception as Next’s shared config. Forwarded responses use the shared framablePathRegexes() boundary, retain their upstream sandbox CSP unchanged and have no X-Frame-Options; neighboring paths retain the framing denial. The exception does not bypass API authorization, robots, no-store or Cookie variance. On other paths, filter framing directives separately in every comma-separated CSP policy and retain every policy and its non-framing directives. The bridge handler uses the same shared framing patterns, while app configs keep handler CSP unset. Regressions exercise the real bridge and central forwarder, with authentication and upstream network responses isolated at fixture boundaries. Robots decisions read APP_SEO_POLICY, not a deployment environment switch. Nova preserves its policy’s public learning and competition pages on the production host, including supported locale prefixes. Other Nova paths and all staging responses are private. Inventory’s current policy has no indexed paths, including its Storefront-facing APIs. Storefront’s separate SEO policy continues to own its customer pages. Every non-asset response forces Cache-Control: private, no-store, replacing upstream max-age directives, and varies on Cookie while retaining other Vary values. Only GET/HEAD requests under /assets/ or for exact generated public files preserve asset caching. A file-like API path or a mutation method does not receive that exemption. Unit tests cover upstream max-age overrides and method/path negatives; each app’s response-policy.runtime.mjs checks the real emitted Worker on production, staging and rejected hosts. Browser acceptance also verifies CSP-compatible hydration and navigation. The app-owned config keeps its top-level imports compatible with Node type stripping for the generated mount probe. The response decorator loads its app-owned policy module when it handles a response. The compatibility CLI scripts/ci/nova-inventory-native-artifact.py delegates to the generated per-app artifact helper, so compatibility flags and public-file identity have one canonical owner.

Nova server actions

The generated page adapter sends existing React use server calls through the shared Start server-action transport. Keep the original action imports and server implementations; do not maintain a second app-specific transport or move admin-client imports into a client entry. The shared function proxy refreshes/guards transport; it does not replace these domain checks. Layout authorization is also insufficient for a direct callable. The action-security tests cover the original suppliers. Shared bridge tests cover reply encoding and dispatch; emitted RPC and browser execution require native acceptance.

Inventory offline behavior

Existing workspace actions use the shared server-action transport with the current request’s forwarded auth. The offline mutation route remains the existing thin adapter to @tuturuuu/inventory-core/offline-create-route. Inventory deliberately has no browser service worker. Preserve its OfflineProvider register={false} and missing-worker regression. The shared offline package’s default root-scoped registration must not become active as a side effect of moving layouts. Do not invent a worker route, register a root worker on the apex, or clear another app’s registrations to pass acceptance. offline-handler.test.ts exercises the real route adapter through the shared Worker handler at root and /inventory: original operation/workspace forwarding, 201/200 receipt status, contract header, terminal 503, missing worker and method denial. Proxy and domain boundaries use fixtures. Retain the inventory-core permission/replay tests, offline/stale checkout denial and actor/workspace-scoped season-merge recovery tests. See Inventory browser offline behavior.

Qualification and cutover

Run unit/types on Sonha and heavy build/runtime/browser work on Thanh through the shared resource queue. Frozen installs can make workspace bin files executable; require a clean source check and reconcile such a mode change with the owning package rather than silently accepting a different source tree. When transferring built workspace dependencies between runners, bind them to the exact source and lockfile, verify the archive hash and allowlist its paths before extraction. On macOS, create these archives with COPYFILE_DISABLE=1 so AppleDouble metadata does not add siblings outside the declared dist directories. Regenerate a rejected archive; keep the extraction whitelist intact. For each exact candidate head require applicable lint/types/unit checks, real vp build, source-bound sealed output, emitted Worker acceptance and browser acceptance at root and the app mount. Exercise anonymous/signed/wrong-app sessions, refresh and logout, native RPC, central forwarding and private/public route boundaries. Inventory additionally verifies that no service-worker registration is attempted, checkout is blocked offline, and unresolved recovery retains the same operation identity after reload. Nova verifies both OG image responses and manager/participant/foreign-user action controls. At the apex, assert the actual central-login destination and nested callback, not just a redirect status: Web and the mounted app share an origin, while their login paths remain distinct. A bare /verify-token request intentionally redirects when its token is missing; it is not an anonymous HTML or offline-provider probe. After the native build, use scripts/ci/nova-inventory-native-artifact.py with nova or inventory as its first argument. It reuses the reviewed Apps artifact contract with the selected app’s staging Worker identity and every checked-out public asset. Set START_BRIDGE_SOURCE_SHA to the exact built commit. The remaining arguments are the shared helper’s seal, verify, pack and restore commands; provide runner-owned receipt/archive paths. Restore requires a fresh output tree with the same checked-out public sources. Verify the seal again after runtime/browser checks. The helper performs no publication. python3 scripts/ci/nova-inventory-native-artifact.test.py checks sealing, restoration, identity refusal and tamper detection using synthetic output. Those fixtures do not qualify an emitted Worker or replace runtime/browser checks. When running the shared artifact source tests on macOS, use a canonical TMPDIR (for example /private/tmp): the race-injection fixture compares the move destination with its temporary path, while the implementation resolves that path. A /var to /private/var alias prevents the fixture from injecting its symlink; it is not evidence that the negative control ran. An M4 storage refusal moves check:changed to a verified Thanh bundle; it does not authorize a disk override. Independent security review and an explicit cutover PR remain required. No migrations or production publication are part of the preparatory ports.