Releases: ProofOfTechOrg/anchorage
Release list
@proofoftech/flowsafe@0.10.0
Minor Changes
-
cb0f861: Replace connector ID approval arrays with structured connector grants. Durable-agent approvals now bind to the exact Mastra tool call, workflow approvals bind to the exact suspension, and standing grants require explicit run scope.
This is intentionally breaking:
APPROVED_CONNECTORS_CONTEXT_KEY,BREAKWATER_APPROVED_CONNECTORS_KEY, andapprovedConnectorsForLeg()are removed. Legacy arrays and approval rows without explicit scope fail closed. Migrate trusted hosts toCONNECTOR_GRANTS_CONTEXT_KEY,CONNECTOR_EXECUTION_CONTEXT_KEY, andconnectorGrantsForLeg().
Patch Changes
- f654696: Register runtime-driven durable agents with the runtime-owned Mastra instance so approved runs can resolve their agent and resume after isolate eviction on newer Mastra versions.
@proofoftech/breakwater@0.8.0
Minor Changes
-
cb0f861: Replace connector ID approval arrays with structured connector grants. Durable-agent approvals now bind to the exact Mastra tool call, workflow approvals bind to the exact suspension, and standing grants require explicit run scope.
This is intentionally breaking:
APPROVED_CONNECTORS_CONTEXT_KEY,BREAKWATER_APPROVED_CONNECTORS_KEY, andapprovedConnectorsForLeg()are removed. Legacy arrays and approval rows without explicit scope fail closed. Migrate trusted hosts toCONNECTOR_GRANTS_CONTEXT_KEY,CONNECTOR_EXECUTION_CONTEXT_KEY, andconnectorGrantsForLeg().
@proofoftech/flowsafe@0.9.0
Minor Changes
-
3a259b8: Add first-class execution principals so automated work stops impersonating people.
Every automated path previously fabricated a human to satisfy the one identity the platform had: the schedule tick, cron SLA maintenance, signal-provider delivery, and the suspension-reconcile bridge all minted
role: 'operator'. That lost provenance and gave autonomous execution an operator's authority.Breakwater's
Actorgains an optionalkind(human|service|agent|system, absent meaning human), and bothRBACMiddlewareandcreateGuardedAgentgainallowedPrincipalKinds, defaulting to['human']. The gate checks kind before role and does not consult the role allowlist for a non-human kind, because an automated principal carries a role only to satisfy the required field — consulting it would either admit whatever role the host projected, or force hosts to allow that role and thereby admit real humans holding it. Both the processor gate and the direct-call gate enforce it. An existing agent therefore denies every automated principal without a config change.Flowsafe adds
ExecutionPrincipal, withpurposerequired on every automated kind, and persists it in agent-run state and approval resume targets.AgentMeta.allowedAutomationdeclares which principal kinds may enter on which entry paths; absent or empty denies all automated entry, and an optional host authorizer can only narrow it further.ApprovalActoris unchanged and still means an authenticated human at the HTTP boundary or a reviewer deciding an approval — a human approval never transfers the decider's authority into the resumed run.The
@proofoftech/flowsafe/agent-hostentry point exports its automation policy types, includingAgentAutomationRule,AutomationCheck,AutomatedEntryRequest, andAutomatedEntryAuthorizer, so public catalog and host signatures never require deep imports.ApprovalServicegainscreateAsPrincipalandsupersedeStaleAsPrincipalfor trusted platform bridges. They replace the human role gate with a kind-and-tenant check rather than widening it. There is deliberately no principal-takingdecide,claim, ordelegate.trustAutomationPrincipal()returns a branded, frozen canonical clone rather than the caller's own object. Validating a principal and handing the same reference back left the vouch time-of-check/time-of-use: the caller kept a mutable alias and could rewrite a vouchedsystemprincipal into{kind:'human', role:'admin'}before the service readkind. The trusted entries now recheck the own brand, the automated shape, the kind, and that every field is a plain data property — an accessor survivesObject.freezeand would reopen the same hole — instead of trusting a parameter type that does not exist at runtime.ExecutionPrincipalfields arereadonly.AutomatedExecutionPrincipalis added for duties that want provenance but derive no authority from the principal, so the trust brand is demanded only where it is read.sweepSLAandSlaSweepMaintenanceOptionstake it, andsweepSLArefuses a human or malformed principal outright: it writes across every tenant, and a human there would stampprincipalKind: 'human'onto cron escalations.TRUSTED_AUTOMATIONis not on the package barrel —trustAutomationPrincipalis the sanctioned constructor.Audit correlation now carries
principalKind,principalId,purpose, anddelegatedByalongside the existing tenant, run, thread, and entry-path fields.x-flowsafe-actorandx-flowsafe-roleare retired from the wire. The principal is now the sole identity channel: a thread Durable Object projectsscope.actorfrom it, so a host's separateTenantContext.actorcan no longer disagree with what executes. Both header constants are removed from@proofoftech/flowsafe/do-runner; the topology strips the names on send and forward, andcreateTenantResolverstill refuses them on inbound requests so a mixed-version client fails loudly.queueApprovalForSuspension,reconcileApprovalsForSummary, andresumeRunWithRequeuetake asystemActorIdstring instead of a principal, and mint their own bookkeeping identity against the service's tenant binding. Hosts no longer perform a trust assertion for the platform's own bookkeeping.ApprovalServiceexposes itstenantIdfor that.The principal travels to a Durable Object in a trusted
x-flowsafe-principalheader thatcreateThreadTopologystamps on every send and forward. A thread DO refuses a request that carries none rather than treating the caller as a human, andcreateTenantResolverrefuses the header on inbound requests exactly as it does the tenant, actor, and role headers.BREAKING for in-flight state, deliberately and without an upgrade path:
AgentRunRecordis version 2 andagent-threadresume targets now store anExecutionPrincipal. Records written by the previous release fail closed, so a suspended agent run started before this upgrade cannot resume. A version-1 record cannot be upgraded honestly — aschedule.firerun storedrole: 'operator', so reading it back as a human would launder exactly the authority this change removes. Flowsafe's breakwater peer floor moves to>=0.7.0.rejectReservedAgentContextis removed from@proofoftech/flowsafe/agent-host; it was exported but never called on any path, and every real caller usessanitizeStoredAgentContext.A thread Durable Object now requires the principal header on every request, so a deployment whose Worker and Durable Object resolve different
@proofoftech/flowsafeversions returns 403 until both sides ship this release. Cloudflare's single-bundle model makes that skew unlikely, but there is no negotiation.
@proofoftech/breakwater@0.7.0
Minor Changes
-
3a259b8: Add first-class execution principals so automated work stops impersonating people.
Every automated path previously fabricated a human to satisfy the one identity the platform had: the schedule tick, cron SLA maintenance, signal-provider delivery, and the suspension-reconcile bridge all minted
role: 'operator'. That lost provenance and gave autonomous execution an operator's authority.Breakwater's
Actorgains an optionalkind(human|service|agent|system, absent meaning human), and bothRBACMiddlewareandcreateGuardedAgentgainallowedPrincipalKinds, defaulting to['human']. The gate checks kind before role and does not consult the role allowlist for a non-human kind, because an automated principal carries a role only to satisfy the required field — consulting it would either admit whatever role the host projected, or force hosts to allow that role and thereby admit real humans holding it. Both the processor gate and the direct-call gate enforce it. An existing agent therefore denies every automated principal without a config change.Flowsafe adds
ExecutionPrincipal, withpurposerequired on every automated kind, and persists it in agent-run state and approval resume targets.AgentMeta.allowedAutomationdeclares which principal kinds may enter on which entry paths; absent or empty denies all automated entry, and an optional host authorizer can only narrow it further.ApprovalActoris unchanged and still means an authenticated human at the HTTP boundary or a reviewer deciding an approval — a human approval never transfers the decider's authority into the resumed run.The
@proofoftech/flowsafe/agent-hostentry point exports its automation policy types, includingAgentAutomationRule,AutomationCheck,AutomatedEntryRequest, andAutomatedEntryAuthorizer, so public catalog and host signatures never require deep imports.ApprovalServicegainscreateAsPrincipalandsupersedeStaleAsPrincipalfor trusted platform bridges. They replace the human role gate with a kind-and-tenant check rather than widening it. There is deliberately no principal-takingdecide,claim, ordelegate.trustAutomationPrincipal()returns a branded, frozen canonical clone rather than the caller's own object. Validating a principal and handing the same reference back left the vouch time-of-check/time-of-use: the caller kept a mutable alias and could rewrite a vouchedsystemprincipal into{kind:'human', role:'admin'}before the service readkind. The trusted entries now recheck the own brand, the automated shape, the kind, and that every field is a plain data property — an accessor survivesObject.freezeand would reopen the same hole — instead of trusting a parameter type that does not exist at runtime.ExecutionPrincipalfields arereadonly.AutomatedExecutionPrincipalis added for duties that want provenance but derive no authority from the principal, so the trust brand is demanded only where it is read.sweepSLAandSlaSweepMaintenanceOptionstake it, andsweepSLArefuses a human or malformed principal outright: it writes across every tenant, and a human there would stampprincipalKind: 'human'onto cron escalations.TRUSTED_AUTOMATIONis not on the package barrel —trustAutomationPrincipalis the sanctioned constructor.Audit correlation now carries
principalKind,principalId,purpose, anddelegatedByalongside the existing tenant, run, thread, and entry-path fields.x-flowsafe-actorandx-flowsafe-roleare retired from the wire. The principal is now the sole identity channel: a thread Durable Object projectsscope.actorfrom it, so a host's separateTenantContext.actorcan no longer disagree with what executes. Both header constants are removed from@proofoftech/flowsafe/do-runner; the topology strips the names on send and forward, andcreateTenantResolverstill refuses them on inbound requests so a mixed-version client fails loudly.queueApprovalForSuspension,reconcileApprovalsForSummary, andresumeRunWithRequeuetake asystemActorIdstring instead of a principal, and mint their own bookkeeping identity against the service's tenant binding. Hosts no longer perform a trust assertion for the platform's own bookkeeping.ApprovalServiceexposes itstenantIdfor that.The principal travels to a Durable Object in a trusted
x-flowsafe-principalheader thatcreateThreadTopologystamps on every send and forward. A thread DO refuses a request that carries none rather than treating the caller as a human, andcreateTenantResolverrefuses the header on inbound requests exactly as it does the tenant, actor, and role headers.BREAKING for in-flight state, deliberately and without an upgrade path:
AgentRunRecordis version 2 andagent-threadresume targets now store anExecutionPrincipal. Records written by the previous release fail closed, so a suspended agent run started before this upgrade cannot resume. A version-1 record cannot be upgraded honestly — aschedule.firerun storedrole: 'operator', so reading it back as a human would launder exactly the authority this change removes. Flowsafe's breakwater peer floor moves to>=0.7.0.rejectReservedAgentContextis removed from@proofoftech/flowsafe/agent-host; it was exported but never called on any path, and every real caller usessanitizeStoredAgentContext.A thread Durable Object now requires the principal header on every request, so a deployment whose Worker and Durable Object resolve different
@proofoftech/flowsafeversions returns 403 until both sides ship this release. Cloudflare's single-bundle model makes that skew unlikely, but there is no negotiation.
@proofoftech/flowsafe@0.8.0
Minor Changes
- 09a4406: Add guarded Breakwater agents and Flowsafe's authenticated, catalog-driven agent host. Agent starts now derive trusted identity and execution context, agent resumes require an approval-bound capability, and status and NDJSON observation remain tenant-bound.
Patch Changes
- 6670285: Prevent approval resume after isolate eviction from rerunning application input processors or input policy evaluation. Durable-agent recovery now reauthorizes the stored principal, restores both Mastra run registries with complete runtime processor chains, and fails before resumed tool execution when authorization is denied.
@proofoftech/breakwater@0.6.0
Minor Changes
- 09a4406: Add guarded Breakwater agents and Flowsafe's authenticated, catalog-driven agent host. Agent starts now derive trusted identity and execution context, agent resumes require an approval-bound capability, and status and NDJSON observation remain tenant-bound.
@proofoftech/flowsafe@0.7.0
Minor Changes
- def3b37: Complete and document the public flowsafe surface. The root entry point now
re-exports the approval API, Durable Object runner, artifacts, and audit export
surfaces with parity tests. Signal-provider hosts gain a tenant-safe topology,
stable polling alarms, and automatic post-mutation polling reconciliation.
Publish comprehensive package, deployment, approval, durable-agent, operations,
and API-reference documentation, plus a full advanced starter host.
@proofoftech/breakwater@0.5.0
Minor Changes
- def3b37: Harden public connector and Agent CLI boundaries for the first public release.
Agent CLI connectors now expose structured, redacted errors, pass workspace-edit
permission flags to Claude Code and Codex, and keep prompts and option values out
of diagnostics and audit events. Connector, policy-evaluator, and actor-lookup
failures now emit static safe audit reasons. Add exhaustive export sentinels and
a packed-tarball consumer test, move Zod to runtime dependencies, and publish
complete package and connector guides.
@proofoftech/flowsafe@0.6.0
Minor Changes
-
eca3b6e: Close the durable-agent, agent-schedule, notification-dispatch, and D1 background-task execution residuals with tenant-safe thread routing and eviction-safe approval resume. Harden stored schedule context and core schedule-contract validation; make D1 notification creation preserve explicit-id coalescing, insertion-order targets, rollback-safe atomic migration, and concurrent partial updates; priority-plan summary and individual delivery across state-stable 100-id batches; accept Mastra's raw constructor pubsub with rollback-safe workers and a synchronous enqueue shutdown gate; close failed resume streams; validate public numeric configuration synchronously; preserve nested Mastra background-task SSE events; and require a proven process shutdown before spike restart.
-
d54d2be: Track D — schedules (
@proofoftech/flowsafe/schedules, additive, opt-in). A new
subpath ships the D1 schedules domain, a CAS-driven tick we own, and a tenant
facade — all on the single DO + D1 RunnerRuntime substrate (P1), no new
ApprovalRecordshape or existing signature changed, unconfigured hosts
byte-identical.D1SchedulesStorage+createScheduleStorageDomains— the flowsafe-owned D1
domain overmastra_schedules/mastra_schedule_triggers(the
@mastra/cloudflare-d1adapter ships neither), mirroring core's
SchedulesStoragecontract incl. the CASupdateScheduleNextFire. Composed
intocreateD1Storagevia the injecteddomainsseam.createScheduleTick— we OWN the tick (DL-012):listDueSchedules→ CAS claim
→ workflow targets mint a fresh INV-1 runId and fire through the host's
run-start seam; agent targets are GUARDED OFF (their only public fire path,
schedules.run(id), enqueues onto core's pubsub worker loop we do not run, so
firing is a fail-closed audited skip — agent-target execution is deferred). An
injectable run-cap seam (DL-007) skips a capped tenant while the schedule stays
healthy. The P4 stored-context barrier strips reserved keys before any leg.createScheduleRouter— the tenant facade (DL-013): server-minted ids,
metadata.tenantIdstamping, tenant-filtered reads, ownership 404s (no
oracle), per-tenant count + fire-rate caps, and P4 reserved-key rejection on
create/update (the wholebreakwater.namespace +mastra:goal).- Storage triad (DL-003): both tables register in the schema-guard inventory
(8 → 10) with a new metadata-filteredpurgeTenantkind
(TENANT_METADATA_PURGE_TABLES), pluspurgeExpiredScheduleTriggersfor the
trigger-history TTL.createFlowsafeWorkergains an opt-inscheduleTickseam
(its own failure-isolated cron duty) and aSCHEDULE_TRIGGER_RETENTION_DAYS
purge duty.
-
0f4f70a: Track E (M-007) — signal providers: a new subpath
@proofoftech/flowsafe/signal-providers
(additive, opt-in, subpath-only). Host external-event providers on a Durable
Object with alarm-driven polling, terminate provider webhooks on the Worker, and
persist subscriptions in a flowsafe-owned D1 table.SignalProviderHost— a per-tenant provider host DO (idFromName(tenantId))
whose alarm rehydrates subscriptions from D1 (core's registry is in-memory,
lost on eviction) and polls each of the tenant's providers with per-provider +
per-delivery failure isolation, delivering through Track C's thread-DO topology.D1SubscriptionStoreFactory— a flowsafe-owned, tenant-columned
flowsafe_signal_subscriptionsstore mirroring the approval store's INV-2
posture (.forTenant()tenant-bound,.system().listByResource()the webhook's
cross-tenant authority). Registered inpurgeTenant(PurgeTenantResult.subscriptions);
retention isnone(standing config reaped only at offboarding).createWebhookRouter— webhook ingress that verifies the provider signature
over the RAW bytes BEFORE parsing, maps the payload to a tenant via the
subscription ROW only (never the payload), rate-caps per provider+tenant, and
audits every ingest with a bounded forgery audit.createSubscriptionRouter—
the human-only HTTP subscribe/unsubscribe surface (never exposed as model
tools; mints no capability).githubSignalProvider— a binding-gated GitHub reference provider
(X-Hub-Signature-256verified constant-time via WebCrypto).createWebhookSignalProvider
is the generic path.
Also adds a
subscriptionscounter toPurgeTenantResult(the DL-003 offboarding
coverage for the new flowsafe-owned table). -
6c80e92: Track F (M-005) — goals. New subpath-only export
@proofoftech/flowsafe/goals:
createObjectiveRouter, a role-gated + audited objective HTTP surface
(set/get/update/clear over/api/threads/:threadId/goal) that writes the
thread-scoped goal record in Track C'smastra_thread_statedomain
(GOAL_STATE_TYPE'goal'). The write path is a P6-lite ingestion boundary
(auth → coarse role → thread-prefix ownership 404 → size cap →
assertNoClientMemoryIds→ field allowlist → maxRuns host cap → audit) and
persists through@mastra/core's ownwriteObjective/readObjective/
clearObjective, so a record it writes is byte-identical to what the durable
goal step reads viaresolveGoalStore(DL-018 — no thread-DO affinity needed
for the write). A requestedmaxRunsabove the host cap is rejected, not
clamped (default the coreDEFAULT_GOAL_MAX_RUNS, 50; DL-007). Goals never mint
capability (P8) and Track F starts no runs — per-tenant run budgets stay
enforced at the existing seams.GOAL_REQUEST_CONTEXT_KEY('mastra:goal') is
reserved with a no-collision pin against the runtime's requestContext base keys.
Hosts mount the surface opt-in throughcreateFlowsafeWorker's new
buildObjectiveRouterseam; absent config is byte-identical. Additive only — no
new table, schema-guard, purge, or TTL change (reuses the Track C thread-state
domain), and no existing signature orApprovalRecordshape changes.
Patch Changes
- 8e3562f: Make approval filing atomic across terminal and open records for the same captured suspension fingerprint, preventing stale reconciliation from filing over a decision.
- 97cb097: Harden the P6 ingestion routers against a pre-auth malformed-path fault. The
signal, goal, and schedule routers decoded the threadId/schedule-id path
segment with baredecodeURIComponentbefore authentication, and
createFlowsafeWorker's fetch handler did not wrap the router calls — so an
unauthenticated request with malformed percent-encoding (e.g.
POST /api/threads/%/message) threw aURIErrorout offetch()as a
per-request 500. The three routers now use a sharedsafeDecodeSegment
(host-kit) that treats malformed encoding as route-absent (byte-identical to a
non-matching path), matching the Track E webhook router; the worker fetch
handler gains a top-level try/catch that contains any handler throw as a
generic 500 without leakingerror.message. The same helper closes the whole
class: the background-tasks read route (post-auth, DO-mounted) adopts it too, so
a malformed taskId returns the no-oracle 404 instead of throwing. Additive and
behavior-preserving for all valid paths.
@proofoftech/breakwater@0.4.0
Minor Changes
-
15d4ec3: Track B (background tasks): the additive, opt-in substrate + defenses for
Mastra background tasks on the one Durable-Object + D1 chokepoint. No existing
signature or theApprovalRecordshape changed; hosts stay byte-identical with
background tasks unconfigured.- breakwater
_backgroundmodel-override defense (DL-005), the ONE breakwater
change (MINOR).createConnector's wrappedexecute/dryRunExecutereject
tool-call args carrying a_backgroundfield (coreLLMBackgroundOverride)
unless the manifest opts in viapermissions.background— the argv-flag-
smuggling posture of the agent-clibuildFlagsdefense.background: trueis
allowed only on a read-only connector (a write-class opt-in throws at
construction); v1 keeps write/approval-carrying connectors foreground-only.
Plus abackgroundExecutiontool-policy evaluator (deny-by-default for the
write class) as the defense-in-depth counterpart at the gate loop. Both are
DEFENSE-IN-DEPTH for DIRECT / NESTED calls, NOT the agent-path guard: on the
agent path core deletes_backgroundfrom the args before dispatch (schema or
not), and core's ownresolveBackgroundConfigbaseEnabled gate — a breakwater
connector sets no background config — already prevents the model from
backgrounding an ineligible tool, so the breakwater reads see stripped args and
fire on nothing there. The real write boundary on every path (including inside
the background executor) is the requestContext grant. mastra_background_tasksadopted into the D1 substrate in ONE change
(DL-003). Registered in the schema-guard inventory (coveragetenant-range,
a newbackground-task-ttlretention kind), inpurgeTenant(ranged over the
INV-1 saltedrun_id; newPurgeTenantResult.backgroundTasks), and given a
storage-layer TTL cleanuppurgeExpiredBackgroundTasks(+
BACKGROUND_TASK_TTL_PURGE_TABLES) mirroring core's two-window
BackgroundTaskManager.cleanupso a purge cron reaps terminal rows without a
live manager. Surfaced throughFlowsafeWorkerConfig.backgroundTasksas the
purge cron's own failure-isolated duty (undefined = no duty, byte-identical).@proofoftech/flowsafe/background-tasks(new subpath):backgroundTasksStore
(the async accessor onto @mastra/cloudflare-d1'sBackgroundTasksStorageD1—
the D1 domain the adapter already ships; not reimplemented, per "what NOT to
build"),BackgroundTaskHost(hosts aBackgroundTaskManageron a DO with the
DL-015 boot/alarm lifecycle), andcreateBackgroundTaskRoutes(READ-only,
tenant-bound by construction, DL-014: list/stream REQUIRE a runId/threadId
filter and validate its salted prefix;getTask404s a missing OR foreign
task with no oracle; the raw manager is never exposed).- Recovery seam pinned (R-002, spike B-S2): DO eviction is survived by
re-registering the static tool executors and calling the PUBLIC async
manager.init(pubsub)at DO boot — which fires the manager's own (private)
recoverStaleTasks()internally. No private method is ever called.
Known substrate limitation (spike B-S1 findings R-B1/R-B2/R-B3, documented in
background-tasks/host.ts): durable background-task execution does not yet
run on the Cloudflare substrate. Core runs task bodies on the evented
execution engine, which refuses tocreateRununless the workflows store
reportssupportsConcurrentUpdates().@mastra/cloudflare-d1returnsfalse
AND leavesupdateWorkflowResults/updateWorkflowStateas unimplemented throws
("D1 does not support atomic read-modify-write") — so R-B1 is NOT a flag to
flip: overriding it passes core's gate then throws on the first step-update,
stranding the task atrunning. The P9 fix is an adapter that implements
atomic partial-updates (the DO's single-threaded lease makes that safe), plus
mastra.startWorkers()to run the evented workers (R-B2 — the two close
together). A latent tenant-isolation residual (R-B3) rides along: core keys the
internal__background-taskrun by the UNSALTEDtaskId, so its snapshot row
escapes tenant offboarding — inert while execution is blocked, but it MUST be
closed in the same change that enables execution, and a CI guard
(background-tasks/d1-storage.test.ts) fails the instant
supportsConcurrentUpdates()returns true. Persistence, the recovery seam,
tenant purge + TTL, the read routes, and the_backgrounddefense all work
regardless.BackgroundTaskHost.boot()warns once so the limitation is loud,
not a stray async throw. - breakwater