Conversation
…rProvisioningApi port Checkpoint B1a of the Worker-native control-plane work. WranglerLoopBackend now reaches the provider only through PlainWorkerProvisioningApi (extends the unchanged PlainWorkerRouteApi); every CLI mechanic — argv, JSON parsing, staging directories, generated Wrangler config, secret input files, scratch export files, and the durable-store write with its independent integrity comparison — lives in the new WranglerPlainWorkerProvisioningApi adapter. Provider-neutral policy stays in the backend for extraction into a shared core (B1b) reused by the direct Cloudflare-API backend (B2). Public API, constructor options, CLI argv at all 11 call sites, generated configuration, and the existing backend suite (74/74, unedited) are unchanged. Deliberate behavior differences from the previous implementation, each pinned by a test: 1. Export integrity is computed independently of the durable store's consumption (an under-reading store can no longer self-certify). 2. A pre-dispatch rejection (fence assertion or duration preflight) on createDatabase / uploadCandidate / createDeployment propagates raw — no readback, no rollback. 3. Upload scratch-cleanup failure travels on the value channel and is surfaced after reconciliation; WorkerDeploymentError is constructed once with an AggregateError cause; a pre-dispatch rejection whose cleanup also failed rejects with an AggregateError of both instead of masking. 4. A d1 binding with id '' and database_id set is refused at classification (adapter-level only; end-to-end ordering is unchanged). 5. PlainWorkerVersionDetail.versionId preserves provider absence. 6. findDatabase refuses a `d1 list` row with uuid ''. 7. deleteWorkerScript asserts the fence before the not-found classifier, so a lease denial can no longer be swallowed as absence. 8. Upload scratch is adapter-owned for the duration of uploadCandidate and is allocated only when an upload is needed, after the status/version reads. Also adds plainWorkerBindingsToProviderShape + assertSupportedPlainWorkerBindings (reconstruct-and-delegate over the neutral binding shape), adapter and port-contract test suites, and shared test fixtures. The port is not exported yet; the fleet-control changeset for the Worker-native control plane lands with Checkpoint C2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At85hntr2BHB6jwKFoFaRD
…end core Checkpoint B1b of the Worker-native control-plane work. The ordinary-Worker policy that lived in WranglerLoopBackend moves, as a rename, into an exported PlainWorkerBackend core that depends only on PlainWorkerProvisioningApi plus a diagnostic identityCaller and injected fetch, clock, and maintenance timeout. WranglerLoopBackend is now a 57-line wrapper: it validates three adapter inputs and the maintenance timeout in the historic order, builds WranglerPlainWorkerProvisioningApi, and extends the core. The direct Cloudflare-API backend (B2) reuses the same core with a REST adapter. Public API (minor changeset): PlainWorkerBackend, PlainWorkerBackendOptions, PlainWorkerProvisioningApi and its record, outcome, and intent types. The core is documented as not a supported extension point; its constructor options are unstable until the direct-API backend lands. Deliberate differences from the previous commit, each pinned by a test: 1. Eleven Wrangler-worded diagnostic sites (ten distinct messages) now use provider-neutral wording; error-message text compatibility is not claimed. 2. WranglerLoopBackend extends PlainWorkerBackend (same options, same members); validation order preserved via resolveMaintenanceRequestTimeoutMs. 3. New public exports, changeset, README and API-reference entries. 4. A scratch-cleanup failure after a successful upload is rethrown as WorkerDeploymentError with createdByAttempt set from the attempt's created flag and resourceState 'present', so provisioning rolls back only a Worker this attempt created instead of orphaning it; pinned in the core suite, the wrapper suite, and two provisionDeployment-level tests (created and pre-existing arms). 5. The route-mutation fence contract is documented on PlainWorkerRouteApi (members unchanged) and pinned by ordering tests in both withMutationFence shapes, and the backend-owned pre-assertions before promotion attach, traffic detach, and the maintenance request are pinned in both shapes; no assertion sites changed. Adds an in-memory PlainWorkerProvisioningApi fake and a core-unit suite whose policy cases run the core with no CLI adapter present; the legacy backend suite remains the behavioral compatibility proof (six literal expectations updated for wording). The packed-consumer probe type-checks the new exports against the tarball. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At85hntr2BHB6jwKFoFaRD
Checkpoint B2a of the Worker-native control-plane work. Add
CloudflareApiPlainWorkerBackend, a PlainWorkerBackend subclass that drives
ordinary Workers through Cloudflare's APIs instead of a Wrangler process, over
an internal adapter that talks to CloudflareProvisioningClient. The client
gains a plain-only construction plane ({ plane: 'plain-worker' }, which rejects
any dispatchNamespace key), a named CloudflarePlaneCapabilityError for the
members that need the configured dispatch namespace, ordinary-Worker fact
members, and two upload paths that send Wrangler's single JSON metadata part
so limits and version annotations survive. It also exports
CloudflareApiPlainWorkerBackendOptions, PlainWorkerCloudflareClientOptions and
CloudflarePlaneCapabilityError, and exposes the configured provider request
timeout through a public CloudflareProvisioningClient.requestTimeoutMs getter.
Cross-cutting changes, each declared in the changeset as a BEHAVIOR CHANGE
(the queued-execution-context change is described below): SDK logging
is forced off regardless of CLOUDFLARE_LOG; every paginated inventory
is bounded by item count and fails rather than truncating; SDK retries
are disabled on Worker upload and deployment, D1 creation, the D1 query
path shared with WorkersForPlatformsBackend, and each D1 export poll;
database-export failures pass through one redaction boundary that drops
signed URLs, provider bodies, headers, and original causes; uploaded secret
plaintext is replaced in ordinary-Worker provider error messages before
a failed outcome is returned; the three database and R2 reconciliation
arms assert the lease before the attempt and again before any readback
so a mid-flight lease loss surfaces instead of being masked; a reconciled
Worker upload is refused unless the Worker's workers.dev and preview-URL
state match the intent; and PlainWorkerBackend rejects an identityCaller
that is not a 1-128 character printable single-line ASCII token. Under
Workers for Platforms construction the dispatch-namespace listing's 404
still propagates and blocks destructive teardown. Only a plain-only client
treats a 404 from that listing as an empty scan, and only before the first
namespace is yielded; a later 404, and the same case on the ordinary-Worker
version listing, propagates rather than classifying absence.
The adapter's outcome members re-assert the lease before returning a failed
outcome, prepare uploads and deployments before entering the fenced dispatch
boundary, and share the binding normalizer and deployment validator with the
Wrangler adapter, which now calls the same functions. The direct adapter
classifies each mutation through a package-internal dispatch tracker that the
client marks only when a provider mutation request is invoked, so an SDK-side
preparation failure, a provider read failure, or a lease-assertion failure
that never reached a mutation rejects instead of resolving a failed outcome
the core would try to reconcile. Queued provider operations now run under
their own execution context: p-queue starts a deferred task from the previous
task's microtask, so a queued mutation previously asserted the preceding
operation's lease under concurrency pressure. Upload failures that never
reached the provider keep a bounded, secret-redacted cause chain, so the
originating fence or transport message survives redaction.
Adds a recording fetch fixture with a provider world in port vocabulary and a
REST projection (the seed for the shared conformance suite), client, adapter
and backend suites, threat-model and documentation updates, a minor changeset,
logLevel off on the credentialed runner's narrow read client, and
packed-consumer probes with a positive control.
Known limitation recorded in the docs: a non-Workers-for-Platforms account that
answers 403 to the namespace scan blocks destructive D1 teardown by design, and
Cloudflare error 10220 can refuse a deployment when secrets changed after the
version upload; and a persistently failing workers.dev write leaves the refused
Worker in the account for operator cleanup with resourceState 'unknown'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At85hntr2BHB6jwKFoFaRD
…ackends Both built-in ordinary-Worker backends now run the same 16-scenario conformance suite through one describe body, over a provider world that holds canonical raw-provider state and mints every id it hands out. A REST projection drives the direct Cloudflare API backend through the real provisioning client; a Wrangler CLI projection drives the loop backend over the same world. A divergence between the two lanes now fails the suite instead of hiding in one lane's fixtures. A cross-backend continuation suite covers ready convergence, snapshot and migration resume, abort and compensation, phased decommission, and ambiguous request boundaries where a mutation may have committed after its response was lost. Retire the plainWorkerIngressModule re-export from WranglerLoopBackend; its two test importers take the symbol from plain-worker-backend directly. The re-export is unreachable from the package exports, so this carries no changeset. Record in the fleet-control guide what the shared suite asserts, what it deliberately leaves to lane-specific tests, and why staged public-access state is not among its assertions.
Eight module-level symbols move verbatim out of cloudflare-client.ts into the new src/cloudflare-provider-errors.ts: the sanitizer chain (MAX_SANITIZED_ERROR_CAUSE_DEPTH, redactSecretValues, readErrorFieldSafely, isErrorSafely, sanitizedErrorName, sanitizedErrorCause, sanitizeProviderError) and the isNotFound predicate. Bodies and comments are byte-identical; only `export` is added, and the call sites are unchanged. The new module imports APIConnectionError and APIError from 'cloudflare' and readField from './provider-binding-inventory.js', and nothing from cloudflare-client.ts, so the dependency runs one way. cloudflare-client.ts now imports sanitizeProviderError for the upload dispatch, readErrorFieldSafely and sanitizedErrorName for the D1 export failure path, and isNotFound for its 24 call sites; its 'cloudflare' import keeps only the default Cloudflare. readField stays with three remaining uses. No behavior, public API, or emitted declaration change: dist/index.d.ts and dist/cloudflare-client.d.ts are byte-identical to the pre-move build, so no changeset accompanies this commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
… context Seventeen public ordinary-Worker members of CloudflareProvisioningClient and the private #ordinaryWorkerSecretNames helper become exported free functions in cloudflare-ordinary-worker-operations.ts. Their bodies move verbatim apart from 69 receiver substitutions onto a new OrdinaryWorkerContext; each context-taking function declares the Pick slice it needs. The client keeps every one of them as a one-line forward with the identical declaration, and builds one context from values and bound arrows as the constructor's last statement. withMutationFence is reached through this, so an instance override stays observable. The attestation cluster (ProviderDeployment, exactActiveVersionId, observedTrafficSplit, attestedActiveVersionId) moves to active-route.ts. .dependency-cruiser.cjs gains fleet-control-client-layers-are-one-way, which forbids a back-import into the client from the three modules under it, with its positive-control fixture and registry entry. The fleet-control CLAUDE.md source map now names the operations and provider-error modules. No public API or behavior change, so no changeset: dist/index.d.ts, the 54 runtime export names and dist/cloudflare-provider-errors.d.ts are byte-identical, and the CloudflareProvisioningClient body in dist/cloudflare-client.d.ts diffs empty. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
…port D1FleetStateDatabase adapts a Workers D1Database binding to the state store's FleetStateDatabase port: query through all(), execute through run(), batch through the binding's batch. query and batch check the whole envelope (success, meta, results, row shape, batch count and order). execute checks the acknowledgement alone, because the Workers D1 shim returns run()'s envelope without backfilling results, and execute reads no rows. An error defined beside success: true is refused as contradictory. Binding errors propagate unchanged so the state store's duplicate-column cause traversal and the migration ledger's causes keep working. An empty batch resolves without a D1 call. MigrationDatabase.batch widens from Promise<void> to Promise<unknown>, so the same instance serves applyMigrationsWithLedger; the ledger discards that value and the existing implementers stay assignable. The two Wrangler harness probes drop their private near-duplicate adapters and construct the production adapter, so both harness suites exercise it on local D1. The fleet-state probe's lost-batch-response wrapper delegates explicitly, and a new cold-start case races sixteen stores' first writes on storage recreated by TestHarness.reset(), asserting the four tables and every tenant's tag. Node fake tests pin bindings, ordering, envelope refusals, and unchanged error propagation. The package source map gains the adapter, and its provisioning-backend line is corrected: it named two files after the ordinary-Worker core and its direct-API adapter had landed. The module is internal until the Worker-facing export lands, so dist/index.d.ts is byte-identical and there is no changeset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
The `execute` TypeDoc said "the shim" with no antecedent on the page. It
now names workerd's D1 shim, which is checkable: in the installed
@cloudflare/workerd-linux-64 binary `run()` returns the raw
`_sendOrThrow('/execute', ...)` value, while `toArrayOfObjects`
backfills `results` on the `all()`, `raw()`, and `batch()` paths.
`validateAck` becomes `validateAcknowledgement` at its declaration and
its two call sites, and the comment inside it says "acknowledgement"
rather than "envelope", agreeing with the class TypeDoc and with the
function it sits in. Its clause break moves to the semicolon.
The Node fake test for `execute` is renamed to say that it resolves
acknowledgements without results, so trimming the fixture that omits
`results` no longer leaves the name true. The harness cold-start case
gains a comment saying that resetting the server recreates storage and
rebinds `worker`, hence its position last in the sequential block, and
its callback index parameter is spelled out.
Behavior is unchanged: reverting the rename textually leaves the source
byte-identical to daa7ef8 apart from four comment lines, the six error
messages are untouched, and dist/index.d.ts, dist/migration-ledger.d.ts,
and both of their maps match the baseline build byte for byte.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
…mmit Move the `DurableDatabaseExportStore` contract into its own leaf so a Worker-facing store never reaches the Cloudflare client, and move the portable-segment check into `export-file-name.ts` so both stores share one validator and one message. `FileSystemDatabaseExportStore` keeps its behavior and its message byte for byte. `R2DatabaseExportStore` streams an export into R2 under an integrity contract: a `FixedLengthStream` body behind a conditional put that cannot overwrite, SHA-256 taken over an R2 readback rather than over the upload, a per-attempt UUID key, cleanup only after a put that fulfilled with an object, and a refusal for a body that is already locked. A missing `contentLength` fails closed ahead of the upload, so a direct D1 download without a usable `Content-Length` leaves the database undeleted. Tests: Node `DigestStream`/`FixedLengthStream` fakes and a structural R2 bucket cover the state machine, and a Wrangler harness drives the store against real miniflare R2 for a 1 MiB round trip, an empty and a short refusal, and a conditional collision. Also reflows one carried comment in `test/state-store.harness.test.ts`. `dist/index.d.ts` is byte-identical, so there is no changeset; the new modules are exported in a later change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
…ections The store wrapped three single-operation awaits in `Promise.allSettled([one])`. A module-private `settled()` helper now carries that shape at all three, and resolving the operation through a promise makes a synchronous throw from an injected `get` or `delete` reach the same fixed message and `cause` that a rejection reaches, as a throwing `DigestStream` constructor already did. A synchronous `get` throw therefore deletes the attempt-owned key instead of orphaning it, and a synchronous `delete` throw aggregates with the export error. The put stays bare because wrapping it would start the put after the pipe; its comment now narrows to that one site and records the constraint. The tests add a case pinning both new consequences, merge the two retry cases into one table over a per-row fixture, take the bucket as a parameter in the construction-failure table, move the two pre-upload refusals beside the refusal family, infer `metadata()` and check it with `satisfies` so the spread-restored method can go, and adopt the file's `satisfies` table idiom and camelCase message constants. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
The synchronous-throw case pinned only the aggregate's second error. It now pins the first the way `aggregates an owned failure with a cleanup rejection` does: the instance, then the fixed readback-absence message, then the sentinel identity. The case also moves from after `accepts a multi-segment key prefix` to directly after the aggregate case whose shape it now mirrors. The moved block is byte-identical apart from those assertions, no other case moves, and the case count stays at 26. `settled()` gains one comment line for why the operation is called through a resolved promise rather than passed to `Promise.allSettled` directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
PlainWorkerProvisioningApi.listDatabases takes an optional name filter. The direct Cloudflare API adapter forwards it as the D1 list query, so a deployment no longer lists the whole account inventory to find one database; the Wrangler adapter keeps its pinned argv and filters the parsed rows locally. PlainWorkerBackend.findDatabase passes the deployment's database name and keeps its exact-name comparison, because a name filter narrows rather than matches, along with its duplicate-name and missing-UUID refusals. An absent filter sends byte-identical requests on both adapters. The direct conformance suite gains a staged-upload case asserting that the direct adapter converges workers.dev and preview-URL settings, the behavior the shared suite deliberately leaves unasserted. The threat model records the upload-error redaction residual: the redaction knows only the upload intent's plaintext secret values, a consumer-injected fetch can echo the account token into an error, and an SDK coercion can throw a consumer-controlled value that bypasses redaction. The Secrets section's categorical no-plaintext-in-errors claim is narrowed to match. A changeset records the port change as a fleet-control minor release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
…names The direct-API case now seeds a second, non-matching database and asserts the unfiltered listing as two rows in insertion order, so the filtered expectation fails if the name filter stops reaching the wire. The core-policy cases take their database names from the deployment spec, and the empty-uuid row is keyed by its own id. The fixture's list filter keeps a concise arrow with its comment above the return, matching the Wrangler adapter. Three over-length comments are rewrapped with the same words, and the threat model's cross-reference to the Direct Cloudflare API backend becomes a checked anchor link with the same rendered sentence. No behavior changes; the built declarations stay byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
…ayering Move readField, readStringField, and readArrayField out of provider-binding-inventory.ts into a json-field-reads.ts leaf that imports nothing, and repoint the four modules that read them. The provider-error module now imports only that leaf and the Cloudflare SDK. Drop the export keyword on MAX_SANITIZED_ERROR_CAUSE_DEPTH, redactSecretValues, isErrorSafely, and sanitizedErrorCause, which no module outside cloudflare-provider-errors.ts imports. Import workerMigrations in the switch provider from cloudflare-ordinary-worker-operations.ts and delete the client's re-export, so the switch provider no longer reaches the client. Collapse the two open-coded 404 predicates in cloudflare-client.ts onto the isNotFound that module already imports. Restate fleet-control-client-layers-are-one-way as everything under src/ except the client, its two direct-API importers, and index.ts, so a new module is covered the day it lands. Add fleet-control-ports-do-not-reach-d1-adapter and fleet-control-worker-reachable-modules-avoid-node-builtins, the second over the published Workers as well as the R2 and D1 modules, each with a positive control. Run the source-only build program under typecheck alongside the source-plus-test program, and correct the direct-API fake's seed-state comment: buckets is exercised through getR2Bucket. dist/index.d.ts is byte-identical; no published entry changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
…ments The ordinary-Worker operations header held a 27-column orphan line; the paragraph now wraps at 75 and 63 columns, with the same words in the same order. The port-to-adapter rule comment now names backend-switch.ts, the hop through which state-store.ts reaches migration-ledger.ts, so its cycle warrant covers both port modules rather than state-store.ts alone. The Worker-reachable-builtin rule comment now states why its modules are listed and why a Node builtin matters there, instead of restating the path regex two lines below it in prose. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
FileSystemDatabaseExportStore's failure cleanup awaited the reader's cancel. On a tee() branch that promise settles when the tee source is exhausted or the other branch is cancelled, so a store-internal refusal held its rejection and its temporary file until then, and held both indefinitely when nothing drove the source. Cleanup now starts the cancel without awaiting it and swallows its rejection. The surrounding try still catches a synchronous throw from an injected body, and the finally still releases the lock. A new case drives the size refusal from a tee branch whose source stays readable, then asserts the write rejects and leaves the export root empty. A changeset records the fix as a fleet-control patch release. The migration ledger harness gains a cold-application probe action and a case for two concurrent first applications against a dropped ledger table: the case pins the table's absence before the race, and both applications fulfil, leaving one ledger row and one value row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
The tee-branch cancel case in export-store.test.ts ran its last setup statement straight into the acting try block. One blank line now separates them, as the file's other cases already do. The migration ledger probe's cold-application sqlite_master count sat on one 86-column line. It now breaks after the table name and indents the WHERE clause seven spaces, the shape readAcrossBoundary already uses. The break and indent replace one space between two tokens, so SQLite parses the same statement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
fleet-control-client-does-not-reach-its-consumers forbids a reachable edge from cloudflare-client.ts into index.ts, cloudflare-api-plain-worker-backend.ts, or cloudflare-api-plain-worker-provisioning-api.ts, the three modules that import the client. fleet-control-client-layers-are-one-way holds the client and those three in its pathNot, and packages/fleet-control sits outside no-new-architecture-cycles, so that back-edge was covered by no rule. fleet-control-export-port-does-not-reach-adapters forbids a reachable edge from database-export-store.ts into export-store.ts or r2-export-store.ts, the two stores that import DurableDatabaseExportStore from it to implement it. tsPreCompilationDeps keeps those type-only imports in the graph. Each rule carries a positive-control fixture under scripts/architecture-fixtures/ and its registry entry in scripts/architecture-positive-controls.test.mjs, raising the control set to seventeen. The cruiser config is not a build input, so no runtime or declaration output changes and no changeset is owed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
The positive-control harness reads each dependency-cruiser JSON report back through spawnSync without a maxBuffer, so Node's 1 MiB default applies. Each report carries the fixture's resolved module graph and the rule set, and both grow with the repository. Once a report crosses the default, the harness reports an opaque ENOBUFS spawn failure instead of a rule verdict. Set maxBuffer to 64 MiB, the value already used at packages/showcase/scripts/run-react-doctor.mjs. The reports are well under the default today; the cap keeps a future overflow from arriving as a misleading spawn error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
`wranglerQuery` in the `flowsafe-provision` bin ran `wrangler d1 execute --json` through `spawnSync` with no `maxBuffer`. Node's default is 1 MiB counted across the captured stdout and stderr together, so a larger response was truncated and the run threw `failed to execute Wrangler 4` with an `ENOBUFS` cause instead of returning parsed rows. The seed script now passes a 64 MiB `maxBuffer`, the value at the repository's two existing `maxBuffer` sites. The packed provisioning gate pins it. The fake Wrangler shim's schema-scan branch appends a padding row sized by `FAKE_WRANGLER_PAD_BYTES`, and the preview arguments are re-run with a 1536 KiB pad, asserting the CLI still exits 0 with its verification line. The protocol filters `sqlite_`-prefixed names out of its application-table list and matches the sentinel by exact name, so the padding row leaves the outcome unchanged. `invokeProvision` takes the same `maxBuffer` so a regression reports the CLI's own message rather than a second capture overflow. A patch changeset records the change for `@proofoftech/flowsafe`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
The packed provisioning test's oversized-capture case checked exit status and stdout but not stderr, so output on the error channel would not fail it. It now carries the same stderr clause as the valid and preview cases beside it: the fake Wrangler's schema-scan branch writes nothing there, padded response included. Drop the invocation-log reset that preceded that run. Both reads of the log come earlier in the file, so the reset scoped no read. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
`R2DatabaseExportStore.write` cancels the body when `#prepare` refuses and does not await that cancel. An injected `body` whose `cancel` threw synchronously let that throw escape the refusal handler and replace the error `write()` rejects with. `src/r2-export-store.ts` now wraps the cancel call in `try`/`catch` and keeps `throw error;` outside it, so the refusal still propagates. This narrows one escape from that store's fixed-message set rather than closing it; other paths, the `randomUUID` call and the bare put among them, reach a caller by their own routes. `test/r2-export-store.test.ts` adds a case driving an injected `cancel` that throws, asserting the empty-body refusal survives, that `cancel` was reached exactly once with the refusal object itself as its reason, and that no put started. `test/export-store.test.ts` pins the `try`/`catch` that `FileSystemDatabaseExportStore` already holds around its reader cancel, which no test reached: an injected reader whose `cancel` throws must still surface the content-length error and leave no temporary file. `src/export-store.ts`, the R2 class doc, and `.changeset/tee-branch-cancel.md` correct the wording of the tee-branch cancel's settling condition, which named two routes and omitted a third; the cancel also settles when the tee source errors. The class doc drops its "stays pending until" phrasing for the "settles when" wording the other sites carry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XyG1jJCrVSWfVQ3Zab15Rd
Capture the filesystem export refusal separately from fulfillment and assert that the injected reader receives that exact object when cancellation throws synchronously. This also follows the suite's store-binding idiom and closes the C1c-D review nits.
Start the R2 put through a non-assimilating barrier before piping, classify synchronous injected failures through fixed stage messages, and clean every owned post-put failure. Digest rejection is observed before readback without making a failed read wait on a hostile pending digest. Unit and real-workerd cases pin ordering, cancellation, cleanup, injected accessors, and mid-transfer source failure before the store becomes public.
Assert that an asynchronous provider rejection reaches source cancellation as the exact original reason. Scope the paired-readable settlement requirement to FixedLengthStream, the injected constructor that actually feeds the R2 put.
Add resumable D1 and R2 attachment traversal with strict progress validation and page-independent evidence. Keep raw dispatch authentication private while preserving complete-list behavior.
Single-home scanner contracts and provider bounds, remove dead retry state, and pin the effective SDK retry ceiling without changing scan behavior.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
anchorage-showcase-single-tenant | 7a7838e | Sep 16 2026, 05:28 AM |
Refuse notification ingestion on an unpatched @mastra/core in the thread route, after the request's own validation and before the content-policy gate, the record-only branch and Core's inline sender, so an unpatched install answers ingestion the way it answers dispatch: a 502 whose server log line names the patch. A deployment that ingests here and delegates dispatch elsewhere needs the patch as well. Constructing a tick with a zero limit needs no patch, because that tick does no delivery work; constructing a delivering tick refuses. A pass whose notification leg refuses reports no schedule result, while what the schedule leg fired stays accounted through the audit sink, and the tick's memo serves a host that retains and re-invokes one built tick. The composer invokes a scheduleTick builder outside the tick duty's try, so a factory that can refuse at construction belongs inside the returned closure. Declare that batch results carry rows on the D1 seams the signal, schedule, snapshot and initial-admission stores read, so a hand-written adapter to the exported types reads that requirement off the type instead of meeting it at runtime; the changeset names the tightening, the initial-admission seam derives its element from the snapshot seam, and a type fixture pins that a D1Database satisfies those seams. Merge the two write bodies of the flowsafe test database helper so a run and a batch element carry the same envelope, and share one SQLite-to-D1 stub between the two starter suites that use it. In the docs check, resolve an absolute link into this repository's main blob path back to its file, with or without a fragment, and report it through the target, internal-file and fragment guards the relative check runs. Pin that the packed tarball ships exactly the patch files the source tree holds, keep assertNotificationSourceKeysPatched out of the packed export surface, write the source-key probe from one constant at both consumers, unescape the documented postinstall command before running it, and pass --batch -N on its forward leg, so a tree missing the patch's target files fails instead of prompting and a partly patched tree converges. The maintainer guide's retirement list names the canary expectation, the toolchain sentence, the seam test, the starter's lazy tick with its suite, and the CI manifest edit with its comment; its retirement signal is a canary whose gated blocks run green rather than skip. An updatedAt outside the supported grammar sorts its row last in an unlimited listNotifications page. Cover the ingestion refusal in the seam suite and the packed unpatched consumer, the zero-limit tick, the starter tick's invocation and memo under a constructing factory, the merged helper envelope, and the absolute-link check's resolving case, its missing-anchor failure, and its missing-file, escaping-path and internal-file failures with a fragment and without, beside the relative check's own internal-file failure. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The template's executable proof drives the run DO over a stub state and calls the maintenance seam directly, so it owns two contracts a deployment supplies: the run-owner recovery journal writes through state.storage, and the purge duty advances a retention cursor its caller holds. Without them the start route answers 500 and stale run rows survive the purge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Merge the worker control-plane branch (36 commits, ending in the direct scenario checkpoint and its pending checkpoint) into dev, which carries the F5/Core, F5 pending and deploy-e2e checkpoints and a docs commit since the merge base. Two files conflict and are resolved by hand: the packed agent-host proof's host-kit import takes the branch's line, which already carries both new type names; the signal-ingestion integration suite unions both sides' imports, records the exchange before the after-response hook observes the response, and takes the reservation helper's new parameter while keeping dev's inferred return type, so its sendSignal mock stays exposed. Seven more overlapping paths merge automatically. The merged tree passes lint, the architecture, Markdown docs and GitHub checks, the build, the four packed consumers and the conformance config check. Without a built fleet-control dist, typecheck and the API docs fail on that package's own specifiers and thirteen of its suites fail without running a test; with the dist, seven tests in four of its suites fail as they do at the branch tip, where four commits tightened shared contracts and a shared test fixture without carrying those suites. This merge carries all of that unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The package's direct scripts import it by its own name, which the exports map resolves to dist. The test-including tsconfig program and the API docs therefore fail in a tree that has not built the package, and CI runs typecheck and test before build. Map both specifiers to src in tsconfig.json for that program and for TypeDoc, and have the package's pretypecheck build it so the Worker-typed script program and the test step find the dist they resolve through exports. The script program keeps that resolution: mapped to src instead, it compiles this package's src as checked source and reports four diagnostics program 1 does not. Those four are left as they are; this checkpoint changes no src file. Four suites that predate the branch fail at its tip because commits on it tightened the shared contracts they consume without carrying them: a versions-list stub answering with an object the parser now refuses, a D1 list row with an empty uuid now refused by the adapter before the backend, an inline footprint double without the two flags the completeness guard now requires, a fixture record without the wfpMode the immutable-mapping check now compares, and a synthetic version upload without the entrypoint module part the shared fetch fixture now requires. The stubs and fixtures now satisfy the tightened contract, so each case again exercises what it targets; the port-contract case instead expects the adapter's refusal, which now preempts the backend's. One assertion that compared a record to itself now compares it to a copy taken before the refused admission. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Merge the flowsafe suspension-deadline branch (two commits: the public deadline helpers with do-runner constants and testing subpaths and their docs, and two do-runner comments cut to one line each) into dev, which now carries the worker control-plane merge and its fleet-control fix on top of the F5, F5 pending and deploy-e2e checkpoints. Git merged every path without a conflict; four paths were changed on both sides and merged as unions: the API reference, the flowsafe README, the flowsafe package manifest, and the packed agent-host proof. The workspace installs offline from the frozen lockfile, and the merged tree passes typecheck, lint, the architecture, Markdown docs and API docs checks, changeset status, the whitespace and scratch-path checks, the flowsafe suite and the three root vitest projects, the agent-starter consumer, the build, and both packed flowsafe consumers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add a private teardown module for the direct credentialed scenario. It disables the reference ingress, deletes the four resources bootstrap created and the two export receipts in a fixed order, one atomic journal write per transition, settles a delete whose answer is not a validated envelope by an exact-identity reread, re-checks identity before re-issuing a delete on resume, and ends with a bounded read-only residual observation over six surfaces. A scenario that did not complete, a pending invocation or bootstrap mutation, a missing receipt, an unexpected object under the prefix, an identity mismatch, a forbidden answer, or an exhausted budget stops the run, and the outcome reports the reason with the identities that survive it. The provider helpers gain an envelope-only settled client, a three-way absence probe, single-page and bucket-page readers, a bounded error unwrap, and bootstrap's inventory and identifier verbatim; its dispatch classification moves with them and now also returns the namespace names. The run journal gains a closed teardown record with decode rules that refuse a receipt still named as pending, monotonic guards, a capacity check against the journal bound, and settled assertions that refuse scenario, invocation and bootstrap mutations mid-teardown. The teardown tests drive an in-process fetch router; nothing here contacts Cloudflare. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The two "contains compiler failures" titles provoked the failure with a thousand nested parentheses and relied on TypeScript's recursive parser overflowing V8's stack. That boundary moves with the host's stack budget and with V8's parser frame sizes: on a runner with more headroom the parser returns, the preflight resolves, and both titles fail. The file now mocks the typescript module, because TypeScript's CommonJS namespace defines createSourceFile as a non-configurable getter that vi.spyOn cannot redefine, and wraps createSourceFile so that it throws a RangeError on the call that inspects the role under test (the first call for the reference artifact, the second for the tenant) and delegates otherwise. The two titles arm that wrapper with a countdown that disarms when it fires, so every other title parses through the real compiler. The preflight script is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The verify job's twenty-minute cap was reached with no step having failed. On the last two cancelled runs, typecheck, the whole test suite, build and API docs took about nineteen minutes, and the job was cut off in its eighteenth step with eight verification steps still to run, one of them carrying its own ten-minute cap. The test step is the growth: it took under four minutes on the last fully green run and over fourteen now. The job now has forty-five minutes, nearly twice the projected full run. The React Doctor step and the mastra-compat job keep their caps. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The reference protocol gains a fence operation module. It reads, drains and reopens a tenant's execution fence, sweeps its inventory, issues a current-epoch schedule mutation and the three labelled epoch probes, all through the tenant's admin and probe routes, under one response contract that projects an allowlist of reading fields and refuses a wrong media type, an oversized body, invalid JSON, an unknown state or a non-integer counter. The lifecycle action type excludes the fence kind, so a fence action does not type as a lifecycle action. The tenant fixture Worker derives its mutation epoch from APPLICATION_RELEASE through one shared rule and gains two probe routes, /__direct/fence-mutate and /__direct/fence-probe. They validate the request's members before the schedule router sees it and drive the real schedule router against the real fence store; fence-mutate refuses a malformed schedule id before any URL is built and reports the outcome by response.ok with the refusal code projected from the router's reason, and fence-probe reports the classification derived from it. The scenario gains the fence-drain, fence-reopen and fence-proofs phases, each measured at nine invocations. The run journal gains a fence proof shape with ordinal bounds, completion gates and member-level preservation across a resume, and its two size thresholds follow from the measured maximal scenario journal and the measured teardown free space. The offline fixture serves the fence, inventory, mutation and labelled-epoch branches over the real fence store, inventory and epoch assertion behind each role's credential. The offline suites prove control-plane composition and the published epoch check. The real-lane tenant suite alone proves that the artifact carries its own epoch: after the fence reopens, a release-1 deployment's schedule create is refused as stale and a release-2 deployment's is admitted on the same database, with no caller supplying an epoch. Malformed members and ids are refused by the route itself, and a draining fence still admits a delete. The docs publish the fence-coordinated rollout order and state that schedule mutations carry the epoch check activation requires. Neither lane reaches Cloudflare: the tenant suite boots local workerd through wrangler's test harness, and the scenario and offline fence suites run in Node against the fixture provider. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The direct credentialed scenario and the offline fence suite take about thirty-five minutes together, and inside the root test run they pushed the verify job past its forty-five-minute cap. They now form the root vitest project fleet-control-direct-scenario, owned by packages/fleet-control/vitest.direct-scenario.config.ts and excluded from the package project so no run lists them twice. The root pnpm test still runs every project; the package test script runs both configs, so pnpm fleet-control:check keeps the pair. CI splits the work. The former verify job is verify-core and runs the workspace without that project. A direct-scenario job builds fleet-control, whose prebuild chain also builds breakwater and flowsafe, and runs the project on its own sixty-minute cap; the suites import flowsafe subpaths and the fleet-control package from their dists. A verify gate job depends on both, runs whether or not they succeed, and fails unless every job in its needs list reports success, so the required status check on main keeps gating on the suites without a ruleset change and a job added to needs is gating by construction; a skipped gate would count as success, which is why it always runs. The contributor and maintainer guides describe the three jobs. The root project breakwater-workers now globs its worker tests from the repository root: vitest's workspace loader overrides a project's root with the config's directory, so the package-relative glob matched nothing and pnpm test ran zero breakwater worker tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The direct credentialed proof has its bootstrap, scenario, run-state journal and teardown modules, and offline suites over them, but no entry point that runs them against an account, resumes after a restart, and writes an evidence artifact that a sentinel scan gates before publication. This adds one, under packages/fleet-control/scripts/. direct-credentialed-conformance-runtime.mjs exports runDirectConformance. It orchestrates injectable module seams for preflight, run-state open and inspect, bootstrap, scenario, teardown and the dist probe, imports the evidence builder, scanner and writer directly, and reads no process state; its defaults run git rev-parse HEAD for the commit field and probe dist/index.js. It admits a live mode only after local preflight, the credential checks, the dist probe and the scenario floor, then dispatches from the journal snapshot: a teardown already complete with no failure yields evidence and calls no provider; any other recorded teardown, and a failed or complete scenario, go straight to teardown; a run with no scenario, or one still in flight, is the only one that reaches bootstrap and the scenario. The evidence status and exit code come from the outcome the invocation resolves. A teardown call that returns cleaned resolves cleaned (0); otherwise the teardown phase read back from the journal snapshot tells a refusal (failed, 1) from nothing deleted (retained, 4). restart-required is 3, a refusal before admission is 2, and a sentinel hit is 5. direct-credentialed-conformance.mjs is the entry. It parses argv, calls the runtime, and prints one DIRECT_CONFORMANCE line on stdout for a resolved run the runtime does not mark stderr-only, and writes any stderr line the runtime returns when the exit code is neither 0 nor 3; argv it cannot parse exits 2 with the fixed usage line on stderr and nothing on stdout. Every exit code goes through one resolver in which an evidence failure outranks an internal error and both outrank the rest, so a late unhandled error cannot be overwritten by a completion and cannot overwrite an evidence failure. Unhandled errors and rejections take exit 1 through that resolver and write the fixed internal-error line, which carries no stack. The runtime assembles the stdout and stderr lines itself from the serialization it scanned, byte-scans and size-checks the assembled line, and returns those bytes or a fixed line; the entry writes what it returns and serializes nothing, so every byte it prints either passed that scan or is a fixed line. The lines the CLI prints without a scan are complete fixed lines held in DIRECT_FIXED_OUTPUT, and a token or invoke secret that one of them contains is refused before the dist probe, the run state and any provider call, with the fixed invalid-input line on stderr and nothing on stdout; when that fixed line would itself contain the credential, the CLI exits 2 and prints nothing. The entry parses argv before it reads anything else. For --help and --preflight it reads no credential and writes the runtime's lines as returned. For --run and --resume it hands the runtime a view of process.env that records the API token and the invoke secret when the runtime reads them, after local preflight, and writes every line through one guard that keeps what it has already written to stdout and stderr, in order, as one transcript, extends the transcript before the write, and drops a line when the transcript plus that line would contain a recorded credential. A dropped summary line sets exit code 5; a dropped stderr line leaves the resolved exit code. direct-credentialed-evidence.mjs projects the journal snapshot, the preflight facts and the outcome onto a fixed key set in a fixed order, serializes that object with a trailing newline, and scans it: every decoded key and string value, then those exact bytes, which are the bytes the file receives, for the API token, the invoke secret and a fixed literal list, and the uuid and version-id fields it lists for a plain identifier shape, so a URL or response text cannot ride in under one of those keys. A hit refuses publication and reports the sentinel class and the key path, neither of which carries a value from the refused artifact. The file lands at mode 0600 through an O_EXCL temporary file, an fsync and a byte read-back, then a rename. The run-state journal records createdAt at initialization and a resumeCount that saturates at DIRECT_RUN_MAX_RESUME_COUNT; a journal holding neither field decodes and re-emits byte-identically. inspectDirectRunState returns a journal snapshot and a close handle with no writer, and shares the platform gate, base directory and exclusive lock with openDirectRunState through attachRunState; an inspection holds that lock until it closes. Package script test:credentialed:direct builds the package and runs the entry, and root script fleet-control:credentialed:direct forwards to that script. Two new test files cover the runtime, the spawned entry and the evidence writer; the run-state suite gains titles for the metadata, resumes, inspection and lock ownership. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The direct credentialed conformance CLI has unit suites over its runtime, entry, evidence writer, bootstrap, scenario and teardown, but none of them drives the runtime, bootstrap, scenario and teardown together. This adds one against the direct reference harness and its provider bridge, extends the shared fixture where the CLI's path reaches routes it did not serve, corrects that fixture's terminal-page metadata, and documents the lane. test/direct-credentialed-conformance.acceptance.test.ts starts from a run whose bootstrap receipts are already confirmed, closes the fixture journal, and drives runDirectConformance in child processes with a scoped bridge fetch, a throwing global fetch and a PATH-only environment. The sequence is: a resume that revalidates bootstrap, runs the scenario to its restart point and exits 3 with restart-required evidence and resumeCount 1; a fresh-process resume that completes the scenario, tears down and exits 0 with cleaned evidence, every residual surface at zero, retainedIdentities all null and resumeCount 2; an evidence-only resume over the completed teardown state that makes no provider request, exits 0 and produces evidence equal to the previous file outside finishedAt, mode, exitCode, status, resumeCount and teardownCall, with mode, exitCode and status asserted as resume, 0 and cleaned on both files and teardownCall null; a run mode against the existing run that exits 1 with run-exists; and a concurrent resume against a held lock that exits 1 with lock-unavailable, leaving the run directory listing, the journal bytes and the absent evidence unchanged. It also runs --preflight and --help through the entry and asserts the package and root workspace scripts statically. Bootstrap revalidates its receipts on both live resumes; its create path is not driven. The shared fixture (cloudflare-fetch-fixture.ts, provider-world.ts) gains the account, subdomain, token-attestation and zone reads that bootstrap's context bind performs, zone names and deployment identities on the world records, a D1 name-prefix filter, namespace_id on dispatch rows and previews_enabled on the subdomain POST response. Its terminal numbered-page responses report the requested page rather than page 1. The harness (direct-reference-harness.ts) gains export-bucket creation, object listing, object deletion and bucket deletion, exempts that bucket from the injected read failure, resolves a deployment by its recorded identity, and exposes the version runtime map. docs/fleet-control.md gains a section on running the direct-API credentialed proof: what the offline lane proves and what only a credentialed run can, the configuration and environment variables, the modes and exit codes, the restart-and-resume protocol, the evidence artifact and its allowlist, what a refusal retains, and the non-claims. The package README links it and CLAUDE.md's source map gains a scripts/ row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A connector's manifest declares the hosts it may reach. The host list alone does not say whether that declaration binds the connector's traffic: a connector whose vendor SDK or child process carries its own transport declares hosts the guarded fetch never sees. A deployment where the guarded fetch is the only egress boundary needs that distinction at construction, at runtime, and in the audit log. permissions.egressEnforcement on PermissionManifest carries the posture: 'enforced' declares that every HTTP request leaves through ConnectorRuntime.fetch, 'declaration-only' that another transport carries it. 'enforced' is a claim about HTTP traffic and not about platform bindings, so it covers a connector that issues no HTTP request at all. An omitted field stays absent from the manifest and resolves to 'declaration-only'. Construction throws a TypeError for a value outside the two literals. connectorEgressPosture(tool) returns the resolved posture for a tool createConnector built and undefined for any other, exported beside connectorManifest(tool). record() adds that posture to every connector audit event as detail.egressEnforcement, spread last so a per-event detail does not replace it. policies.requireEgressEnforcement refuses at construction a connector whose resolved posture is not 'enforced'. singleTenantConnectorPolicies accepts the same flag, freezes it into the policies it returns, and refuses a policy object whose flag changed after validation. The Agent CLI adapters declare 'declaration-only', matching their child-process boundary, so a deployment that sets the flag cannot construct one. The packed-consumer script imports connectorEgressPosture and the ConnectorEgressPosture type from the packed tarball and asserts the resolver and the refusal there. The agent-starter manifests declare 'enforced', and its smoke test reads the record-action connector's posture back through the compiled package. CONNECTORS.md and docs/connector-interface.md describe the field and the refusal; the package README, docs/breakwater-architecture.md and docs/security-threat-model.md describe the field. A minor changeset records the addition. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A connector's manifest declares the hosts it may reach, and permissions.egressEnforcement: 'enforced' declares that every HTTP request leaves through ConnectorRuntime.fetch. The package carries no check of that declaration against the connector's behavior in a consumer's own test suite. assertConnectorConformance(factory, options) is that check. For each supplied case it replaces globalThis.fetch and every supplied entry point with a trap that records the attempt and refuses, builds the connector through the factory under those traps, hands it an inert base transport and an audit logger to wire, and runs the case. A factory result the package's registries do not resolve, such as undefined, a plain Mastra tool or a connector from another copy of the package, refuses the run at the probe and fails a case with SUBJECT_UNREGISTERED without invoking it. A request that reaches a trap fails the case with NETWORK_IO_OUTSIDE_RUNTIME_FETCH, including when the connector catches the refusal. The inert transport refuses a host the registered manifest does not declare, so a call around the guard to such a host fails the same way. Entry points are instrumented one at a time, globalThis.fetch first; each write is validated before and verified after it, and a failure at any entry point restores the rollback stack. An absent or configurable entry point is instrumented with an accessor whose getter returns the trap: an assignment to it during a case is recorded as INSTRUMENTATION_REPLACED and not applied, so later calls stay observed. A writable non-configurable entry point is instrumented by assignment, with no such defence. Instrumentation is restored after the case settles, throws, partially installs, or times out; the probe and case restorations first check the own descriptor of each entry point against what the harness installed, without invoking a getter the case may have installed, and a redefinition still in place fails the case with INSTRUMENTATION_REPLACED, its calls after the redefinition unobserved, or refuses the run when the factory redefined it during the probe. A redefinition the case itself reverses before it settles is outside what the harness observes, and the finite-case limit says so. An invocation that rejects with anything other than a validation, invocation, or policy error, or the harness's own refusal, fails the case with CASE_INVOCATION_FAILED, whose reason names the error's constructor or the thrown value's type, not its message. A restoration the harness cannot prove fails the run and ends it. A run started while another is active, two cases sharing a name, two entry points sharing a label, two entry points naming the same target and property, an accessor or locked descriptor, and a target that ignores a write are refused. A case that times out ends the run, and no later run is accepted in that isolate. The harness certifies only a connector whose resolved posture is 'enforced'. An empty case set, a set that never reaches the supplied transport for a connector declaring egress, a case that requests a declared host through globalThis.fetch without reaching the harness transport, and a case that reaches its gate boundary without an audit witness on the supplied logger are reported as findings, not passes. Every report carries the finite-case limit: the supplied cases, this isolate, the lifetime of each case. The harness lives in connector-sdk/egress-conformance.ts and imports only types from the SDK barrel; the barrel binds its three runtime collaborators and exports the assertion, ConnectorConformanceError and the report types from the SDK and root entry points. A workers test loads the barrel inside workerd and exercises the conformant, escaping, restoring and replacement paths and the fetch descriptor rule there; the vitest workers pool's own workerd (1.20260730.1) crashes in its module resolver while loading the barrel's Mastra dependency, so a pnpm override pins workerd 1.20260903.1 under that pool's miniflare. The packed-consumer script imports the assertion from the packed tarball and exercises it. CONNECTORS.md and docs/connector-interface.md describe the harness, the connector SDK's CLAUDE.md names the module, and the README lists the exports. A minor changeset records the addition. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The direct credentialed conformance CLI and the fleet-control package refused provider states the live Cloudflare API returns and local workerd does not produce. Zone listing sent the SDK's array type filter as repeated type= query parameters, and the live API answers that form with no rows, so no zone matched ownedHostname. The listing now carries no type filter and the four accepted zone kinds are applied client-side on each row's type; an unsupported type drops the row, and a missing or malformed type refuses the listing. The package's own account-wide zone-route discovery drops the same filter and applies the four kinds per row. The version resource of an uploaded script omits compatibility_flags when the list is empty, while the settings endpoint returns an empty list; the strict comparison against the configured list refused the absent key. Bootstrap's runtime check and the observation phase's version check both read an absent key as an empty list; an absent key still refuses when the configuration declares flags. Enabling a script's workers.dev ingress returns before the route serves, and the route then answers platform error pages intermittently for a few seconds; bootstrap sent the first control read at once and the invocation ended with its outcome unknown, which the journal cannot resume. Bootstrap now probes the reference endpoint without credentials until three consecutive answers are exactly the contract's 401 refusal, bounded by a 120-second deadline, before it reserves the control read; a deadline that passes refuses provider-unavailable with nothing reserved. Live list envelopes carry errors: null and result_info: null where the fixtures carried empty arrays and objects, and the package's response validators, the CLI's provider layer and its D1 statement check refused the null. Each now reads a null errors or result_info as absent, and still refuses a non-empty errors array or a non-array value; a null page metadata ends a scan as a terminal page. A freshly enabled workers.dev route serves the platform's own error pages for some seconds, and the plain-Worker backend sent its first maintenance request at once. Both maintenance requests now go through one helper that re-sends the same request while the answer is a 404 or a 500 carrying a text/plain or text/html media type without cache-control: no-store and without WWW-Authenticate, every two seconds for up to one minute by default, re-asserting the mutation fence before each POST. Any other answer is parsed at once, the ingress module's own bodiless 404 included, and a deadline error names the wait and the compatibility flag below. The concrete backend forwards both readiness options. Cloudflare answers a Worker's fetch of another Worker on the same account's workers.dev subdomain with error 1042, an HTTP 404 text/plain page, unless the fetching Worker enables global_fetch_strictly_public, and the reference Worker fetched tenant maintenance origins without it. The direct configuration validator accepts the ordered flag lists built from nodejs_compat and global_fetch_strictly_public, requires the latter on the reference Worker, and the example configuration and the harness fixtures carry it; docs/fleet-control.md states the requirement. After a deployment change, the workers.dev route keeps answering a request that names the new version through a version override from the previous version for a few seconds, and the backend failed the maintenance handshake on the first such answer. A well-formed maintenance health that attests a different specification digest is now re-requested, with the same request and the fence re-asserted before each POST, within the readiness deadline the platform-page wait already uses; a missing or malformed digest still fails at once. The CLI treated every non-contract answer to an invocation as an unknown outcome, which ends the run. The invocation client now re-sends the identical request under the same journal reservation: a read-only action (control-read, inventory-read, audit-page, migration-page, cleanup-receipt, decommission-export and the read-only tenant-probe and tenant-fence operations) after any non-contract answer or transport failure, a mutation only after an unmarked text 404 page, because the reference journal does not deduplicate deliveries. Re-delivery is bounded by the shorter of 120 seconds and the invocation timeout, measured from the first attempt; the first send and any answer whose contract headers have arrived run under the invocation timeout alone. An outcome-unknown raised while the delivery is still pending names the class it saw: platform-page, transport-failure, non-contract-answer, or delivery-window-expired when re-delivery exhausts that window. The R2 object API compresses an export object for a client that accepts gzip, and the CLI's byte-exact export check refuses a compressed transfer. The export read now asks for an identity transfer; the checks on encoding, size and digest are unchanged. The client verified an API token through the user token family only, which refuses an account-owned token. Verification now tries the account family first and falls back to the user family on 401, 403, 404, 405, 429 and 5xx. The CLI's reference invocation went through the runtime's default fetch, whose header timeout of 300 seconds sat below the live run's 600-second invocation timeout, so a provisioning invocation that waited for the route was reported with its outcome unknown. The invocation now uses a node:https transport bounded only by the invocation timeout, and refuses a redirect without following it. The inventory's R2 stage lists all three jurisdictions, and an account without FedRAMP entitlement answers that listing with 403, error 10003. The stage now records a non-default jurisdiction whose first page is refused that way as unavailable, in the required FleetResourceInventory.unavailableR2Jurisdictions array, and still fails on the default jurisdiction, a later or resumed page, or any other error; the persisted stage metadata and the page digest keep an unavailable jurisdiction distinct from an empty one. The plain-Worker version listing follows the SDK's paging: after the last item the SDK requests one more page, so a Worker deleted between pages answers that request with 404. The listing now reports a 404 on any page as an absent Worker instead of failing the post-deletion residual check, which reads the listing right after the delete. A Worker upload, a D1 or R2 creation or a deployment change that the platform answers with a transient failure (a 5xx answer, a 408, a 429, or a connection or timeout rejection carrying no status) is now retried, up to three attempts with 2-second and 4-second delays, only after a read proves the intended effect absent, so a version or resource the failed answer did create is adopted rather than created twice. Each attempt is preceded by the mutation-duration check, R2 creation also re-asserts fence ownership, and the delay runs through the backend's injectable wait. A refusal is still not retried; the shared plain-Worker conformance suite pins a retried transport failure and a persistent one. The bootstrap, invocation, observation, provider, config, client, backend, shared conformance, scan, inventory, run-store, fleet, audit, runtime, evidence, run-state, scenario, fetch-fixture, reference harness and Wrangler provisioning suites pin these behaviours. The shared fixtures carry a zone type, the reference flag, null envelope fields, a provider failure repeatable across attempts and the empty jurisdiction array; the bootstrap suite's fixture takes an optional configured flag list, and the reference harness test and the packed probe's provider answer both token verification endpoints. The R2 jurisdiction type the inventory reports is exported from the control-plane and package entries, with sixteen other types the package entry reaches. docs/fleet-control.md gains the provisioning retry, the compatibility flag, the deployment-change wait, the unavailable R2 jurisdiction and the CLI re-delivery rules. A patch changeset accompanies the package changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pending-notifications inventory category listed only the pending rows whose delivery or summary time had come and reported the rest as a notDue total. A notification scheduled for later is work the runner still owes, so a drain proof taken from entries alone could declare a deployment drained while such a row was pending. The category lists every pending row, due or not, in key order. Its count is the number of rows in the category, as in every other category, and is taken on the first page of a sweep; the notDue total still says how many of the listed rows the dispatch scan does not select, including rows carrying neither timestamp; an entry carries summaryAt beside deliverAt when the row carries them, so an operator can tell the two apart against the reading's time. The CategoryQuery fields that let one category aggregate over a wider predicate than it paged had no other user and are removed; the count query is a single COUNT(*) over the category's own predicate. The operator drain procedures in the package README and the deployment reference say that a pending notification scheduled for later, or carrying no due timestamp, keeps the proof open; the do-runner design doc says how such a row leaves. A new integration test drives, through the production dispatch chain, a notification whose thread became ownerless after it was created. Each attempt reaches the thread and is refused with a 404 because the thread has no owner, is recorded as a bounded failure, and the row is discarded with delivery-attempts-exhausted after the configured two attempts; a further pass changes nothing. The inventory suite pins the enumeration, the per-fixture counts and the notDue total for rows with one, both or neither timestamp, and a sweep that stays non-empty while only a not-yet-due row is pending. A minor changeset records the package-visible change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The transport-neutral type module imported a type from the Cloudflare inventory implementation, and that implementation imported APIError from the cloudflare package as a runtime value. Forty modules under src/ import ./types.js, so the first edge let all forty reach the provider layer: pnpm run architecture:check:rules reported 47 violations over 623 modules at 7a446d2, under eight fleet-control layering rules and the cycle rule. The second edge broke fleet-control-runtime-sdk-stays-in-provider-modules, which exempts cloudflare-client.ts, cloudflare-ordinary-worker-operations.ts and cloudflare-provider-errors.ts; the rules half of the gate drops that edge with its node_modules exclusion, so architecture:controls is what reports it. types.ts now declares R2_JURISDICTIONS, a frozen ordered list, derives R2Jurisdiction from it in place of the literal union it already exported, and declares FleetInventoryR2Jurisdiction as an alias of R2Jurisdiction. The inventory implementation imports the list and the alias instead of declaring a const and a second union of its own. Both type names stay exported from the root and cloudflare-control-plane entries and name the same three jurisdictions, so the packed export surface carries the same names. R2_JURISDICTIONS is exported from neither entry; typedoc.json lists it under intentionallyNotExported, because the public R2Jurisdiction derives from it and docs:api treats that warning as an error. cloudflare-provider-errors.ts gains isR2JurisdictionAccessRefusal, beside isNotFound and isTransientProviderError: an APIError with HTTP status 403 carrying provider code 10003 is how an R2 list refuses a jurisdiction the account has no entitlement to. That module already imports APIError and the SDK rule exempts it. The R2 inventory stage calls the predicate in place of four inline clauses; its jurisdiction === 'default' and startAfter !== undefined conditions still rethrow, and the unavailable-r2-jurisdiction meta row, the page identity digest and the resume cursor are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The direct-credentialed teardown attested a resource's ownership only when it resumed a pending step: the identity read for the Worker, the two D1 databases and the export bucket ran inside the resume branch of the shared mutate helper, which also held the probe, so a first attempt neither probed nor attested before its delete. The ingress-disable and export-object steps carried no identity check at all: they mutated a Worker and a bucket whose ownership only a later step attested, and only on a resume. The helper now probes on every attempt, settles an absent resource by reread, and runs the step's ownership check before the prepare read and the dispatch-intent write. The Worker and bucket identity checks are named functions: the ingress step shares the Worker's, and the bucket's runs once after the object listings and before the object deletes. A first attempt that settles by reread also advances the journal phase, because the run-state decoder rejects a phase that jumps two positions. The secret-name comparison compares the complete observed set. The recording predicate that filtered it dropped an over-long or control-character name before the equality check, so a script carrying an extra secret under such a name still matched the reference set. Seven tests pin the first-attempt refusals, the two rejected-name cases and the settle-by-reread path; the pinned request sequence grows from 27 to 40 entries. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The fleet migration composition took a clock but no abort signal and no completion callback, while the audit, inventory, cleanup and decommission advances all accept a signal. AdvanceFleetMigrationOptions and CloudflareAdvanceFleetMigrationOptions gain both as optional members. The signal is checked at the public entry and at the top of advanceItem, before that function's try block. Its catch records a failed item and a failed operation durably, so a check inside it would turn a cancellation into a permanent failure; at the top, an abort leaves the operation and its items as they were and a later continue resumes. onComplete runs at the public entry on every call that returns complete, after the account operation lease is released, so a host callback never runs under the lease. A continue on a finalized operation and a replayed start of the same operation report complete again and invoke it again; delivery is at least once and the host deduplicates on the operation id, as settlementFor's contract already requires on its settlement key. A rejection propagates and leaves the finalization intact. The Cloudflare control plane forwards the signal as its siblings do and binds onComplete to the input object as it binds settlementFor. Seven tests pin the aborted start, the resumable abort before the item step, delivery after the durable finalize, redelivery on a later continue, a rejecting and a throwing callback, and a drain under inert options that matches a drain with both omitted; the forwarding test pins signal identity and the bound receiver. A minor changeset records the two optional members. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three private fetch wrappers set no redirect policy: the provisioning client's transport, which serves the Cloudflare SDK, the raw dispatch script page request that sends the account API token, and the signed export download; the plain Worker maintenance transport, which sends the maintenance admin secret; and the Workers for Platforms maintenance transport, which sends a minted capability token. A provider or tenant answering with a 3xx could send the request on to an address the control plane did not choose. Among their call sites only the signed export download set `redirect: 'manual'`, at the call site. A Workers for Platforms maintenance call that received a 302 carrying a valid signed receipt succeeded, because the backend reads only the receipt header and hands the health reader a freshly constructed response. Each wrapper now forces `redirect: 'manual'` after the caller's init. The provisioning client's `#request` returns a 3xx to its caller: the SDK retries an error thrown by its injected fetch and reports it as a connection error, whereas a returned 302 becomes an `APIError` with status 302 on the first attempt, which `isTransientProviderError` does not classify as transient. Where the reader would not otherwise classify the status, the redirect is refused by name: the raw dispatch script page request refuses at its call site, and the Workers for Platforms maintenance wrapper refuses for both its callers; each throws `CredentialedRedirectRefusedError` and cancels the body. The plain Worker maintenance path needs no bespoke refusal, because `readMaintenanceHealth` refuses a non-ok response. The export download carries no redirect policy of its own and keeps its non-ok refusal. `isRedirectStatus` and the error live in `cloudflare-provider-errors.ts` beside the predicates that classify SDK errors; this one classifies a raw provider response, and the module header names both kinds. No package entry re-exports the module, so the exported surface carries no new name and the changeset is a patch, while the error still reaches a consumer by name as a thrown value. The threat model names the three transports and gains a row for a credential following a redirect. Tests cover manual redirect handling on each transport, the two named refusals with body cancellation, the SDK-routed 302 classification and the redacted export-download refusal; two exact-init expectations in the Wrangler loop suite gain the forced policy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The threat model's provisioning boundary described the deployment sentinel, the migration fence and the redirect refusals, but not the traffic a promotion leaves routed to a version the control plane has not attested. The active-route section of `docs/fleet-control.md` holds that material; the threat model had no entry for it. A `### Active-route attestation` subsection sits between the bounded fleet migration and the deployment sentinel. It names the threat (a staged traffic split, a stale or duplicated route, a hostname mapping that resolves to another physical script, an external writer changing routing between the control plane's read and its write), the provider-state reads each backend performs to attest, the promotion paths that attest through `attestConvergedActiveRoute()`, the bounded retry and the `ActiveRouteAttestationError` refusal, and the refusal by `exactActiveVersionId` of a split that names a second version. It states the residual: an unattested version can serve traffic from the promotion until an attestation matches or the operation refuses; the convergence budget bounds the attestation's own retry, not that window, and `advanceFleetMigration()` commits the promotion step and the attestation step as separate advances, so on that path the host driver's cadence between advances bounds the window; a routing change by another authorized account token is observed on the next read rather than excluded. One row in the threats-and-controls table, after the redirect row, carries the threat, the attestation control and the residual. Both of the subsection's links resolve under the docs checker's slugger. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The connector conformance harness classified a thrown value with four separate `instanceof` reads inside a try block that has no catch. A value whose `getPrototypeOf` is a trap made the classification throw, so the run rejected with the trap's error and built no report. On the execute path `createConnector`'s own reads of that value fired the trap first, so the report named the trap's `Error` instead. A rejection with `undefined` was not counted as a failure at all. A case's escapes were converted to findings once and the same live array was placed in the case result, the run's findings array was the object the report exposed on every exit, and `conformant` was computed once, so an escape reaching a trap or the case transport after the case settled entered a result without a finding, or sat beside `conformant: true`. The audit witness was read from the event object the connector holds. `classifyInvocationError` answers `boundary`, `policy`, `refusal` or `foreign` once, with its four reads inside a single try/catch, and a value it cannot read is `foreign`, so the report carries the `CASE_INVOCATION_FAILED` finding; a separate flag records that the invocation failed, whatever value it rejected with. An `isInstanceOf` predicate in `connector-decision.ts` guards seven reads of the connector's thrown value inside `createConnector` in `connector-sdk/index.ts`, among them the keyed attempt's catch, where releasing the reservation depends on the read, and a `readProperty` helper beside it guards the `connector` read that follows the policy error check. The case logger's sink keeps a harness-owned copy of each audit event, taken before the connector sees the object. A case result carries a snapshot of its escapes taken where they become findings; an escape observed after the case settled is a run-level `NETWORK_IO_OUTSIDE_RUNTIME_FETCH` finding whose reason names the case; `recordRun` drops a finding once the run is closed, the two early refusals close the run before they throw, and the fall-through report snapshots `findings` and `cases` and computes `conformant` from the snapshot. `CONFORMANCE_LIMIT` keeps its timed-out-case sentence and states in a sentence of its own the channel a settled case's abandoned work reaches, an undeclared host on the supplied base transport or a captured trap, recorded as a run-level finding naming the case until the report is built and dropped after; the two documentation mirrors and the pending harness changeset that carry it verbatim follow. Tests cover the option-getter, prototype-trap, thenable and `undefined` seams, the mutated audit event, the late escape, the post-report and post-refusal drops, and two in the SDK suite pin that `invokeConnector` rejects with the connector's own thrown value; the new changeset is a patch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The direct credentialed teardown recorded every single-page residual surface as `exhaustive: false`, whatever the provider sent about the page, and it did not list the account's queues. A teardown therefore recorded no completeness for the scripts, domains and routes pages, and a queue carrying the run's prefix went unrecorded. `singlePage` in `direct-credentialed-provider.mjs` returns `exhaustive` from the provider's own attestation. On a single-page listing response the transport's `json()` records, in a module-level `WeakMap` keyed on the envelope's `result` array, whether `result_info` corroborated a complete page: a `total_count` equal to the row count, a `total_pages` of one, or zero pages with zero rows. A page carrying no such record reads `false`. `queues` joins `DIRECT_RESIDUAL_SURFACES`, so the surface schema and `isSettled` carry it, and a prefixed queue retains the run as `residual-present`. `observe()` in `direct-credentialed-teardown.mjs` lists the account's queues and matches on `queue_name`; a 404 records an empty surface the provider did not attest (`exhaustive: false`), and a 403 becomes `forbidden` as on the other surfaces. `isSettled` reads each surface's `prefixCount` and `globalCount`, not its `exhaustive`; no teardown failure code and no configuration flag are added. The declarations follow: `exhaustive: boolean` in `direct-credentialed-provider.d.mts`, `'queues'` in `direct-credentialed-run-state.d.mts`. `docs/fleet-control.md` states how `exhaustive` is derived, what `isSettled` asserts prefix-scoped and what it asserts only under `disposableAccount: true`, and that a journal written before this version is refused on resume; the token row says the token must permit the queues read. `world()` in the teardown suite serves a queues page and gains a `corroborate` option over the scripts, domains, routes and queues listings, and `restProjection` in `cloudflare-fetch-fixture.ts` serves an empty queues page. Fourteen tests cover the three corroborating `result_info` shapes, the four uninformative ones and the two the transport refuses, the prefixed queue, the attestation on every single-page surface, the shared-account settle, the queues 404, and a journal recorded before the queues surface; the forbidden-surface loop and the pinned request order gain the queues entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`CONFORMANCE_LIMIT` was a paragraph of 1,457 characters, placed on every `ConnectorConformanceReport` as `limit` and mirrored byte-for-byte in `packages/breakwater/CONNECTORS.md`, `docs/connector-interface.md` and `.changeset/connector-conformance-harness.md`. Correcting one clause meant changing four copies of the paragraph. Its clauses restated rules those documents state elsewhere, and partial statements of the same channels stood beside it in `CONNECTORS.md` and in `docs/connector-interface.md`. `CONFORMANCE_LIMIT` is one sentence: the finite-case scope, then the permanent URL of the section that lists the channels the harness does not observe. That section is `### Conformance limits` in `packages/breakwater/CONNECTORS.md`, a sibling of `### Assert connector conformance`, immediately before `## Contribute a connector`. It opens with a one-line lead-in and the sentence as a blockquote, a mirror the pin test reads, then a sentence on the finite evidence a run produces and eight numbered items covering the scope of that evidence and the channels a run does not observe. Items 2 to 5, 7 and 8 refer to a rule the file states earlier instead of restating it. The precedence paragraph under the outcome table states once the condition under which a case reports hosts and runs the outcome check, and item 4 defers to it for whether a served call's host proves `guarded-request`; item 7 states that, for work abandoned by any case, a read of `globalThis.fetch` when no case trap is installed reaches the restored global if restoration succeeds, and that a request through that global is neither trapped nor recorded. The paragraph on supplied fixtures and the sentences on a redefinition the case reverses and on a timed-out case's restored global are deleted where they stood and folded into items 8, 2 and 7. `docs/connector-interface.md` keeps the sentence as its blockquote mirror, and its sentence on a redefinition the case reverses points at the section. The harness changeset carries the new sentence, and the paraphrase in `.changeset/breakwater-conformance-foreign-values-and-late-escapes.md` points at the section. The pin test still requires the constant verbatim in both documents and, while it exists, the harness changeset; the assertion that matched old prose in `limit` compares to the constant; a test comment that quoted an old sentence names the section. `CONNECTORS.md` ships with the package, and `pnpm docs:check` validates the URL's fragment from the Markdown mirrors. What the harness traps, records, snapshots and drops does not change; a host that displays or stores `report.limit` sees the shorter text, and the changeset is a patch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`cross-backend-continuation.test.ts` gains the describe block `ordinary Worker cross-backend audit continuation`: three cases, each over its own provider world that the Wrangler-origin and direct backends share. Each starts a fleet audit under the Wrangler-origin backend, advances until the first record is inspected, and resumes on that token with `backendFor` switched to the direct backend over the same operation store, inventory store and world. The first case asserts the resumed token keeps the operation id, the revision advances, the audit reaches `complete` at generation 1 with a record count equal to the fleet's, and the ordered inspection log names each record once. The second asserts no `ensureMaintenance` call on either leg and a world snapshot equal to the one taken before the switch. The third asserts the findings page equals a single-backend audit's findings over the same records at the same pinned clock, both lists empty, and that the log records a live outcome for each inspection. `test/fixtures/fleet-operation-fakes.ts` holds the in-memory `FleetOperationStore` and `FleetInventoryRunStore` fakes, the frozen audit clock and the uuid helper, which `fleet-audit-advance.test.ts` and `cross-backend-continuation.test.ts` import. `cloudflare-client-plain-worker.test.ts` adds 429 to the refusal matrix over the four ordinary-Worker deployment and version reads and adds a case where each of those reads rejects with `APIConnectionTimeoutError` and none resolves to `undefined`. `cloudflare-api-plain-worker-provisioning-api.test.ts` extends `classifies only provider 404 as an absent Worker` with a refused 429, a refused 500 and a timeout that carries no status. `plain-worker-backend.test.ts` asserts that a 429 and an `APIConnectionTimeoutError` are transient and that neither is `isNotFound`, raw or sanitized. No file outside `packages/fleet-control/test/` changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`provisionDeployment()` refused a stored `decommissioned` row that cleared the decommission, cleanup and backend-switch guards with the generic phase message, so a slug whose teardown had completed could not be provisioned again until `forceDecommissionDeployment()` cleared the ledger row; no audit sweep clears one. `auditFleetDrift()` reported that row as `incomplete-provisioning` once it aged past `staleAfterMs`, misclassifying retained state as stalled provisioning. A stored row is read once, before the lifecycle guards, the immutable-mapping asserts, the phase refusal and the reservation-ownership flag. A terminal `decommissioned` row is normalized to an absent prior when it carries no unfinished decommission, cleanup or backend-switch operation and `isCompleteTerminalRecord()` holds: the boolean form of `assertCompleteRecord` in `decommission-intent.ts`, which requires the database export record and all `applicationResources` at `deleted`, so a row a force decommission stranded without its export is not one. Provisioning then replaces the row from the supplied `DeploymentSpec`: the slug, logical script name, database name and route hostname come from that specification, equal to the retired row's when that specification retains them; the database and its provider-minted ID, the seeded deployment identity, the application R2 resources and the artifact version are new, and no export location, digest or byte count is carried. A terminal row that retains an application resource, or whose teardown evidence is incomplete, refuses with a message naming that residue and directing to `forceDecommissionDeployment()`; an unfinished operation refuses through the guard that owns it, and every other non-resumable phase keeps the generic refusal. Over a retired terminal row the reserved-name database check runs before the first `lease.put`, so a re-provision the check refuses leaves the row byte-identical and the failed-provision unwind, which also requires a record this attempt claimed, deletes nothing; a provision with no stored row keeps its original order. `auditFleetDrift()` treats a terminal `decommissioned` row as retained state and reports no `incomplete-provisioning` for it. `forceDecommissionDeployment()` is unchanged, and a completed force leaves no stored row, so the provision that follows takes the fresh path unchanged. `decommissionDeployment()` replays a terminal row's export for a late retry only while that row is the stored row; once a re-provision has replaced it and the replacement is `ready`, a same-spec call is a new decommission of the replacement, because the one-call facade carries no operation identity. `docs/fleet-control.md` and the changeset state that, direct an in-flight retry to `advanceDecommissionDeployment()`, whose token carries the identity, and state what a re-provision takes from the specification, what it mints, and the residue refusal. The changeset is a minor bump. `provision.test.ts` adds the admission under a changed specification, the `previousDurableObjectTag` refusal, the generic phase refusal, the residue refusal, the stranded force row, the byte-identical refused re-provision, the fresh unwind, the switch-retired row and the same-spec decommission after a re-provision; `cross-backend-continuation.test.ts` and `plain-worker-backend.test.ts` cover the re-provision across backends and the backend's refusal to upload over an existing Worker with drifted ownership; `fleet.test.ts` pins the audit sweep. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The direct credentialed scenario recorded no proof that a tenant's custom-domain routes survive the A to B migration. Its force phases ran against the recovery role, so the scenario never cleared role `a`'s terminal `decommissioned` row, and the evidence recorded `cost: 'unknown'`. The requirements audit ruled `UP-AR-ACC1.1`, `UP-AR-ACC2.1` and `SP-CR-R.8` not met on that evidence. An inventory proof in `direct-credentialed-scenario.mjs` reads the provider-derived inventory before and after the migration, keeps the custom-domain route rows of the plain-worker backend for each normal role's script, and requires two hostnames. On the after observation it asserts that the generation increases, that the sorted hostname list equals the before list, and, per role, that that role's rows are the single row bearing its configured `routeHostname`. The journal schema gains a DNS-shaped hostname validator bounded at 253 characters and a `routeHostnames` array of at most two entries on the inventory shape, which each nullable before and after inventory proof carries; the maximal fixture, the cap test and the evidence projection follow. A `force-terminal` action for role `a` joins the reference contract and the router ahead of the generic role-bearing branch. Its handler intercepts the force API's own deployment lease and refuses a tenant tag or environment other than role `a`'s, a phase other than `decommissioned`, an unfinished decommission intent, a cleanup intent, and a row whose role is not `a`; it captures the row's database and script identity under that lease, lets `forceDecommissionDeployment()` delete the row, and requires absence afterwards. An already-absent row returns a null before identity, which makes the call re-enterable. A settled `force-terminal` call persists that returned identity in the journal beside its outcome and attempts, and the schema requires the field on a settled force-terminal call and refuses it elsewhere. The `force-terminal-a` phase follows `decommission-b`: fresh entry issues the force and requires it to return with the row absent, the returned identity to equal role `a`'s decommission proof, and the provider, maintenance and application attempt counters to be zero; a resumed settlement requires the persisted identity to equal that proof and builds the proof from that mutation's ordinal, identity and attempts without issuing the force again; either branch then confirms through a control read that the row is absent. A persisted null or foreign identity and a persisted nonzero-attempt witness fail with `observation-mismatch`; a persisted proof is kept. The completion predicate, the phase presence rule, the nullable proof schema, the proof's ordinal bound, the monotonic proof group, the `force-terminal` reconciliation allowance, the budget entry at `measured: 4`, the declarations, fixtures and evidence carry the phase. The evidence's `cost` object records the run's own request counts under `basis: 'request-counters'`: the reference Worker's provider, maintenance and application attempts, the local SDK session's requests, the journal's invocation reservations and the durable teardown provider counter. The four scenario counters and the teardown counter read `null` when their snapshot section is absent; `referenceInvocations` reads the snapshot's invocation count and has no null form, and `billed` is `null` because nothing reads a billing source. `docs/fleet-control.md` states that cost record and the two new proofs. Neither pre-fence run continuation (`UP-AR-ACC1.3`) nor a re-provision of role `a` after the force (`UP-AR-ACC2.2`) is built here: no tenant Worker, fence route, re-provision action or operation-slot change belongs to this diff. The journal fixtures measure 138,063 B for the maximal scenario against the 141,312 B threshold and leave 92,923 B free for the complete scenario plus teardown against the 92,160 B floor. Tests cover the route filter and the hostname bound, the terminal-force schema, its persisted identity and its frozen proof, the contract acceptance and refusals, the force handler through the lifecycle harness, nine settlement cases, five resume cases and the cost projection. No changeset: the package ships `dist`, `README.md`, `CHANGELOG.md` and `LICENSE`, so these scripts, tests and the repository doc change nothing it publishes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The root `pnpm.overrides` key `miniflare@5.20260730.0-alpha>workerd` moves into sorted position; the key set and every value are unchanged. `pnpm-lock.yaml` carries no hunk: pnpm 10.34.4 compares the overrides map order-insensitively, so an offline `install --lockfile-only` leaves the lockfile byte-identical, and an offline `install --frozen-lockfile` exits 0 against the reordered manifest. `scripts/architecture-positive-controls.test.mjs` gains a free-standing control over the root `vitest.config.ts` `projects` list. Each entry globs to at least one existing file; the resolved set is the four root project configs, the fleet-control direct-scenario config and one `vitest.config.ts` per package that has one; the direct-scenario project is named `fleet-control-direct-scenario`, the name the root `--project` scripts pass; and each of that project's `include` entries globs to a file and appears in the fleet-control package project's `exclude`. The control parses the configs with the TypeScript compiler API and globs the tree; it starts no vitest run. `tsconfig.harness.json` includes `packages/fleet-control/vitest.direct-scenario.config.ts` and `vitest.workerd-lifecycle.config.ts`, two of the root-registered project configs that no tsconfig program compiled. Comments record the projects list's selection scripts and CI jobs, the placement rule for package-local project configs and the standalone project's non-inheritance (`vitest.config.ts`); the miniflare the Workers pool resolves, the workerd that miniflare declares, the override that re-points it, the pool-upgrade rule, and the file-relative `configPath` beside the repository-relative `include` (`vitest.breakwater-workers.config.mts`); and the override's reach into the showcase Vite plugin (`packages/showcase/vite.config.ts`). A comment in `scripts/baseline-recorder.mjs` and a sentence in `scripts/CLAUDE.md` state that `--check` compares the exports `config.exports` declares and leaves a committed export without a declaration uncompared; the recorder's code is unchanged. `.dependency-cruiser.cjs` is unchanged: `no-new-architecture-cycles` still omits `packages/breakwater/src` from its `from.path`. Widening it there reports thirteen pre-existing cycles through the breakwater barrels, so the widening lands with the change that removes them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The `verify` job's step in `.github/workflows/ci.yml` asserts
`to_entries | length > 0 and all(.value.result == "success")` over the
`needs` context, so an emptied `needs` list fails the required check
instead of passing it with nothing verified. A non-empty context passes
only when every entry reports `success`, the listing of each entry's
result runs first, and `jq -e`'s exit status is the step's result. The
comment above the job states that assertion and keeps the reason for
`if: always()`: without it the job would be skipped, and GitHub reports
a skipped job as success for a required check. It drops the sentence
that a timed-out or never-run dependency requires `always()` and
nothing narrower, and the block is rewrapped.
`scripts/github-yaml-check.test.mjs` gains the case `the ci.yml gate
rejects an empty or non-success needs context`. It parses the tracked
workflow, takes the single `run` step of the `verify` job, and runs
that script under `bash` with `NEEDS` set to an empty object, to two
`success` entries, and to three contexts pairing a `success` entry with
`failure`, `cancelled` and `skipped`, asserting the exit status and the
result listing on stdout for each. Without `jq` on `PATH` the case
skips with a stated reason.
The `concurrency` group `ci-${{ github.event_name }}-${{ github.ref }}`
with `cancel-in-progress: true` cancels an in-flight run of the same
event and ref. The event name is part of the key, so the push run and
the pull_request run for one commit sit in different groups. The push
and pull_request triggers are unchanged.
The Test step carries `timeout-minutes: 30`, about 1.8x the slowest
measured run of that step (968 s) and inside the 45-minute cap on
`verify-core`. Its comment states that derivation and records that the
Typecheck step produces the fleet-control dist the step consumes. The
35-minute measurement of the `direct-scenario` job stays in that job's
comment, above the cap it justifies, and no longer appears in the
Test-step comment. That job's build step is named `Build fleet-control
and its prerequisites`, and the comment on the packed fleet-control
step names the `./workers/*` export entries; neither states a count.
Other comments in the file still carry counts.
The note in `.github/workflows/release.yml` states that `verify` is the
required check on a pull request into `main` and not on a push to it,
and that repeating the check in the release job fails before the
publish step. No changeset: the change is two workflows and one test
script.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…rProvisioningApi port
Checkpoint B1a of the Worker-native control-plane work. WranglerLoopBackend now
reaches the provider only through PlainWorkerProvisioningApi (extends the
unchanged PlainWorkerRouteApi); every CLI mechanic — argv, JSON parsing,
staging directories, generated Wrangler config, secret input files, scratch
export files, and the durable-store write with its independent integrity
comparison — lives in the new WranglerPlainWorkerProvisioningApi adapter.
Provider-neutral policy stays in the backend for extraction into a shared
core (B1b) reused by the direct Cloudflare-API backend (B2).
Public API, constructor options, CLI argv at all 11 call sites, generated
configuration, and the existing backend suite (74/74, unedited) are
unchanged. Deliberate behavior differences from the previous implementation,
each pinned by a test:
consumption (an under-reading store can no longer self-certify).
createDatabase / uploadCandidate / createDeployment propagates raw — no
readback, no rollback.
surfaced after reconciliation; WorkerDeploymentError is constructed once
with an AggregateError cause; a pre-dispatch rejection whose cleanup also
failed rejects with an AggregateError of both instead of masking.
(adapter-level only; end-to-end ordering is unchanged).
d1 listrow with uuid ''.a lease denial can no longer be swallowed as absence.
is allocated only when an upload is needed, after the status/version
reads.
Also adds plainWorkerBindingsToProviderShape + assertSupportedPlainWorkerBindings
(reconstruct-and-delegate over the neutral binding shape), adapter and
port-contract test suites, and shared
test fixtures. The port is not exported yet; the fleet-control changeset for
the Worker-native control plane lands with Checkpoint C2.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01At85hntr2BHB6jwKFoFaRD## Summary
Describe the problem, the root cause, and the resulting behavior.
User impact
Explain which package, export, workflow, or deployment is affected and under
what conditions.
Verification
List the exact commands and scenarios used to verify the change.
Checklist
pnpm docs:checkpasses; generateddocs/api/output is not committed.the PR explains why none is required.
execution were reviewed where applicable.
contributor.