diff --git a/.changeset/bounded-attachment-scans.md b/.changeset/bounded-attachment-scans.md deleted file mode 100644 index 6f9abc94..00000000 --- a/.changeset/bounded-attachment-scans.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Harden account-wide D1 and R2 attachment scans with a request-bounded, page-independent resumable engine. Rechecked inventory drift, malformed provider metadata, non-string or repeated dispatch cursors, and page or item overflows now fail closed instead of allowing an incomplete absence proof. diff --git a/.changeset/bounded-cleanup-receipts.md b/.changeset/bounded-cleanup-receipts.md deleted file mode 100644 index 78611630..00000000 --- a/.changeset/bounded-cleanup-receipts.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -Add token-driven bounded no-export cleanup with durable operation-keyed terminal receipts. `advanceCleanupDeployment()` performs at most one bounded scan chunk or one action group per call; the terminal call persists an immutable receipt, releases the deployment's ownership claims, and deletes the fleet row in one D1 batch. Receipts survive same-key reprovisioning and force decommission; read them with `readCleanupReceipt()` and garbage-collect them explicitly with `pruneCleanupReceipts()` (database-time cutoff, stable order, limit 1..1,000). `cleanupDeploymentArtifacts()` and the default failed-provision rollback drain this engine on capable stacks. - -- **BEHAVIOR CHANGE:** No-export cleanup is narrowed to deployments that provably never authorized a candidate invocation. New records persist an invocation-authority carrier on their first durable write, and every candidate-invoking dispatch (external candidate upload, first maintenance request, version override, promotion) commits an authorization timestamp durably before the provider call. Authorized rows, legacy carrier-less rows at `application-resources-deployed` through `maintenance-armed`, and rows with external staging evidence now refuse toward export-backed decommissioning; trusted plain deployments keep no-export cleanup through `worker-deployed`. -- **BEHAVIOR CHANGE:** Workers for Platforms and external-artifact deployments refuse no-export cleanup as an untrusted data binding and route to export-backed decommissioning. -- **BEHAVIOR CHANGE:** A failed provision whose rollback admitted the bounded engine is durably `cleanup-advancing`. `provisionDeployment()` refuses to resume it with a fixed redirect to cleanup; complete the cleanup (receipt) and reprovision fresh. Previously the row kept its provisioning phase and could be retried as provisioning. -- **BEHAVIOR CHANGE:** External-candidate and WFP failed-provision rollback no longer tears the deployment down. The engine refuses before any mutation, the row keeps its phase and stays provisioning-retryable, and teardown routes to export-backed decommissioning. -- **BEHAVIOR CHANGE:** Cleanup completion releases the deployment's ownership claims; decommission claim behavior is unchanged. Force decommission releases current claims on capable stores, refuses during an active bounded cleanup, and on legacy lease implementations without `deleteReleasingClaims` deletes the row and leaves claims for later reconciliation. Force does not delete the ordinary Worker script or application R2, so do not reprovision the same names until residual physical resources are confirmed removed; provisioning fails closed on ownership mismatch. -- **BEHAVIOR CHANGE:** `auditFleetDrift()` treats a deployment under active bounded cleanup as its own reconciliation authority: no expectation-based, orphan, or record-level findings (including `incomplete-provisioning`) while the cleanup intent is active. - -Add `ProvisionDeploymentOptions.failureCleanup: 'drain' | 'bounded'` (default `'drain'`); with `'bounded'` the rollback performs at most one bounded advance and surfaces the resumable outcome through the new `ProvisioningError.cleanup` field. diff --git a/.changeset/bounded-decommission.md b/.changeset/bounded-decommission.md deleted file mode 100644 index 06477316..00000000 --- a/.changeset/bounded-decommission.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -Add token-driven bounded normal decommissioning for at-least-once control-plane Worker workflows. Fleet D1 owns scan progress. Each call performs at most one bounded scan chunk; only an exact matching verify may immediately consume that result through its single same-lease resource action. Other calls perform at most one lifecycle or resource action group. - -Persist an immutable database-export receipt authority before the first D1 scan or export. Retries after artifact commit or Fleet state-write loss converge on the same filesystem or R2 receipt; authority changes and byte collisions preserve the committed winner and fail closed. Custom bounded backends must expose the paired receipt authority and export capability. Queue-driven bounded decommissioning requires Workers Paid because its bounded multi-R2 read groups can exceed the Free plan external-subrequest limit. - -Add a root-only bounded backend-switch advance API that uses the same durable token and receipt guarantees. It binds teardown to one immutable switch snapshot and captured entry subphase, advances at most one release, R2 resource, scan chunk, or D1 action group per call, and preserves legacy recovery after a shell-less deployment reaches export authorization. A pending ordinary Worker requires lossless exact-version inspection, authoritative secret-name inventory, and its persisted Durable Object namespace identities. Custom switch providers must expose the bounded scan, receipt, database, residual, delete, and conditional pending-artifact inspection capabilities required by the durable state they resume. diff --git a/.changeset/bounded-fleet-audit.md b/.changeset/bounded-fleet-audit.md deleted file mode 100644 index 3997ea9b..00000000 --- a/.changeset/bounded-fleet-audit.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -Add a bounded, resumable fleet drift audit API with a durable, provider-neutral operation store. `advanceFleetAudit()` performs at most one bounded stage chunk per call — one global-stage slice of up to `maxItemsPerCall` items (1..2,000, default 500), or exactly one Fleet record's inspection and re-arm — against a `FleetOperationStore`; `D1FleetOperationStore` implements that port over the existing Fleet D1 binding with account-and-kind-scoped leases, lease-fenced guarded batches, and audit generation pinning. Call `start` with an operation id, the audited records, and `staleAfterMs`, then re-enqueue only the pending token each call returns. Start requires at most 10,000 records whose canonical bytes total at most 16 MiB, with each record within the 96 KiB staged-row byte bound and the per-record structure bounds: plain JSON data (no `undefined`-valued properties, dates, class instances, or cycles) within depth 64, 8,192 nodes, and 4 KiB per string value or object key. Every record must satisfy the deployment identifier grammar, and an explicit generation must be a positive safe integer. Every such refusal has a fixed message and precedes every durable effect. The `staleAfterMs` and operation-id refusals also occur before the lease; after the lease row is written, the foreign-kind, no-finalized-generation, and `auditClock`-sample refusals write nothing else. These accepted inputs are intentionally narrower than `auditFleetDrift()`, which does not require that identifier grammar, an explicit generation, or the bounded path's row and structure bounds. Read the findings back page by page with `readFleetAuditFindingsPage()` once the operation is terminal. Each page comes back in ordinal order whatever order the store's page arrived in and carries `nextAfterOrdinal`, the cursor to pass back as `afterOrdinal` on the next call — absent only on an empty page, which is legal only when `done` is set. The reader verifies the page rather than trusting it: a page that is empty while unfinished, or that is not the contiguous ordinal run following the cursor, refuses with `fleet operation state is malformed`. `abandonFleetAuditOperation()` unblocks a stuck running operation and releases any pin an already-terminal one still holds. - -- `auditFleetDrift()` keeps its exact signature, refusal message, finding vocabulary, finding order, provider interaction order, return value, and stop behavior. It now drains the same decomposed stages in memory, and a frozen golden baseline (findings and the full store/backend/resolver call log) pins all of that. -- **HARDENING:** the bounded engine never persists the raw diagnostic bytes a one-shot audit composes call-locally. The three resolvers, the inspection, the re-arm, and the segmented multi-duty `maintenance-stale` composition durably record a fixed template alone; any finding detail, composed by the engine or passed through from the pinned inventory generation, that fails a non-throwing credential-substring and control-byte gate persists a fixed withheld-detail fallback instead of aborting the operation. -- An audit start pins exactly one finalized `@proofoftech/fleet-control` R3 inventory generation and keeps it through completion, so a finding page stays interpretable against the exact generation it was computed from; only explicit result garbage collection, terminal failure, or abandonment releases it. A replayed start never re-resolves "latest": it reuses the persisted generation. -- Every finding or fact must fit the staged-row envelope: a 16 KiB JSON-serialized payload, 4 KiB per string, and the codec's depth and node bounds. The coordinator detects an excess before the store sees the row, fails the whole operation with the durable `emission-bound-exceeded` reason, and releases the pin. One record's whole per-call emission set (its findings plus the cross-record ownership facts it newly claims) must also fit inside the one guarded D1 batch its `per-record` call commits — a ceiling of 99 rows: 99 emitted rows plus the one run-record update is exactly the 100-statement budget and is accepted, while 100 or more emitted rows fail. A record whose live inspection alone would emit 100 rows fails with the same reason rather than emitting a partial finding set. -- The bounded path differs from the drain in exactly four classes: every resolver, inspection, and re-arm failure and every multi-duty `maintenance-stale` finding persists one of the six fixed detail-template families where the drain composes the raw diagnostic; any unsafe finding detail, composed or passed through, becomes the fixed withheld-detail fallback; concurrent mutation can cause either a re-arm refusal on a Fleet reread mismatch or inspection-derived findings against later provider truth, with the bounded path's typically older snapshot making both more likely; and `emission-bound-exceeded` (from the staged-row envelope or the 99-row ceiling) and `generation-unavailable` are terminal whole-operation failures with no drain counterpart, while `auditFleetDrift()` completes and returns its full finding array over the identical world and clocks. Every other output is proven byte-for-byte equivalent to the drain under identical frozen worlds and clocks. The four-class claim also assumes `backendFor`, `specFor`, and `maintenanceSecretFor` are functions of record *value*: the bounded path hands them canonical snapshots rebuilt from the staged rows, never the caller's own objects, so an identity- or prototype-keyed resolver diverges from the drain in a fifth way this list does not cover. -- Per-call cost is not free: every advance call that runs a stage chunk re-reads the pinned generation in full and re-pages the accumulated `record` rows; a record-processing `per-record` call additionally re-pages the accumulated `fact` rows. Each stage-running call also structurally re-parses every accumulated `record` row through three plain-data traversals. That three is this coordinator's own ingress only: against the D1 store each of those same rows additionally costs a `JSON.parse` of the stored payload and the staged-row codec's own bounded-plain pass, so the real per-row constant factor is higher than three. `finding` rows are never re-paged. A stale token, a `start`, and a `finalize` call read neither. -- Aggregate cost: one bounded audit spans `1 + records + Σ max(1, ⌈stage_i / maxItemsPerCall⌉)` stage-running calls, for a `maxItemsPerCall` held constant across the operation — the option is per-call, so varying it between calls changes the count: one per-record-to-finalize transition, one processing call per record, and at least one call per global stage. The sum runs over the eleven global stages rather than over distinct sources: `deployment-gaps`, `namespace-expectations`, and `r2-expected` each chunk the audited-record array independently, so that one array is walked by three separate stage runs. Every such call re-reads O(G) generation rows and re-pages and structurally re-parses O(R) accumulated `record` rows. In the records-dominated case, this is O(records) full generation re-reads, O(records²/1,000) accumulated-row page reads, O(records²) billed rows read, and O(records²) structural `FleetRecord` re-parses at three plain-data traversals each, the dominant CPU term. This checkpoint's in-memory suite measured roughly 0.25 ms per record-row re-parse and roughly 0.5 s for one `per-record` call over 1,001 accumulated rows. Multiplying the first figure by that row count accounts for about half the second; the remainder is the call's fixed cost: the pinned-generation re-read, both row pagings, and the record's own provider step. Read the per-row rate as the 0.25-to-0.5 ms band those two figures bracket rather than as a single constant, and as an order of magnitude from in-memory fakes rather than a production measurement. A late per-record call at the 10,000-record ceiling therefore spends seconds of isolate CPU re-parsing before its provider work. The per-call guarantee covers bounded provider work and bounded emission, not bounded CPU or bounded rows read. See the fleet control guide for the full envelope. - -The operation store rejects invalid row-page selectors before schema work. Invalid public selectors use input-specific errors; malformed durable records retain `FleetOperationStateError`. - -`stageRows` rejects conflicting immutable staged payloads within its current batch. `commitProgress` refuses conflicting immutable payloads and missing item-update targets without advancing progress or retaining sibling mutations from that batch. Exact restaging remains idempotent. Replaying an uncertain commit requires the same intended run record and row payloads; composing a new transition requires reading persisted progress first. Database errors propagate to the trusted caller. Lease expiry can still leave earlier staged rows while the progress update refuses, so immutable retry payloads remain a caller obligation. - -`failOperation` accepts at most one item update. Terminal transitions refuse operation IDs belonging to another operation kind. Row-watermark refusals preserve sibling rows, and noncontiguous caller inserts below a claimed watermark refuse before SQL. See `FleetOperationLease` for the operation-store contract. - -The new `readFleetAuditFindingsPage()` resolves to the exported `FleetAuditFindingsPage` type, a `done`-discriminated result: `{findings, done: true, nextAfterOrdinal?}` or `{findings, done: false, nextAfterOrdinal}`. No existing public export changes shape, and the Worker subpath is unchanged. diff --git a/.changeset/bounded-fleet-inventory.md b/.changeset/bounded-fleet-inventory.md deleted file mode 100644 index c7b11c9d..00000000 --- a/.changeset/bounded-fleet-inventory.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -Add a bounded, resumable account inventory API with durable generations. `advanceFleetInventory()` performs at most one provider stage chunk per call against a `FleetInventoryRunStore`; `D1FleetInventoryRunStore` implements that port over the existing Fleet D1 binding with operation-keyed runs, lease-fenced guarded batches, generation pinning, and bounded garbage collection. Build the provider seam with `cloudflareFleetInventoryContext(client)`, call `start` with an operation id, then re-enqueue only the pending token each call returns. The final call returns a `FleetInventoryGenerationRef`; read the rows back as today's `FleetResourceInventory` with `readFleetInventoryGeneration()`. Budgets are caller-supplied and validated: `maxProviderRequests` 9..1,000 and `maxStagedRowsPerChunk` 1..2,000 (default 500). - -Starts reject malformed identity and inconsistent options before claiming durable run state. Chunk commits retain the operation's account, physical generation and options. Conflicting immutable row or fact payloads roll back sibling writes; duplicate keys refuse before SQL. Exact replay compares the intended run record as well as staged bytes, so a failed run at the same revision cannot be returned as a successful chunk. Database errors propagate, and callers must retain immutable payloads across uncertain responses and lease-expiry retries. - -Cross-account operation-ID collisions roll back the losing head claim and generation allocation. Exact failure and finalized continuation retries repair interrupted head cleanup while preserving newer operations and historical pin requirements. Future tokens refuse before fallback repair. Pruning retains active terminal generations until head cleanup finishes. Pin admission verifies the finalized physical target in its INSERT, preventing orphan pins when reclamation wins a concurrent call. Pin admission also checks retained row/fact manifests, so partial reclamation cannot create a pin over missing data. Finalization and pinning require dense row ordinals. - -- `collectFleetInventory()` keeps its exact signature, refusal message, provider encounter order, finding vocabulary, finding order, and result bytes. It now drains the same engine in memory, and a frozen golden baseline pins all of that. The one exception is the scale limit below. -- **BEHAVIOR CHANGE (scale limit):** `collectFleetInventory()` is now subject to the same `maxProviderRequests` bound as a bounded run, capped at 1,000 per stage chunk, and six stages carry no resumption cursor so they must finish in one chunk. An account whose largest such stage needs more than 1,000 provider operations — in practice roughly 1,000 prefix-matching plain Workers, which `route-claims` reaches first — now rejects with `fleet inventory stage '' cannot complete one chunk within its provider request budget` instead of returning an inventory, where the previous single-pass enumeration completed under the 10,000-item collection bound. Nothing is written and no partial result is returned. There is deliberately no unbounded mode; narrow `scriptNamePrefix` to split such an account. See the fleet control guide for the stage list and the arithmetic. -- Its options parameter gains the exported alias `CollectFleetInventoryOptions`. The shape is identical, so this is not a break. -- **HARDENING:** the two durable finding details that previously interpolated a provider error string now store the fixed templates `registered script '' could not be inspected` and `plain Worker '' could not be inventoried`. The transient text stays call-local, so `collectFleetInventory()` still returns today's exact bytes while a persisted row carries no provider text. -- **HARDENING:** a raw host-routing KV key name that is over-length or credential-shaped refuses the run; one that is merely unprintable or base64-shaped yields a `malformed-script-registration` finding naming the key by its listing ordinal rather than by its bytes. That finding is positionally attributable but does not carry the offending name. -- Only a finalized generation is readable. The latest finalized generation reads without a pin; every older generation must be pinned before it is read, because pruning removes finalized-or-failed, non-latest, unpinned generations. -- A generation is a point-in-time-per-stage snapshot, not a globally consistent one. A resource that changes between stages is recorded exactly as the single-call enumeration surfaces it. -- Bounded cursor history beyond the last committed offset is deliberately out of scope: a single-pass resumable scan needs only the last offset. -- **INTERNAL:** `inventoryBoundExceeded` is consolidated into `cloudflare-client-config.ts` and shared by both provider modules. The refusal messages are byte-identical. - -No existing public export changes shape, and the Worker subpath is unchanged. diff --git a/.changeset/bounded-fleet-migration.md b/.changeset/bounded-fleet-migration.md deleted file mode 100644 index b6f681f8..00000000 --- a/.changeset/bounded-fleet-migration.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@proofoftech/fleet-control": minor ---- - -Add durable fleet upgrades through `advanceFleetMigration()`, with a frozen target digest and per-item plan, one admission or plan step per call, first-error stopping, item paging and explicit abandonment. The bounded API requires a `FleetOperationStore` alongside the existing deployment store; custom deployment stores can continue using the one-call `migrateFleet()` drain. - -Fleet and provider state remain mutation authority. Continuations re-read that state under per-call deployment leases and recover cursor-loss windows through the shared migration engine. Public documentation describes accepted inter-call races, strict fresh-admission limits after Durable Object tag movement, at-least-once settlement, operation-store convergence residuals, and the repeated resolver/provider and whole-item-read costs. One step is not a fixed provider-request, CPU or row-read budget. - -The existing drain retains its recorded ordering and behavior apart from the separately documented correction to target application-binding validation during plain Worker upgrades. diff --git a/.changeset/breakwater-conformance-calls-unobserved.md b/.changeset/breakwater-conformance-calls-unobserved.md deleted file mode 100644 index 2868334d..00000000 --- a/.changeset/breakwater-conformance-calls-unobserved.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@proofoftech/breakwater': minor ---- - -Say what the connector conformance harness established about an entry point a case deleted outright. -An `INSTRUMENTATION_REPLACED` finding for a property that is gone at verification now reads -`globalThis.fetch descriptor differs from the one the harness installed: absent property; calls made -after the replacement were not observed`. The finding stopped at `absent property` before, which is -also how the harness reports an entry point that still holds the installed trap, though a read after -the deletion resolves through the prototype chain or to `undefined` and never to the trap. A finding -for a replacement that is itself an accessor still says the harness did not check, and one for a data -property that still holds the trap still carries no such clause. diff --git a/.changeset/breakwater-conformance-foreign-values-and-late-escapes.md b/.changeset/breakwater-conformance-foreign-values-and-late-escapes.md deleted file mode 100644 index 306ab7a9..00000000 --- a/.changeset/breakwater-conformance-foreign-values-and-late-escapes.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -'@proofoftech/breakwater': patch ---- - -Build the connector conformance report even when a case throws a value that cannot say what it is. -The harness classified a thrown value with four separate `instanceof` reads; a value whose -`getPrototypeOf` is a trap made the classification itself throw, so the run rejected with the trap's -error and produced no report. One `classifyInvocationError` call now answers `boundary`, `policy`, -`refusal` or `foreign` once, and a value it cannot read is `foreign`: the report carries the named -`CASE_INVOCATION_FAILED` finding the contract requires. `createConnector` guards every read it makes -of the connector's own thrown value — seven guards over eight reads, and none of them in -`invokeConnector` — so that value reaches the harness intact and the execute error is still audited. - -Say what each diagnostic observed, and no more. A refusal on the supplied base transport reports the -host the registered egress declaration does not cover, instead of asserting a bypass of -`runtime.fetch` that did not happen — the connector used the transport the harness gave it. A -`POLICIES_NOT_WIRED` `audit` finding for a case whose invocation failed says the invocation ended -before a witness could be recorded, instead of asserting the subject reached its gate boundary. An -`INSTRUMENTATION_REPLACED` finding for a replacement that is itself an accessor says the harness did -not check whether later calls reached the trap, instead of leaving a silence that read as though it -had. `SUBJECT_UNREGISTERED` names this copy of `createConnector()`, which is what a connector from a -second copy of the package fails against. A thrown function is described as `a function` rather than -by its source text. An escape whose address cannot be parsed — including one the URL global's -disappearance makes unparseable — is recorded with a null host instead of raising inside the -connector's own call. - -Record an escape observed after its case settled. A connector that keeps the supplied base transport -or a trap reference alive past its case used to append to the case result's `escapes` array with no -finding beside it, and — once the report was built — beside a `conformant: true` the caller was -already holding. A case result now carries a snapshot of its own escapes, taken where those escapes -become findings; a later observation is a run-level `NETWORK_IO_OUTSIDE_RUNTIME_FETCH` finding whose -reason names the settled case and carries no `case`. The report snapshots `findings` and `cases` and -computes `conformant` from the snapshot, and an escape observed after that is dropped. - -`CONFORMANCE_LIMIT` points at -[Conformance limits](https://github.com/ProofOfTechOrg/anchorage/blob/main/packages/breakwater/CONNECTORS.md#conformance-limits) -in the connector authoring guide, which states what a settled case's retained transport or trap -reaches, what a read of the restored global after the run closes bypasses, and what a timed-out -case's abandoned work never reaches. diff --git a/.changeset/breakwater-conformance-late-escape-case-field.md b/.changeset/breakwater-conformance-late-escape-case-field.md deleted file mode 100644 index 04c11c6b..00000000 --- a/.changeset/breakwater-conformance-late-escape-case-field.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@proofoftech/breakwater': patch ---- - -Name the settled case on a late conformance finding as a field, not only in prose. -`ConnectorConformanceFinding` gains an optional `observedAfterCase`, which carries the name of the -case whose abandoned work produced a run-level finding — an escape or a finding that arrived after -that case settled. Such a finding still carries no `case`, because the case's own result is already -on the report, and its `reason` is unchanged, so a host that reads the sentence keeps reading it. A -finding the probe phase produces carries no `observedAfterCase`: no case had settled. diff --git a/.changeset/breakwater-conformance-limit-pointer.md b/.changeset/breakwater-conformance-limit-pointer.md deleted file mode 100644 index 1abfa431..00000000 --- a/.changeset/breakwater-conformance-limit-pointer.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@proofoftech/breakwater': patch ---- - -Point the connector conformance report's `limit` at the documented channel list. The field carried a -paragraph naming the channels a run does not observe; it is now one sentence naming the permanent -URL of -[Conformance limits](https://github.com/ProofOfTechOrg/anchorage/blob/main/packages/breakwater/CONNECTORS.md#conformance-limits), -a section of the connector authoring guide that ships with the package and describes channels a run -observes and channels it does not. A host that displays or stores `report.limit` sees the shorter -text, and a host that asserts on its content asserts on the new sentence. -`CONFORMANCE_LIMIT` is not exported from the package entry points, so consumers read it only as -`report.limit`. Nothing about what the harness traps, records, snapshots or drops changes. diff --git a/.changeset/complete-attachment-binding-identities.md b/.changeset/complete-attachment-binding-identities.md deleted file mode 100644 index a1dfe9ce..00000000 --- a/.changeset/complete-attachment-binding-identities.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Refuse incomplete Worker binding metadata during D1 and R2 attachment scans so cleanup cannot mistake an unreadable attachment for an unused resource. diff --git a/.changeset/connector-conformance-harness.md b/.changeset/connector-conformance-harness.md deleted file mode 100644 index 75e4a635..00000000 --- a/.changeset/connector-conformance-harness.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -'@proofoftech/breakwater': minor ---- - -Export `assertConnectorConformance`, a case-scoped harness a consumer runs in its own suite. For each -supplied case it builds the connector through a factory, hands it a trusted inert base transport and -an audit logger to wire, and replaces `globalThis.fetch` and any supplied transport entry point with -a trap that records the attempt and refuses. A case that reaches one fails with the named result -`NETWORK_IO_OUTSIDE_RUNTIME_FETCH`, including when the connector catches the refusal. The supplied -base transport refuses any host the registered manifest does not declare, so calling it around the -guard fails the same way. - -On an absent or configurable entry point, the harness installs an accessor whose getter returns the trap. An assignment to that instrumented entry point is recorded when it happens as `INSTRUMENTATION_REPLACED` and is not applied; the trap stays in place. A writable non-configurable data property uses assignment installation, which offers no defence against assignments during execution. A redefinition or assignment still in place when the case settles or times out, or when the probe factory returns, is reported as `INSTRUMENTATION_REPLACED`. A redefinition or deletion the case itself reverses before it settles is one of the channels listed under [Conformance limits](https://github.com/ProofOfTechOrg/anchorage/blob/main/packages/breakwater/CONNECTORS.md#conformance-limits). When verification fails, the case is ineligible for the outcome check and proves `nothing`. An `INSTRUMENTATION_UNSUPPORTED` or `INSTRUMENTATION_REPLACED` finding during probe construction refuses the run. - -A case reports `CASE_INVOCATION_FAILED` when invocation setup fails or the invocation itself fails; the reason names the error's constructor, uses `unknown` when that name is unavailable, unreadable, or not a plain identifier of at most 64 characters, or describes a thrown non-Error value by type, without the message or value. During invocation, policy denials, boundary errors, and harness refusals retain their existing classifications, and a setup failure carrying a harness refusal is reported as the escape behind it rather than as an invocation failure. - -The harness requires `TextEncoder` with its other host globals before a run starts, reports an entry point replaced before an install failure unwinds it, and names the phase — a settled case, or the probe factory — when an attempt or a finding arrives after it. - -Instrumentation is restored after the case settles, after a throw, after a partial install, and after -a per-case timeout; a restoration that cannot be proved fails the run and ends it, rather than -running later cases over a property the harness knows it could not put back. Entry points are -instrumented **one at a time, in order, `globalThis.fetch` first**, and every property the harness -writes is checked before it is written and verified after — both its own descriptor and the value a -caller actually reads. An accessor, an inherited accessor, a locked property descriptor, a target -that accepts a write and ignores it, and a target whose descriptor holds the replacement while the -property still resolves to the original are all refused rather than skipped — for a supplied entry -point the case is refused; for `globalThis.fetch` the whole run is when the refusal comes before any -case runs, and the case is when a case's own work makes the global uninstrumentable mid-run. Either -way the finding names the descriptor shape it found, and a failure at any entry point restores the -whole rollback stack: a (d) install or (e) verification failure includes the failing entry point; an -(a) validation or descriptor-read failure precedes capture and push, so the stack holds only the -entry points attempted before it. A restore the target silently ignores -is reported as a failed restoration. Two entry points naming one -property is refused before any case runs, and so are two cases sharing a name, two entry points -sharing a label, and an overlapping or nested run. A factory that throws is reported as a -finding, not an opaque rejection. Each case is bounded by `timeoutMs`, 2000 ms by default; a case -that times out ends the run and no further run is accepted in that isolate, because work abandoned by -one case would otherwise be recorded against a later one. Put a test that expects a timeout last in -its file, or in a file of its own. - -The harness certifies only a connector declaring `egressEnforcement: 'enforced'`. An empty case set -is reported as an empty case set, and — for a connector that declares `egress` — a set whose cases -never reach the supplied transport is reported as no transport evidence; neither is a pass, and a run -with no cases raises one finding, not both. A case whose expectations depend on policies the factory -did not wire is reported as a wiring failure naming the member, alongside the escape record, which is -kept. Every report states the finite-case limit: - -> conformance covers only the supplied cases, in this isolate, for the duration of each case; channels a run observes, and channels it does not, are described under Conformance limits in the CONNECTORS.md that ships with this package, at https://github.com/ProofOfTechOrg/anchorage/blob/main/packages/breakwater/CONNECTORS.md#conformance-limits - -The harness itself uses no Node built-ins, no `vm`, and no filesystem, and runs on workerd. The -barrel it ships from imports `@mastra/core/tools`, whose bundled chunks statically import Node -built-ins under unprefixed specifiers, so a Worker importing the barrel needs Node.js compatibility -enabled — the `nodejs_compat` compatibility flag, or a `compatibility_date` recent enough that your -Workers runtime turns it on by default; check your runtime's compatibility-date documentation for -that date. The flag is a necessary condition, not a sufficient one: the workerd build behind the -runtime also has to load the barrel. Loading it crashed `workerd@1.20260730.1` during module -resolution in this package's own workerd test pool, and `workerd@1.20260903.1` loads it, which is -the build this repository pins through a pnpm override on `miniflare@5.20260730.0-alpha`. diff --git a/.changeset/connector-egress-posture.md b/.changeset/connector-egress-posture.md deleted file mode 100644 index 6cfbfac6..00000000 --- a/.changeset/connector-egress-posture.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'@proofoftech/breakwater': minor ---- - -Add an egress posture to the connector manifest. `permissions.egressEnforcement` declares whether -the declared hosts bind the connector's actual traffic — `'enforced'` when every **HTTP** request -leaves through `ConnectorRuntime.fetch`, `'declaration-only'` when a vendor SDK or child process -carries its own transport. It is a claim about HTTP traffic, not about platform bindings (D1, KV, R2, -service bindings), which the guard never sees, so a connector that issues no HTTP request at all is -`'enforced'`. An omitted field resolves to `'declaration-only'`. - -`connectorEgressPosture(tool)` reads the resolved posture beside `connectorManifest(tool)`, and every -connector audit event carries it as `detail.egressEnforcement`, so an operator can answer from the -log which connectors declare enforcement. The logged value is the author's declaration resolved -against the omitted-field default, not an observation of the connector's traffic. - -`policies.requireEgressEnforcement` refuses, at construction, a connector whose posture is not -`'enforced'`; the single-tenant preset accepts and pins the same flag. Construction also rejects an -`egressEnforcement` value outside the two literals. The Agent CLI adapters declare -`'declaration-only'`, matching their documented child-process boundary — so a `createConnector()` -call whose `policies` set `requireEgressEnforcement` cannot register an Agent CLI adapter, by -design. Put the child behind a host network boundary, and pass that flag on the calls whose -connectors declare `'enforced'`. - -Migration: `connectorManifest(tool)` returns `egressEnforcement` for every connector built by an -Agent CLI factory, which the adapters declare as `'declaration-only'`. An assertion comparing a -returned manifest for exact equality with a literal fails until that key is added to the expected -object. diff --git a/.changeset/direct-cloudflare-workers.md b/.changeset/direct-cloudflare-workers.md deleted file mode 100644 index b6f7d863..00000000 --- a/.changeset/direct-cloudflare-workers.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -Add `CloudflareApiPlainWorkerBackend`, a direct Cloudflare API backend for platform-authored ordinary Workers. Its adapter classifies each mutation's dispatch under the operation's own execution context, so a queued mutation's pre-dispatch failure rejects instead of resolving `{ status: 'failed' }`. Construct it with a plain-only `CloudflareProvisioningClient`. Configure that client with a shared rate coordinator and a durable export store. The existing Wrangler and Workers for Platforms backends keep their public provisioning contracts. - -Expose the configured provider request timeout through the public `CloudflareProvisioningClient.requestTimeoutMs` getter. - -Harden provider behavior across the built-in backends: - -- **BEHAVIOR CHANGE:** Disable Cloudflare SDK logging even when `CLOUDFLARE_LOG` requests verbose output. -- **BEHAVIOR CHANGE:** Bound every paginated Cloudflare inventory and fail instead of truncating an over-bound result. -- **BEHAVIOR CHANGE:** Disable SDK retries for Worker upload and deployment, D1 creation and query, and each D1 export poll. This includes the D1 query path shared by both provider backends. -- **BEHAVIOR CHANGE:** Redact signed URLs, provider response details, headers, and original causes from database export failures. -- **BEHAVIOR CHANGE:** Replace uploaded secret plaintext in **ordinary-Worker** provider error messages, and in the cause chain of transport failures behind them, before returning a failed mutation outcome. -- **BEHAVIOR CHANGE:** Surface a lost lease before database or R2 reconciliation in all three affected create paths. -- **BEHAVIOR CHANGE:** Reject `PlainWorkerBackend.identityCaller` values that are not printable, single-line ASCII tokens from 1 through 128 characters. -- **BEHAVIOR CHANGE:** Refuse a reconciled Worker upload whose workers.dev or preview-URL state does not match the intent instead of accepting it by tag rediscovery. -- **BEHAVIOR CHANGE:** Queued provider mutations assert their own lease. Under concurrency pressure a queued mutation previously ran under the preceding operation's execution context. - -When upgrading, construct direct ordinary-Worker clients with `plane: 'plain-worker'` and no `dispatchNamespace`. Keep `dispatchNamespace` on Workers for Platforms clients, provide one quota coordinator across every replica sharing a provider token, and ensure the token can complete the documented attachment scans before destructive teardown. diff --git a/.changeset/durable-export-parent-directories.md b/.changeset/durable-export-parent-directories.md deleted file mode 100644 index fadbeb22..00000000 --- a/.changeset/durable-export-parent-directories.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Synchronize the parent directories of filesystem exports before returning a durable location, including when an earlier attempt left a newly created directory behind. - -Cancel abandoned export streams when filesystem setup or a supplied export store fails. diff --git a/.changeset/fleet-control-credentialed-redirect-refusals.md b/.changeset/fleet-control-credentialed-redirect-refusals.md deleted file mode 100644 index 74d46b71..00000000 --- a/.changeset/fleet-control-credentialed-redirect-refusals.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Refuse redirects on the three credentialed provider transports. `CloudflareProvisioningClient`, `PlainWorkerBackend`, and `WorkersForPlatformsBackend` force `redirect: 'manual'` after a caller's `init`, so a bearer credential is not carried to an address the control plane did not choose. The raw dispatch script page refuses a redirect status where it reads the raw response, and the Workers for Platforms maintenance transport refuses one in its wrapper, because its callers read only the signed receipt header. Each throws `CredentialedRedirectRefusedError` on a 301, 302, 303, 307, or 308 and cancels the unconsumed body; the message names the operation and the status, never the address. An SDK-routed redirect surfaces as an `APIError` with the original status, not retried on the status alone and not classified transient. The Cloudflare SDK obeys an `x-should-retry: true` response header ahead of the status, so a redirect carrying that header is retried; each attempt goes through the same forced `redirect: 'manual'`, so no attempt follows the redirect. - -A host whose injected fetch follows redirects itself is unaffected: supply that fetch only from trusted control-plane code. diff --git a/.changeset/fleet-control-live-provisioning.md b/.changeset/fleet-control-live-provisioning.md deleted file mode 100644 index 8a2f92dd..00000000 --- a/.changeset/fleet-control-live-provisioning.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Accept successful Cloudflare list responses with `errors: null` or `result_info: null`. Retry plain-Worker maintenance requests answered by a workers.dev platform 404 or 500 text page, for up to 60 seconds at 2-second intervals by default. Both `PlainWorkerBackendOptions` and `CloudflareApiPlainWorkerBackendOptions` expose `maintenanceRouteReadyTimeoutMs` and `maintenanceRouteReadyIntervalMs` to configure these bounds, and `wait` to delay reconciled mutation retries. Host Workers that fetch tenant maintenance origins on the same account's workers.dev subdomain require the `global_fetch_strictly_public` compatibility flag. Verify account-owned API tokens through the account endpoint, with fallback to user-token verification when the account endpoint is unavailable. - -Record first-page R2 access refusals (403, error 10003) for non-default jurisdictions in the required `FleetResourceInventory.unavailableR2Jurisdictions` array, preserving failures for default, later pages, and other errors. - -Export `FleetInventoryR2Jurisdiction` from the root and Cloudflare control-plane entries, and expose the root's reachable `D1FleetInventoryRunStoreOptions`, `D1FleetOperationStoreOptions`, `FleetInventoryDeploymentFactKind`, `FleetInventoryFailureReason`, `FleetInventoryGeneration`, `FleetInventoryRowKind`, `FleetInventoryRunProgress`, `FleetInventoryRunRecord`, `FleetInventoryStage`, `FleetInventoryStagedFact`, `FleetInventoryStagedRow`, `FleetInventoryStageInput`, `FleetInventoryStageResult`, `OrdinaryWorkerDeploymentVersion`, `PreparedOrdinaryWorkerDeploymentVersions`, and `PreparedOrdinaryWorkerUpload` types. - -Wait within the maintenance readiness deadline when a plain-Worker maintenance response attests the previous deployment specification, including version-override requests. - -Retry transient ordinary Worker provisioning failures up to three total backend attempts when provider reconciliation confirms that the upload, database or bucket creation, or deployment change did not take effect. diff --git a/.changeset/fleet-control-multipart-worker-uploads.md b/.changeset/fleet-control-multipart-worker-uploads.md deleted file mode 100644 index 49f3c240..00000000 --- a/.changeset/fleet-control-multipart-worker-uploads.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Encode Worker upload metadata as JSON, name module parts by their declared paths, and let fetch set the multipart boundary for ordinary, control, dispatch and state Worker uploads. diff --git a/.changeset/fleet-control-native-fetch-receivers.md b/.changeset/fleet-control-native-fetch-receivers.md deleted file mode 100644 index 4af7587c..00000000 --- a/.changeset/fleet-control-native-fetch-receivers.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Call supplied fetch functions without a client or backend receiver so native Worker fetch works in the Cloudflare client and maintenance backends. diff --git a/.changeset/fleet-control-prior-release-inspection.md b/.changeset/fleet-control-prior-release-inspection.md deleted file mode 100644 index fdd527c6..00000000 --- a/.changeset/fleet-control-prior-release-inspection.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Allow ordinary Worker migration to inspect a previous release with different application variables, service bindings or queue bindings. Versions claiming the requested specification digest retain the binding checks. diff --git a/.changeset/fleet-control-worker-export-redirects.md b/.changeset/fleet-control-worker-export-redirects.md deleted file mode 100644 index 91919739..00000000 --- a/.changeset/fleet-control-worker-export-redirects.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Use manual redirect handling for D1 export downloads so Worker fetch accepts the request and redirects remain rejected before export data is stored. diff --git a/.changeset/fleet-control-worker-library.md b/.changeset/fleet-control-worker-library.md deleted file mode 100644 index 570f4f22..00000000 --- a/.changeset/fleet-control-worker-library.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -Expose `@proofoftech/fleet-control/cloudflare-control-plane` for a dedicated trusted Cloudflare Worker. The factory composes direct Cloudflare operations with durable Fleet D1 state, shared D1 quota coordination, and private R2 database exports. Continuation tokens resume bounded lifecycle operations against stored authority. - -Install the required `@cloudflare/workers-types >=5.20260730.1 <6` peer when typechecking consumers. Keep the Cloudflare token and control-plane bindings outside tenant-serving Workers, and authenticate and authorize operations before calling the library. - -The packed verification uses an unminified namespace-import Worker with raw and gzip regression budgets of 4,128,768 and 589,824 bytes. Those budgets apply 25% headroom to the initial packed measurements, rounded up to 64 KiB. They are repository regression limits; size inventory and audit workloads for the documented Worker resource envelope. diff --git a/.changeset/flowsafe-inventory-not-due-notifications.md b/.changeset/flowsafe-inventory-not-due-notifications.md deleted file mode 100644 index 4d9c7fea..00000000 --- a/.changeset/flowsafe-inventory-not-due-notifications.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@proofoftech/flowsafe': minor ---- - -The pending-notifications inventory lists pending agent-inbox notifications whether due, scheduled for later, or carrying no due timestamp. Its count includes these rows, its notDue total identifies those not yet due, and entry details expose summaryAt alongside deliverAt. - -Pending notifications keep the deployment drain proof open until delivered, discarded, or deleted. Rescheduling alone does not clear the proof; a pending notification with neither timestamp requires a direct write or deletion because dispatch and retention leave it pending. diff --git a/.changeset/flowsafe-notification-delivery-bound.md b/.changeset/flowsafe-notification-delivery-bound.md deleted file mode 100644 index ef9a5225..00000000 --- a/.changeset/flowsafe-notification-delivery-bound.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@proofoftech/flowsafe': minor ---- - -Bound notification delivery to ten failed attempts by default, configurable through `maxDeliveryAttempts` on tick and thread-route factories. Discard exhausted rows before another send, retain their error/count receipts, and remove them from due scans while preserving retry delays below the bound. - -Require conditional failure writes through `NotificationDeliveryStorage` for dispatch. `D1NotificationsStorage` implements the atomic operation; custom stores must adopt it. Preserve newer summary, delivery and content-denial receipts after response loss, and count each local outcome once without inferring unconfirmed success. Ordinary Core storage still serves notification ingestion; the `@mastra/core` patch is required for either path. Require a custom `SignalDatabase` or `ScheduleDatabase` `batch()` to resolve elements carrying `results`, as a real `D1Result` does; `SnapshotDatabase` and `InitialAdmissionDatabase` `batch()` declare the same element. - -Compare notification dates chronologically before bounded selection and retention, including expanded years and numeric offsets. Direct database writers must use ISO dates or explicitly zoned ISO date-times; conditional failure writes reject raw timestamp text outside that grammar, and neither due selection nor retention matches such a value. - -Ship the `@mastra/core@1.53.0` patch under `patches/`. Application roots must apply it for own-property-safe summary source counts and source delivery policies; `@mastra/core@1.53.0` otherwise reads inherited `Object.prototype` members at both sites (mastra-ai/mastra#23693, mastra-ai/mastra#23694). The getting-started guide documents the pnpm, npm and Yarn routes. Flowsafe refuses to construct a notification dispatch tick that does delivery work, and refuses notification ingestion and dispatch requests, when the installed `@mastra/core` lacks the patch. diff --git a/.changeset/flowsafe-run-router-internal-errors.md b/.changeset/flowsafe-run-router-internal-errors.md deleted file mode 100644 index 9d655186..00000000 --- a/.changeset/flowsafe-run-router-internal-errors.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/flowsafe': patch ---- - -Return a generic internal-error response for unexpected run-router failures while retaining the original error in server diagnostics. Preserve typed refusal status, message and reason contracts. Contain diagnostic conversion and logging failures so they cannot prevent the generic HTTP response. diff --git a/.changeset/forward-classified-core-members.md b/.changeset/forward-classified-core-members.md deleted file mode 100644 index 37ddd957..00000000 --- a/.changeset/forward-classified-core-members.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@proofoftech/flowsafe': patch ---- - -Refuse two `Agent` entry points that `@mastra/core` releases newer than the declared peer expose. `FlowsafeDurableAgent.listActiveThreadRuns()` throws instead of returning the run, thread and resource ids of every thread on the pubsub instance with a run in flight, which Core scopes by neither principal nor agent. `FlowsafeDurableAgent.__setThreadRuntimeAgent()` throws instead of installing another agent as the target every thread-runtime path resolves through — `subscribeToThread()`, `claimThreadOwnership()`, `sendMessage()`, `queueMessage()`, `sendStateSignal()`, `sendNotificationSignal()` and `sendSignal()` — where one call would move every run those paths start, and `subscribeToThread()`'s replay target with them, onto an agent that carries none of the wrapper's guards. Both refusals carry the reason table's message; an installed 1.53.0 exposes neither member on Core, so no call that resolves today changes, and a caller that feature-detects either member now finds it on the wrapper and takes the refusal where the call was a `TypeError` before. - -The durable-agent surface inventory now holds against the pinned peer and against newer 1.x releases together. diff --git a/.changeset/maintenance-purge-cursor-contract.md b/.changeset/maintenance-purge-cursor-contract.md deleted file mode 100644 index c2654b8b..00000000 --- a/.changeset/maintenance-purge-cursor-contract.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/flowsafe': minor ---- - -Require the retention cursor seam on the purge duty. `runMaintenanceDuty('purge', env, context)` takes the new `MaintenancePurgeDutyContext`, whose `advanceRetentionCursor` is required, matching the `advanceCursor` the run-retention purge itself requires; the other duties keep the optional `MaintenanceDutyContext`. `FlowsafeWorker.runMaintenanceDuty` declares that split as two overloads — `'purge'` with a required `MaintenancePurgeDutyContext`, and `Exclude` with the optional `MaintenanceDutyContext` — so a caller holding a union-typed `duty` narrows it to one branch before calling: a single call spanning the whole union matches neither overload and no longer compiles. A purge invocation whose context omits the callback is refused under a `config-error` naming `maintenance.purge.advanceRetentionCursor` before any purge surface runs, rather than purging the remaining surfaces and reporting a `retention-purge` failure. diff --git a/.changeset/migration-completion-and-abort.md b/.changeset/migration-completion-and-abort.md deleted file mode 100644 index c699d54e..00000000 --- a/.changeset/migration-completion-and-abort.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -`advanceFleetMigration()` accepts an `AbortSignal`, matching the audit advance, and a completion callback. `signal` is call-local and never persisted, and cancellation is cooperative: the composition checks it before dispatch and before each item advance, and work already started runs to completion. A cancellation this composition observes leaves the operation and its items exactly as they were and a later continue resumes; a signal the host also wires into its own provider or store surfaces inside the item step and durably fails the item like any other step failure. - -`onComplete` is delivered at least once for a finalized operation, after the finalization is durable and before the call that observed it returns, so the host deduplicates on `operationId`. `AdvanceFleetMigrationOptions.onComplete` names the calls that deliver it. The alternative that fires only on the running-to-finalized transition loses the notification when the process dies between the durable finalize and the callback. A rejection propagates to the caller and leaves the durable finalization intact. - -`CloudflareAdvanceFleetMigrationOptions` carries both, forwarding `signal` unbound and `onComplete` bound to the caller's options object. Both additions are optional members, so existing callers are unchanged. diff --git a/.changeset/named-databases.md b/.changeset/named-databases.md deleted file mode 100644 index 4ed973e9..00000000 --- a/.changeset/named-databases.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -`PlainWorkerProvisioningApi.listDatabases` accepts an optional name filter. The direct Cloudflare API adapter forwards it as the D1 list query and the Wrangler adapter filters its parsed inventory locally; `PlainWorkerBackend.findDatabase` passes the deployment's database name and keeps its exact-name comparison and its duplicate-name and missing-UUID refusals. diff --git a/.changeset/neat-workers-share.md b/.changeset/neat-workers-share.md deleted file mode 100644 index f4fabe13..00000000 --- a/.changeset/neat-workers-share.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -Export the shared `PlainWorkerBackend`, its `PlainWorkerProvisioningApi` port, and the port's ordinary-Worker record types. Port adapters must verify database exports independently against the durable store's committed size and digest. `WranglerLoopBackend` now extends this core while retaining the same constructor options and provisioning members. - -Harden ordinary-Worker provisioning and teardown: - -- **BEHAVIOR CHANGE:** Surface a lost external mutation lease as a failure instead of masking it behind a post-dispatch readback. -- Preserve both operation and scratch-cleanup failures without masking either. -- **BEHAVIOR CHANGE:** Refuse D1 bindings and database inventory entries with an empty primary identifier instead of accepting a fallback field or malformed inventory. -- **BEHAVIOR CHANGE:** Reject malformed Worker version inventory that omits an identifier instead of treating the entry as provider absence. -- **BEHAVIOR CHANGE:** Keep lease denials distinct from provider absence during Worker deletion. -- Allocate adapter-owned upload scratch only when an upload is required. -- **BEHAVIOR CHANGE:** Classify post-install scratch-cleanup failures so callers remove only Workers created by the failed attempt. - -Provider-neutral error messages now describe plain-Worker and provider operations instead of Wrangler. Error-message text compatibility is not claimed by this release. - -When upgrading, consumers that matched `deployWorker` rejections by identity or message text should catch `WorkerDeploymentError` and read `createdByAttempt` and `resourceState`. diff --git a/.changeset/notification-tick-capability-at-construction.md b/.changeset/notification-tick-capability-at-construction.md deleted file mode 100644 index 7f4c9971..00000000 --- a/.changeset/notification-tick-capability-at-construction.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@proofoftech/flowsafe': minor ---- - -Capture the conditional-delivery capability when a notification dispatch tick is built, whatever its `limit`. `createNotificationDispatchTick()` reads `storage` and refuses one without `updateNotificationDeliveryIfUnchanged` for every configuration, including `limit: 0`, so the `NotificationDeliveryStorage` requirement no longer depends on the limit. A `limit: 0` tick still resolves `{ due: 0, delivered: 0, failed: 0 }` without reading due rows, calling storage, or needing the `@mastra/core` patch; invalid numeric policy still fails ahead of the capture. - -The notification ingestion route refuses when the installed `@mastra/core` lacks the delivery-policy half of that patch. `createThreadSignalRoutes()` probes `resolveNotificationDeliveryDecision` at the ingestion gate, where delivery runs through `agent.sendNotificationSignal` and reaches the delivery-policy lookup, and answers 502 with the message naming the patch on the server log. The dispatch route carries the synchronous source-key probe its summaries need, and delivers through `agent.sendSignal`, which never reaches that lookup. The probe resolves once per isolate; tick construction keeps its synchronous probe and is unaffected. diff --git a/.changeset/operation-store-port-requirements.md b/.changeset/operation-store-port-requirements.md deleted file mode 100644 index 6708438d..00000000 --- a/.changeset/operation-store-port-requirements.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -State three requirements a `FleetOperationStore` implementation must meet, and hold the shipped D1 store to them. - -- `withAccountOperationLease` requires that the promise it returns settle only after the lease release completes, so a composition that awaits one call holds no lease when the next one takes it. `D1FleetOperationStore` already awaits its release before returning. -- **BEHAVIOR CHANGE:** `readOperationRowsPage` requires a page of at most `limit` rows and serves a `limit` above 1,000 at 1,000, the one documented ceiling and the maximum page `D1FleetOperationStore` already enforced. An over-large `limit` costs the caller the rows beyond the ceiling rather than the read, where the D1 store refused it before. `readFleetAuditFindingsPage` and `readFleetMigrationItemsPage` forward a caller's `limit` unchanged, so they answer the same way against any conforming store. A `limit` that is not an integer of at least 1 is refused. -- **BEHAVIOR CHANGE:** `failOperation` reports an update target missing at its post-batch readback as a conflict, the classification `commitProgress` convergence already uses for a missing row, rather than as a divergence. Divergence keeps its narrower meaning: landed bytes that differ from the intended ones. diff --git a/.changeset/ordinary-worker-subrequest-limits.md b/.changeset/ordinary-worker-subrequest-limits.md deleted file mode 100644 index 2a4a7852..00000000 --- a/.changeset/ordinary-worker-subrequest-limits.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Forward configured `DeploymentSpec.subrequestLimit` values through ordinary-Worker uploads in the direct Cloudflare and Wrangler adapters, including staged versions. The setting was validated and included in the specification digest but omitted from upload requests. It now accompanies `cpuLimitMs`; an omitted setting remains unspecified. - -Review existing ordinary-Worker subrequest settings when upgrading because those configured values now reach the provider. diff --git a/.changeset/plain-worker-ingress-inspection.md b/.changeset/plain-worker-ingress-inspection.md deleted file mode 100644 index f7570756..00000000 --- a/.changeset/plain-worker-ingress-inspection.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -Export `plainWorkerIngressModule` from the root entry for tools that inspect ordinary Worker ingress or calculate the complete upload size. The backend appends this generated module during upload. diff --git a/.changeset/plain-worker-migration-bindings.md b/.changeset/plain-worker-migration-bindings.md deleted file mode 100644 index 6aaa902a..00000000 --- a/.changeset/plain-worker-migration-bindings.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@proofoftech/fleet-control": patch ---- - -Validate plain Worker upgrades against the target application's bindings during candidate inspection, promotion, and settlement. Changing application variables or secrets no longer deploys the new bindings and then rejects them against the previous deployment's binding configuration. diff --git a/.changeset/positive-worker-ingress-evidence.md b/.changeset/positive-worker-ingress-evidence.md deleted file mode 100644 index e5282b68..00000000 --- a/.changeset/positive-worker-ingress-evidence.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Reject incomplete Worker subdomain observations before interpreting ingress as disabled. Require explicit disabled flags for a present Worker through the ordinary and backend-switch proof paths while preserving an authoritatively absent parent. diff --git a/.changeset/provision-output-limit.md b/.changeset/provision-output-limit.md deleted file mode 100644 index 086bc402..00000000 --- a/.changeset/provision-output-limit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/flowsafe': patch ---- - -`flowsafe-provision` sets a 64 MiB `maxBuffer` on the `wrangler d1 execute --json` child process whose output it parses. Node's default is 1 MiB counted across the captured stdout and stderr together, so a response past that was truncated and the run surfaced as `failed to execute Wrangler 4` with an `ENOBUFS` cause instead of the parsed rows. diff --git a/.changeset/public-suspension-deadline-helpers.md b/.changeset/public-suspension-deadline-helpers.md deleted file mode 100644 index 4ba7c4ab..00000000 --- a/.changeset/public-suspension-deadline-helpers.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@proofoftech/flowsafe': minor ---- - -Expose `isArmableSuspensionDeadlineMs` and `suspensionDeadlinesOf` from `do-runner`, together with the `SuspensionDeadlineEntry` and `RejectedSuspensionDeadline` types that projection returns. Add the lightweight `do-runner/constants` entry for deadline values and timeout detection, and `do-runner/testing` for constructing fixtures with the same timeout envelope as the alarm path. - -Reuse the existing arming bounds, derivation and alarm payload factory. The test helper does not authorize a resume or mint an approval grant. diff --git a/.changeset/run-start-request-context.md b/.changeset/run-start-request-context.md deleted file mode 100644 index fba22b9d..00000000 --- a/.changeset/run-start-request-context.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@proofoftech/flowsafe': minor ---- - -Accept optional non-reserved `requestContext` on authenticated run starts and carry it through the protected Durable Object topology into stored application context. Expose the validated value to router and Worker start-policy hooks while preserving shorter hook signatures. - -Reject malformed context and reserved keys with HTTP 400. Verified schedule targets retain precedence, including absent context; provider application values override stored values and trusted capabilities retain their authority. Application context survives resume. Keyed replay validates input and runs host policy again, then preserves the first writer's context without comparing or overwriting it. - -Correct public agent status, stream and ordinary termination lookups to return not found for a coherent snapshot belonging to another agent or thread. Private replay, proof and recovery retain strict failures so a foreign snapshot cannot be treated as absent. diff --git a/.changeset/stable-connector-decisions.md b/.changeset/stable-connector-decisions.md deleted file mode 100644 index ec67da20..00000000 --- a/.changeset/stable-connector-decisions.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@proofoftech/breakwater': minor ---- - -Add stable connector decision codes, canonical policy categories, retryability and safe structured details to authored errors and audit events. Preserve custom policy names and the three-string policy-error constructor. - -Wrap store and evaluator failures with typed errors. Pre-execution store failures retain their original cause at the base connector boundary; Agent CLI adapters preserve the classification without exposing raw causes. Post-effect commit and best-effort release failures retain their existing suppressed disposition and non-retryable audit codes. Direct invocation and validation errors gain stable tags, and egress refusals retain their transport checks. - -Contain audit error-observer failures so they cannot replace completed connector results, release a pending reservation after failed result storage, or replace an original execution error. Rejected observer promises stay isolated too. diff --git a/.changeset/sticky-fence-epochs.md b/.changeset/sticky-fence-epochs.md deleted file mode 100644 index 767f92d8..00000000 --- a/.changeset/sticky-fence-epochs.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -'@proofoftech/flowsafe': minor ---- - -Add versioned execution-fence administration with artifact epochs, a sticky epoch requirement, transition revisions and exact last-command retry receipts. Upgrade supported legacy schemas additively without changing existing state or timestamps. Missing rows in new-format schemas fail closed. Admin responses omit receipts, proof execution identity and tokens; legacy commands remain compatible only while the epoch requirement is optional. - -Activate v2 Runtime generations with independently generated execution tokens and preserved original principal, logical target, caller epoch and agent mode across resume legs. Fenced starts require the actual D1 domain's positive initial-write witness before engine entry, binding the winning reservation and proof in the same admission transaction. Capable D1 without a fence keeps its actual namespace and ordinary persistence options; custom storage explicitly asserts no D1 namespace. Unfenced keyed starts bind their prepared identity before creation and retain uncertain outcomes. - -Persist preparing, prepared and prepared-unfenced journals in both managed hosts. Recover exact owned initial generations through the existing raw-row conditional repair without replaying effects or deleting tokenless snapshots. Require strict terminal reservation settlement before approval, dispatch, owner and lifecycle cleanup, then clear only the matching journal. Cold agent alarms initialize actual wrappers with the verified instance scope. Legacy journals and uncertain unfenced pending/absent outcomes remain unresolved. - -Keep the thread blocking-run check and run-record installation under the same lock. Validate workflow journals against the owning object's address and recheck complete agent journals after recovery waits before releasing reservations. Keyed recovery requires its configured reservation store before bookkeeping, including nonterminal outcomes. - -Share cold agent-wrapper initialization across concurrent requests. Probe the owning execution's liveness before reclaiming an existing reserved key, so a stream awaiting Core cleanup keeps retries pending without stranding the key. Unreadable liveness replies refuse the retry instead of authorizing a claim. - -Require a matching nonpending durable observation before modern start/resume success or the agent persistence acknowledgement. Return `RUN_START_PENDING` for a valid initial generation, preserving journals, watchdogs and pending schedule/deadline budgets. Project root-local summaries from the same selected observation, retaining detailed nested resume preparation and legacy compatibility. - -Preserve valid v1 and absent-provenance ordinary status, resume and lifecycle completion through one authoritative observation. Apply the existing binding, canonical-record and principal checks to legacy status. Legacy terminal cleanup requires no recovery journal and a confirmed raw terminal outcome; it never manufactures generation identity or spends a start key. Normal termination retains canonical agent records until lifecycle completion confirms. - -Create modern-unbound reservations and replace run-only claim, release and settlement with exact observed-row operations. Claim/release stamps advance without serving as generation tokens; only the caller's own valid write result proves a winning claim. Private replay compares the full generation before pending/result classification and preserves the value from its one authoritative read. Alias binding and prepared binding retain their distinct response-loss rules. Remove `claim`, `release`, `settleRun` and `rollbackFencedStart`; custom router wiring must provide the private `persistedStart` callback. - -Guard replay proof nomination with its original proof round/caller epoch, current exact snapshot and bound reservation at the final SQL write. Legacy proof setters cannot overwrite modern identity. Runtime, workflow-host, approval and signal re-entry gates compare complete physical generations and retain their original expectation through relevant waits. Direct approval compositions now pass an explicit trusted workflow namespace. External effects are not transactional with these checks. - -Capture trusted authority before asynchronous work across Worker configuration, protected JSON/header transport and the eighth agent-start authority argument. Keep public bodies and application context from supplying a winning claim. Preserve source-owner versus initiating-principal attribution, exact lifecycle counter exhaustion checks and rejection of sparse economic-operation lists. - -Make workflow retention generation-aware, protect run owners across supported snapshot namespaces, and pair reservation cleanup with the complete bound execution. Preserve reserved owners, uncertain generations and legacy keys that cannot be safely associated. Recheck schema and selected identity at the mutation; keep artifact deletion ahead of D1 cleanup. - -Custom `purgeExpiredWorkflowRuns` callers must now provide transactional `database.batch()` and an `advanceCursor` callback, retain its exported `RunRetentionCursor`, and supply that cursor on the next call. Composed maintenance persists the cursor across alarms and restarts. Finite scan cycles revisit skipped candidates without letting continuous inserts extend the current cycle; unproved D1 outcomes and failed cursor writes do not advance progress. - -Enforce captured caller epochs and semantic fence/schema observations in final D1 schedule mutations. Guard owned deletion participants independently, preserve admitted trigger settlement, and provide fixed pause/resume methods plus guarded no-op observations. Resume rejects a concurrent cron/timezone change. Fenced custom facades require the same-binding `FENCED_SCHEDULE_STORAGE` capability before activation; direct D1 authoring requires `batch()` and refuses omitted epochs once enforcement is active. Publish structured schedule conflict and unknown-outcome errors, retain server-side causes, and contain route audit failures without changing the selected response. - -Apply the shared final schema, singleton and typed semantic fence check to Runtime initial admission. Refuse unreadable authority before snapshot or dependent reservation/proof writes while preserving exact committed-write recovery. diff --git a/.changeset/strict-inventory-absence-proofs.md b/.changeset/strict-inventory-absence-proofs.md deleted file mode 100644 index 633cffbc..00000000 --- a/.changeset/strict-inventory-absence-proofs.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Validate inventory page structure and resource identity before recording absence, removing ingress or verifying secret revocation. Keep SDK pagination and the direct/Wrangler lookup paths consistent when metadata is incomplete. diff --git a/.changeset/strict-signal-ingress-audit.md b/.changeset/strict-signal-ingress-audit.md deleted file mode 100644 index eeea09c5..00000000 --- a/.changeset/strict-signal-ingress-audit.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@proofoftech/flowsafe': minor ---- - -Add optional `SignalRouterOptions.validateThreadTarget` using the existing bound-thread validator contract, with captured actor context before asynchronous validation. Hosts can enforce strict ownership before forwarding. Normalize thread refusals with status 404 and audit the final downstream result. - -Contain audit and diagnostic failures in signal, objective, subscription and webhook routes so they preserve the selected response. Use own-property lookup for signal channels, objective methods and webhook provider configuration. diff --git a/.changeset/tee-branch-cancel.md b/.changeset/tee-branch-cancel.md deleted file mode 100644 index ed08c9f4..00000000 --- a/.changeset/tee-branch-cancel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -`FileSystemDatabaseExportStore.write` no longer awaits the reader's cancel in its failure cleanup. When the body is a `tee()` branch, that cancel settles when the tee source is exhausted or errors, or the other branch is cancelled, so a write refused by the store's own checks held its rejection and its temporary file until then, and held both indefinitely when nothing drove the source. It now rejects with the store's error and removes the file without awaiting the cancel. diff --git a/.changeset/terminal-row-admission.md b/.changeset/terminal-row-admission.md deleted file mode 100644 index 3ca071b1..00000000 --- a/.changeset/terminal-row-admission.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@proofoftech/fleet-control': minor ---- - -Admit a retired terminal `decommissioned` record as an absent prior in `provisionDeployment()`, so a host can reprovision a decommissioned slug without first clearing its ledger row. A stored row qualifies when it carries the record a completed decommission leaves — its `applicationResources` entries `deleted`, its database export location, digest, and byte count recorded, and no pending lifecycle field — and no unfinished decommission or cleanup operation; a row carrying a backend-switch record qualifies only at subphase `decommissioned`, whose teardown committed that same export. The read is normalized once, before the lifecycle guards, the immutable-mapping asserts, the phase refusal, and the reservation-ownership flag that drives the failed-provision unwind. Force-then-provision continues to work unchanged: `forceDecommissionDeployment()` still removes a terminal row, and a provision over the empty key takes the path a provision with no stored row takes, where the durable reservation is written first. - -- **BEHAVIOR CHANGE:** A retired terminal record no longer refuses with `cannot be provisioned from phase 'decommissioned'`. Provisioning 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 the specification retains them, while the database and its provider-minted ID, the seeded deployment identity, and the application R2 resources are new. The artifact version is read from the provider for this deployment rather than carried over, which does not promise a value different from the retired row's. The replacement's export location, digest, and byte count are absent rather than newly minted. A changed specification is accepted, because the immutable-mapping and digest guards read a prior and a retired record is not one; `previousDurableObjectTag` is refused as it is for any other new deployment. -- **BEHAVIOR CHANGE:** `decommissionDeployment()` replays a terminal record's `DatabaseExport` for a late retry while that terminal row is the stored row. Once a re-provision has replaced the row and the replacement reaches `ready`, a same-spec `decommissionDeployment()` call is a new decommission of the replacement and proceeds against it: the one-call facade carries no operation identity that separates a retry from a new request. Read the export from its retained location before reprovisioning the name; carry an in-flight decommission through `advanceDecommissionDeployment()`, whose token carries the operation identity; or confirm the row's deployment identity — its database ID — before issuing a one-call decommission after a re-provision. -- **BEHAVIOR CHANGE:** A terminal row that retains an application resource, whose teardown evidence is incomplete — a force-produced row records no database export — or that carries a backend-switch record in a subphase other than `decommissioned` refuses with a message that names that residue and directs to `forceDecommissionDeployment()` after the residual physical resources are confirmed removed, in place of the generic phase message. An unfinished decommission or cleanup operation refuses through the guard that owns it, as it does from any other lifecycle entry. Every other non-resumable phase keeps the generic refusal. -- **BEHAVIOR CHANGE:** A backend-switch teardown that completes on the legacy path writes its `applicationR2Progress` deletions into the record's top-level `applicationResources`, the projection the bounded path already writes. A row that teardown leaves therefore qualifies for retirement, where before it was refused for the application resources the switch had released. -- **BEHAVIOR CHANGE:** A retired terminal record that persisted `wfpMode: "platform-catalog"` refuses a specification reserving another deployment mode, before the first durable write, with a message naming the persisted mode and the reserved one and directing to `forceDecommissionDeployment()`. The store pins an established catalog mode, so the replacement upsert would otherwise fail on that constraint. -- **BEHAVIOR CHANGE:** Over a retired terminal record, the reserved-name database check runs before the first `lease.put`, so a re-provision that refuses a pre-existing database leaves that record — including its export location, digest, and byte count — byte-identical, and the failed-provision unwind no longer deletes a row the attempt never claimed. A provision with no stored row keeps its original order, where the durable reservation is written first. -- **BEHAVIOR CHANGE:** `auditFleetDrift()` no longer reports `incomplete-provisioning` for a terminal `decommissioned` record. The phase is retained state rather than a phase that advances, so the staleness check misclassified intended retained state as incomplete provisioning. diff --git a/.changeset/wfp-catalog-signed-maintenance.md b/.changeset/wfp-catalog-signed-maintenance.md deleted file mode 100644 index 9ca4f4a7..00000000 --- a/.changeset/wfp-catalog-signed-maintenance.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@proofoftech/fleet-control': patch -'@proofoftech/flowsafe': patch ---- - -Support signed maintenance for platform-authored Workers for Platforms catalogs. Catalog signing profiles can supply the maintenance keys without external-state artifacts. Catalog uploads receive their public verifier and local identity; FlowSafe relays capabilities while retaining the local receipt secret and validates the catalog script and digest before maintenance work. - -Existing catalog artifacts need a rebuilt FlowSafe runtime and explicit maintenance enrollment. The host configures the matching global dispatcher verifier. - -Persist catalog ownership explicitly in Fleet records and preserve it through native D1 migration and export-backed teardown. Catalog cleanup checks its own script and namespace authority. Force re-entry on completed or reserved records uses claim-releasing deletion when the store supports it. - -Preserve the prior mutable Worker schema identity while D1 advances and retain migration authority through compatibility teardown retries. Permit declared catalog binding changes with exact owner and uploaded-target checks. - -Allow ordinary spec-free force recovery after a candidate upload by clearing migration-only scalar fields when teardown begins, while preserving the recorded resource identity. diff --git a/.changeset/worker-native-inventory-hostnames.md b/.changeset/worker-native-inventory-hostnames.md deleted file mode 100644 index c906ea7d..00000000 --- a/.changeset/worker-native-inventory-hostnames.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Use the platform URL hostname parser for inventory finding validation so the ordinary Worker control plane does not require node:url. Preserve international hostname validation, rejected-input behavior and original diagnostic text. - -Use crypto.randomBytes results directly for R2 reservation nonces and deployment-secret encoding, preserving their byte lengths and base64url representation. diff --git a/.changeset/wrangler-export-stream-closure.md b/.changeset/wrangler-export-stream-closure.md deleted file mode 100644 index f74b5874..00000000 --- a/.changeset/wrangler-export-stream-closure.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@proofoftech/fleet-control': patch ---- - -Close staged Wrangler export streams before removing scratch files when a supplied store finishes or fails. diff --git a/packages/agent-starter/CHANGELOG.md b/packages/agent-starter/CHANGELOG.md index f585fa58..8df017a6 100644 --- a/packages/agent-starter/CHANGELOG.md +++ b/packages/agent-starter/CHANGELOG.md @@ -1,5 +1,31 @@ # anchorage-agent-starter +## 0.0.17 + +### Patch Changes + +- Updated dependencies [8fc708a] +- Updated dependencies [0e14950] +- Updated dependencies [7fec4af] +- Updated dependencies [f48a525] +- Updated dependencies [8b7f087] +- Updated dependencies [7eb450f] +- Updated dependencies [c8c5039] +- Updated dependencies [4cb59a1] +- Updated dependencies [a027f13] +- Updated dependencies [f05e598] +- Updated dependencies [6f54bc6] +- Updated dependencies [6f54bc6] +- Updated dependencies [a086f24] +- Updated dependencies [647092e] +- Updated dependencies [e79b92a] +- Updated dependencies [37c0fee] +- Updated dependencies [6bd8bfc] +- Updated dependencies [323c2ce] +- Updated dependencies [8c43533] + - @proofoftech/breakwater@0.14.0 + - @proofoftech/flowsafe@0.21.0 + ## 0.0.16 ### Patch Changes diff --git a/packages/agent-starter/package.json b/packages/agent-starter/package.json index f5a2679d..288a45ec 100644 --- a/packages/agent-starter/package.json +++ b/packages/agent-starter/package.json @@ -1,6 +1,6 @@ { "name": "anchorage-agent-starter", - "version": "0.0.16", + "version": "0.0.17", "private": true, "description": "Production-shaped Cloudflare Workers starter for durable, approval-gated Anchorage agents", "type": "module", diff --git a/packages/breakwater/CHANGELOG.md b/packages/breakwater/CHANGELOG.md index 8a64756e..e4c8aee6 100644 --- a/packages/breakwater/CHANGELOG.md +++ b/packages/breakwater/CHANGELOG.md @@ -1,5 +1,157 @@ # @proofoftech/breakwater +## 0.14.0 + +### Minor Changes + +- 8fc708a: Say what the connector conformance harness established about an entry point a case deleted outright. + An `INSTRUMENTATION_REPLACED` finding for a property that is gone at verification now reads + `globalThis.fetch descriptor differs from the one the harness installed: absent property; calls made +after the replacement were not observed`. The finding stopped at `absent property` before, which is + also how the harness reports an entry point that still holds the installed trap, though a read after + the deletion resolves through the prototype chain or to `undefined` and never to the trap. A finding + for a replacement that is itself an accessor still says the harness did not check, and one for a data + property that still holds the trap still carries no such clause. +- 8b7f087: Export `assertConnectorConformance`, a case-scoped harness a consumer runs in its own suite. For each + supplied case it builds the connector through a factory, hands it a trusted inert base transport and + an audit logger to wire, and replaces `globalThis.fetch` and any supplied transport entry point with + a trap that records the attempt and refuses. A case that reaches one fails with the named result + `NETWORK_IO_OUTSIDE_RUNTIME_FETCH`, including when the connector catches the refusal. The supplied + base transport refuses any host the registered manifest does not declare, so calling it around the + guard fails the same way. + + On an absent or configurable entry point, the harness installs an accessor whose getter returns the trap. An assignment to that instrumented entry point is recorded when it happens as `INSTRUMENTATION_REPLACED` and is not applied; the trap stays in place. A writable non-configurable data property uses assignment installation, which offers no defence against assignments during execution. A redefinition or assignment still in place when the case settles or times out, or when the probe factory returns, is reported as `INSTRUMENTATION_REPLACED`. A redefinition or deletion the case itself reverses before it settles is one of the channels listed under [Conformance limits](https://github.com/ProofOfTechOrg/anchorage/blob/main/packages/breakwater/CONNECTORS.md#conformance-limits). When verification fails, the case is ineligible for the outcome check and proves `nothing`. An `INSTRUMENTATION_UNSUPPORTED` or `INSTRUMENTATION_REPLACED` finding during probe construction refuses the run. + + A case reports `CASE_INVOCATION_FAILED` when invocation setup fails or the invocation itself fails; the reason names the error's constructor, uses `unknown` when that name is unavailable, unreadable, or not a plain identifier of at most 64 characters, or describes a thrown non-Error value by type, without the message or value. During invocation, policy denials, boundary errors, and harness refusals retain their existing classifications, and a setup failure carrying a harness refusal is reported as the escape behind it rather than as an invocation failure. + + The harness requires `TextEncoder` with its other host globals before a run starts, reports an entry point replaced before an install failure unwinds it, and names the phase — a settled case, or the probe factory — when an attempt or a finding arrives after it. + + Instrumentation is restored after the case settles, after a throw, after a partial install, and after + a per-case timeout; a restoration that cannot be proved fails the run and ends it, rather than + running later cases over a property the harness knows it could not put back. Entry points are + instrumented **one at a time, in order, `globalThis.fetch` first**, and every property the harness + writes is checked before it is written and verified after — both its own descriptor and the value a + caller actually reads. An accessor, an inherited accessor, a locked property descriptor, a target + that accepts a write and ignores it, and a target whose descriptor holds the replacement while the + property still resolves to the original are all refused rather than skipped — for a supplied entry + point the case is refused; for `globalThis.fetch` the whole run is when the refusal comes before any + case runs, and the case is when a case's own work makes the global uninstrumentable mid-run. Either + way the finding names the descriptor shape it found, and a failure at any entry point restores the + whole rollback stack: a (d) install or (e) verification failure includes the failing entry point; an + (a) validation or descriptor-read failure precedes capture and push, so the stack holds only the + entry points attempted before it. A restore the target silently ignores + is reported as a failed restoration. Two entry points naming one + property is refused before any case runs, and so are two cases sharing a name, two entry points + sharing a label, and an overlapping or nested run. A factory that throws is reported as a + finding, not an opaque rejection. Each case is bounded by `timeoutMs`, 2000 ms by default; a case + that times out ends the run and no further run is accepted in that isolate, because work abandoned by + one case would otherwise be recorded against a later one. Put a test that expects a timeout last in + its file, or in a file of its own. + + The harness certifies only a connector declaring `egressEnforcement: 'enforced'`. An empty case set + is reported as an empty case set, and — for a connector that declares `egress` — a set whose cases + never reach the supplied transport is reported as no transport evidence; neither is a pass, and a run + with no cases raises one finding, not both. A case whose expectations depend on policies the factory + did not wire is reported as a wiring failure naming the member, alongside the escape record, which is + kept. Every report states the finite-case limit: + + > conformance covers only the supplied cases, in this isolate, for the duration of each case; channels a run observes, and channels it does not, are described under Conformance limits in the CONNECTORS.md that ships with this package, at https://github.com/ProofOfTechOrg/anchorage/blob/main/packages/breakwater/CONNECTORS.md#conformance-limits + + The harness itself uses no Node built-ins, no `vm`, and no filesystem, and runs on workerd. The + barrel it ships from imports `@mastra/core/tools`, whose bundled chunks statically import Node + built-ins under unprefixed specifiers, so a Worker importing the barrel needs Node.js compatibility + enabled — the `nodejs_compat` compatibility flag, or a `compatibility_date` recent enough that your + Workers runtime turns it on by default; check your runtime's compatibility-date documentation for + that date. The flag is a necessary condition, not a sufficient one: the workerd build behind the + runtime also has to load the barrel. Loading it crashed `workerd@1.20260730.1` during module + resolution in this package's own workerd test pool, and `workerd@1.20260903.1` loads it, which is + the build this repository pins through a pnpm override on `miniflare@5.20260730.0-alpha`. + +- 7eb450f: Add an egress posture to the connector manifest. `permissions.egressEnforcement` declares whether + the declared hosts bind the connector's actual traffic — `'enforced'` when every **HTTP** request + leaves through `ConnectorRuntime.fetch`, `'declaration-only'` when a vendor SDK or child process + carries its own transport. It is a claim about HTTP traffic, not about platform bindings (D1, KV, R2, + service bindings), which the guard never sees, so a connector that issues no HTTP request at all is + `'enforced'`. An omitted field resolves to `'declaration-only'`. + + `connectorEgressPosture(tool)` reads the resolved posture beside `connectorManifest(tool)`, and every + connector audit event carries it as `detail.egressEnforcement`, so an operator can answer from the + log which connectors declare enforcement. The logged value is the author's declaration resolved + against the omitted-field default, not an observation of the connector's traffic. + + `policies.requireEgressEnforcement` refuses, at construction, a connector whose posture is not + `'enforced'`; the single-tenant preset accepts and pins the same flag. Construction also rejects an + `egressEnforcement` value outside the two literals. The Agent CLI adapters declare + `'declaration-only'`, matching their documented child-process boundary — so a `createConnector()` + call whose `policies` set `requireEgressEnforcement` cannot register an Agent CLI adapter, by + design. Put the child behind a host network boundary, and pass that flag on the calls whose + connectors declare `'enforced'`. + + Migration: `connectorManifest(tool)` returns `egressEnforcement` for every connector built by an + Agent CLI factory, which the adapters declare as `'declaration-only'`. An assertion comparing a + returned manifest for exact equality with a literal fails until that key is added to the expected + object. + +- 37c0fee: Add stable connector decision codes, canonical policy categories, retryability and safe structured details to authored errors and audit events. Preserve custom policy names and the three-string policy-error constructor. + + Wrap store and evaluator failures with typed errors. Pre-execution store failures retain their original cause at the base connector boundary; Agent CLI adapters preserve the classification without exposing raw causes. Post-effect commit and best-effort release failures retain their existing suppressed disposition and non-retryable audit codes. Direct invocation and validation errors gain stable tags, and egress refusals retain their transport checks. + + Contain audit error-observer failures so they cannot replace completed connector results, release a pending reservation after failed result storage, or replace an original execution error. Rejected observer promises stay isolated too. + +### Patch Changes + +- 0e14950: Build the connector conformance report even when a case throws a value that cannot say what it is. + The harness classified a thrown value with four separate `instanceof` reads; a value whose + `getPrototypeOf` is a trap made the classification itself throw, so the run rejected with the trap's + error and produced no report. One `classifyInvocationError` call now answers `boundary`, `policy`, + `refusal` or `foreign` once, and a value it cannot read is `foreign`: the report carries the named + `CASE_INVOCATION_FAILED` finding the contract requires. `createConnector` guards every read it makes + of the connector's own thrown value — seven guards over eight reads, and none of them in + `invokeConnector` — so that value reaches the harness intact and the execute error is still audited. + + Say what each diagnostic observed, and no more. A refusal on the supplied base transport reports the + host the registered egress declaration does not cover, instead of asserting a bypass of + `runtime.fetch` that did not happen — the connector used the transport the harness gave it. A + `POLICIES_NOT_WIRED` `audit` finding for a case whose invocation failed says the invocation ended + before a witness could be recorded, instead of asserting the subject reached its gate boundary. An + `INSTRUMENTATION_REPLACED` finding for a replacement that is itself an accessor says the harness did + not check whether later calls reached the trap, instead of leaving a silence that read as though it + had. `SUBJECT_UNREGISTERED` names this copy of `createConnector()`, which is what a connector from a + second copy of the package fails against. A thrown function is described as `a function` rather than + by its source text. An escape whose address cannot be parsed — including one the URL global's + disappearance makes unparseable — is recorded with a null host instead of raising inside the + connector's own call. + + Record an escape observed after its case settled. A connector that keeps the supplied base transport + or a trap reference alive past its case used to append to the case result's `escapes` array with no + finding beside it, and — once the report was built — beside a `conformant: true` the caller was + already holding. A case result now carries a snapshot of its own escapes, taken where those escapes + become findings; a later observation is a run-level `NETWORK_IO_OUTSIDE_RUNTIME_FETCH` finding whose + reason names the settled case and carries no `case`. The report snapshots `findings` and `cases` and + computes `conformant` from the snapshot, and an escape observed after that is dropped. + + `CONFORMANCE_LIMIT` points at + [Conformance limits](https://github.com/ProofOfTechOrg/anchorage/blob/main/packages/breakwater/CONNECTORS.md#conformance-limits) + in the connector authoring guide, which states what a settled case's retained transport or trap + reaches, what a read of the restored global after the run closes bypasses, and what a timed-out + case's abandoned work never reaches. + +- 7fec4af: Name the settled case on a late conformance finding as a field, not only in prose. + `ConnectorConformanceFinding` gains an optional `observedAfterCase`, which carries the name of the + case whose abandoned work produced a run-level finding — an escape or a finding that arrived after + that case settled. Such a finding still carries no `case`, because the case's own result is already + on the report, and its `reason` is unchanged, so a host that reads the sentence keeps reading it. A + finding the probe phase produces carries no `observedAfterCase`: no case had settled. +- f48a525: Point the connector conformance report's `limit` at the documented channel list. The field carried a + paragraph naming the channels a run does not observe; it is now one sentence naming the permanent + URL of + [Conformance limits](https://github.com/ProofOfTechOrg/anchorage/blob/main/packages/breakwater/CONNECTORS.md#conformance-limits), + a section of the connector authoring guide that ships with the package and describes channels a run + observes and channels it does not. A host that displays or stores `report.limit` sees the shorter + text, and a host that asserts on its content asserts on the new sentence. + `CONFORMANCE_LIMIT` is not exported from the package entry points, so consumers read it only as + `report.limit`. Nothing about what the harness traps, records, snapshots or drops changes. + ## 0.13.0 ### Minor Changes diff --git a/packages/breakwater/package.json b/packages/breakwater/package.json index eeecb3be..42baf2f2 100644 --- a/packages/breakwater/package.json +++ b/packages/breakwater/package.json @@ -1,6 +1,6 @@ { "name": "@proofoftech/breakwater", - "version": "0.13.0", + "version": "0.14.0", "publishConfig": { "access": "public" }, diff --git a/packages/fleet-control/CHANGELOG.md b/packages/fleet-control/CHANGELOG.md index 4d8dd6e4..0e70725b 100644 --- a/packages/fleet-control/CHANGELOG.md +++ b/packages/fleet-control/CHANGELOG.md @@ -1,5 +1,195 @@ # @proofoftech/fleet-control +## 0.5.0 + +### Minor Changes + +- 8ae28bf: Add token-driven bounded no-export cleanup with durable operation-keyed terminal receipts. `advanceCleanupDeployment()` performs at most one bounded scan chunk or one action group per call; the terminal call persists an immutable receipt, releases the deployment's ownership claims, and deletes the fleet row in one D1 batch. Receipts survive same-key reprovisioning and force decommission; read them with `readCleanupReceipt()` and garbage-collect them explicitly with `pruneCleanupReceipts()` (database-time cutoff, stable order, limit 1..1,000). `cleanupDeploymentArtifacts()` and the default failed-provision rollback drain this engine on capable stacks. + + - **BEHAVIOR CHANGE:** No-export cleanup is narrowed to deployments that provably never authorized a candidate invocation. New records persist an invocation-authority carrier on their first durable write, and every candidate-invoking dispatch (external candidate upload, first maintenance request, version override, promotion) commits an authorization timestamp durably before the provider call. Authorized rows, legacy carrier-less rows at `application-resources-deployed` through `maintenance-armed`, and rows with external staging evidence now refuse toward export-backed decommissioning; trusted plain deployments keep no-export cleanup through `worker-deployed`. + - **BEHAVIOR CHANGE:** Workers for Platforms and external-artifact deployments refuse no-export cleanup as an untrusted data binding and route to export-backed decommissioning. + - **BEHAVIOR CHANGE:** A failed provision whose rollback admitted the bounded engine is durably `cleanup-advancing`. `provisionDeployment()` refuses to resume it with a fixed redirect to cleanup; complete the cleanup (receipt) and reprovision fresh. Previously the row kept its provisioning phase and could be retried as provisioning. + - **BEHAVIOR CHANGE:** External-candidate and WFP failed-provision rollback no longer tears the deployment down. The engine refuses before any mutation, the row keeps its phase and stays provisioning-retryable, and teardown routes to export-backed decommissioning. + - **BEHAVIOR CHANGE:** Cleanup completion releases the deployment's ownership claims; decommission claim behavior is unchanged. Force decommission releases current claims on capable stores, refuses during an active bounded cleanup, and on legacy lease implementations without `deleteReleasingClaims` deletes the row and leaves claims for later reconciliation. Force does not delete the ordinary Worker script or application R2, so do not reprovision the same names until residual physical resources are confirmed removed; provisioning fails closed on ownership mismatch. + - **BEHAVIOR CHANGE:** `auditFleetDrift()` treats a deployment under active bounded cleanup as its own reconciliation authority: no expectation-based, orphan, or record-level findings (including `incomplete-provisioning`) while the cleanup intent is active. + + Add `ProvisionDeploymentOptions.failureCleanup: 'drain' | 'bounded'` (default `'drain'`); with `'bounded'` the rollback performs at most one bounded advance and surfaces the resumable outcome through the new `ProvisioningError.cleanup` field. + +- 36a4b7c: Add token-driven bounded normal decommissioning for at-least-once control-plane Worker workflows. Fleet D1 owns scan progress. Each call performs at most one bounded scan chunk; only an exact matching verify may immediately consume that result through its single same-lease resource action. Other calls perform at most one lifecycle or resource action group. + + Persist an immutable database-export receipt authority before the first D1 scan or export. Retries after artifact commit or Fleet state-write loss converge on the same filesystem or R2 receipt; authority changes and byte collisions preserve the committed winner and fail closed. Custom bounded backends must expose the paired receipt authority and export capability. Queue-driven bounded decommissioning requires Workers Paid because its bounded multi-R2 read groups can exceed the Free plan external-subrequest limit. + + Add a root-only bounded backend-switch advance API that uses the same durable token and receipt guarantees. It binds teardown to one immutable switch snapshot and captured entry subphase, advances at most one release, R2 resource, scan chunk, or D1 action group per call, and preserves legacy recovery after a shell-less deployment reaches export authorization. A pending ordinary Worker requires lossless exact-version inspection, authoritative secret-name inventory, and its persisted Durable Object namespace identities. Custom switch providers must expose the bounded scan, receipt, database, residual, delete, and conditional pending-artifact inspection capabilities required by the durable state they resume. + +- d9f864f: Add a bounded, resumable fleet drift audit API with a durable, provider-neutral operation store. `advanceFleetAudit()` performs at most one bounded stage chunk per call — one global-stage slice of up to `maxItemsPerCall` items (1..2,000, default 500), or exactly one Fleet record's inspection and re-arm — against a `FleetOperationStore`; `D1FleetOperationStore` implements that port over the existing Fleet D1 binding with account-and-kind-scoped leases, lease-fenced guarded batches, and audit generation pinning. Call `start` with an operation id, the audited records, and `staleAfterMs`, then re-enqueue only the pending token each call returns. Start requires at most 10,000 records whose canonical bytes total at most 16 MiB, with each record within the 96 KiB staged-row byte bound and the per-record structure bounds: plain JSON data (no `undefined`-valued properties, dates, class instances, or cycles) within depth 64, 8,192 nodes, and 4 KiB per string value or object key. Every record must satisfy the deployment identifier grammar, and an explicit generation must be a positive safe integer. Every such refusal has a fixed message and precedes every durable effect. The `staleAfterMs` and operation-id refusals also occur before the lease; after the lease row is written, the foreign-kind, no-finalized-generation, and `auditClock`-sample refusals write nothing else. These accepted inputs are intentionally narrower than `auditFleetDrift()`, which does not require that identifier grammar, an explicit generation, or the bounded path's row and structure bounds. Read the findings back page by page with `readFleetAuditFindingsPage()` once the operation is terminal. Each page comes back in ordinal order whatever order the store's page arrived in and carries `nextAfterOrdinal`, the cursor to pass back as `afterOrdinal` on the next call — absent only on an empty page, which is legal only when `done` is set. The reader verifies the page rather than trusting it: a page that is empty while unfinished, or that is not the contiguous ordinal run following the cursor, refuses with `fleet operation state is malformed`. `abandonFleetAuditOperation()` unblocks a stuck running operation and releases any pin an already-terminal one still holds. + + - `auditFleetDrift()` keeps its exact signature, refusal message, finding vocabulary, finding order, provider interaction order, return value, and stop behavior. It now drains the same decomposed stages in memory, and a frozen golden baseline (findings and the full store/backend/resolver call log) pins all of that. + - **HARDENING:** the bounded engine never persists the raw diagnostic bytes a one-shot audit composes call-locally. The three resolvers, the inspection, the re-arm, and the segmented multi-duty `maintenance-stale` composition durably record a fixed template alone; any finding detail, composed by the engine or passed through from the pinned inventory generation, that fails a non-throwing credential-substring and control-byte gate persists a fixed withheld-detail fallback instead of aborting the operation. + - An audit start pins exactly one finalized `@proofoftech/fleet-control` R3 inventory generation and keeps it through completion, so a finding page stays interpretable against the exact generation it was computed from; only explicit result garbage collection, terminal failure, or abandonment releases it. A replayed start never re-resolves "latest": it reuses the persisted generation. + - Every finding or fact must fit the staged-row envelope: a 16 KiB JSON-serialized payload, 4 KiB per string, and the codec's depth and node bounds. The coordinator detects an excess before the store sees the row, fails the whole operation with the durable `emission-bound-exceeded` reason, and releases the pin. One record's whole per-call emission set (its findings plus the cross-record ownership facts it newly claims) must also fit inside the one guarded D1 batch its `per-record` call commits — a ceiling of 99 rows: 99 emitted rows plus the one run-record update is exactly the 100-statement budget and is accepted, while 100 or more emitted rows fail. A record whose live inspection alone would emit 100 rows fails with the same reason rather than emitting a partial finding set. + - The bounded path differs from the drain in exactly four classes: every resolver, inspection, and re-arm failure and every multi-duty `maintenance-stale` finding persists one of the six fixed detail-template families where the drain composes the raw diagnostic; any unsafe finding detail, composed or passed through, becomes the fixed withheld-detail fallback; concurrent mutation can cause either a re-arm refusal on a Fleet reread mismatch or inspection-derived findings against later provider truth, with the bounded path's typically older snapshot making both more likely; and `emission-bound-exceeded` (from the staged-row envelope or the 99-row ceiling) and `generation-unavailable` are terminal whole-operation failures with no drain counterpart, while `auditFleetDrift()` completes and returns its full finding array over the identical world and clocks. Every other output is proven byte-for-byte equivalent to the drain under identical frozen worlds and clocks. The four-class claim also assumes `backendFor`, `specFor`, and `maintenanceSecretFor` are functions of record _value_: the bounded path hands them canonical snapshots rebuilt from the staged rows, never the caller's own objects, so an identity- or prototype-keyed resolver diverges from the drain in a fifth way this list does not cover. + - Per-call cost is not free: every advance call that runs a stage chunk re-reads the pinned generation in full and re-pages the accumulated `record` rows; a record-processing `per-record` call additionally re-pages the accumulated `fact` rows. Each stage-running call also structurally re-parses every accumulated `record` row through three plain-data traversals. That three is this coordinator's own ingress only: against the D1 store each of those same rows additionally costs a `JSON.parse` of the stored payload and the staged-row codec's own bounded-plain pass, so the real per-row constant factor is higher than three. `finding` rows are never re-paged. A stale token, a `start`, and a `finalize` call read neither. + - Aggregate cost: one bounded audit spans `1 + records + Σ max(1, ⌈stage_i / maxItemsPerCall⌉)` stage-running calls, for a `maxItemsPerCall` held constant across the operation — the option is per-call, so varying it between calls changes the count: one per-record-to-finalize transition, one processing call per record, and at least one call per global stage. The sum runs over the eleven global stages rather than over distinct sources: `deployment-gaps`, `namespace-expectations`, and `r2-expected` each chunk the audited-record array independently, so that one array is walked by three separate stage runs. Every such call re-reads O(G) generation rows and re-pages and structurally re-parses O(R) accumulated `record` rows. In the records-dominated case, this is O(records) full generation re-reads, O(records²/1,000) accumulated-row page reads, O(records²) billed rows read, and O(records²) structural `FleetRecord` re-parses at three plain-data traversals each, the dominant CPU term. This checkpoint's in-memory suite measured roughly 0.25 ms per record-row re-parse and roughly 0.5 s for one `per-record` call over 1,001 accumulated rows. Multiplying the first figure by that row count accounts for about half the second; the remainder is the call's fixed cost: the pinned-generation re-read, both row pagings, and the record's own provider step. Read the per-row rate as the 0.25-to-0.5 ms band those two figures bracket rather than as a single constant, and as an order of magnitude from in-memory fakes rather than a production measurement. A late per-record call at the 10,000-record ceiling therefore spends seconds of isolate CPU re-parsing before its provider work. The per-call guarantee covers bounded provider work and bounded emission, not bounded CPU or bounded rows read. See the fleet control guide for the full envelope. + + The operation store rejects invalid row-page selectors before schema work. Invalid public selectors use input-specific errors; malformed durable records retain `FleetOperationStateError`. + + `stageRows` rejects conflicting immutable staged payloads within its current batch. `commitProgress` refuses conflicting immutable payloads and missing item-update targets without advancing progress or retaining sibling mutations from that batch. Exact restaging remains idempotent. Replaying an uncertain commit requires the same intended run record and row payloads; composing a new transition requires reading persisted progress first. Database errors propagate to the trusted caller. Lease expiry can still leave earlier staged rows while the progress update refuses, so immutable retry payloads remain a caller obligation. + + `failOperation` accepts at most one item update. Terminal transitions refuse operation IDs belonging to another operation kind. Row-watermark refusals preserve sibling rows, and noncontiguous caller inserts below a claimed watermark refuse before SQL. See `FleetOperationLease` for the operation-store contract. + + The new `readFleetAuditFindingsPage()` resolves to the exported `FleetAuditFindingsPage` type, a `done`-discriminated result: `{findings, done: true, nextAfterOrdinal?}` or `{findings, done: false, nextAfterOrdinal}`. No existing public export changes shape, and the Worker subpath is unchanged. + +- 2600c07: Add a bounded, resumable account inventory API with durable generations. `advanceFleetInventory()` performs at most one provider stage chunk per call against a `FleetInventoryRunStore`; `D1FleetInventoryRunStore` implements that port over the existing Fleet D1 binding with operation-keyed runs, lease-fenced guarded batches, generation pinning, and bounded garbage collection. Build the provider seam with `cloudflareFleetInventoryContext(client)`, call `start` with an operation id, then re-enqueue only the pending token each call returns. The final call returns a `FleetInventoryGenerationRef`; read the rows back as today's `FleetResourceInventory` with `readFleetInventoryGeneration()`. Budgets are caller-supplied and validated: `maxProviderRequests` 9..1,000 and `maxStagedRowsPerChunk` 1..2,000 (default 500). + + Starts reject malformed identity and inconsistent options before claiming durable run state. Chunk commits retain the operation's account, physical generation and options. Conflicting immutable row or fact payloads roll back sibling writes; duplicate keys refuse before SQL. Exact replay compares the intended run record as well as staged bytes, so a failed run at the same revision cannot be returned as a successful chunk. Database errors propagate, and callers must retain immutable payloads across uncertain responses and lease-expiry retries. + + Cross-account operation-ID collisions roll back the losing head claim and generation allocation. Exact failure and finalized continuation retries repair interrupted head cleanup while preserving newer operations and historical pin requirements. Future tokens refuse before fallback repair. Pruning retains active terminal generations until head cleanup finishes. Pin admission verifies the finalized physical target in its INSERT, preventing orphan pins when reclamation wins a concurrent call. Pin admission also checks retained row/fact manifests, so partial reclamation cannot create a pin over missing data. Finalization and pinning require dense row ordinals. + + - `collectFleetInventory()` keeps its exact signature, refusal message, provider encounter order, finding vocabulary, finding order, and result bytes. It now drains the same engine in memory, and a frozen golden baseline pins all of that. The one exception is the scale limit below. + - **BEHAVIOR CHANGE (scale limit):** `collectFleetInventory()` is now subject to the same `maxProviderRequests` bound as a bounded run, capped at 1,000 per stage chunk, and six stages carry no resumption cursor so they must finish in one chunk. An account whose largest such stage needs more than 1,000 provider operations — in practice roughly 1,000 prefix-matching plain Workers, which `route-claims` reaches first — now rejects with `fleet inventory stage '' cannot complete one chunk within its provider request budget` instead of returning an inventory, where the previous single-pass enumeration completed under the 10,000-item collection bound. Nothing is written and no partial result is returned. There is deliberately no unbounded mode; narrow `scriptNamePrefix` to split such an account. See the fleet control guide for the stage list and the arithmetic. + - Its options parameter gains the exported alias `CollectFleetInventoryOptions`. The shape is identical, so this is not a break. + - **HARDENING:** the two durable finding details that previously interpolated a provider error string now store the fixed templates `registered script '' could not be inspected` and `plain Worker '' could not be inventoried`. The transient text stays call-local, so `collectFleetInventory()` still returns today's exact bytes while a persisted row carries no provider text. + - **HARDENING:** a raw host-routing KV key name that is over-length or credential-shaped refuses the run; one that is merely unprintable or base64-shaped yields a `malformed-script-registration` finding naming the key by its listing ordinal rather than by its bytes. That finding is positionally attributable but does not carry the offending name. + - Only a finalized generation is readable. The latest finalized generation reads without a pin; every older generation must be pinned before it is read, because pruning removes finalized-or-failed, non-latest, unpinned generations. + - A generation is a point-in-time-per-stage snapshot, not a globally consistent one. A resource that changes between stages is recorded exactly as the single-call enumeration surfaces it. + - Bounded cursor history beyond the last committed offset is deliberately out of scope: a single-pass resumable scan needs only the last offset. + - **INTERNAL:** `inventoryBoundExceeded` is consolidated into `cloudflare-client-config.ts` and shared by both provider modules. The refusal messages are byte-identical. + + No existing public export changes shape, and the Worker subpath is unchanged. + +- e3edf87: Add durable fleet upgrades through `advanceFleetMigration()`, with a frozen target digest and per-item plan, one admission or plan step per call, first-error stopping, item paging and explicit abandonment. The bounded API requires a `FleetOperationStore` alongside the existing deployment store; custom deployment stores can continue using the one-call `migrateFleet()` drain. + + Fleet and provider state remain mutation authority. Continuations re-read that state under per-call deployment leases and recover cursor-loss windows through the shared migration engine. Public documentation describes accepted inter-call races, strict fresh-admission limits after Durable Object tag movement, at-least-once settlement, operation-store convergence residuals, and the repeated resolver/provider and whole-item-read costs. One step is not a fixed provider-request, CPU or row-read budget. + + The existing drain retains its recorded ordering and behavior apart from the separately documented correction to target application-binding validation during plain Worker upgrades. + +- bb9291c: Add `CloudflareApiPlainWorkerBackend`, a direct Cloudflare API backend for platform-authored ordinary Workers. Its adapter classifies each mutation's dispatch under the operation's own execution context, so a queued mutation's pre-dispatch failure rejects instead of resolving `{ status: 'failed' }`. Construct it with a plain-only `CloudflareProvisioningClient`. Configure that client with a shared rate coordinator and a durable export store. The existing Wrangler and Workers for Platforms backends keep their public provisioning contracts. + + Expose the configured provider request timeout through the public `CloudflareProvisioningClient.requestTimeoutMs` getter. + + Harden provider behavior across the built-in backends: + + - **BEHAVIOR CHANGE:** Disable Cloudflare SDK logging even when `CLOUDFLARE_LOG` requests verbose output. + - **BEHAVIOR CHANGE:** Bound every paginated Cloudflare inventory and fail instead of truncating an over-bound result. + - **BEHAVIOR CHANGE:** Disable SDK retries for Worker upload and deployment, D1 creation and query, and each D1 export poll. This includes the D1 query path shared by both provider backends. + - **BEHAVIOR CHANGE:** Redact signed URLs, provider response details, headers, and original causes from database export failures. + - **BEHAVIOR CHANGE:** Replace uploaded secret plaintext in **ordinary-Worker** provider error messages, and in the cause chain of transport failures behind them, before returning a failed mutation outcome. + - **BEHAVIOR CHANGE:** Surface a lost lease before database or R2 reconciliation in all three affected create paths. + - **BEHAVIOR CHANGE:** Reject `PlainWorkerBackend.identityCaller` values that are not printable, single-line ASCII tokens from 1 through 128 characters. + - **BEHAVIOR CHANGE:** Refuse a reconciled Worker upload whose workers.dev or preview-URL state does not match the intent instead of accepting it by tag rediscovery. + - **BEHAVIOR CHANGE:** Queued provider mutations assert their own lease. Under concurrency pressure a queued mutation previously ran under the preceding operation's execution context. + + When upgrading, construct direct ordinary-Worker clients with `plane: 'plain-worker'` and no `dispatchNamespace`. Keep `dispatchNamespace` on Workers for Platforms clients, provide one quota coordinator across every replica sharing a provider token, and ensure the token can complete the documented attachment scans before destructive teardown. + +- 308bd39: Expose `@proofoftech/fleet-control/cloudflare-control-plane` for a dedicated trusted Cloudflare Worker. The factory composes direct Cloudflare operations with durable Fleet D1 state, shared D1 quota coordination, and private R2 database exports. Continuation tokens resume bounded lifecycle operations against stored authority. + + Install the required `@cloudflare/workers-types >=5.20260730.1 <6` peer when typechecking consumers. Keep the Cloudflare token and control-plane bindings outside tenant-serving Workers, and authenticate and authorize operations before calling the library. + + The packed verification uses an unminified namespace-import Worker with raw and gzip regression budgets of 4,128,768 and 589,824 bytes. Those budgets apply 25% headroom to the initial packed measurements, rounded up to 64 KiB. They are repository regression limits; size inventory and audit workloads for the documented Worker resource envelope. + +- a534f63: `advanceFleetMigration()` accepts an `AbortSignal`, matching the audit advance, and a completion callback. `signal` is call-local and never persisted, and cancellation is cooperative: the composition checks it before dispatch and before each item advance, and work already started runs to completion. A cancellation this composition observes leaves the operation and its items exactly as they were and a later continue resumes; a signal the host also wires into its own provider or store surfaces inside the item step and durably fails the item like any other step failure. + + `onComplete` is delivered at least once for a finalized operation, after the finalization is durable and before the call that observed it returns, so the host deduplicates on `operationId`. `AdvanceFleetMigrationOptions.onComplete` names the calls that deliver it. The alternative that fires only on the running-to-finalized transition loses the notification when the process dies between the durable finalize and the callback. A rejection propagates to the caller and leaves the durable finalization intact. + + `CloudflareAdvanceFleetMigrationOptions` carries both, forwarding `signal` unbound and `onComplete` bound to the caller's options object. Both additions are optional members, so existing callers are unchanged. + +- 224421b: `PlainWorkerProvisioningApi.listDatabases` accepts an optional name filter. The direct Cloudflare API adapter forwards it as the D1 list query and the Wrangler adapter filters its parsed inventory locally; `PlainWorkerBackend.findDatabase` passes the deployment's database name and keeps its exact-name comparison and its duplicate-name and missing-UUID refusals. +- cfcd24c: Export the shared `PlainWorkerBackend`, its `PlainWorkerProvisioningApi` port, and the port's ordinary-Worker record types. Port adapters must verify database exports independently against the durable store's committed size and digest. `WranglerLoopBackend` now extends this core while retaining the same constructor options and provisioning members. + + Harden ordinary-Worker provisioning and teardown: + + - **BEHAVIOR CHANGE:** Surface a lost external mutation lease as a failure instead of masking it behind a post-dispatch readback. + - Preserve both operation and scratch-cleanup failures without masking either. + - **BEHAVIOR CHANGE:** Refuse D1 bindings and database inventory entries with an empty primary identifier instead of accepting a fallback field or malformed inventory. + - **BEHAVIOR CHANGE:** Reject malformed Worker version inventory that omits an identifier instead of treating the entry as provider absence. + - **BEHAVIOR CHANGE:** Keep lease denials distinct from provider absence during Worker deletion. + - Allocate adapter-owned upload scratch only when an upload is required. + - **BEHAVIOR CHANGE:** Classify post-install scratch-cleanup failures so callers remove only Workers created by the failed attempt. + + Provider-neutral error messages now describe plain-Worker and provider operations instead of Wrangler. Error-message text compatibility is not claimed by this release. + + When upgrading, consumers that matched `deployWorker` rejections by identity or message text should catch `WorkerDeploymentError` and read `createdByAttempt` and `resourceState`. + +- c6e3c9f: State three requirements a `FleetOperationStore` implementation must meet, and hold the shipped D1 store to them. + + - `withAccountOperationLease` requires that the promise it returns settle only after the lease release completes, so a composition that awaits one call holds no lease when the next one takes it. `D1FleetOperationStore` already awaits its release before returning. + - **BEHAVIOR CHANGE:** `readOperationRowsPage` requires a page of at most `limit` rows and serves a `limit` above 1,000 at 1,000, the one documented ceiling and the maximum page `D1FleetOperationStore` already enforced. An over-large `limit` costs the caller the rows beyond the ceiling rather than the read, where the D1 store refused it before. `readFleetAuditFindingsPage` and `readFleetMigrationItemsPage` forward a caller's `limit` unchanged, so they answer the same way against any conforming store. A `limit` that is not an integer of at least 1 is refused. + - **BEHAVIOR CHANGE:** `failOperation` reports an update target missing at its post-batch readback as a conflict, the classification `commitProgress` convergence already uses for a missing row, rather than as a divergence. Divergence keeps its narrower meaning: landed bytes that differ from the intended ones. + +- 3c13571: Export `plainWorkerIngressModule` from the root entry for tools that inspect ordinary Worker ingress or calculate the complete upload size. The backend appends this generated module during upload. +- ce0913c: Admit a retired terminal `decommissioned` record as an absent prior in `provisionDeployment()`, so a host can reprovision a decommissioned slug without first clearing its ledger row. A stored row qualifies when it carries the record a completed decommission leaves — its `applicationResources` entries `deleted`, its database export location, digest, and byte count recorded, and no pending lifecycle field — and no unfinished decommission or cleanup operation; a row carrying a backend-switch record qualifies only at subphase `decommissioned`, whose teardown committed that same export. The read is normalized once, before the lifecycle guards, the immutable-mapping asserts, the phase refusal, and the reservation-ownership flag that drives the failed-provision unwind. Force-then-provision continues to work unchanged: `forceDecommissionDeployment()` still removes a terminal row, and a provision over the empty key takes the path a provision with no stored row takes, where the durable reservation is written first. + + - **BEHAVIOR CHANGE:** A retired terminal record no longer refuses with `cannot be provisioned from phase 'decommissioned'`. Provisioning 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 the specification retains them, while the database and its provider-minted ID, the seeded deployment identity, and the application R2 resources are new. The artifact version is read from the provider for this deployment rather than carried over, which does not promise a value different from the retired row's. The replacement's export location, digest, and byte count are absent rather than newly minted. A changed specification is accepted, because the immutable-mapping and digest guards read a prior and a retired record is not one; `previousDurableObjectTag` is refused as it is for any other new deployment. + - **BEHAVIOR CHANGE:** `decommissionDeployment()` replays a terminal record's `DatabaseExport` for a late retry while that terminal row is the stored row. Once a re-provision has replaced the row and the replacement reaches `ready`, a same-spec `decommissionDeployment()` call is a new decommission of the replacement and proceeds against it: the one-call facade carries no operation identity that separates a retry from a new request. Read the export from its retained location before reprovisioning the name; carry an in-flight decommission through `advanceDecommissionDeployment()`, whose token carries the operation identity; or confirm the row's deployment identity — its database ID — before issuing a one-call decommission after a re-provision. + - **BEHAVIOR CHANGE:** A terminal row that retains an application resource, whose teardown evidence is incomplete — a force-produced row records no database export — or that carries a backend-switch record in a subphase other than `decommissioned` refuses with a message that names that residue and directs to `forceDecommissionDeployment()` after the residual physical resources are confirmed removed, in place of the generic phase message. An unfinished decommission or cleanup operation refuses through the guard that owns it, as it does from any other lifecycle entry. Every other non-resumable phase keeps the generic refusal. + - **BEHAVIOR CHANGE:** A backend-switch teardown that completes on the legacy path writes its `applicationR2Progress` deletions into the record's top-level `applicationResources`, the projection the bounded path already writes. A row that teardown leaves therefore qualifies for retirement, where before it was refused for the application resources the switch had released. + - **BEHAVIOR CHANGE:** A retired terminal record that persisted `wfpMode: "platform-catalog"` refuses a specification reserving another deployment mode, before the first durable write, with a message naming the persisted mode and the reserved one and directing to `forceDecommissionDeployment()`. The store pins an established catalog mode, so the replacement upsert would otherwise fail on that constraint. + - **BEHAVIOR CHANGE:** Over a retired terminal record, the reserved-name database check runs before the first `lease.put`, so a re-provision that refuses a pre-existing database leaves that record — including its export location, digest, and byte count — byte-identical, and the failed-provision unwind no longer deletes a row the attempt never claimed. A provision with no stored row keeps its original order, where the durable reservation is written first. + - **BEHAVIOR CHANGE:** `auditFleetDrift()` no longer reports `incomplete-provisioning` for a terminal `decommissioned` record. The phase is retained state rather than a phase that advances, so the staleness check misclassified intended retained state as incomplete provisioning. + +### Patch Changes + +- f100ab3: Harden account-wide D1 and R2 attachment scans with a request-bounded, page-independent resumable engine. Rechecked inventory drift, malformed provider metadata, non-string or repeated dispatch cursors, and page or item overflows now fail closed instead of allowing an incomplete absence proof. +- f593e66: Refuse incomplete Worker binding metadata during D1 and R2 attachment scans so cleanup cannot mistake an unreadable attachment for an unused resource. +- ceab639: Synchronize the parent directories of filesystem exports before returning a durable location, including when an earlier attempt left a newly created directory behind. + + Cancel abandoned export streams when filesystem setup or a supplied export store fails. + +- 621fda7: Refuse redirects on the three credentialed provider transports. `CloudflareProvisioningClient`, `PlainWorkerBackend`, and `WorkersForPlatformsBackend` force `redirect: 'manual'` after a caller's `init`, so a bearer credential is not carried to an address the control plane did not choose. The raw dispatch script page refuses a redirect status where it reads the raw response, and the Workers for Platforms maintenance transport refuses one in its wrapper, because its callers read only the signed receipt header. Each throws `CredentialedRedirectRefusedError` on a 301, 302, 303, 307, or 308 and cancels the unconsumed body; the message names the operation and the status, never the address. An SDK-routed redirect surfaces as an `APIError` with the original status, not retried on the status alone and not classified transient. The Cloudflare SDK obeys an `x-should-retry: true` response header ahead of the status, so a redirect carrying that header is retried; each attempt goes through the same forced `redirect: 'manual'`, so no attempt follows the redirect. + + A host whose injected fetch follows redirects itself is unaffected: supply that fetch only from trusted control-plane code. + +- 7a446d2: Accept successful Cloudflare list responses with `errors: null` or `result_info: null`. Retry plain-Worker maintenance requests answered by a workers.dev platform 404 or 500 text page, for up to 60 seconds at 2-second intervals by default. Both `PlainWorkerBackendOptions` and `CloudflareApiPlainWorkerBackendOptions` expose `maintenanceRouteReadyTimeoutMs` and `maintenanceRouteReadyIntervalMs` to configure these bounds, and `wait` to delay reconciled mutation retries. Host Workers that fetch tenant maintenance origins on the same account's workers.dev subdomain require the `global_fetch_strictly_public` compatibility flag. Verify account-owned API tokens through the account endpoint, with fallback to user-token verification when the account endpoint is unavailable. + + Record first-page R2 access refusals (403, error 10003) for non-default jurisdictions in the required `FleetResourceInventory.unavailableR2Jurisdictions` array, preserving failures for default, later pages, and other errors. + + Export `FleetInventoryR2Jurisdiction` from the root and Cloudflare control-plane entries, and expose the root's reachable `D1FleetInventoryRunStoreOptions`, `D1FleetOperationStoreOptions`, `FleetInventoryDeploymentFactKind`, `FleetInventoryFailureReason`, `FleetInventoryGeneration`, `FleetInventoryRowKind`, `FleetInventoryRunProgress`, `FleetInventoryRunRecord`, `FleetInventoryStage`, `FleetInventoryStagedFact`, `FleetInventoryStagedRow`, `FleetInventoryStageInput`, `FleetInventoryStageResult`, `OrdinaryWorkerDeploymentVersion`, `PreparedOrdinaryWorkerDeploymentVersions`, and `PreparedOrdinaryWorkerUpload` types. + + Wait within the maintenance readiness deadline when a plain-Worker maintenance response attests the previous deployment specification, including version-override requests. + + Retry transient ordinary Worker provisioning failures up to three total backend attempts when provider reconciliation confirms that the upload, database or bucket creation, or deployment change did not take effect. + +- 4f4da55: Encode Worker upload metadata as JSON, name module parts by their declared paths, and let fetch set the multipart boundary for ordinary, control, dispatch and state Worker uploads. +- 25c976a: Call supplied fetch functions without a client or backend receiver so native Worker fetch works in the Cloudflare client and maintenance backends. +- 6b55190: Allow ordinary Worker migration to inspect a previous release with different application variables, service bindings or queue bindings. Versions claiming the requested specification digest retain the binding checks. +- 2cdeed4: Use manual redirect handling for D1 export downloads so Worker fetch accepts the request and redirects remain rejected before export data is stored. +- f10379b: Forward configured `DeploymentSpec.subrequestLimit` values through ordinary-Worker uploads in the direct Cloudflare and Wrangler adapters, including staged versions. The setting was validated and included in the specification digest but omitted from upload requests. It now accompanies `cpuLimitMs`; an omitted setting remains unspecified. + + Review existing ordinary-Worker subrequest settings when upgrading because those configured values now reach the provider. + +- 9111eb9: Validate plain Worker upgrades against the target application's bindings during candidate inspection, promotion, and settlement. Changing application variables or secrets no longer deploys the new bindings and then rejects them against the previous deployment's binding configuration. +- 0a09088: Reject incomplete Worker subdomain observations before interpreting ingress as disabled. Require explicit disabled flags for a present Worker through the ordinary and backend-switch proof paths while preserving an authoritatively absent parent. +- 121dd63: Validate inventory page structure and resource identity before recording absence, removing ingress or verifying secret revocation. Keep SDK pagination and the direct/Wrangler lookup paths consistent when metadata is incomplete. +- 6543c6f: `FileSystemDatabaseExportStore.write` no longer awaits the reader's cancel in its failure cleanup. When the body is a `tee()` branch, that cancel settles when the tee source is exhausted or errors, or the other branch is cancelled, so a write refused by the store's own checks held its rejection and its temporary file until then, and held both indefinitely when nothing drove the source. It now rejects with the store's error and removes the file without awaiting the cancel. +- 8c43533: Support signed maintenance for platform-authored Workers for Platforms catalogs. Catalog signing profiles can supply the maintenance keys without external-state artifacts. Catalog uploads receive their public verifier and local identity; FlowSafe relays capabilities while retaining the local receipt secret and validates the catalog script and digest before maintenance work. + + Existing catalog artifacts need a rebuilt FlowSafe runtime and explicit maintenance enrollment. The host configures the matching global dispatcher verifier. + + Persist catalog ownership explicitly in Fleet records and preserve it through native D1 migration and export-backed teardown. Catalog cleanup checks its own script and namespace authority. Force re-entry on completed or reserved records uses claim-releasing deletion when the store supports it. + + Preserve the prior mutable Worker schema identity while D1 advances and retain migration authority through compatibility teardown retries. Permit declared catalog binding changes with exact owner and uploaded-target checks. + + Allow ordinary spec-free force recovery after a candidate upload by clearing migration-only scalar fields when teardown begins, while preserving the recorded resource identity. + +- 77e90e7: Use the platform URL hostname parser for inventory finding validation so the ordinary Worker control plane does not require node:url. Preserve international hostname validation, rejected-input behavior and original diagnostic text. + + Use crypto.randomBytes results directly for R2 reservation nonces and deployment-secret encoding, preserving their byte lengths and base64url representation. + +- dd282de: Close staged Wrangler export streams before removing scratch files when a supplied store finishes or fails. +- Updated dependencies [c8c5039] +- Updated dependencies [4cb59a1] +- Updated dependencies [a027f13] +- Updated dependencies [f05e598] +- Updated dependencies [6f54bc6] +- Updated dependencies [6f54bc6] +- Updated dependencies [a086f24] +- Updated dependencies [647092e] +- Updated dependencies [e79b92a] +- Updated dependencies [6bd8bfc] +- Updated dependencies [323c2ce] +- Updated dependencies [8c43533] + - @proofoftech/flowsafe@0.21.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/fleet-control/package.json b/packages/fleet-control/package.json index 2f979f41..06e6c3e4 100644 --- a/packages/fleet-control/package.json +++ b/packages/fleet-control/package.json @@ -1,6 +1,6 @@ { "name": "@proofoftech/fleet-control", - "version": "0.4.0", + "version": "0.5.0", "publishConfig": { "access": "public" }, diff --git a/packages/flowsafe/CHANGELOG.md b/packages/flowsafe/CHANGELOG.md index a1197e88..96bb8720 100644 --- a/packages/flowsafe/CHANGELOG.md +++ b/packages/flowsafe/CHANGELOG.md @@ -1,5 +1,86 @@ # @proofoftech/flowsafe +## 0.21.0 + +### Minor Changes + +- c8c5039: The pending-notifications inventory lists pending agent-inbox notifications whether due, scheduled for later, or carrying no due timestamp. Its count includes these rows, its notDue total identifies those not yet due, and entry details expose summaryAt alongside deliverAt. + + Pending notifications keep the deployment drain proof open until delivered, discarded, or deleted. Rescheduling alone does not clear the proof; a pending notification with neither timestamp requires a direct write or deletion because dispatch and retention leave it pending. + +- 4cb59a1: Bound notification delivery to ten failed attempts by default, configurable through `maxDeliveryAttempts` on tick and thread-route factories. Discard exhausted rows before another send, retain their error/count receipts, and remove them from due scans while preserving retry delays below the bound. + + Require conditional failure writes through `NotificationDeliveryStorage` for dispatch. `D1NotificationsStorage` implements the atomic operation; custom stores must adopt it. Preserve newer summary, delivery and content-denial receipts after response loss, and count each local outcome once without inferring unconfirmed success. Ordinary Core storage still serves notification ingestion; the `@mastra/core` patch is required for either path. Require a custom `SignalDatabase` or `ScheduleDatabase` `batch()` to resolve elements carrying `results`, as a real `D1Result` does; `SnapshotDatabase` and `InitialAdmissionDatabase` `batch()` declare the same element. + + Compare notification dates chronologically before bounded selection and retention, including expanded years and numeric offsets. Direct database writers must use ISO dates or explicitly zoned ISO date-times; conditional failure writes reject raw timestamp text outside that grammar, and neither due selection nor retention matches such a value. + + Ship the `@mastra/core@1.53.0` patch under `patches/`. Application roots must apply it for own-property-safe summary source counts and source delivery policies; `@mastra/core@1.53.0` otherwise reads inherited `Object.prototype` members at both sites (mastra-ai/mastra#23693, mastra-ai/mastra#23694). The getting-started guide documents the pnpm, npm and Yarn routes. Flowsafe refuses to construct a notification dispatch tick that does delivery work, and refuses notification ingestion and dispatch requests, when the installed `@mastra/core` lacks the patch. + +- 6f54bc6: Require the retention cursor seam on the purge duty. `runMaintenanceDuty('purge', env, context)` takes the new `MaintenancePurgeDutyContext`, whose `advanceRetentionCursor` is required, matching the `advanceCursor` the run-retention purge itself requires; the other duties keep the optional `MaintenanceDutyContext`. `FlowsafeWorker.runMaintenanceDuty` declares that split as two overloads — `'purge'` with a required `MaintenancePurgeDutyContext`, and `Exclude` with the optional `MaintenanceDutyContext` — so a caller holding a union-typed `duty` narrows it to one branch before calling: a single call spanning the whole union matches neither overload and no longer compiles. A purge invocation whose context omits the callback is refused under a `config-error` naming `maintenance.purge.advanceRetentionCursor` before any purge surface runs, rather than purging the remaining surfaces and reporting a `retention-purge` failure. +- 6f54bc6: Capture the conditional-delivery capability when a notification dispatch tick is built, whatever its `limit`. `createNotificationDispatchTick()` reads `storage` and refuses one without `updateNotificationDeliveryIfUnchanged` for every configuration, including `limit: 0`, so the `NotificationDeliveryStorage` requirement no longer depends on the limit. A `limit: 0` tick still resolves `{ due: 0, delivered: 0, failed: 0 }` without reading due rows, calling storage, or needing the `@mastra/core` patch; invalid numeric policy still fails ahead of the capture. + + The notification ingestion route refuses when the installed `@mastra/core` lacks the delivery-policy half of that patch. `createThreadSignalRoutes()` probes `resolveNotificationDeliveryDecision` at the ingestion gate, where delivery runs through `agent.sendNotificationSignal` and reaches the delivery-policy lookup, and answers 502 with the message naming the patch on the server log. The dispatch route carries the synchronous source-key probe its summaries need, and delivers through `agent.sendSignal`, which never reaches that lookup. The probe resolves once per isolate; tick construction keeps its synchronous probe and is unaffected. + +- 647092e: Expose `isArmableSuspensionDeadlineMs` and `suspensionDeadlinesOf` from `do-runner`, together with the `SuspensionDeadlineEntry` and `RejectedSuspensionDeadline` types that projection returns. Add the lightweight `do-runner/constants` entry for deadline values and timeout detection, and `do-runner/testing` for constructing fixtures with the same timeout envelope as the alarm path. + + Reuse the existing arming bounds, derivation and alarm payload factory. The test helper does not authorize a resume or mint an approval grant. + +- e79b92a: Accept optional non-reserved `requestContext` on authenticated run starts and carry it through the protected Durable Object topology into stored application context. Expose the validated value to router and Worker start-policy hooks while preserving shorter hook signatures. + + Reject malformed context and reserved keys with HTTP 400. Verified schedule targets retain precedence, including absent context; provider application values override stored values and trusted capabilities retain their authority. Application context survives resume. Keyed replay validates input and runs host policy again, then preserves the first writer's context without comparing or overwriting it. + + Correct public agent status, stream and ordinary termination lookups to return not found for a coherent snapshot belonging to another agent or thread. Private replay, proof and recovery retain strict failures so a foreign snapshot cannot be treated as absent. + +- 6bd8bfc: Add versioned execution-fence administration with artifact epochs, a sticky epoch requirement, transition revisions and exact last-command retry receipts. Upgrade supported legacy schemas additively without changing existing state or timestamps. Missing rows in new-format schemas fail closed. Admin responses omit receipts, proof execution identity and tokens; legacy commands remain compatible only while the epoch requirement is optional. + + Activate v2 Runtime generations with independently generated execution tokens and preserved original principal, logical target, caller epoch and agent mode across resume legs. Fenced starts require the actual D1 domain's positive initial-write witness before engine entry, binding the winning reservation and proof in the same admission transaction. Capable D1 without a fence keeps its actual namespace and ordinary persistence options; custom storage explicitly asserts no D1 namespace. Unfenced keyed starts bind their prepared identity before creation and retain uncertain outcomes. + + Persist preparing, prepared and prepared-unfenced journals in both managed hosts. Recover exact owned initial generations through the existing raw-row conditional repair without replaying effects or deleting tokenless snapshots. Require strict terminal reservation settlement before approval, dispatch, owner and lifecycle cleanup, then clear only the matching journal. Cold agent alarms initialize actual wrappers with the verified instance scope. Legacy journals and uncertain unfenced pending/absent outcomes remain unresolved. + + Keep the thread blocking-run check and run-record installation under the same lock. Validate workflow journals against the owning object's address and recheck complete agent journals after recovery waits before releasing reservations. Keyed recovery requires its configured reservation store before bookkeeping, including nonterminal outcomes. + + Share cold agent-wrapper initialization across concurrent requests. Probe the owning execution's liveness before reclaiming an existing reserved key, so a stream awaiting Core cleanup keeps retries pending without stranding the key. Unreadable liveness replies refuse the retry instead of authorizing a claim. + + Require a matching nonpending durable observation before modern start/resume success or the agent persistence acknowledgement. Return `RUN_START_PENDING` for a valid initial generation, preserving journals, watchdogs and pending schedule/deadline budgets. Project root-local summaries from the same selected observation, retaining detailed nested resume preparation and legacy compatibility. + + Preserve valid v1 and absent-provenance ordinary status, resume and lifecycle completion through one authoritative observation. Apply the existing binding, canonical-record and principal checks to legacy status. Legacy terminal cleanup requires no recovery journal and a confirmed raw terminal outcome; it never manufactures generation identity or spends a start key. Normal termination retains canonical agent records until lifecycle completion confirms. + + Create modern-unbound reservations and replace run-only claim, release and settlement with exact observed-row operations. Claim/release stamps advance without serving as generation tokens; only the caller's own valid write result proves a winning claim. Private replay compares the full generation before pending/result classification and preserves the value from its one authoritative read. Alias binding and prepared binding retain their distinct response-loss rules. Remove `claim`, `release`, `settleRun` and `rollbackFencedStart`; custom router wiring must provide the private `persistedStart` callback. + + Guard replay proof nomination with its original proof round/caller epoch, current exact snapshot and bound reservation at the final SQL write. Legacy proof setters cannot overwrite modern identity. Runtime, workflow-host, approval and signal re-entry gates compare complete physical generations and retain their original expectation through relevant waits. Direct approval compositions now pass an explicit trusted workflow namespace. External effects are not transactional with these checks. + + Capture trusted authority before asynchronous work across Worker configuration, protected JSON/header transport and the eighth agent-start authority argument. Keep public bodies and application context from supplying a winning claim. Preserve source-owner versus initiating-principal attribution, exact lifecycle counter exhaustion checks and rejection of sparse economic-operation lists. + + Make workflow retention generation-aware, protect run owners across supported snapshot namespaces, and pair reservation cleanup with the complete bound execution. Preserve reserved owners, uncertain generations and legacy keys that cannot be safely associated. Recheck schema and selected identity at the mutation; keep artifact deletion ahead of D1 cleanup. + + Custom `purgeExpiredWorkflowRuns` callers must now provide transactional `database.batch()` and an `advanceCursor` callback, retain its exported `RunRetentionCursor`, and supply that cursor on the next call. Composed maintenance persists the cursor across alarms and restarts. Finite scan cycles revisit skipped candidates without letting continuous inserts extend the current cycle; unproved D1 outcomes and failed cursor writes do not advance progress. + + Enforce captured caller epochs and semantic fence/schema observations in final D1 schedule mutations. Guard owned deletion participants independently, preserve admitted trigger settlement, and provide fixed pause/resume methods plus guarded no-op observations. Resume rejects a concurrent cron/timezone change. Fenced custom facades require the same-binding `FENCED_SCHEDULE_STORAGE` capability before activation; direct D1 authoring requires `batch()` and refuses omitted epochs once enforcement is active. Publish structured schedule conflict and unknown-outcome errors, retain server-side causes, and contain route audit failures without changing the selected response. + + Apply the shared final schema, singleton and typed semantic fence check to Runtime initial admission. Refuse unreadable authority before snapshot or dependent reservation/proof writes while preserving exact committed-write recovery. + +- 323c2ce: Add optional `SignalRouterOptions.validateThreadTarget` using the existing bound-thread validator contract, with captured actor context before asynchronous validation. Hosts can enforce strict ownership before forwarding. Normalize thread refusals with status 404 and audit the final downstream result. + + Contain audit and diagnostic failures in signal, objective, subscription and webhook routes so they preserve the selected response. Use own-property lookup for signal channels, objective methods and webhook provider configuration. + +### Patch Changes + +- a027f13: Return a generic internal-error response for unexpected run-router failures while retaining the original error in server diagnostics. Preserve typed refusal status, message and reason contracts. Contain diagnostic conversion and logging failures so they cannot prevent the generic HTTP response. +- f05e598: Refuse two `Agent` entry points that `@mastra/core` releases newer than the declared peer expose. `FlowsafeDurableAgent.listActiveThreadRuns()` throws instead of returning the run, thread and resource ids of every thread on the pubsub instance with a run in flight, which Core scopes by neither principal nor agent. `FlowsafeDurableAgent.__setThreadRuntimeAgent()` throws instead of installing another agent as the target every thread-runtime path resolves through — `subscribeToThread()`, `claimThreadOwnership()`, `sendMessage()`, `queueMessage()`, `sendStateSignal()`, `sendNotificationSignal()` and `sendSignal()` — where one call would move every run those paths start, and `subscribeToThread()`'s replay target with them, onto an agent that carries none of the wrapper's guards. Both refusals carry the reason table's message; an installed 1.53.0 exposes neither member on Core, so no call that resolves today changes, and a caller that feature-detects either member now finds it on the wrapper and takes the refusal where the call was a `TypeError` before. + + The durable-agent surface inventory now holds against the pinned peer and against newer 1.x releases together. + +- a086f24: `flowsafe-provision` sets a 64 MiB `maxBuffer` on the `wrangler d1 execute --json` child process whose output it parses. Node's default is 1 MiB counted across the captured stdout and stderr together, so a response past that was truncated and the run surfaced as `failed to execute Wrangler 4` with an `ENOBUFS` cause instead of the parsed rows. +- 8c43533: Support signed maintenance for platform-authored Workers for Platforms catalogs. Catalog signing profiles can supply the maintenance keys without external-state artifacts. Catalog uploads receive their public verifier and local identity; FlowSafe relays capabilities while retaining the local receipt secret and validates the catalog script and digest before maintenance work. + + Existing catalog artifacts need a rebuilt FlowSafe runtime and explicit maintenance enrollment. The host configures the matching global dispatcher verifier. + + Persist catalog ownership explicitly in Fleet records and preserve it through native D1 migration and export-backed teardown. Catalog cleanup checks its own script and namespace authority. Force re-entry on completed or reserved records uses claim-releasing deletion when the store supports it. + + Preserve the prior mutable Worker schema identity while D1 advances and retain migration authority through compatibility teardown retries. Permit declared catalog binding changes with exact owner and uploaded-target checks. + + Allow ordinary spec-free force recovery after a candidate upload by clearing migration-only scalar fields when teardown begins, while preserving the recorded resource identity. + ## 0.20.0 ### Minor Changes diff --git a/packages/flowsafe/package.json b/packages/flowsafe/package.json index 21189f0a..36b20573 100644 --- a/packages/flowsafe/package.json +++ b/packages/flowsafe/package.json @@ -1,6 +1,6 @@ { "name": "@proofoftech/flowsafe", - "version": "0.20.0", + "version": "0.21.0", "publishConfig": { "access": "public" }, diff --git a/packages/showcase/CHANGELOG.md b/packages/showcase/CHANGELOG.md index c7ed2bc7..eafd687e 100644 --- a/packages/showcase/CHANGELOG.md +++ b/packages/showcase/CHANGELOG.md @@ -1,5 +1,31 @@ # showcase +## 0.0.23 + +### Patch Changes + +- Updated dependencies [8fc708a] +- Updated dependencies [0e14950] +- Updated dependencies [7fec4af] +- Updated dependencies [f48a525] +- Updated dependencies [8b7f087] +- Updated dependencies [7eb450f] +- Updated dependencies [c8c5039] +- Updated dependencies [4cb59a1] +- Updated dependencies [a027f13] +- Updated dependencies [f05e598] +- Updated dependencies [6f54bc6] +- Updated dependencies [6f54bc6] +- Updated dependencies [a086f24] +- Updated dependencies [647092e] +- Updated dependencies [e79b92a] +- Updated dependencies [37c0fee] +- Updated dependencies [6bd8bfc] +- Updated dependencies [323c2ce] +- Updated dependencies [8c43533] + - @proofoftech/breakwater@0.14.0 + - @proofoftech/flowsafe@0.21.0 + ## 0.0.22 ### Patch Changes diff --git a/packages/showcase/package.json b/packages/showcase/package.json index f63c559b..6b614923 100644 --- a/packages/showcase/package.json +++ b/packages/showcase/package.json @@ -1,6 +1,6 @@ { "name": "showcase", - "version": "0.0.22", + "version": "0.0.23", "private": true, "description": "Anchorage showcase — six runnable workflows and seven guardrail scenarios behind one React frontend", "type": "module",