Skip to content

refactor(fleet-control): isolate the Wrangler CLI behind a PlainWorke… - #104

Open
gcharang wants to merge 164 commits into
mainfrom
dev
Open

gcharang wants to merge 164 commits into
mainfrom
dev

Conversation

@gcharang

Copy link
Copy Markdown
Contributor

…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## 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

  • The change is scoped to the stated problem and follows existing patterns.
  • Tests cover the changed behavior and the relevant failure paths.
  • Public behavior, setup, configuration, and exports are documented.
  • pnpm docs:check passes; generated docs/api/ output is not committed.
  • A changeset is included for a user-visible published-package change, or
    the PR explains why none is required.
  • Security, deployment isolation, capability minting, retention, and unattended
    execution were reviewed where applicable.
  • Any AI-assisted code or prose has been reviewed and verified by the
    contributor.

gcharang and others added 30 commits August 26, 2026 06:38
…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.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 12, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
anchorage-showcase-single-tenant 7a7838e Sep 16 2026, 05:28 AM

gcharang and others added 29 commits September 12, 2026 23:36
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant