@creatornader/agent-bridge 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +150 -1
  2. package/README.md +239 -5
  3. package/ROADMAP.md +59 -7
  4. package/SECURITY.md +55 -5
  5. package/SKILL.md +103 -0
  6. package/dist/archive.d.ts +2 -2
  7. package/dist/archive.js +2 -2
  8. package/dist/{bridge-service-hX1ZQp4W.d.ts → bridge-service-YTjGr91A.d.ts} +1 -1
  9. package/dist/{bridge-store-DPVSCwfs.d.ts → bridge-store-Boaj-ZbO.d.ts} +2 -0
  10. package/dist/bridge.d.ts +17 -7
  11. package/dist/bridge.js +15 -7
  12. package/dist/{chunk-WAI2IYIQ.js → chunk-3YHVUPU2.js} +48 -5
  13. package/dist/chunk-3YHVUPU2.js.map +1 -0
  14. package/dist/{chunk-45BZGB77.js → chunk-6BTJE6D4.js} +168 -7
  15. package/dist/chunk-6BTJE6D4.js.map +1 -0
  16. package/dist/{chunk-5GMB3A77.js → chunk-DQOZJJZ2.js} +22 -10
  17. package/dist/chunk-DQOZJJZ2.js.map +1 -0
  18. package/dist/{chunk-NCO7RLBM.js → chunk-GTK4KOED.js} +108 -8
  19. package/dist/chunk-GTK4KOED.js.map +1 -0
  20. package/dist/{chunk-GADH244D.js → chunk-IHZFI7TH.js} +12 -4
  21. package/dist/chunk-IHZFI7TH.js.map +1 -0
  22. package/dist/{chunk-CEXBMOI5.js → chunk-LAHNQZDN.js} +2 -2
  23. package/dist/{chunk-UAHMP6GC.js → chunk-MCKKVNCE.js} +9 -6
  24. package/dist/chunk-MCKKVNCE.js.map +1 -0
  25. package/dist/{chunk-MBPZXTJA.js → chunk-PDIHDBP3.js} +173 -20
  26. package/dist/chunk-PDIHDBP3.js.map +1 -0
  27. package/dist/{chunk-Q6PPMXJC.js → chunk-SV3SVBYS.js} +377 -21
  28. package/dist/chunk-SV3SVBYS.js.map +1 -0
  29. package/dist/{chunk-SPSMZOCJ.js → chunk-SYGONYMM.js} +4 -4
  30. package/dist/{chunk-A6W4QALF.js → chunk-TC6A7GMF.js} +2 -2
  31. package/dist/cli.js +5865 -436
  32. package/dist/cli.js.map +1 -1
  33. package/dist/client-runtime.d.ts +4 -2
  34. package/dist/client-runtime.js +6 -6
  35. package/dist/contracts/generate.js +1 -1
  36. package/dist/dr.d.ts +9 -2
  37. package/dist/dr.js +6 -4
  38. package/dist/{gateway-CggQ8vTC.d.ts → gateway-BQHpV-MZ.d.ts} +27 -3
  39. package/dist/gateway-main.js +69 -5
  40. package/dist/gateway-main.js.map +1 -1
  41. package/dist/gateway.d.ts +3 -3
  42. package/dist/gateway.js +2 -2
  43. package/dist/index.js +7 -7
  44. package/dist/{postgres-bridge-store-Bfd3hRId.d.ts → postgres-bridge-store-WASumj26.d.ts} +1 -1
  45. package/dist/server.js +7 -7
  46. package/dist/sqlite-edge-store-Bc15DvpX.d.ts +148 -0
  47. package/dist/sqlite.d.ts +19 -80
  48. package/dist/sqlite.js +11 -3
  49. package/docs/architecture-v2.md +256 -10
  50. package/docs/decisions/0004-client-lifecycle-and-endpoint-migration.md +204 -0
  51. package/docs/deployment.md +205 -0
  52. package/docs/troubleshooting.md +23 -0
  53. package/llms.txt +67 -0
  54. package/openapi/agent-bridge-v2.openapi.json +1023 -0
  55. package/package.json +6 -2
  56. package/schemas/agent-bridge-v2.capabilities.json +24 -0
  57. package/schemas/agent-bridge-v2.mcp.json +6 -0
  58. package/schemas/agent-bridge-v2.schema.json +163 -0
  59. package/sql/migrations/017_gateway_authority_binding.sql +99 -0
  60. package/sql/migrations/018_endpoint_migration_challenges.sql +1028 -0
  61. package/sql/migrations/019_endpoint_migration_same_successor.sql +324 -0
  62. package/dist/chunk-45BZGB77.js.map +0 -1
  63. package/dist/chunk-5GMB3A77.js.map +0 -1
  64. package/dist/chunk-GADH244D.js.map +0 -1
  65. package/dist/chunk-MBPZXTJA.js.map +0 -1
  66. package/dist/chunk-NCO7RLBM.js.map +0 -1
  67. package/dist/chunk-Q6PPMXJC.js.map +0 -1
  68. package/dist/chunk-UAHMP6GC.js.map +0 -1
  69. package/dist/chunk-WAI2IYIQ.js.map +0 -1
  70. /package/dist/{chunk-CEXBMOI5.js.map → chunk-LAHNQZDN.js.map} +0 -0
  71. /package/dist/{chunk-SPSMZOCJ.js.map → chunk-SYGONYMM.js.map} +0 -0
  72. /package/dist/{chunk-A6W4QALF.js.map → chunk-TC6A7GMF.js.map} +0 -0
package/dist/sqlite.d.ts CHANGED
@@ -1,81 +1,7 @@
1
- import { b as BridgeMessage, c as BridgePrincipal, B as BridgeDelivery, g as DeliveryState, R as RetryPolicy, A as AgentPresence, a as BridgeDeliveryEvent } from './bridge-domain-CBX3IVqC.js';
2
- import { d as MessageQuery, M as MessagePage, B as BridgeDiagnostics, I as InsertMessageResult, a as BridgeStore, C as ClaimOptions, c as DeliveryQuery, b as DeliveryPage, D as DeliveryEventPage } from './bridge-store-DPVSCwfs.js';
3
-
4
- type PendingMessage = Omit<BridgeMessage, "sequence" | "createdAt">;
5
- interface EdgeScope {
6
- endpoint: string;
7
- principal: BridgePrincipal;
8
- }
9
- interface EdgeOutboxRecord {
10
- position: number;
11
- draft: PendingMessage;
12
- payloadHash: string;
13
- attempts: number;
14
- leaseToken: string;
15
- createdAt: string;
16
- }
17
- interface EdgeEnqueueResult {
18
- draft: PendingMessage;
19
- created: boolean;
20
- }
21
- interface EdgeStats {
22
- pending: number;
23
- due: number;
24
- scheduled: number;
25
- leased: number;
26
- blocked: number;
27
- cached: number;
28
- pullCursor?: string;
29
- lastSyncAt?: string;
30
- lastError?: string;
31
- oldestDueAt?: string;
32
- queueLagMs: number;
33
- oldestBlockedAt?: string;
34
- blockedAgeMs: number | null;
35
- blockedMaxAttempts: number;
36
- blockedLastError?: string;
37
- nextRetryAt?: string;
38
- lastOutboundSyncAt?: string;
39
- lastInboundSyncAt?: string;
40
- lastAttemptAt?: string;
41
- }
42
- declare class EdgeConflictError extends Error {
43
- readonly code = "edge_idempotency_conflict";
44
- }
45
- declare function edgeMessageFingerprint(message: PendingMessage | BridgeMessage): string;
46
- declare function stableIdempotency(message: PendingMessage): PendingMessage;
47
- declare function edgeScopeKey(scope: EdgeScope): string;
48
- declare class SQLiteEdgeStore {
49
- private readonly path;
50
- private readonly scope;
51
- private readonly busyTimeoutMs;
52
- private readonly db;
53
- private readonly databasePath;
54
- private readonly preexistingFiles;
55
- private readonly key;
56
- private initialized;
57
- private initialization?;
58
- constructor(path: string, scope: EdgeScope, busyTimeoutMs?: number);
59
- private restrictFiles;
60
- get scopeKey(): string;
61
- initialize(): Promise<void>;
62
- private initializeOnce;
63
- private ready;
64
- enqueue(input: PendingMessage, now?: Date): Promise<EdgeEnqueueResult>;
65
- claimNext(now?: Date, leaseMs?: number): Promise<EdgeOutboxRecord | undefined>;
66
- retry(record: EdgeOutboxRecord, error: string, availableAt: Date): Promise<void>;
67
- block(record: EdgeOutboxRecord, error: string, now?: Date): Promise<void>;
68
- commit(record: EdgeOutboxRecord, message: BridgeMessage, now?: Date): Promise<void>;
69
- private cacheOne;
70
- cachePage(messages: BridgeMessage[], cursor: string | undefined, now?: Date): Promise<void>;
71
- cacheLatest(messages: BridgeMessage[], now?: Date): Promise<void>;
72
- list(query?: MessageQuery): Promise<MessagePage>;
73
- pullCursor(): Promise<string | undefined>;
74
- noteError(error: string): Promise<void>;
75
- noteAttempt(now?: Date): Promise<void>;
76
- stats(now?: Date): Promise<EdgeStats>;
77
- close(): Promise<void>;
78
- }
1
+ import { c as BridgePrincipal, B as BridgeDelivery, g as DeliveryState, R as RetryPolicy, A as AgentPresence, b as BridgeMessage, a as BridgeDeliveryEvent } from './bridge-domain-CBX3IVqC.js';
2
+ import { M as MessagePage, B as BridgeDiagnostics, I as InsertMessageResult, a as BridgeStore, d as MessageQuery, C as ClaimOptions, c as DeliveryQuery, b as DeliveryPage, D as DeliveryEventPage } from './bridge-store-Boaj-ZbO.js';
3
+ import { e as EdgeMigrationGateState, S as SQLiteEdgeStore, a as EdgeDrainLease, P as PendingMessage } from './sqlite-edge-store-Bc15DvpX.js';
4
+ export { E as EdgeConflictError, b as EdgeEnqueueResult, c as EdgeMigrationDrainingError, d as EdgeMigrationGate, f as EdgeMigrationLeaseError, g as EdgeMigrationRetiredError, h as EdgeOutboxRecord, i as EdgeReadOnlyInspection, j as EdgeScope, k as EdgeStats, l as edgeMessageFingerprint, m as edgeScopeKey, n as inspectEdgeScopeReadOnly, s as stableIdempotency } from './sqlite-edge-store-Bc15DvpX.js';
79
5
 
80
6
  interface SyncInsertResult extends InsertMessageResult {
81
7
  disposition: "committed" | "queued";
@@ -100,6 +26,9 @@ interface SyncReport {
100
26
  lastSyncedAt?: string;
101
27
  lastError?: string;
102
28
  failureRetryable?: boolean;
29
+ migrationState?: EdgeMigrationGateState;
30
+ migrationOperationId?: string;
31
+ migrationLeaseExpiresAt?: string;
103
32
  }
104
33
  interface SyncDiagnostics extends BridgeDiagnostics {
105
34
  remoteReachable: boolean | null;
@@ -121,9 +50,12 @@ interface SyncDiagnostics extends BridgeDiagnostics {
121
50
  lastOutboundSyncAt?: string;
122
51
  lastInboundSyncAt?: string;
123
52
  lastSyncAttemptAt?: string;
124
- syncLoopState: "disabled" | "idle" | "running" | "backoff" | "stopped" | "failed";
53
+ syncLoopState: "disabled" | "idle" | "running" | "backoff" | "paused" | "stopped" | "failed";
125
54
  syncLoopError?: string;
126
55
  remoteError?: string;
56
+ migrationState: EdgeMigrationGateState;
57
+ migrationOperationId?: string;
58
+ migrationLeaseExpiresAt?: string;
127
59
  }
128
60
  interface SyncingBridgeStoreOptions {
129
61
  now?: () => Date;
@@ -133,6 +65,9 @@ interface SyncingBridgeStoreOptions {
133
65
  leaseMs?: number;
134
66
  autoSync?: boolean;
135
67
  idleDelayMs?: number;
68
+ edgeDrainLease?: EdgeDrainLease;
69
+ /** A maintenance caller may retain the edge while closing the sync wrapper. */
70
+ closeEdge?: boolean;
136
71
  }
137
72
  declare function isRetryableSyncError(error: unknown): boolean;
138
73
  declare class SyncingBridgeStore implements BridgeStore {
@@ -146,6 +81,8 @@ declare class SyncingBridgeStore implements BridgeStore {
146
81
  private readonly leaseMs;
147
82
  private readonly autoSync;
148
83
  private readonly idleDelayMs;
84
+ private edgeDrainLease?;
85
+ private readonly closeEdge;
149
86
  private remoteReady;
150
87
  private stopped;
151
88
  private wakeTimer;
@@ -158,6 +95,8 @@ declare class SyncingBridgeStore implements BridgeStore {
158
95
  private loopState;
159
96
  constructor(edge: SQLiteEdgeStore, remote: BridgeStore, principal: BridgePrincipal, options?: SyncingBridgeStoreOptions);
160
97
  initialize(): Promise<void>;
98
+ /** Replace a renewed maintenance lease without creating another edge wrapper. */
99
+ setDrainLease(lease: EdgeDrainLease | undefined): void;
161
100
  private startTransportLoop;
162
101
  private wait;
163
102
  private wakeTransport;
@@ -235,4 +174,4 @@ declare class SQLiteBridgeStore implements BridgeStore {
235
174
  close(): Promise<void>;
236
175
  }
237
176
 
238
- export { type CachedMessagePage, EdgeConflictError, type EdgeEnqueueResult, type EdgeOutboxRecord, type EdgeScope, type EdgeStats, type PendingMessage, SQLiteBridgeStore, SQLiteEdgeStore, type SyncDiagnostics, type SyncInsertResult, type SyncReport, SyncingBridgeStore, type SyncingBridgeStoreOptions, edgeMessageFingerprint, edgeScopeKey, isRetryableSyncError, stableIdempotency };
177
+ export { type CachedMessagePage, EdgeDrainLease, EdgeMigrationGateState, PendingMessage, SQLiteBridgeStore, SQLiteEdgeStore, type SyncDiagnostics, type SyncInsertResult, type SyncReport, SyncingBridgeStore, type SyncingBridgeStoreOptions, isRetryableSyncError };
package/dist/sqlite.js CHANGED
@@ -1,26 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  EdgeConflictError,
4
+ EdgeMigrationDrainingError,
5
+ EdgeMigrationLeaseError,
6
+ EdgeMigrationRetiredError,
4
7
  SQLiteEdgeStore,
5
8
  SyncingBridgeStore,
6
9
  edgeMessageFingerprint,
7
10
  edgeScopeKey,
11
+ inspectEdgeScopeReadOnly,
8
12
  isRetryableSyncError,
9
13
  stableIdempotency
10
- } from "./chunk-Q6PPMXJC.js";
14
+ } from "./chunk-SV3SVBYS.js";
11
15
  import {
12
16
  SQLiteBridgeStore
13
- } from "./chunk-5GMB3A77.js";
14
- import "./chunk-NCO7RLBM.js";
17
+ } from "./chunk-DQOZJJZ2.js";
18
+ import "./chunk-GTK4KOED.js";
15
19
  import "./chunk-VOQIDTCJ.js";
16
20
  import "./chunk-BZLMF5O5.js";
17
21
  export {
18
22
  EdgeConflictError,
23
+ EdgeMigrationDrainingError,
24
+ EdgeMigrationLeaseError,
25
+ EdgeMigrationRetiredError,
19
26
  SQLiteBridgeStore,
20
27
  SQLiteEdgeStore,
21
28
  SyncingBridgeStore,
22
29
  edgeMessageFingerprint,
23
30
  edgeScopeKey,
31
+ inspectEdgeScopeReadOnly,
24
32
  isRetryableSyncError,
25
33
  stableIdempotency
26
34
  };
@@ -1,6 +1,6 @@
1
1
  # Agent Bridge v2 architecture
2
2
 
3
- Status: living architecture. Version 0.3.0 was released on July 15, 2026.
3
+ Status: living architecture for the 0.4.x package line.
4
4
 
5
5
  ## Product boundary
6
6
 
@@ -31,6 +31,26 @@ A standalone Agent Bridge API sits between remote clients and PostgreSQL. Client
31
31
 
32
32
  Supabase can still host the database. Existing direct PostgREST deployments remain available through an explicitly named legacy adapter while they migrate.
33
33
 
34
+ ### Deployment separates schema and request authority
35
+
36
+ The schema-owner connection runs migrations as an explicit one-shot operation. A
37
+ separate bootstrap step creates or updates a restricted runtime login and grants only
38
+ the database-derived runtime role. The gateway starts only after both steps succeed and
39
+ receives no schema-owner authority. Its readiness check fails on an incomplete
40
+ migration plan, unsupported PostgreSQL major, row-isolation drift, protected catalog
41
+ drift, or an invalid owner-control membership graph.
42
+
43
+ The repository Compose stack publishes the gateway and PostgreSQL on loopback for
44
+ development. Production deployments put PostgreSQL on a private network, terminate TLS
45
+ before every non-loopback gateway, and source credentials from the platform's secret
46
+ manager. A persistent volume is runtime storage, not a backup.
47
+
48
+ Upgrade order is database backup, migration, runtime bootstrap, readiness, then client
49
+ or gateway rollout. Because an older image may reject the newer migration plan, schema
50
+ rollback cannot rely on starting the previous container image. Operators apply a
51
+ forward fix or restore a verified native DR bundle into a fresh target before switching
52
+ authority. [The deployment guide](deployment.md) records the operational procedure.
53
+
34
54
  ### SQLite is local storage
35
55
 
36
56
  SQLite WAL provides local-only operation, a durable outbox, an inbox cache, cursor state, and a cheap pending-work check. Outbox writes use `synchronous=FULL`. It is not the canonical multi-machine database.
@@ -39,7 +59,17 @@ The gateway outbox queues immutable message publication. Receipt and lease mutat
39
59
 
40
60
  Long-lived gateway MCP clients own a cancellable transport loop that replays publications and refreshes the inbox cache with bounded exponential backoff. It is transport maintenance, not agent monitoring. Manual MCP and CLI sync use the same replay path. A publication timeout after the gateway may have committed remains queued under its stable idempotency key. A retry resolves to the original immutable message instead of publishing a duplicate.
41
61
 
42
- Local initialization must be idempotent. Every connection uses a bounded busy timeout. The minimum supported SQLite build must include the WAL-reset corruption fix. Node 22.23.1 on the current development machine embeds SQLite 3.51.3, which includes that fix.
62
+ Local initialization must be idempotent. Concurrent initialization and schema upgrade
63
+ use a 15-second minimum busy-retry window. Normal database operations retain the
64
+ configured timeout. The minimum supported SQLite build must include the WAL-reset
65
+ corruption fix. Node 22.23.1 on the current development machine embeds SQLite 3.51.3,
66
+ which includes that fix.
67
+
68
+ On Windows, an opening store verifies the private parent and main database before it
69
+ connects. It applies and verifies explicit ACLs on live WAL and shared-memory sidecars
70
+ after the connection establishes WAL and completes the serialized schema transaction.
71
+ This avoids first-open sidecar churn between concurrent processes. A sidecar that is
72
+ replaced during one ACL validation still fails closed.
43
73
 
44
74
  ### Portable archives cross storage engines without copying runtime state
45
75
 
@@ -123,10 +153,15 @@ PostgreSQL backup takes a repeatable-read exported snapshot while holding the na
123
153
  advisory fence. `pg_dump` captures only the `agent_bridge` schema. A separate canonical
124
154
  role inventory records the bounded role shells, membership options, and global or
125
155
  schema-scoped default ACLs that a schema-only dump omits. The dump excludes data for
126
- agent instances, rate-limit buckets, request authorities, and archive transaction
127
- authorizations. The manifest binds migration inventory, table counts, claimed-delivery
128
- count, tool major, role inventory, and the security, row-isolation, owner-control, and
129
- portable-archive readiness definitions.
156
+ agent instances, rate-limit buckets, request authorities, endpoint-migration challenge
157
+ rows, and archive transaction authorizations. The manifest binds migration
158
+ inventory, table counts, claimed-delivery count, tool major, role inventory, and the
159
+ security, row-isolation, owner-control, and portable-archive readiness definitions.
160
+
161
+ The immutable gateway authority UUID is ordinary PostgreSQL data, so native backup and
162
+ restore preserve it. The UUID proves that a restored target represents the same logical
163
+ authority. It does not fence a live clone. Operators must still keep the source and
164
+ restored target from serving traffic at the same time.
130
165
 
131
166
  PostgreSQL restore requires a dedicated fresh database, the same database name and
132
167
  server major, an exact-major `pg_restore`, and superuser authority. It restores role
@@ -250,6 +285,189 @@ Protocol logic depends on a `BridgeStore` interface, not a provider SDK. The ini
250
285
 
251
286
  Host and wakeup adapters are independent from storage. A client manifest declares identity injection, supported wake modes, config locations, health checks, and install actions for Codex, Claude Code, Claude Desktop, OpenClaw, or a generic MCP client. The v1 manifest key named `runtime` identifies an installation target for compatibility. It does not identify a unique running process. The `codex` adapter configures the profile shared by the Codex CLI and the Codex surface in the ChatGPT desktop app. Scripts, CI jobs, daemons, and application services may instead call the CLI or HTTPS API or embed the Node library without pretending to be a harness adapter.
252
287
 
288
+ Client lifecycle ownership is separate from registration existence. Local inspection
289
+ classifies a requested Codex, Claude Code, or Claude Desktop contract as absent,
290
+ unmanaged, managed, or drifted by comparing the exact identity, stable instance key,
291
+ backend path, scope, launch contract, and nonsecret registration locator. Desktop
292
+ locators name the normalized host config, Codex locators name the active profile
293
+ config, and Claude Code local or project locators include the invocation directory
294
+ because the native CLI exposes no stronger target. Adoption is plan-first and writes
295
+ only owner-private, credential-free metadata when `--apply` is explicit and the
296
+ unmanaged registration is exact. Backend files and their immediate parents must pass
297
+ the owner-only no-link policy. Codex JSON comparisons ignore diagnostic/status fields
298
+ and property order but require exactly the three Agent Bridge environment keys;
299
+ Claude Code connectivity status is not registration state. Claude Desktop inspection
300
+ shares the installer's validated absolute launch resolver. Applied adoption
301
+ re-inspects its postcondition. It does not read backend values or mutate the
302
+ registration.
303
+ Enrollment-based first-time provisioning retains strict registration and backend-file
304
+ collision refusal. See
305
+ [ADR 0004](decisions/0004-client-lifecycle-and-endpoint-migration.md).
306
+
307
+ Managed-client repair, update, and uninstall use a local crash-safe substrate. Strict
308
+ owner-private metadata is the mutation authority. Runtime plus stable instance locate
309
+ the metadata, and identity must match both the record and the immutable repair or
310
+ update request. Uninstall also binds the identity. Repair restores its launch contract. Update validates a new launch
311
+ before it creates the journal and stores only the identity, normalized command,
312
+ arguments, scope, and fixed Agent Bridge environment key names.
313
+ Caller backend, scope, and host-config flags are rejected. Owner-private revisioned
314
+ manifests live under
315
+ `~/.agent-bridge/operations/<uuid>` with private snapshots and an exclusive lock keyed
316
+ by runtime plus stable instance. Manifest publication fsyncs files, atomically renames,
317
+ verifies private paths, and fsyncs directories where supported. Snapshot publication
318
+ uses atomic no-replace links, so residue from an interrupted manifest update cannot be
319
+ overwritten. Sensitive file access pins its immediate directory. Operation-root and
320
+ target identities remain pinned throughout creation and destructive cleanup. The root
321
+ and locks identities remain pinned throughout stale recovery, and the artifact-directory
322
+ identity remains pinned across enumeration and every read. A typed credential-agnostic
323
+ request and immutable plan record non-sensitive locators, unique no-replace before and
324
+ after artifacts, and expected digests. Backend artifacts contain only a role, file
325
+ identity, and private or repairable policy state. They never contain backend bytes,
326
+ credentials, URLs, or workspace values. Begin is lock-covered and refuses another
327
+ unfinished operation; any blocked journal fences new mutation, including resume. Resume
328
+ is same-host only. The journal advances through prepared,
329
+ snapshotted, in-progress, applied, cleaning, and committed. Inspection separately
330
+ reports resumable, classification-required, blocked, or complete.
331
+
332
+ Repair and uninstall journals use operation format version 3. New update and reverse
333
+ rollback journals use v4. A v4 update request and terminal completion retain a bounded
334
+ credential-free inverse contract: prior managed metadata, prior exact registration
335
+ contract, and forward metadata and registration digests. Version 2 manifests retain
336
+ their released request interpretation and remain inspectable. A non-terminal version 2
337
+ record is blocked because it cannot prove the identity-bound request needed for these
338
+ public mutations.
339
+
340
+ Before a mutation, repair, update, and uninstall compare the full strict metadata record after
341
+ acquiring the lock. Each non-metadata step repeats that authority check. Registration
342
+ proofs include a bounded observation of the Agent Bridge entry and its full target
343
+ contract. Unknown environment keys, malformed values, unsafe arguments, an unexpected
344
+ scope, or an opaque registration shape stop before a journal is created.
345
+
346
+ Native repair and update remove the managed entry, prove absence, add the exact target,
347
+ and change metadata last when the launch changes. A native update command is one
348
+ executable contract. Bare commands cannot contain argument separators, while absolute
349
+ paths must resolve to executable files before an update is journaled. Codex runs get,
350
+ remove, and add with the recorded profile home over the supplied environment. Claude
351
+ Code uses its recorded scope, working directory, and supplied environment. Claude
352
+ Desktop reads its recorded config without following links or link ancestors, replaces
353
+ only `mcpServers.agent-bridge`, writes through an operation-scoped private temporary
354
+ file, and verifies the published entry. Node cannot make that update atomic with an
355
+ uncooperative same-user Desktop writer. The code pins and rechecks the file and parent
356
+ identities immediately before rename, but this is an advisory race boundary, not an OS
357
+ transaction guarantee. Windows accepts an already owner-private backend path and
358
+ refuses a non-private backend instead of claiming a safe permission tightening.
359
+
360
+ Uninstall removes a managed registration, proves its absence, deletes the private
361
+ backend file, then deletes the management metadata. It does not tighten an unsafe
362
+ backend before deletion. A backend that is not already private stops the plan before
363
+ it removes the registration. Uninstall never rolls a completed step back. Desktop
364
+ deletes only `mcpServers.agent-bridge` and preserves unrelated JSON. Before a backend
365
+ or metadata unlink, the code checks the private parent and file identities. POSIX
366
+ syncs that parent after deletion. Windows reports verified deletion with unavailable
367
+ directory durability. Node pathname deletion cannot make this atomic against an
368
+ uncooperative same-user writer, so that race remains advisory.
369
+
370
+ Rollback is an explicit update-only operation. `clients rollback` locates authority
371
+ only from a committed same-host v4 update record and requires the recorded identity as
372
+ an assertion. It rejects a changed forward registration or metadata state before it
373
+ creates a new reverse journal. Native rollback removes the forward registration,
374
+ proves absence, adds the prior registration, proves it, and writes prior metadata last.
375
+ Desktop replaces only `mcpServers.agent-bridge` and writes prior metadata last. The
376
+ reverse journal retains its source operation UUID and inverse contract so generic
377
+ resume needs no caller-provided backend, scope, command, runtime, or instance. A
378
+ failed forward update is never compensated automatically. Repair has no rollback, and
379
+ uninstall recovery requires re-enrollment.
380
+
381
+ Native Windows ACL checks start uncached for each acquired or resumed mutation lock.
382
+ While that lock remains held, later directory checks may reuse the result only for the
383
+ same directory path, device, and inode. File checks always use the native policy.
384
+ Passive inspection starts without a cache, and POSIX mode checks run on every access.
385
+ A same-user process can still change an ACL on an unchanged directory or replace it
386
+ with a directory that reuses the same filesystem identity. Local mode already treats
387
+ that OS user as trusted. Agent Bridge does not delete a cached directory during a
388
+ successful managed-client mutation.
389
+
390
+ Cleanup durably records per-artifact intent, verifies and unlinks the pinned artifact,
391
+ then records POSIX directory sync or explicit Windows unavailability. An absent file
392
+ is resumable only when intent predates the cleanup attempt. Inspection also recognizes
393
+ one verified after artifact left by an interrupted manifest publication. Other missing
394
+ or extra files block. Committed means verified writes and removed artifacts. Its
395
+ manifest drops requests, steps, locators, digests, and artifact metadata. A v4 update
396
+ completion retains only its inverse contract. A v5 stage and a v6 migration phase
397
+ retain bounded credential-free contracts. Other terminal completions retain the
398
+ operation kind, step count, completion time, and cleanup durability for audit. Uninstall
399
+ adds no rolled-back state. Physical erasure remains outside the filesystem contract.
400
+
401
+ `clients resume <operation-id>` accepts stored v3 repair, update, and uninstall
402
+ operations, supported v4 update and rollback operations, a committed v5 migration
403
+ stage, and v6 endpoint-migration operations, with optional same-host stale-lock
404
+ recovery. It derives authority from the immutable request. It rejects v2 records and
405
+ cannot take replacement caller authority. Generic resume finishes an uninstall
406
+ interrupted after metadata deletion and a reverse rollback interrupted after any
407
+ recorded boundary.
408
+
409
+ ### Gateway client migration staging prepares a later cutover
410
+
411
+ `clients migrate stage <runtime>` creates a private successor backend and a
412
+ credential-free stage record. It leaves the managed registration, active backend, and
413
+ source edge gate state unchanged after initializing the gate schema. It requires a
414
+ rotation enrollment that keeps the predecessor valid through a future grace cutoff.
415
+ The stage record binds the managed
416
+ identity, source and target endpoint digests, both credential IDs, the source edge
417
+ database path and scope key, and the grace cutoff. It does not store either token or a
418
+ raw gateway URL.
419
+
420
+ The source backend must bind an absolute, normalized edge database file. Relative and
421
+ in-memory paths cannot identify the same durable outbox across harness working
422
+ directories, so staging rejects them.
423
+
424
+ An applied stage probes the active and staged bearer credentials, records the
425
+ enrollment credential IDs, initializes the recorded source edge database, and requires
426
+ its migration gate to be active. SQLite rejects a new outbox insert as soon as that
427
+ gate begins draining, including inserts from a client that had already opened the
428
+ database. These checks prepare a later drain and cutover. They do not authorize either
429
+ action.
430
+
431
+ The stage does not prove that source and target reach the same database authority. A
432
+ later endpoint cutover may use alternate URLs only after it dynamically attests one
433
+ logical authority. The authority identifier must survive clone and restore. Moving to
434
+ an independent database requires a separate owner-mediated fence and is outside this
435
+ staging command. A copied authority identifier does not prove that a live clone is
436
+ fenced.
437
+
438
+ ### Endpoint cutover uses a separate v6 journal
439
+
440
+ `clients migrate cutover <stage-operation-id> --exclusive-edge --apply` accepts only a
441
+ committed v5 stage with its retained contract. It checks both live gateway status
442
+ responses under HTTP 2.1. Each response must bind the recorded workspace, principal,
443
+ and credential ID, and both must return the recorded immutable authority ID. The
444
+ successor must grant `status:read` and `messages:write`. The source issues a direct
445
+ predecessor-to-successor route challenge that the target consumes before the command
446
+ changes an edge gate or host registration.
447
+
448
+ The forward journal records full credential-free source and target metadata and
449
+ registration proofs, source and target edge paths, a normalized credential-free source
450
+ gateway URL, the v5 stage contract, and digest bindings for each proof. Once that
451
+ journal exists, every mutation uses a fresh same-successor route proof issued from the
452
+ retained source URL and consumed by the target. The source edge replays through the
453
+ target gateway with the successor credential. It creates a durable source drain lease.
454
+ The edge rejects new outbox inserts while draining. The lease worker must publish all
455
+ work and prove a zero outbox before a native remove or add, a Desktop replacement, or
456
+ the metadata-last switch. The command renews and reasserts the lease immediately before
457
+ each host write. It rechecks the live target authority before every mutation. Source
458
+ and target edge files must be distinct private regular files. The command rejects a
459
+ managed cohort that shares the source edge, including a hardlink alias.
460
+ `--exclusive-edge` covers unmanaged publishers that cannot be enumerated.
461
+
462
+ Finalization requires a fresh assertion after the grace cutoff. It verifies the
463
+ successor's live status binding and retires the retained source edge. Returning to a
464
+ former endpoint requires a new owner credential rotation and a separate forward
465
+ cutover. Cutover and finalization use exact durable before and after proofs for restart
466
+ classification. A replacement worker can resume a draining edge only after the prior
467
+ lease expires. Dry runs use read-only edge inspection. They do not initialize SQLite,
468
+ create sidecars, or contact gateways. A dry run refuses an edge with live WAL sidecars
469
+ because an immutable read could miss uncheckpointed outbox state.
470
+
253
471
  ### v1 compatibility is additive
254
472
 
255
473
  The MCP tools `post_context`, `get_context`, and `ack_context` remain available. Existing CLI verbs and flags remain accepted. Text responses remain present while v2 adds structured result data.
@@ -314,6 +532,33 @@ The stored definition is scoped to the control plane and its dependencies. It in
314
532
 
315
533
  Migration 012 removes direct runtime reads of credential digest material and establishes transaction-bound request authority. Node computes the credential hash, so PostgreSQL never receives the raw bearer credential. Request authority, security accounting, and domain work use one checked-out PostgreSQL client and one explicit transaction. A security-definer opener matches the credential ID and hash against current revocation, expiry, successor-grace, agent, and workspace state in one locked statement, then records the database-derived credential, workspace, principal, and scopes for that backend and transaction. Instance remains validated same-principal attribution and never establishes authority. Claim, cancel, and requeue reuse the outer transaction without nested `BEGIN`.
316
534
 
535
+ Migration 017 adds an immutable singleton authority UUID and a separate bound opener.
536
+ The released opener retains its original result shape. The bound opener returns the
537
+ same credential, workspace, principal, and scopes with the authority UUID in the same
538
+ request transaction. Runtime readiness checks the singleton row, mutation trigger,
539
+ function catalog, and direct table and column privileges. The UUID is returned only by
540
+ authenticated gateway status. It is not a capability field, log field, or readiness
541
+ field.
542
+
543
+ Migration 018 adds issue and consume operations for a gateway-only HTTP 2.1
544
+ endpoint-migration challenge. The issuer provides a lowercase 64-character hexadecimal
545
+ challenge, expected authority UUID, and direct active successor credential. The
546
+ successor consumes the same commitment with its active transaction-bound authority.
547
+ PostgreSQL hashes the challenge with a domain separator before persistence and retains
548
+ no raw challenge in records, events, or replies. Challenges expire within 60 seconds,
549
+ can be consumed once, and do not permit endpoint cutover. The migration 018
550
+ predecessor-to-successor relation alone does not prove that two distinct URLs share
551
+ live database state. The migration extends the credential-security catalog and records
552
+ endpoint and owner-control v5 attestations before readiness succeeds.
553
+
554
+ Migration 019 keeps the direct predecessor-to-successor challenge for initial
555
+ preflight and also permits one active successor credential to issue and consume a
556
+ challenge across the retained source and target URLs. It preserves the migration 018
557
+ challenge data, events, security boundaries, and native DR treatment. Endpoint v2 and
558
+ owner-control v6 attestations seal the updated catalog. Authenticated HTTP 2.1
559
+ capabilities report the credential's granted scopes so cutover can reject a successor
560
+ that lacks `status:read` or `messages:write` before it begins a journal.
561
+
317
562
  The domain savepoint separates expected rejected domain work from security effects: expected scope and rate accounting can commit while failed domain mutations are rolled back. An abort before commit rolls the whole request back. Once commit dispatch begins, the gateway never retries and reports `mutation_outcome_unknown` if the outcome is ambiguous. A failed rollback discards the pooled connection.
318
563
 
319
564
  Migration 013 enables and forces RLS on the five domain tables: messages, receipts, deliveries, delivery events, and agent instances. A database-specific no-login role owns those tables. Two more no-login roles isolate request-context reads and delivery-event writes. The gateway login inherits only the runtime role, which has `NOBYPASSRLS` and cannot inherit any of the owner roles. Zero-argument stable context functions read the workspace and principal from the authority row bound to the current backend, transaction ID, and session user. Policies call those functions through scalar subqueries so PostgreSQL evaluates them as InitPlans rather than once per row.
@@ -330,7 +575,7 @@ Limits apply to content bytes, payload bytes, metadata depth, target count, batc
330
575
 
331
576
  The CLI provides `init`, `doctor`, `status`, `demo`, `send`, `inbox`, `history`, `claim`, `ack`, `nack`, `watch`, `sync`, portable archive, and migration commands. Existing `post` and `get` aliases remain available.
332
577
 
333
- `/readyz` reports storage and schema readiness. Authenticated HTTP status reports the bound principal, provider schema, delivery counts, and the oldest due delivery. CLI `doctor` and `status` use a separate client-status contract that also reports local edge state and remote gateway reachability.
578
+ `/readyz` reports storage and schema readiness. Authenticated HTTP status reports the bound principal, provider schema, delivery counts, and the oldest due delivery. When production request authority is active, it also returns additive `gatewayAuthorityId` and `credentialId` fields. They are optional in HTTP 2.1. CLI `doctor` and `status` use a separate client-status contract that also reports local edge state and remote gateway reachability.
334
579
 
335
580
  Gateway responses carry a request ID, including stable error envelopes. Scope failures return required scopes under `error.details`. Rate failures return a rounded retry delay in both the HTTP header and error details. Authenticated Prometheus output counts requests, errors, timeouts, and authentication failures. Responses, metrics, and append-only security events exclude bearer tokens, hashes, database URLs, payload bodies, arbitrary metadata, and credential material.
336
581
 
@@ -346,10 +591,11 @@ source, a tag-to-version check, and npm provenance. The npm environment does not
346
591
  currently require a human reviewer, so the release process must not claim a manual
347
592
  approval gate.
348
593
 
349
- GitHub immutable releases are enabled, and GitHub reports release `v0.3.0` as
594
+ GitHub immutable releases are enabled, and GitHub reports release `v0.3.1` as
350
595
  immutable. A tag ruleset also blocks update and deletion of `v*` tags with no bypass
351
- actor. Future recovery dispatches must run the workflow at the release tag ref so the
352
- provenance workflow identity and checked-out source commit do not diverge.
596
+ actor. Recovery dispatches must run the workflow at the release tag ref so the
597
+ provenance workflow identity and checked-out source commit do not diverge. Release
598
+ packaging also waits for the exact commit to pass the full `test.yml` matrix on `main`.
353
599
 
354
600
  ## Acceptance checks
355
601