You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -115,10 +115,10 @@ The SDK is the canonical kernel — a single typed client with a `CredentialsPro
115
115
-**All bytes ride through CAS.** The plan request body never carries inline bytes — only `ContentRef` objects. When the normalized spec exceeds 5 MB JSON, the SDK uploads the manifest itself as a CAS object and references it (`manifest_ref` escape hatch — no body-size cliff).
116
116
-**Manifest adapters live in the Node SDK.**`loadDeployManifest(path)` and `normalizeDeployManifest(input)` accept the agent/CLI/MCP JSON shape (`project_id`, `{data,encoding}`, `{path}`, migration `sql_path` / `sql_file`) and return an SDK-native `ReleaseSpec` plus optional `idempotencyKey`. CLI/MCP should call these helpers instead of reimplementing adapter logic.
117
117
-**Strict/no-op validation lives in the SDK.**`Deploy.validateSpec` rejects unknown raw `ReleaseSpec` fields before normalization can drop them, and rejects project/base-only or empty nested specs with `MANIFEST_EMPTY` before hashing, uploading, or planning. The Node manifest adapter is strict too, so agent JSON typos such as `subdomain` or `sqlPath` do not become partial deploys.
118
-
-**Replace vs patch semantics per resource.**`site.replace` = "this is the whole site" (files absent are removed in the new release); `site.patch.put` / `patch.delete` = surgical updates. Same for `functions`. Secrets are value-free declarations: `secrets.require` asserts keys already exist, and `secrets.delete` removes keys at activation. Set secret values out-of-band through the secrets API. `subdomains` use `set` / `add` / `remove`. `routes` is `undefined | null | { replace: RouteSpec[] }`: omitted/null carries forward base routes, `replace: []` clears dynamic routes, and route entries target materialized functions with `{ type: "function", name }`. Top-level absence = leave untouched.
118
+
- **Replace vs patch semantics per resource.** `site.replace` = "this is the whole site" (files absent are removed in the new release); `site.patch.put` / `patch.delete` = surgical updates; `site.public_paths` is the direct browser reachability table for static assets. Explicit mode uses a complete map such as `{ "/events": { asset: "events.html", cache_class: "html" } }`, so `/events` serves the release asset `events.html` while `/events.html` is not public unless separately declared. `mode: "implicit"` restores filename-derived reachability and can widen access. Public-path-only site specs are deployable. Same replace/patch split for `functions`. Secrets are value-free declarations: `secrets.require` asserts keys already exist, and `secrets.delete` removes keys at activation. Set secret values out-of-band through the secrets API. `subdomains` use `set` / `add` / `remove`. `routes` is `undefined | null | { replace: RouteSpec[] }`: omitted/null carries forward base routes, `replace: []` clears dynamic routes, and route entries target materialized functions with `{ type: "function", name }` or exact method-aware static aliases with `{ type: "static", file: "events.html" }`, where `file` is a release asset path, not a public path, URL, CAS hash, rewrite, or redirect. Prefer `site.public_paths` for ordinary clean static URLs. Top-level absence = leave untouched.
119
119
-**Structured warnings.** Plan responses include `warnings: WarningEntry[]`. `deploy.apply` emits `plan.warnings` and aborts before upload/commit when a warning requires confirmation (including `MISSING_REQUIRED_SECRET`) unless the caller explicitly passes `allowWarnings`.
120
120
-**Server-authoritative dry-runs.**`deploy.plan(spec, { dryRun: true })` calls `POST /deploy/v2/plans?dry_run=true`; the gateway returns the v2 flat plan envelope without creating plan or operation rows, so `plan_id` and `operation_id` are `null` and the response cannot be uploaded or committed.
121
-
-**Release observability.**`getRelease({ project, releaseId, siteLimit? })`, `getActiveRelease({ project, siteLimit? })`, and `diff({ project, from, to, limit? })` are typed apikey reads over `/deploy/v2/releases*`. `active` means the current-live target; inventories expose materialized routesand warnings when returned; release-to-release diffs expose `migrations.applied_between_releases`, not plan migration buckets. Secret diffs expose keys only; route diffs expose `added` / `removed` / `changed`.
121
+
-**Release observability.**`getRelease({ project, releaseId, siteLimit? })`, `getActiveRelease({ project, siteLimit? })`, and `diff({ project, from, to, limit? })` are typed apikey reads over `/deploy/v2/releases*`. `active` means the current-live target; inventories expose materialized routes, `static_public_paths` when returned, and warnings when returned. `site.paths` is the release static asset inventory; `static_public_paths[]` is the browser reachability inventory with `public_path`, `asset_path`, `reachability_authority`, `direct`, cache class, and content type. Release-to-release diffs expose `migrations.applied_between_releases`, not plan migration buckets. Secret diffs expose keys only; route diffs expose `added` / `removed` / `changed`.
122
122
-**Server-authoritative manifest digest.** The gateway returns the canonical digest in the plan response. The SDK no longer requires byte-for-byte canonicalize agreement — `canonicalize.ts` is now a UX helper only.
123
123
-**Convenience shims.**`sites.deployDir` is a Node-only wrapper that uses `fileSetFromDir(dir)` and delegates to `deploy.apply`; its event callback emits only unified `DeployEvent` shapes.
124
124
-**MCP/CLI surface.**`deploy` and `deploy_resume` MCP tools (in `src/tools/deploy.ts` and `src/tools/deploy-resume.ts`) expose the primitive directly; `deploy_release_get` / `deploy_release_active` / `deploy_release_diff` expose release observability reads. CLI subcommands `run402 deploy apply`, `run402 deploy resume`, and `run402 deploy release <get|active|diff>` (in `cli/lib/deploy-v2.mjs`) mirror them. Use a v2 `ReleaseSpec` through `deploy` / `deploy apply`.
@@ -128,7 +128,7 @@ The SDK is the canonical kernel — a single typed client with a `CredentialsPro
128
128
-**`namespaces/ci.ts`** — `/ci/v1/*` SDK surface: `createBinding`, `listBindings`, `getBinding`, `revokeBinding`, `exchangeToken`, plus canonical delegation builders (`buildCiDelegationStatement`, `buildCiDelegationResourceUri`) and validators.
129
129
-**`ci-credentials.ts`** — isomorphic CI-session credential providers. `githubActionsCredentials({ projectId })` requests the GitHub OIDC subject token, exchanges it through `ci.exchangeToken`, caches the Run402 session until `expires_in - refreshBeforeSeconds`, and marks credentials with `CI_SESSION_CREDENTIALS`.
130
130
-**`node/ci.ts`** — Node-only `signCiDelegation(values, opts?)`; reads the local allowance and signs the canonical SIWX delegation for `/ci/v1/bindings`. Default delegation chain id is `eip155:84532` unless overridden.
131
-
-**Deploy integration is credential-driven.**`Deploy` detects the CI credential marker internally. Do not add public `ci` options, `r.ci.deployApply`, or broad CI deploy wrapper tools without a new design. CI deploys allow only `project`, `database`, `functions`, `site`, absent/current `base`, and `routes` authorized by the binding's `route_scopes`; every `spec.secrets` shape (including value-free `require`/`delete`), subdomains, checks, unknown top-level fields, non-current base, and `manifest_ref` are rejected before upload/plan. Gateway planning enforces route diffs and returns`CI_ROUTE_SCOPE_DENIED` for out-of-scope route declarations.
131
+
-**Deploy integration is credential-driven.**`Deploy` detects the CI credential marker internally. Do not add public `ci` options, `r.ci.deployApply`, or broad CI deploy wrapper tools without a new design. CI deploys allow only `project`, `database`, `functions`, the complete `site` resource including `site.public_paths`, absent/current `base`, and `routes` authorized by the binding's `route_scopes`; every `spec.secrets` shape (including value-free `require`/`delete`), subdomains, checks, unknown top-level fields, non-current base, and `manifest_ref` are rejected before upload/plan. Gateway planning enforces route diffs and nested public-path validation/authorization, returning canonical errors such as`CI_ROUTE_SCOPE_DENIED` for out-of-scope route declarations.
132
132
-**CLI DX.**`run402 ci link github` creates a deploy-scoped binding and generated workflow that calls `run402 deploy apply --manifest <manifest> --project <project>`. Repeatable `--route-scope <pattern>` delegates exact public paths such as `/admin` or final-wildcard prefixes such as `/api/*`; no scopes means no CI route authority. `run402 ci list` and `run402 ci revoke` manage bindings. V1 intentionally omits raw subject/wildcard/event/PR-deploy flags and requires GitHub repository-id binding.
### Same-origin web routes — static site + function ingress
126
126
127
-
Deploy-v2 routes are release resources: they activate atomically with the site, functions, migrations, secrets, and subdomains in the same `deploy apply`. Lead with a small route table: ordinary static files do not need routes, API prefixes should use narrow methods, and a static route target is for an exact public path to one deployed file.
127
+
Deploy-v2 routes and static public paths are release resources: they activate atomically with the site, functions, migrations, secrets, and subdomains in the same `deploy apply`. Release static asset paths such as `events.html` are distinct from browser-visible public static paths such as `/events`. Use `site.public_paths` for ordinary clean static URLs; keep routes for function ingress and exact, method-aware static aliases.
128
128
129
129
```json
130
130
{
@@ -133,6 +133,12 @@ Deploy-v2 routes are release resources: they activate atomically with the site,
Omit `routes` or pass `routes: null` to carry forward base routes. Use `routes: { "replace": [] }` to clear the route table. Route entries are an ordered `replace` list, not a path-keyed map. Function targets use `{ "type": "function", "name": "<materialized function name>" }`. Static route targets use exact patterns only, methods `["GET"]` or `["GET","HEAD"]`, and `{ "type": "static", "file": "events.html" }` with a relative deployed file path, no leading slash, wildcard, directory shorthand, query, or fragment. Direct `/functions/v1/:name` calls remain API-key protected; browser-routed paths are public same-origin ingress.
167
+
`site.public_paths.mode: "explicit"` means only the complete `public_paths.replace` table is directly reachable as static URLs. In the example, `/events` serves the release asset `events.html`, while `/events.html` is not public unless separately declared. `mode: "implicit"` restores filename-derived public reachability and can widen access, so review gateway warnings before confirming it.
168
+
169
+
Omit `routes` or pass `routes: null` to carry forward base routes. Use `routes: { "replace": [] }` to clear the route table. Route entries are an ordered `replace` list, not a path-keyed map. Function targets use `{ "type": "function", "name": "<materialized function name>" }`. Static route targets use exact patterns only, methods `["GET"]` or `["GET","HEAD"]`, and `{ "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } }` where `file` is a release static asset path, not a public path, URL, CAS hash, rewrite, or redirect. Use static route targets for method-aware aliases such as static `GET /login` plus function `POST /login`; in explicit public path mode the backing asset can stay private by filename. Direct `/functions/v1/:name` calls remain API-key protected; browser-routed paths are public same-origin ingress.
163
170
164
171
Matching is exact or final-prefix-wildcard only. `/admin` and `/admin/` are exact trailing-slash equivalents; `/admin/*` matches children but not `/admin`, `/admin/`, `/admin.css`, or `/administrator`, so deploy both `/admin` and `/admin/*` for a routed section root. Query strings are ignored for matching and preserved in the handler's full public `req.url`. Exact routes beat prefix routes; longest prefix wins; method-compatible dynamic routes beat static assets. A `POST /login` route can coexist with static `GET /login` HTML. Unsafe method mismatch returns `405`, and matched dynamic route failures fail closed instead of falling back to static files.
165
172
166
173
Routed functions use the Node 22 Fetch Request -> Response contract: `export default async function handler(req) { ... }`. `req.method` is the browser method, and `req.url` is the full public URL on managed subdomains, deployment hosts, and verified custom domains. Derive OAuth callbacks from it, for example `new URL("/admin/oauth/google/callback", new URL(req.url).origin)`. Append multiple cookies with `headers.append("Set-Cookie", value)`; redirects, cookies, and query strings are preserved. The raw `run402.routed_http.v1` envelope is internal; do not write route handlers against it.
167
174
168
-
Avoid routing every static file, broad method lists by default, wildcard static route targets, leading-slash static files, directory shorthand, and one-static-route-target-per-page tables that exhaust route limits. Also watch wildcard function routes that shadow static assets. Warning codes to handle include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`.
175
+
Avoid routing every static file, broad method lists by default, wildcard static route targets, leading-slash static files, directory shorthand, and one-static-route-target-per-page tables that exhaust route limits. Also watch wildcard function routes that shadow direct public static paths. Warning codes to handle include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`; inspect active routes, `static_public_paths`, and resolve diagnostics to distinguish the route pattern from the backing `asset_path`.
169
176
170
177
Diagnose public URLs with the URL-first CLI or MCP/SDK equivalents:
`deploy_diagnose_url` and `r.deploy.resolve({ project, url, method: "GET" })` return `would_serve`, `diagnostic_status`, `match`, normalized request data, warnings, full resolution JSON, and next steps. Today the public resolve contract is authoritative for host/static/SPAfallback diagnostics, not complete route introspection unless the gateway returns future route context. Known `match` literals are `host_missing`, `manifest_missing`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, and `spa_fallback_missing`; preserve unknown future strings. `result` is the diagnostic body status, not the HTTP status of the SDK call, so host misses can still be successful CLI/MCP/SDK calls with `would_serve: false`. Do not treat resolve/diagnose as a fetch, cache purge, or cache-policy oracle; do not hard-code `cache_policy` strings. Branch on `cache_class` when present and preserve unknown cache classes.
185
+
`deploy_diagnose_url` and `r.deploy.resolve({ project, url, method: "GET" })` return `would_serve`, `diagnostic_status`, `match`, normalized request data, warnings, full resolution JSON, and next steps. When returned, `asset_path`, `reachability_authority`, and `direct` explain which release asset backs the public URL and whether reachability came from implicit file-path mode, explicit `site.public_paths`, or a route-only static alias. Today the public resolve contract is authoritative for host/static/SPAfallback diagnostics, not complete route introspection unless the gateway returns future route context. Known `match` literals are `host_missing`, `manifest_missing`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, and `spa_fallback_missing`; preserve unknown future strings. `result` is the diagnostic body status, not the HTTP status of the SDK call, so host misses can still be successful CLI/MCP/SDK calls with `would_serve: false`. Do not treat resolve/diagnose as a fetch, cache purge, or cache-policy oracle; do not hard-code `cache_policy` strings. Branch on `cache_class` when present and preserve unknown cache classes.
179
186
180
-
Release observability exposes stable asset identity. Inventories include `release_generation`, `static_manifest_sha256`, and nullable `static_manifest_metadata` (`file_count`, `total_bytes`, `cache_classes`, `cache_class_sources`, `spa_fallback`); `static_manifest_metadata: null` means unavailable, not zero. Plan and release diffs expose `static_assets` counters: unchanged/changed/added/removed, `newly_uploaded_cas_bytes`, `reused_cas_bytes`, `deployment_copy_bytes_eliminated`, `legacy_immutable_warnings`, `previous_immutable_failures`, and `cas_authorization_failures`.
187
+
Release observability exposes stable asset identity and public reachability. Inventories include `release_generation`, `static_manifest_sha256`, nullable `static_manifest_metadata` (`file_count`, `total_bytes`, `cache_classes`, `cache_class_sources`, `spa_fallback`), and `static_public_paths[]` when returned. `site.paths` lists release static assets; `static_public_paths[]` lists browser-visible public paths with `public_path`, `asset_path`, `reachability_authority`, `direct`, cache class, and content type. Plan and release diffs expose `static_assets` counters: unchanged/changed/added/removed, `newly_uploaded_cas_bytes`, `reused_cas_bytes`, `deployment_copy_bytes_eliminated`, `legacy_immutable_warnings`, `previous_immutable_failures`, and `cas_authorization_failures`.
181
188
182
189
Runtime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED` (manifest/propagation), `ROUTED_INVOKE_WORKER_SECRET_MISSING` (custom-domain Worker secret), `ROUTED_INVOKE_AUTH_FAILED` (internal invoke signature), `ROUTED_ROUTE_STALE` (selected route failed release revalidation), `ROUTE_METHOD_NOT_ALLOWED` (method mismatch), and `ROUTED_RESPONSE_TOO_LARGE` (body over 6 MiB).
0 commit comments