Skip to content

release: flowsafe 0.20.0, fleet-control 0.4.0 - #102

Merged
gcharang merged 9 commits into
mainfrom
dev
Aug 25, 2026
Merged

release: flowsafe 0.20.0, fleet-control 0.4.0#102
gcharang merged 9 commits into
mainfrom
dev

Conversation

@gcharang

Copy link
Copy Markdown
Contributor

Promotes dev to main to publish @proofoftech/flowsafe 0.20.0 and @proofoftech/fleet-control 0.4.0 (breakwater stays 0.13.0; agent-starter and showcase are private patch bumps).

@proofoftech/flowsafe 0.20.0 (minor)

  • Deployment-wide execution fence (open / draining / migration-locked / proof-only) with CAS transitions via POST /admin/execution-fence, GET /admin/execution-fence, stable 503 EXECUTION_FENCED refusals, and DO-runner-level enforcement on every execution entry. New flowsafe_execution_fence table (absent row reads open).
  • Owner-bound idempotent start: idempotencyKey on POST /runs, trusted agent-host starts, and streamUntilPersisted() converge on one server-minted run; eight structured reason codes on the keyed-start surface; flowsafe_start_idempotency table and START_IDEMPOTENCY_RETENTION_DAYS.
  • Read-only drain inventory: GET /admin/inventory (work vs standing categories), INVENTORY_DRAIN_PROOF, INVENTORY_UNENUMERABLE, FLOWSAFE_TABLES census.
  • BREAKING: executionFence wiring is required on every leaf host option type; startIdempotency is required on RunRouterOptions / AgentThreadTopologyOptions / StorageInitOptions; provisioning requires an explicit initial fence state; BackgroundTaskHost.manager removed.
  • BEHAVIOR CHANGE: stream routers preserve structured DO refusals (503/409 + reason instead of a bare 500); refusal message text no longer exposes internal plan identifiers.

@proofoftech/fleet-control 0.4.0 (minor)

  • Active-route attestation after every package-owned promotion (attestFleetRecordActiveRoute, attestConvergedActiveRoute, ActiveRouteAttestationError): refuses absent, two-version, or mismatched routing — fail closed (BEHAVIOR CHANGE vs 0.3.x).
  • Lease-held, at-least-once settlement callbacks keyed by fleetSettlementKey (migrateFleet({ settlementFor }), rollbackExternalRelease({ settlement })), FleetRecord.settledSettlementKey persisted (nullable column added on first open).
  • BREAKING: ProvisioningBackend.attestActiveRoute() required; PlainWorkerRouteApi.inspectActiveWorkerRoute() required; seedDeploymentIdentity(…, { initialExecutionFenceState }); provisionDeployment() requires initialExecutionFenceState and is async on every path.
  • Runtime dependency on @proofoftech/flowsafe publishes as the exact matching release (0.20.0).

Checkpoints on dev: 14c5f88 fence · 6099f78 required wiring + lifecycle · 2aedc85 idempotent start · 0882d25 inventory · 7c5b261 attestation + settlement · 1212ba5 changesets/docs/review carry-over · 39154d2 comment wraps · 93f0b51 version packages. Full 21-step CI-mirror gate green on the versioned dev SHA.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SP4MwdaWpy527Xj3M94F3p

gcharang and others added 9 commits August 23, 2026 17:56
…d runner enforcement

Adds the F1 execution fence: a persisted deployment-wide fence
(open / draining / migration-locked / proof-only) in a new
flowsafe_execution_fence D1 table with CAS transitions and proof-run
binding, admin control routes on the maintenance-admin trust boundary,
and fail-closed enforcement at every execution entry - run start and
resume (runtime chokepoints plus pre-write DO route checks), schedule
authoring and the pre-claim tick read, signal ingestion with a
wake-to-persist degrade while draining, notification dispatch,
approval decide ahead of the decision CAS, goal mutations, the
background-task dispatcher (registration split from fence-gated
dispatch startup so nothing claims under a lock; in-flight bodies
suspend and resume after reopen), and the suspension-deadline alarm
lane (uncharged, watchdog cadence).

Seeding writes an explicit fence row with a required, defaultless
initial state, healed on every protocol invocation: threaded through
the deployment-identity protocol, seedDeploymentIdentity, the
flowsafe-provision CLI, and fleet-control's provisioning surface and
backend contract (kept out of the deployment spec digest). Refusals
carry the stable EXECUTION_FENCED reason code end to end, including
through the agent-host and stream routers. The workerd spike proves
draining and locked refusals, decide-not-committed, kill/restart
persistence, and reopen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… harden the fenced task-host lifecycle

The fence option becomes a required ExecutionFenceWiring
(store | 'none') on all eight leaf option types, resolved through one
readExecutionFence authority, so an unwired surface is a compile
error instead of a silently open one; every in-repo host
(agent-starter including its conformance artifacts, spike, host-kit
composition) now wires a real store. BackgroundTaskHost.manager is
private behind a narrow read surface plus the fence-gated enqueue, so
no path registers an ungated executor. The fenced boot lifecycle is
hardened: boot and dispatch attempts are recorded synchronously
(closing a shutdown race during the fence read and a double
start/init interleaving between request and alarm boots), and the
post-reopen resume sweep converges - boot-paced, alarm-budgeted,
paged with restart-on-progress, per-row failure handling for core's
concurrency-limit throws.

Also: the admin credential gate is one shared function across the
maintenance and fence routes; raw fence constants are single-homed on
the deployment-identity-protocol subpath; fleet-control's
seedDeploymentIdentity takes an options object and provisionDeployment
validates the initial fence state at entry; missing-fence-table
detection walks error causes; objective clear is exempt from the
draining gate like schedule pause/delete; unreadable-fence refusals
are pinned to never leak storage error text through the agent-host
and stream routers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and the proof-only producer

Adds the start-reservation primitive: a host-supplied idempotency key
is reserved in a new flowsafe_start_idempotency table (INSERT OR
IGNORE + read-back, owner checked before target so a non-owner learns
nothing) and claimed through a single conditional UPDATE that is the
cross-isolate serializer - the claim lands before any start executes,
so a retry can replay but never re-run. Replay resolves through five
states with distinct refusal codes: the persisted run is returned
as-is (reconciling, never re-filing, approvals), an unclaimed
reservation is re-claimed onto the same server-minted run id, an
in-flight start answers 503 PENDING via liveness probes on the owning
objects (no timers), a claimed-but-absent run answers 409 UNRESOLVABLE
with investigate-first guidance, and a settled-then-purged key answers
409 ALREADY_SETTLED inside a retention horizon floored at the run TTL
(purge pairing plus an orphan sweep guarded by snapshot existence).
Both start surfaces carry the key over the internal channel only;
caller run ids remain refused; the execution fence's proof-only state
gains its producer - a start carrying the fence's proof key is
admitted and bound to exactly one run inside the runtime - and a fence
refusal after the claim rolls the reservation back so a mid-start
transition cannot strand keys. The workerd spike proves same-run
replay across kill+restart, one run under concurrent same-key starts,
and the proof-only admission end to end.

Also lands the carry-over batch: shared per-binding fence-store
factory, the last unswept approval-service leaf, a compile-time wiring
census with negative controls, background-task boot/shutdown lifecycle
fixes with a persistent sweep cursor, root-cause-only missing-table
detection, the WeakMap test seam, and the admin gate's explicit
delegation flag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…leteness guarantee

Adds GET /admin/inventory behind the shared admin credential gate: seven
work categories that must reach empty for a drain proof (nonterminal
runs with owner annotation, waiting approvals, deferred schedule
dispatches, due notifications, nonterminal background tasks with a
fence-suspended sub-count, unsettled resource owners, open start
reservations) and two standing categories reported for reconciliation
(schedules, signal subscriptions). Read-onlyness is a property of the
type - the database seam exposes no write method - and every category
predicate derives from the production reader or purge that owns the
same notion, with the terminal-run SQL shared between the retention
purge and the inventory so the code that acts and the code that
certifies cannot disagree. Completeness is maintained by census, not
care: a table registry maps every flowsafe-owned table to a category or
a justified exclusion, four census tests fail CI on any drift
(uninventoried tables, phantom tables, restated names, declared-table
vs issued-SQL mismatches), and genuinely invisible state is declared -
the per-run recovery-journal crash window with its bound, and persisted
idle signals, which the fence's own degrade rule deliberately carries
across a migration for the new deployment to wake. The drain-proof
contract ships as a typed constant in the index response: results are a
lower bound, the proof is empty work categories across two consecutive
sweeps at least one alarm cadence apart, and empty is reachable only
from draining. Keyset pagination never skips or repeats, malformed
cursors are refused rather than silently restarting a sweep, and the
owner-annotation lookup is chunked under D1's 100-bound-parameter cap.
An ownership-ordering pin proves in-flight starts stay visible: the
reservation lands before the engine's first snapshot and settles only
after a summary persists, so no instant exists where both categories
read empty.

Also: an execution-entry matrix test drives thirteen fence entries
across all four states with a source census over all sixteen admission
call sites; the wiring census extends to the start-reservation options
(exposing and fixing two live defects - the starter's run object built
without a reservation store, and thread objects reserving keys they
could never settle); the spike proves exactly-once with durable
execution counters; principal predicates move to an import-free leaf;
a shared bounded cause-chain walker fixes a cyclic-error fail-open;
and reservation requests take unknown input validated at the boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…inside the lease

ProvisioningBackend gains a required attestActiveRoute (BREAKING): the
provider's own truth about which release is receiving traffic, read-only and
lease-free. Plain backend: deployments.list -> exactly one version at 100% ->
versions.get digest binding (two rate-coordinated reads, no CLI). Workers for
Platforms backend: host routing -> routed script -> dispatch inspect.
attestConvergedActiveRoute bounds KV lag with two independent bounds (60s /
nine retries) and retries refusals; attestFleetRecordActiveRoute is the host
read (cache it). Attestation runs unconditionally on all five promote paths;
provision's ready-commit now commits the ATTESTED version.

Settlement: migrateFleet({ settlementFor }) and rollbackExternalRelease({
settlement }) call the host inside the lease, after attest + match and before
the settling put. settle() is at-least-once keyed by settlementKey; the record
persists settledSettlementKey (generalized nullable-column migration) so
routine converges skip settle but still attest. `prior` is defined per entry
(activeRelease on rollback). Branch A skip-when-settled stays write-free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131gd19xxXseRqsuNAjpUC4
…0.20 / fleet-control 0.4

Changesets (flowsafe minor, fleet-control minor) naming every consumer-visible
break: required ExecutionFenceWiring on the twelve leaf option types, required
initial fence state on seeding/CLI/provisioning, BackgroundTaskHost.manager
removed, structured 5xx refusals passed through by the agent-host and stream
routers, required startIdempotency wiring, ProvisioningBackend.attestActiveRoute
and PlainWorkerRouteApi.inspectActiveWorkerRoute required, seedDeploymentIdentity
options object, async provisionDeployment committing the attested version, and
fail-closed post-promote attestation on every promote path.

Docs: execution fence, idempotent start (all seven codes), drain inventory,
control-plane routes, verified fence postcondition, attestation read budget
(two reads plain / three Workers for Platforms), the settlement contract as
the code guarantees it (at-least-once keyed, no package timeout, lease renews
while settle() runs), the deliberate contract deviations, threat-model note,
api-reference fleet-control section, do-runner navigation.

Carry-over batch: fence-error author census + existsSync-checked delegated
pointers; concrete 60s journal cadence; EXECUTION_FENCE_SUSPEND_KEY single-homed
in do-runner (background-tasks re-exports); due-notification predicate hoisted
into an import-free do-runner leaf shared with notification storage; milestone
tokens scrubbed from every file this feature touched; isMissingReservationTable
made file-local; showcase uses executionFenceFor; derived-chunk assertion in the
200-row inventory pin; seven-code taxonomy documented with the guard's scope;
packed probes for every new flowsafe and fleet-control export; PlainWorkerRouteApi
and PlainWorkerCustomDomain moved to types.ts with the client implementing the
interface; typed nullable-column migration list; convergence message-shape
tests; settlement-key provenance and lease-renewal docs corrected; PENDING_
ARTIFACT_VERSION at the provision sites; platform-only lost-write fixture pins
its subphase; positive-control script surfaces spawn errors instead of a JSON
parse error.

Review-fix cycle: the control-plane route docs state both credential
rules (fence and inventory never delegate to fleet capabilities); the
alreadySettled JSDoc describes what the code computes (true only when a
prior settling write recorded the key; false in the lost-write re-fire
window); the drain docs name the enqueue admission under draining and
replace the false lower-bound claim with point-in-time semantics; README and
fleet-control.md sections re-homed under the right headings; the refusal
guard gains a membership test; census walker deduplicated with per-site
anchors and an alias-import rejection; scrub-broken comments repaired. Second cycle: DrainProofContract's
reading field named for what it is (point-in-time, bidirectional) with the
route handler and spike wording aligned; the two 400-class refusals gain
reason codes (INVALID_EXECUTION_FENCE_REQUEST, INVALID_START_IDEMPOTENCY_
REQUEST); the census rejects qualified construction, subclassing and
aliases and scans .tsx; the changeset names the RunRouterStartIdempotency
arm rather than RunRouterOptions; config-table credential split; post-lock
re-sweep guidance covers lock-parked work; observed-state propagation
pinned. Third cycle: the keyed-start docs count eight reason codes and
name the guard's seven-member union distinctly; the fence changeset
claims INVALID_EXECUTION_FENCE_REQUEST only for invalid state or proof-key
values; the run-owner recovery cadence is pinned by test to its constant;
the packed probe covers drainProof.reading; the census rejects qualified
inheritance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131gd19xxXseRqsuNAjpUC4
…n scrub

Comment-only. Rejoins the sentence split across lines in
background-tasks/host.ts (alarm-wake paragraph) and the orphaned
"`at` still" line in approval-api/service.ts sweepSLA. No code tokens
change; lint and the flowsafe suite (2665) pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SP4MwdaWpy527Xj3M94F3p
@cloudflare-workers-and-pages

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 93f0b51 Aug 25 2026, 07:06 PM

@gcharang
gcharang merged commit 3e74ef1 into main Aug 25, 2026
4 of 6 checks passed
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