@creatornader/agent-bridge 0.3.1 → 0.5.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 (90) hide show
  1. package/CHANGELOG.md +222 -1
  2. package/README.md +312 -12
  3. package/ROADMAP.md +88 -7
  4. package/SECURITY.md +78 -5
  5. package/SKILL.md +108 -1
  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-CNBKdYXh.d.ts} +2 -1
  9. package/dist/{bridge-store-DPVSCwfs.d.ts → bridge-store-ByJ6CaJk.d.ts} +3 -0
  10. package/dist/bridge.d.ts +25 -8
  11. package/dist/bridge.js +19 -9
  12. package/dist/{chunk-MBPZXTJA.js → chunk-7VJI2MXI.js} +285 -42
  13. package/dist/chunk-7VJI2MXI.js.map +1 -0
  14. package/dist/{chunk-4VB44KJU.js → chunk-AWE3DFZV.js} +3 -2
  15. package/dist/chunk-AWE3DFZV.js.map +1 -0
  16. package/dist/{chunk-GADH244D.js → chunk-E4OWHGUE.js} +16 -5
  17. package/dist/chunk-E4OWHGUE.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-SPSMZOCJ.js → chunk-HUHG7XTB.js} +6 -5
  21. package/dist/chunk-HUHG7XTB.js.map +1 -0
  22. package/dist/{chunk-5GMB3A77.js → chunk-IVG4XWWS.js} +26 -14
  23. package/dist/chunk-IVG4XWWS.js.map +1 -0
  24. package/dist/{chunk-WAI2IYIQ.js → chunk-P635FAN4.js} +50 -7
  25. package/dist/chunk-P635FAN4.js.map +1 -0
  26. package/dist/{chunk-45BZGB77.js → chunk-QNXHDOJ5.js} +218 -24
  27. package/dist/chunk-QNXHDOJ5.js.map +1 -0
  28. package/dist/{chunk-UAHMP6GC.js → chunk-QPC6JHA3.js} +10 -7
  29. package/dist/chunk-QPC6JHA3.js.map +1 -0
  30. package/dist/{chunk-Q6PPMXJC.js → chunk-SN7PIVDF.js} +377 -21
  31. package/dist/chunk-SN7PIVDF.js.map +1 -0
  32. package/dist/{chunk-A6W4QALF.js → chunk-TC6A7GMF.js} +2 -2
  33. package/dist/{chunk-2DFNOLMO.js → chunk-XDNT6ZNZ.js} +9 -6
  34. package/dist/chunk-XDNT6ZNZ.js.map +1 -0
  35. package/dist/{chunk-CEXBMOI5.js → chunk-Z6JP6MYD.js} +2 -2
  36. package/dist/cli.js +5867 -438
  37. package/dist/cli.js.map +1 -1
  38. package/dist/client-runtime.d.ts +4 -2
  39. package/dist/client-runtime.js +7 -7
  40. package/dist/contracts/generate.js +1 -1
  41. package/dist/dr.d.ts +9 -2
  42. package/dist/dr.js +6 -4
  43. package/dist/{gateway-CggQ8vTC.d.ts → gateway-CW-61lvs.d.ts} +27 -3
  44. package/dist/gateway-main.js +71 -7
  45. package/dist/gateway-main.js.map +1 -1
  46. package/dist/gateway.d.ts +3 -3
  47. package/dist/gateway.js +3 -3
  48. package/dist/index.js +8 -8
  49. package/dist/{postgres-bridge-store-Bfd3hRId.d.ts → postgres-bridge-store-C9KG9Z69.d.ts} +1 -1
  50. package/dist/postgres-production-preflight.d.ts +32 -0
  51. package/dist/postgres-production-preflight.js +249 -0
  52. package/dist/postgres-production-preflight.js.map +1 -0
  53. package/dist/server.js +8 -8
  54. package/dist/sqlite-edge-store-CdMIOrho.d.ts +148 -0
  55. package/dist/sqlite.d.ts +19 -80
  56. package/dist/sqlite.js +11 -3
  57. package/docs/architecture-v2.md +292 -15
  58. package/docs/decisions/0004-client-lifecycle-and-endpoint-migration.md +204 -0
  59. package/docs/deployment.md +349 -0
  60. package/docs/troubleshooting.md +23 -0
  61. package/llms.txt +69 -1
  62. package/openapi/agent-bridge-v2.openapi.json +1151 -124
  63. package/package.json +10 -2
  64. package/schemas/agent-bridge-v2.capabilities.json +26 -1
  65. package/schemas/agent-bridge-v2.mcp.json +11 -1
  66. package/schemas/agent-bridge-v2.schema.json +167 -0
  67. package/sql/migrations/007_runtime_role.sql +8 -4
  68. package/sql/migrations/013_row_isolation.sql +71 -8
  69. package/sql/migrations/014_owner_control_plane.sql +124 -31
  70. package/sql/migrations/015_portable_archives.sql +143 -41
  71. package/sql/migrations/016_native_dr_fence.sql +102 -35
  72. package/sql/migrations/017_gateway_authority_binding.sql +99 -0
  73. package/sql/migrations/018_endpoint_migration_challenges.sql +1054 -0
  74. package/sql/migrations/019_endpoint_migration_same_successor.sql +348 -0
  75. package/sql/migrations/020_managed_authority_compat.sql +698 -0
  76. package/sql/migrations/021_native_backup_reader.sql +421 -0
  77. package/sql/setup.sql +1 -3
  78. package/dist/chunk-2DFNOLMO.js.map +0 -1
  79. package/dist/chunk-45BZGB77.js.map +0 -1
  80. package/dist/chunk-4VB44KJU.js.map +0 -1
  81. package/dist/chunk-5GMB3A77.js.map +0 -1
  82. package/dist/chunk-GADH244D.js.map +0 -1
  83. package/dist/chunk-MBPZXTJA.js.map +0 -1
  84. package/dist/chunk-NCO7RLBM.js.map +0 -1
  85. package/dist/chunk-Q6PPMXJC.js.map +0 -1
  86. package/dist/chunk-SPSMZOCJ.js.map +0 -1
  87. package/dist/chunk-UAHMP6GC.js.map +0 -1
  88. package/dist/chunk-WAI2IYIQ.js.map +0 -1
  89. /package/dist/{chunk-A6W4QALF.js.map → chunk-TC6A7GMF.js.map} +0 -0
  90. /package/dist/{chunk-CEXBMOI5.js.map → chunk-Z6JP6MYD.js.map} +0 -0
@@ -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,42 @@ 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
+ Runtime bootstrap runs in one transaction. Before it changes an existing login, it
44
+ rejects every database-derived Agent Bridge authority name, unexpected role edge, and
45
+ object owner. PostgreSQL 16 and newer may add one managed-provider administrator edge
46
+ when the schema owner creates the login. The bootstrap validates that edge exactly. It
47
+ does not adopt a `NOLOGIN` shell restored by native DR.
48
+
49
+ The schema owner can be a true superuser or a managed-provider administrator with
50
+ `CREATEROLE`. That authority is sufficient for migrations. Native backup requires the
51
+ schema owner to be a true superuser or hold `BYPASSRLS`. A restore target validates the
52
+ intentionally suspended restored schema-owner shell through its superuser session. PostgreSQL 16 and newer may
53
+ split its authority over an internal role between a bootstrap administrator row and a
54
+ schema-owner inherit-and-set row. Native DR may restore the same effective authority as
55
+ one combined bootstrap-superuser row. Security checks aggregate the effective options
56
+ but also validate each allowed physical grant shape. Registered operator and archive
57
+ memberships remain single, schema-owner-granted edges without the admin option.
58
+
59
+ The repository Compose stack publishes the gateway and PostgreSQL on loopback for
60
+ development. Production deployments put PostgreSQL on a private network, terminate TLS
61
+ before every non-loopback gateway, and source credentials from the platform's secret
62
+ manager. A persistent volume is runtime storage, not a backup.
63
+
64
+ Upgrade order is database backup, migration, runtime bootstrap, readiness, then client
65
+ or gateway rollout. Because an older image may reject the newer migration plan, schema
66
+ rollback cannot rely on starting the previous container image. Operators apply a
67
+ forward fix or restore a verified native DR bundle into a fresh target before switching
68
+ authority. [The deployment guide](deployment.md) records the operational procedure.
69
+
34
70
  ### SQLite is local storage
35
71
 
36
72
  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 +75,17 @@ The gateway outbox queues immutable message publication. Receipt and lease mutat
39
75
 
40
76
  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
77
 
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.
78
+ Local initialization must be idempotent. Concurrent initialization and schema upgrade
79
+ use a 15-second minimum busy-retry window. Normal database operations retain the
80
+ configured timeout. The minimum supported SQLite build must include the WAL-reset
81
+ corruption fix. Node 22.23.1 on the current development machine embeds SQLite 3.51.3,
82
+ which includes that fix.
83
+
84
+ On Windows, an opening store verifies the private parent and main database before it
85
+ connects. It applies and verifies explicit ACLs on live WAL and shared-memory sidecars
86
+ after the connection establishes WAL and completes the serialized schema transaction.
87
+ This avoids first-open sidecar churn between concurrent processes. A sidecar that is
88
+ replaced during one ACL validation still fails closed.
43
89
 
44
90
  ### Portable archives cross storage engines without copying runtime state
45
91
 
@@ -123,19 +169,39 @@ PostgreSQL backup takes a repeatable-read exported snapshot while holding the na
123
169
  advisory fence. `pg_dump` captures only the `agent_bridge` schema. A separate canonical
124
170
  role inventory records the bounded role shells, membership options, and global or
125
171
  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.
172
+ agent instances, rate-limit buckets, request authorities, endpoint-migration challenge
173
+ rows, and archive transaction authorizations. The manifest binds migration
174
+ inventory, table counts, claimed-delivery count, tool major, role inventory, and the
175
+ security, row-isolation, owner-control, and portable-archive readiness definitions.
176
+
177
+ The immutable gateway authority UUID is ordinary PostgreSQL data, so native backup and
178
+ restore preserve it. The UUID proves that a restored target represents the same logical
179
+ authority. It does not fence a live clone. Operators must still keep the source and
180
+ restored target from serving traffic at the same time.
130
181
 
131
182
  PostgreSQL restore requires a dedicated fresh database, the same database name and
132
183
  server major, an exact-major `pg_restore`, and superuser authority. It restores role
133
184
  shells as `NOLOGIN`, then schema objects, memberships, and default privileges. Claimed
134
185
  deliveries become retrying deliveries with cleared leases. The restore checks migrations,
135
186
  counts, role inventory, and every readiness attestation before success. External
136
- principals remain `NOLOGIN`. A failure after target mutation begins disables new target
137
- connections. If that offlining step fails, the command reports the target as unsafe and
138
- lists residual role shells.
187
+ principals remain `NOLOGIN`. Runtime memberships are omitted. Active control and archive
188
+ memberships are omitted and receive explicit revocation events after their prior history.
189
+ A matching schema-owner role may be reused only when it is the target session user and
190
+ a true superuser. Every other role collision fails. A failure after target mutation
191
+ begins disables new target connections. If that offlining step fails, the command
192
+ reports the target as unsafe and lists residual role shells.
193
+
194
+ Managed schema owners need read access to protected ledgers and sequences before
195
+ `pg_dump` can produce a complete native backup. A database-derived `NOLOGIN`
196
+ backup-reader role receives only that access and is granted only to the schema owner.
197
+ Native backup requires that schema owner to be a true superuser or hold `BYPASSRLS`.
198
+ During restore, the target superuser may validate the intentionally suspended restored
199
+ schema-owner shell. A restricted runtime login cannot substitute for that authority.
200
+ `CREATEROLE` alone can apply migrations but cannot produce a complete native backup.
201
+ Native-backup readiness attests the role, authority, membership, and object ACLs. The
202
+ gateway and external operator roles cannot inherit the reader. This backup gate is
203
+ separate from gateway runtime readiness, so drift in an owner-only backup object cannot
204
+ take a healthy gateway out of service.
139
205
 
140
206
  PostgreSQL source and target URLs come only from
141
207
  `AGENT_BRIDGE_DR_SOURCE_DATABASE_URL` and
@@ -173,7 +239,7 @@ A receipt records that a principal read a message.
173
239
 
174
240
  A publisher stores an immutable `deliveryPolicy` with each message. Untargeted messages default to mailbox mode. Targeted messages default to leased mode with `maxAttempts: 5`, `retryBaseDelayMs: 1000`, `retryMaxDelayMs: 60000`, and `retryJitterRatio: 0.2`. Leased policy may also set `notBefore`. Mailbox messages create no deliveries, and legacy mode rejects leased policy.
175
241
 
176
- A leased delivery records executable work for a recipient. Its state is one of `pending`, `claimed`, `acked`, `retrying`, `dead`, or `cancelled`. `attempt` is lifetime-monotonic, while `cycleAttempt` resets on publisher requeue and `requeueCount` increments. Stored policy owns retry and exhaustion. Claims order due work by urgent, high, then info priority, followed by availability, message creation time, and the stable delivery ID tie-break.
242
+ A leased delivery records executable work for a recipient. Its state is one of `pending`, `claimed`, `acked`, `retrying`, `dead`, or `cancelled`. `attempt` is lifetime-monotonic, while `cycleAttempt` resets on publisher requeue and `requeueCount` increments. Stored policy owns retry and exhaustion. Claims order due work by urgent, high, then info priority, followed by availability, message creation time, and the stable delivery ID tie-break. HTTP 2.1, MCP, and CLI may optionally identify one message. That form atomically selects only the eligible delivery for the authenticated recipient and scopes claim-time expiry and exhaustion maintenance to the same message. Omitting the filter preserves claim-next behavior. The frozen HTTP 2.0 request rejects the additive field.
177
243
 
178
244
  Consumer-side `maxAttempts` on claim and `retryPolicy` on nack are compatibility inputs for one release. The service validates them and ignores them. This keeps older clients working without letting a consumer change immutable publisher intent.
179
245
 
@@ -250,6 +316,189 @@ Protocol logic depends on a `BridgeStore` interface, not a provider SDK. The ini
250
316
 
251
317
  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
318
 
319
+ Client lifecycle ownership is separate from registration existence. Local inspection
320
+ classifies a requested Codex, Claude Code, or Claude Desktop contract as absent,
321
+ unmanaged, managed, or drifted by comparing the exact identity, stable instance key,
322
+ backend path, scope, launch contract, and nonsecret registration locator. Desktop
323
+ locators name the normalized host config, Codex locators name the active profile
324
+ config, and Claude Code local or project locators include the invocation directory
325
+ because the native CLI exposes no stronger target. Adoption is plan-first and writes
326
+ only owner-private, credential-free metadata when `--apply` is explicit and the
327
+ unmanaged registration is exact. Backend files and their immediate parents must pass
328
+ the owner-only no-link policy. Codex JSON comparisons ignore diagnostic/status fields
329
+ and property order but require exactly the three Agent Bridge environment keys;
330
+ Claude Code connectivity status is not registration state. Claude Desktop inspection
331
+ shares the installer's validated absolute launch resolver. Applied adoption
332
+ re-inspects its postcondition. It does not read backend values or mutate the
333
+ registration.
334
+ Enrollment-based first-time provisioning retains strict registration and backend-file
335
+ collision refusal. See
336
+ [ADR 0004](decisions/0004-client-lifecycle-and-endpoint-migration.md).
337
+
338
+ Managed-client repair, update, and uninstall use a local crash-safe substrate. Strict
339
+ owner-private metadata is the mutation authority. Runtime plus stable instance locate
340
+ the metadata, and identity must match both the record and the immutable repair or
341
+ update request. Uninstall also binds the identity. Repair restores its launch contract. Update validates a new launch
342
+ before it creates the journal and stores only the identity, normalized command,
343
+ arguments, scope, and fixed Agent Bridge environment key names.
344
+ Caller backend, scope, and host-config flags are rejected. Owner-private revisioned
345
+ manifests live under
346
+ `~/.agent-bridge/operations/<uuid>` with private snapshots and an exclusive lock keyed
347
+ by runtime plus stable instance. Manifest publication fsyncs files, atomically renames,
348
+ verifies private paths, and fsyncs directories where supported. Snapshot publication
349
+ uses atomic no-replace links, so residue from an interrupted manifest update cannot be
350
+ overwritten. Sensitive file access pins its immediate directory. Operation-root and
351
+ target identities remain pinned throughout creation and destructive cleanup. The root
352
+ and locks identities remain pinned throughout stale recovery, and the artifact-directory
353
+ identity remains pinned across enumeration and every read. A typed credential-agnostic
354
+ request and immutable plan record non-sensitive locators, unique no-replace before and
355
+ after artifacts, and expected digests. Backend artifacts contain only a role, file
356
+ identity, and private or repairable policy state. They never contain backend bytes,
357
+ credentials, URLs, or workspace values. Begin is lock-covered and refuses another
358
+ unfinished operation; any blocked journal fences new mutation, including resume. Resume
359
+ is same-host only. The journal advances through prepared,
360
+ snapshotted, in-progress, applied, cleaning, and committed. Inspection separately
361
+ reports resumable, classification-required, blocked, or complete.
362
+
363
+ Repair and uninstall journals use operation format version 3. New update and reverse
364
+ rollback journals use v4. A v4 update request and terminal completion retain a bounded
365
+ credential-free inverse contract: prior managed metadata, prior exact registration
366
+ contract, and forward metadata and registration digests. Version 2 manifests retain
367
+ their released request interpretation and remain inspectable. A non-terminal version 2
368
+ record is blocked because it cannot prove the identity-bound request needed for these
369
+ public mutations.
370
+
371
+ Before a mutation, repair, update, and uninstall compare the full strict metadata record after
372
+ acquiring the lock. Each non-metadata step repeats that authority check. Registration
373
+ proofs include a bounded observation of the Agent Bridge entry and its full target
374
+ contract. Unknown environment keys, malformed values, unsafe arguments, an unexpected
375
+ scope, or an opaque registration shape stop before a journal is created.
376
+
377
+ Native repair and update remove the managed entry, prove absence, add the exact target,
378
+ and change metadata last when the launch changes. A native update command is one
379
+ executable contract. Bare commands cannot contain argument separators, while absolute
380
+ paths must resolve to executable files before an update is journaled. Codex runs get,
381
+ remove, and add with the recorded profile home over the supplied environment. Claude
382
+ Code uses its recorded scope, working directory, and supplied environment. Claude
383
+ Desktop reads its recorded config without following links or link ancestors, replaces
384
+ only `mcpServers.agent-bridge`, writes through an operation-scoped private temporary
385
+ file, and verifies the published entry. Node cannot make that update atomic with an
386
+ uncooperative same-user Desktop writer. The code pins and rechecks the file and parent
387
+ identities immediately before rename, but this is an advisory race boundary, not an OS
388
+ transaction guarantee. Windows accepts an already owner-private backend path and
389
+ refuses a non-private backend instead of claiming a safe permission tightening.
390
+
391
+ Uninstall removes a managed registration, proves its absence, deletes the private
392
+ backend file, then deletes the management metadata. It does not tighten an unsafe
393
+ backend before deletion. A backend that is not already private stops the plan before
394
+ it removes the registration. Uninstall never rolls a completed step back. Desktop
395
+ deletes only `mcpServers.agent-bridge` and preserves unrelated JSON. Before a backend
396
+ or metadata unlink, the code checks the private parent and file identities. POSIX
397
+ syncs that parent after deletion. Windows reports verified deletion with unavailable
398
+ directory durability. Node pathname deletion cannot make this atomic against an
399
+ uncooperative same-user writer, so that race remains advisory.
400
+
401
+ Rollback is an explicit update-only operation. `clients rollback` locates authority
402
+ only from a committed same-host v4 update record and requires the recorded identity as
403
+ an assertion. It rejects a changed forward registration or metadata state before it
404
+ creates a new reverse journal. Native rollback removes the forward registration,
405
+ proves absence, adds the prior registration, proves it, and writes prior metadata last.
406
+ Desktop replaces only `mcpServers.agent-bridge` and writes prior metadata last. The
407
+ reverse journal retains its source operation UUID and inverse contract so generic
408
+ resume needs no caller-provided backend, scope, command, runtime, or instance. A
409
+ failed forward update is never compensated automatically. Repair has no rollback, and
410
+ uninstall recovery requires re-enrollment.
411
+
412
+ Native Windows ACL checks start uncached for each acquired or resumed mutation lock.
413
+ While that lock remains held, later directory checks may reuse the result only for the
414
+ same directory path, device, and inode. File checks always use the native policy.
415
+ Passive inspection starts without a cache, and POSIX mode checks run on every access.
416
+ A same-user process can still change an ACL on an unchanged directory or replace it
417
+ with a directory that reuses the same filesystem identity. Local mode already treats
418
+ that OS user as trusted. Agent Bridge does not delete a cached directory during a
419
+ successful managed-client mutation.
420
+
421
+ Cleanup durably records per-artifact intent, verifies and unlinks the pinned artifact,
422
+ then records POSIX directory sync or explicit Windows unavailability. An absent file
423
+ is resumable only when intent predates the cleanup attempt. Inspection also recognizes
424
+ one verified after artifact left by an interrupted manifest publication. Other missing
425
+ or extra files block. Committed means verified writes and removed artifacts. Its
426
+ manifest drops requests, steps, locators, digests, and artifact metadata. A v4 update
427
+ completion retains only its inverse contract. A v5 stage and a v6 migration phase
428
+ retain bounded credential-free contracts. Other terminal completions retain the
429
+ operation kind, step count, completion time, and cleanup durability for audit. Uninstall
430
+ adds no rolled-back state. Physical erasure remains outside the filesystem contract.
431
+
432
+ `clients resume <operation-id>` accepts stored v3 repair, update, and uninstall
433
+ operations, supported v4 update and rollback operations, a committed v5 migration
434
+ stage, and v6 endpoint-migration operations, with optional same-host stale-lock
435
+ recovery. It derives authority from the immutable request. It rejects v2 records and
436
+ cannot take replacement caller authority. Generic resume finishes an uninstall
437
+ interrupted after metadata deletion and a reverse rollback interrupted after any
438
+ recorded boundary.
439
+
440
+ ### Gateway client migration staging prepares a later cutover
441
+
442
+ `clients migrate stage <runtime>` creates a private successor backend and a
443
+ credential-free stage record. It leaves the managed registration, active backend, and
444
+ source edge gate state unchanged after initializing the gate schema. It requires a
445
+ rotation enrollment that keeps the predecessor valid through a future grace cutoff.
446
+ The stage record binds the managed
447
+ identity, source and target endpoint digests, both credential IDs, the source edge
448
+ database path and scope key, and the grace cutoff. It does not store either token or a
449
+ raw gateway URL.
450
+
451
+ The source backend must bind an absolute, normalized edge database file. Relative and
452
+ in-memory paths cannot identify the same durable outbox across harness working
453
+ directories, so staging rejects them.
454
+
455
+ An applied stage probes the active and staged bearer credentials, records the
456
+ enrollment credential IDs, initializes the recorded source edge database, and requires
457
+ its migration gate to be active. SQLite rejects a new outbox insert as soon as that
458
+ gate begins draining, including inserts from a client that had already opened the
459
+ database. These checks prepare a later drain and cutover. They do not authorize either
460
+ action.
461
+
462
+ The stage does not prove that source and target reach the same database authority. A
463
+ later endpoint cutover may use alternate URLs only after it dynamically attests one
464
+ logical authority. The authority identifier must survive clone and restore. Moving to
465
+ an independent database requires a separate owner-mediated fence and is outside this
466
+ staging command. A copied authority identifier does not prove that a live clone is
467
+ fenced.
468
+
469
+ ### Endpoint cutover uses a separate v6 journal
470
+
471
+ `clients migrate cutover <stage-operation-id> --exclusive-edge --apply` accepts only a
472
+ committed v5 stage with its retained contract. It checks both live gateway status
473
+ responses under HTTP 2.1. Each response must bind the recorded workspace, principal,
474
+ and credential ID, and both must return the recorded immutable authority ID. The
475
+ successor must grant `status:read` and `messages:write`. The source issues a direct
476
+ predecessor-to-successor route challenge that the target consumes before the command
477
+ changes an edge gate or host registration.
478
+
479
+ The forward journal records full credential-free source and target metadata and
480
+ registration proofs, source and target edge paths, a normalized credential-free source
481
+ gateway URL, the v5 stage contract, and digest bindings for each proof. Once that
482
+ journal exists, every mutation uses a fresh same-successor route proof issued from the
483
+ retained source URL and consumed by the target. The source edge replays through the
484
+ target gateway with the successor credential. It creates a durable source drain lease.
485
+ The edge rejects new outbox inserts while draining. The lease worker must publish all
486
+ work and prove a zero outbox before a native remove or add, a Desktop replacement, or
487
+ the metadata-last switch. The command renews and reasserts the lease immediately before
488
+ each host write. It rechecks the live target authority before every mutation. Source
489
+ and target edge files must be distinct private regular files. The command rejects a
490
+ managed cohort that shares the source edge, including a hardlink alias.
491
+ `--exclusive-edge` covers unmanaged publishers that cannot be enumerated.
492
+
493
+ Finalization requires a fresh assertion after the grace cutoff. It verifies the
494
+ successor's live status binding and retires the retained source edge. Returning to a
495
+ former endpoint requires a new owner credential rotation and a separate forward
496
+ cutover. Cutover and finalization use exact durable before and after proofs for restart
497
+ classification. A replacement worker can resume a draining edge only after the prior
498
+ lease expires. Dry runs use read-only edge inspection. They do not initialize SQLite,
499
+ create sidecars, or contact gateways. A dry run refuses an edge with live WAL sidecars
500
+ because an immutable read could miss uncheckpointed outbox state.
501
+
253
502
  ### v1 compatibility is additive
254
503
 
255
504
  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.
@@ -294,7 +543,7 @@ must already satisfy that final policy. Node identity and file-type checks run b
294
543
  and after the native policy check. Native reparse attributes
295
544
  reject symlinks, junctions, and other reparse objects before and after DACL work.
296
545
 
297
- The schema owner is the trusted offline role administrator. It holds all three control roles with the admin option and registers eligible login roles through `register_control_member`; `revoke_control_member` removes them. Those functions are not available through HTTP or MCP. Each call is idempotent by request UUID and appends the database session actor to the membership ledger. Runtime readiness compares the active registry with the full `pg_has_role` closure and the direct `pg_auth_members` edges. It rejects unregistered operator or auditor holders, every external owner holder, missing registered grants, unsafe login attributes, extra roles inherited by a registered member, and roles that inherit a registered member. Direct edges must retain their expected grantor, admin, inherit, and set options. Only the schema owner is exempt from the broad closure check because a PostgreSQL superuser reports membership across roles. A direct `GRANT` is never a valid deployment shortcut.
546
+ The schema owner is the trusted offline role administrator. It holds all three control roles with effective admin, inherit, and set authority and registers eligible login roles through `register_control_member`; `revoke_control_member` removes them. Those functions are not available through HTTP or MCP. Each call is idempotent by request UUID and appends the database session actor to the membership ledger. Runtime readiness compares the active registry with the full `pg_has_role` closure and normalized direct `pg_auth_members` edges. It also checks the raw grant origins and option shapes. It rejects unregistered operator or auditor holders, every external owner holder, missing registered grants, unsafe login attributes, extra roles inherited by a registered member, and roles that inherit a registered member. Only the schema owner is exempt from the broad closure check because a PostgreSQL superuser reports membership across roles. A direct `GRANT` is never a valid deployment shortcut.
298
547
 
299
548
  Every protected operation first locks a member-global key for `session_user`, followed by capability keys in operator-before-auditor order. It then checks the active registry, direct membership edge, safe login attributes, and complete upstream and downstream role closure. Inventory accepts either registered capability. Registration and revocation use the same global-first lock hierarchy. Opposite operator and auditor changes for one member therefore cannot deadlock. A revocation waits for an operation that already passed authorization, while every operation begun after revocation observes the new state. A session that remains in a revoked role through `SET ROLE` cannot use that stale authorization.
300
549
 
@@ -314,6 +563,33 @@ The stored definition is scoped to the control plane and its dependencies. It in
314
563
 
315
564
  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
565
 
566
+ Migration 017 adds an immutable singleton authority UUID and a separate bound opener.
567
+ The released opener retains its original result shape. The bound opener returns the
568
+ same credential, workspace, principal, and scopes with the authority UUID in the same
569
+ request transaction. Runtime readiness checks the singleton row, mutation trigger,
570
+ function catalog, and direct table and column privileges. The UUID is returned only by
571
+ authenticated gateway status. It is not a capability field, log field, or readiness
572
+ field.
573
+
574
+ Migration 018 adds issue and consume operations for a gateway-only HTTP 2.1
575
+ endpoint-migration challenge. The issuer provides a lowercase 64-character hexadecimal
576
+ challenge, expected authority UUID, and direct active successor credential. The
577
+ successor consumes the same commitment with its active transaction-bound authority.
578
+ PostgreSQL hashes the challenge with a domain separator before persistence and retains
579
+ no raw challenge in records, events, or replies. Challenges expire within 60 seconds,
580
+ can be consumed once, and do not permit endpoint cutover. The migration 018
581
+ predecessor-to-successor relation alone does not prove that two distinct URLs share
582
+ live database state. The migration extends the credential-security catalog and records
583
+ endpoint and owner-control v5 attestations before readiness succeeds.
584
+
585
+ Migration 019 keeps the direct predecessor-to-successor challenge for initial
586
+ preflight and also permits one active successor credential to issue and consume a
587
+ challenge across the retained source and target URLs. It preserves the migration 018
588
+ challenge data, events, security boundaries, and native DR treatment. Endpoint v2 and
589
+ owner-control v6 attestations seal the updated catalog. Authenticated HTTP 2.1
590
+ capabilities report the credential's granted scopes so cutover can reject a successor
591
+ that lacks `status:read` or `messages:write` before it begins a journal.
592
+
317
593
  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
594
 
319
595
  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 +606,7 @@ Limits apply to content bytes, payload bytes, metadata depth, target count, batc
330
606
 
331
607
  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
608
 
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.
609
+ `/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
610
 
335
611
  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
612
 
@@ -346,10 +622,11 @@ source, a tag-to-version check, and npm provenance. The npm environment does not
346
622
  currently require a human reviewer, so the release process must not claim a manual
347
623
  approval gate.
348
624
 
349
- GitHub immutable releases are enabled, and GitHub reports release `v0.3.0` as
625
+ GitHub immutable releases are enabled, and GitHub reports release `v0.3.1` as
350
626
  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.
627
+ actor. Recovery dispatches must run the workflow at the release tag ref so the
628
+ provenance workflow identity and checked-out source commit do not diverge. Release
629
+ packaging also waits for the exact commit to pass the full `test.yml` matrix on `main`.
353
630
 
354
631
  ## Acceptance checks
355
632
 
@@ -0,0 +1,204 @@
1
+ # ADR 0004: Client lifecycle ownership and endpoint migration
2
+
3
+ Status: accepted
4
+
5
+ Date: 2026-07-18
6
+
7
+ ## Context
8
+
9
+ Agent Bridge can install Codex, Claude Code, and Claude Desktop registrations, but an
10
+ existing registration may have been created by an older package or by an operator.
11
+ Repair, update, uninstall, and endpoint migration must not silently take ownership of
12
+ such state. In particular, host configuration and per-client backend files may contain
13
+ credential-bearing references and queued gateway edge state.
14
+
15
+ Lifecycle unit 1 needs a safe ownership boundary before later mutating operations are
16
+ added. It must distinguish missing state, exact but unowned state, owned state, and
17
+ drift without probing a gateway or exposing backend configuration values.
18
+
19
+ ## Decision
20
+
21
+ The CLI provides two local lifecycle operations for the three automated adapters:
22
+
23
+ - `clients inspect` is read-only. The operator supplies the expected runtime,
24
+ identity, stable instance key, backend file path, launch command, and Claude Code
25
+ scope when applicable. Inspection reads only the host registration, backend path
26
+ existence, and managed metadata. It does not read backend values, contact the
27
+ gateway, interpret connectivity health, rewrite a host config, or create metadata.
28
+ - `clients adopt` uses the same exact contract. Without `--apply` it returns a plan.
29
+ With `--apply` it may create managed metadata only when inspection reports an exact
30
+ unmanaged registration. Adoption never rewrites the registration or backend file.
31
+
32
+ Inspection returns one of four states:
33
+
34
+ - `absent`: the requested registration, backend file, and managed metadata are all absent.
35
+ - `unmanaged`: the host registration, identity, instance, backend path, scope, and
36
+ launch contract are exact, the backend path and its immediate parent pass the
37
+ no-link owner-only private-path policy, and metadata is absent.
38
+ - `managed`: the exact registration and backend file are accompanied by exact managed
39
+ metadata.
40
+ - `drifted`: any other combination, including malformed or inexact registration,
41
+ missing, insecure, or linked backend residue without a registration, stale metadata,
42
+ or metadata that structurally differs from the requested contract.
43
+
44
+ Managed metadata uses schema `agent-bridge.client-management`, version 1, under the
45
+ owner-only `~/.agent-bridge/clients/` directory. It records only runtime, identity,
46
+ instance, absolute backend path, launch command and arguments, scope, and a nonsecret
47
+ registration locator. Claude Desktop records the normalized configuration path.
48
+ Codex records the active profile configuration path. Claude Code records scope and,
49
+ for local or project scope, the normalized invocation directory. Its native CLI does
50
+ not expose a stronger configuration locator. User scope has no directory context. A
51
+ later inspection from a different locator reports drift instead of guessing which
52
+ registration to mutate. Metadata never copies a token, backend setting, credential
53
+ identifier, URL, workspace, or backend file contents. Publication uses a private
54
+ same-directory temporary file, file fsync, atomic rename, private-path verification,
55
+ and directory fsync where supported.
56
+
57
+ Codex inspection compares the semantic registration fields in its real JSON MCP
58
+ description and ignores status, timeout, and transport diagnostic fields. JSON object
59
+ property order is not significant, while the environment must contain exactly the
60
+ three Agent Bridge keys. Claude Code inspection uses its native MCP description and
61
+ exact scope/removal contract but excludes connectivity status from registration
62
+ exactness. Claude Desktop inspection reads only the `agent-bridge` entry in its JSON
63
+ configuration and uses the installer's canonical resolver: an explicit executable is
64
+ validated, while the default is absolute Node plus the absolute server entry.
65
+
66
+ Identity, instance, backend, and command inputs are normalized before comparison and
67
+ metadata publication. Desktop configuration and Codex profile paths are normalized
68
+ before they become locators. Claude Code local and project adoption must run from the
69
+ directory that later lifecycle operations should use. After publication, adoption
70
+ re-inspects the live registration and fails if it changed. Inspection and plan-only
71
+ adoption preserve backend bytes, inode, and modification time. Applied adoption
72
+ writes only the management metadata described above; registration state and client
73
+ health remain separate authorities.
74
+
75
+ First-time `clients install` and enrollment behavior remains unchanged. A provision
76
+ enrollment still refuses an existing backend file or MCP registration, so adoption
77
+ cannot weaken first-install collision protection.
78
+
79
+ `clients repair`, `clients update`, and `clients uninstall` operate only on a strict
80
+ managed metadata record.
81
+ The runtime and stable instance locate that record. `--identity` must match the record
82
+ and the immutable request, but never locates it. These commands reject
83
+ backend path, scope, and config-path flags.
84
+ They plan without writing by default. A no-op exact registration creates no operation.
85
+
86
+ Repair restores the recorded launch. Update resolves a replacement launch before it
87
+ creates a journal. The update request records the identity, command, arguments, scope,
88
+ and fixed three Agent Bridge environment key names. It does not record their values.
89
+ Native launch arguments are empty. A native command is one executable contract. Bare
90
+ commands reject argument separators and URL-like surfaces. Absolute paths must resolve
91
+ to executable files before an update is journaled. Claude Desktop uses the installer's
92
+ absolute launch resolver. An action-specific resume uses that recorded request and
93
+ rejects another action, runtime, instance, identity, or replacement command.
94
+
95
+ Before a mutation starts, metadata load verifies the private management root, clients
96
+ directory, and metadata file. It rejects links, replacement, oversized files, unknown
97
+ keys, incorrect schema versions, malformed locators, and non-absolute backend paths.
98
+ The file is opened without following links and checked against its path identity before
99
+ the loader accepts it. The full record is loaded again after the client lock is acquired
100
+ and before every non-metadata mutation. Any difference blocks the operation.
101
+
102
+ Repair can tighten backend privacy only when that change removes access. Its journal
103
+ proofs contain the role, file identity, and private or repairable policy state. They
104
+ never contain backend bytes, credentials, URLs, workspace, or configuration values.
105
+ Links, disappearance, owner changes, replacement, and a permission change that would
106
+ grant new owner access fail closed. Windows accepts an already private backend path and
107
+ refuses a non-private one because the repair path cannot prove a monotonic ACL change.
108
+
109
+ Registration proofing records a bounded Agent Bridge observation and its full target
110
+ contract. Unknown environment keys, malformed values, unexpected scope, opaque fields,
111
+ or arguments outside the stored current or target launch stop before journal creation.
112
+ For Codex, get, remove, and add run with the recorded `CODEX_HOME` over the supplied
113
+ environment. Claude Code uses the recorded scope, invocation directory, and supplied
114
+ environment. Native changes remove, verify absence, add, verify the target, and write
115
+ changed metadata last. Claude Desktop reads the recorded JSON config without following
116
+ links or link ancestors, changes only `mcpServers.agent-bridge`, and publishes through
117
+ a private operation-scoped temporary file. It pins and rechecks the original file and
118
+ parent identities before rename, then verifies the final entry. Claude Desktop has no
119
+ cooperative transaction protocol. Node cannot guarantee a transaction against an
120
+ independent same-user writer.
121
+
122
+ The operation substrate remains internal with read-only inspection. Revisioned
123
+ manifests contain typed credential-agnostic requests, non-sensitive locators, and
124
+ unique no-replace before/after artifacts. Begin holds the runtime-plus-instance lock
125
+ while refusing another unfinished operation; a blocked journal fences new mutations and
126
+ resume. Resume is same-host only. Journal states
127
+ are prepared, snapshotted, in-progress, applied, cleaning, and committed. Inspection
128
+ separately reports resumable, classification-required, blocked, or complete.
129
+
130
+ Repair and uninstall journals use operation format version 3. New update journals use
131
+ v4 and carry a bounded credential-free inverse contract. Gateway migration staging
132
+ uses v5 and binds a managed identity, source and target endpoint digests, enrollment
133
+ credential IDs, a source edge database path and scope key, and a predecessor grace
134
+ cutoff. It contains no raw gateway URL or token. A version 2 journal keeps its released
135
+ request shape and remains inspectable. A non-terminal version 2 journal cannot resume
136
+ because it lacks an identity-bound request.
137
+
138
+ Endpoint cutover uses v6. Its journal retains the normalized source gateway URL without
139
+ a token so post-journal recovery can reconstruct the source edge route from the target
140
+ successor backend. Initial preflight uses the predecessor and direct successor. Every
141
+ later proof and source-edge replay uses the successor credential at both URLs. There is
142
+ no endpoint reverse operation. Returning to a prior endpoint requires a fresh owner
143
+ rotation and a new forward cutover.
144
+
145
+ Creation pins the operation root before it publishes state. Cleanup pins that root,
146
+ the operation directory, and the snapshots directory before it records intent or
147
+ unlinks anything. Cleanup records durable intent per artifact, verifies and unlinks the pinned file, and
148
+ records POSIX directory sync or explicit Windows unavailability. An absent artifact is
149
+ safe only when intent predates the cleanup attempt. Inspection recognizes a verified
150
+ after artifact for the current intent-recorded step, but rejects other missing or extra
151
+ files. Committed means verified writes and removed artifacts. It drops requests, steps,
152
+ locators, digests, and artifact metadata while retaining a bounded credential-free
153
+ completion record for audit. Public repair, update, and uninstall use this substrate.
154
+
155
+ Uninstall is forward-only. It removes the managed registration and proves absence,
156
+ deletes the already private backend file, then deletes metadata. It refuses a backend
157
+ that still needs privacy repair, so it cannot loosen that policy as a side effect of
158
+ deletion. A later failure does not restore a registration, backend, or metadata file.
159
+ The uninstall plan never stores backend bytes. File deletion confirms the file and
160
+ private parent identities before unlink. POSIX syncs the parent directory after a
161
+ verified absence. Windows records unavailable directory durability. Node's pathname
162
+ unlink cannot provide a transaction against a same-user writer, so this is an advisory
163
+ race boundary. Desktop removes only `mcpServers.agent-bridge`.
164
+
165
+ `clients rollback <source-operation-id> --identity <name>` is plan-first and accepts
166
+ only a committed same-host v4 update source. `--apply` creates a new v4 `rollback`
167
+ operation. It verifies the asserted identity, prior nonsecret metadata and exact
168
+ registration contract, and the current forward metadata and registration digests.
169
+ Native rollback removes the forward registration, proves absence, adds the prior
170
+ registration, proves it, then writes prior metadata. Desktop replaces only
171
+ `mcpServers.agent-bridge` and writes prior metadata last. The source journal remains
172
+ unchanged. Reverse resume derives authority from its recorded source UUID and inverse
173
+ contract. A changed current state, v2 or v3 source, repair, uninstall, unsafe inverse
174
+ fields, or a different host fails closed. A failed forward update never rolls back
175
+ automatically. Repair remains monotonic. Uninstall recovery is re-enrollment.
176
+
177
+ `clients migrate stage <runtime> --identity <name> --instance <key>
178
+ --enrollment-file <path>` is plan-first. With `--apply`, it creates a private staged
179
+ successor backend and a credential-free v5 record without changing the managed
180
+ registration or active backend. It initializes the source edge gate and leaves its
181
+ state active. It probes active and staged bearer credentials and requires a
182
+ non-immediate predecessor grace cutoff. It neither drains the outbox nor authorizes a
183
+ cutover. It requires an absolute, normalized source edge database path and rejects
184
+ in-memory edge state. The stage does not prove that source and target URLs reach the
185
+ same database authority.
186
+
187
+ `clients resume <operation-id> [--recover-lock]` resumes v3 repair, update, and
188
+ uninstall requests, supported v4 update and rollback requests, and v5 migration-stage
189
+ requests. It derives action, runtime, instance, identity, launch, and reverse source
190
+ from the record and accepts no replacement client authority. It cannot resume a version
191
+ 2 operation. It also completes an uninstall interrupted after metadata deletion and a
192
+ rollback interrupted after any ordered step. A later endpoint cutover is limited to
193
+ alternate URLs and must dynamically attest one logical database authority. Physical
194
+ erasure is outside the contract.
195
+
196
+ ## Consequences
197
+
198
+ Managed repair, update, and uninstall have a narrow authority boundary and can fail
199
+ closed on drift, metadata corruption, ambiguous crash state, and unsafe backend policy
200
+ changes. Existing exact registrations still require intentional, reviewable adoption.
201
+ Migration staging preserves active state while it prepares later work. Endpoint
202
+ cutover uses a separate v6 journal to drain the gateway edge outbox, attest shared live
203
+ route state, change the host registration, and retain enough credential-free authority
204
+ for crash recovery and post-grace source retirement.