brainclaw 1.9.0 → 1.10.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 (149) hide show
  1. package/README.md +631 -499
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +18 -1
  4. package/dist/commands/code-map.js +129 -0
  5. package/dist/commands/codev.js +7 -0
  6. package/dist/commands/harvest.js +1 -1
  7. package/dist/commands/hooks.js +73 -73
  8. package/dist/commands/init.js +1 -1
  9. package/dist/commands/install-hooks.js +78 -78
  10. package/dist/commands/mcp-read-handlers.js +57 -14
  11. package/dist/commands/mcp.js +200 -13
  12. package/dist/commands/run-profile.js +3 -2
  13. package/dist/commands/switch.js +125 -93
  14. package/dist/commands/version.js +1 -1
  15. package/dist/core/agent-capability.js +19 -4
  16. package/dist/core/agent-files.js +131 -119
  17. package/dist/core/code-map/backend.js +123 -0
  18. package/dist/core/code-map/core.js +81 -0
  19. package/dist/core/code-map/drafts.js +2 -0
  20. package/dist/core/code-map/extractor.js +29 -0
  21. package/dist/core/code-map/finalizer.js +191 -0
  22. package/dist/core/code-map/freshness.js +108 -0
  23. package/dist/core/code-map/ids.js +0 -0
  24. package/dist/core/code-map/importable.js +35 -0
  25. package/dist/core/code-map/indexes.js +197 -0
  26. package/dist/core/code-map/lang/java/imports.scm +17 -0
  27. package/dist/core/code-map/lang/java/index.js +254 -0
  28. package/dist/core/code-map/lang/java/tags.scm +48 -0
  29. package/dist/core/code-map/lang/php/imports.scm +21 -0
  30. package/dist/core/code-map/lang/php/index.js +251 -0
  31. package/dist/core/code-map/lang/php/tags.scm +44 -0
  32. package/dist/core/code-map/lang/provider.js +9 -0
  33. package/dist/core/code-map/lang/providers.js +24 -0
  34. package/dist/core/code-map/lang/python/imports.scm +90 -0
  35. package/dist/core/code-map/lang/python/index.js +364 -0
  36. package/dist/core/code-map/lang/python/tags.scm +81 -0
  37. package/dist/core/code-map/lang/query-runtime.js +374 -0
  38. package/dist/core/code-map/lang/registry.js +125 -0
  39. package/dist/core/code-map/lang/typescript/imports.scm +90 -0
  40. package/dist/core/code-map/lang/typescript/index.js +306 -0
  41. package/dist/core/code-map/lang/typescript/tags.js.scm +106 -0
  42. package/dist/core/code-map/lang/typescript/tags.scm +151 -0
  43. package/dist/core/code-map/lock.js +210 -0
  44. package/dist/core/code-map/materialized.js +51 -0
  45. package/dist/core/code-map/memory-reader.js +59 -0
  46. package/dist/core/code-map/paths.js +53 -0
  47. package/dist/core/code-map/query.js +568 -0
  48. package/dist/core/code-map/refresh.js +0 -0
  49. package/dist/core/code-map/resolve.js +177 -0
  50. package/dist/core/code-map/store.js +206 -0
  51. package/dist/core/code-map/types.js +288 -0
  52. package/dist/core/code-map/vocabulary.js +57 -0
  53. package/dist/core/code-map/wasm-loader.js +294 -0
  54. package/dist/core/code-map/work-section.js +206 -0
  55. package/dist/core/codev-prompts.js +38 -38
  56. package/dist/core/codev-rounds.js +4 -0
  57. package/dist/core/default-profiles/doctor.yaml +11 -11
  58. package/dist/core/default-profiles/janitor.yaml +11 -11
  59. package/dist/core/default-profiles/onboarder.yaml +11 -11
  60. package/dist/core/default-profiles/reviewer.yaml +13 -13
  61. package/dist/core/dispatcher.js +1 -1
  62. package/dist/core/entity-operations.js +29 -3
  63. package/dist/core/execution-adapters.js +11 -10
  64. package/dist/core/execution-profile.js +58 -0
  65. package/dist/core/execution.js +1 -1
  66. package/dist/core/facade-schema.js +9 -0
  67. package/dist/core/instruction-templates.js +2 -0
  68. package/dist/core/loops/verbs.js +0 -1
  69. package/dist/core/mcp-command-resolution.js +3 -1
  70. package/dist/core/messaging.js +2 -2
  71. package/dist/core/protocol-skills.js +164 -164
  72. package/dist/core/runtime-signals.js +1 -1
  73. package/dist/core/search.js +19 -2
  74. package/dist/core/security-guard.js +207 -207
  75. package/dist/core/spawn-check.js +16 -2
  76. package/dist/core/staleness.js +1 -1
  77. package/dist/core/store-resolution.js +67 -11
  78. package/dist/core/worktree.js +18 -18
  79. package/dist/facts.js +9 -5
  80. package/dist/facts.json +8 -4
  81. package/dist/vendor/web-tree-sitter/tree-sitter.js +3980 -0
  82. package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
  83. package/dist/wasm/tree-sitter-java.wasm +0 -0
  84. package/dist/wasm/tree-sitter-javascript.wasm +0 -0
  85. package/dist/wasm/tree-sitter-php.wasm +0 -0
  86. package/dist/wasm/tree-sitter-python.wasm +0 -0
  87. package/dist/wasm/tree-sitter-tsx.wasm +0 -0
  88. package/dist/wasm/tree-sitter-typescript.wasm +0 -0
  89. package/dist/wasm/tree-sitter.wasm +0 -0
  90. package/docs/PROTOCOL.md +1 -1
  91. package/docs/adapters/openclaw.md +43 -43
  92. package/docs/architecture/project-refs.md +328 -328
  93. package/docs/cli.md +2131 -2093
  94. package/docs/code-map.md +198 -0
  95. package/docs/concepts/coordination.md +52 -52
  96. package/docs/concepts/coordinator-runbook.md +129 -129
  97. package/docs/concepts/dispatch-lifecycle.md +245 -245
  98. package/docs/concepts/event-log-store.md +928 -928
  99. package/docs/concepts/ideation-loop.md +317 -317
  100. package/docs/concepts/loop-engine.md +520 -511
  101. package/docs/concepts/mcp-governance.md +268 -268
  102. package/docs/concepts/memory.md +84 -84
  103. package/docs/concepts/multi-agent-workflows.md +167 -167
  104. package/docs/concepts/observer-protocol.md +361 -361
  105. package/docs/concepts/plans-and-claims.md +217 -217
  106. package/docs/concepts/project-md-convention.md +35 -35
  107. package/docs/concepts/runtime-notes.md +38 -38
  108. package/docs/concepts/troubleshooting.md +254 -254
  109. package/docs/concepts/workspace-bootstrapping.md +142 -142
  110. package/docs/context-format-changelog.md +35 -35
  111. package/docs/context-format.md +48 -48
  112. package/docs/index.md +65 -65
  113. package/docs/integrations/agents.md +158 -158
  114. package/docs/integrations/claude-code.md +23 -23
  115. package/docs/integrations/cline.md +77 -77
  116. package/docs/integrations/continue.md +55 -55
  117. package/docs/integrations/copilot.md +68 -68
  118. package/docs/integrations/cursor.md +23 -23
  119. package/docs/integrations/kilocode.md +72 -72
  120. package/docs/integrations/mcp.md +385 -378
  121. package/docs/integrations/mistral-vibe.md +122 -122
  122. package/docs/integrations/openclaw.md +92 -92
  123. package/docs/integrations/opencode.md +84 -84
  124. package/docs/integrations/overview.md +115 -115
  125. package/docs/integrations/roo.md +71 -71
  126. package/docs/integrations/windsurf.md +77 -77
  127. package/docs/mcp-schema-changelog.md +364 -356
  128. package/docs/playbooks/integration/index.md +121 -121
  129. package/docs/playbooks/orchestration.md +37 -0
  130. package/docs/playbooks/productivity/index.md +99 -99
  131. package/docs/playbooks/team/index.md +117 -117
  132. package/docs/product/agent-first-model.md +184 -184
  133. package/docs/product/entity-model-audit.md +462 -462
  134. package/docs/product/positioning.md +86 -86
  135. package/docs/quickstart-existing-project.md +107 -107
  136. package/docs/quickstart.md +183 -183
  137. package/docs/release-maintenance.md +79 -79
  138. package/docs/reputation.md +52 -52
  139. package/docs/review.md +45 -45
  140. package/docs/security.md +212 -212
  141. package/docs/server-operations.md +118 -118
  142. package/docs/storage.md +106 -106
  143. package/package.json +86 -66
  144. package/docs/concepts/event-log-store-critique-A.md +0 -333
  145. package/docs/concepts/event-log-store-critique-B.md +0 -353
  146. package/docs/concepts/event-log-store-phase0-measurements.md +0 -58
  147. package/docs/concepts/event-log-store-proposal-A.md +0 -365
  148. package/docs/concepts/event-log-store-proposal-B.md +0 -404
  149. package/docs/concepts/identity-model-proposal.md +0 -371
@@ -1,371 +0,0 @@
1
- # Federated Identity Model — PROPOSAL
2
-
3
- > Status: **PROPOSAL** for the architecture loop (Codex schema review + Juan
4
- > product calls). Not a decision. Produced 2026-06-11 from a two-track
5
- > identity analysis (local model + federation readiness); full reports held
6
- > by the coordinator. Couples directly with `event-log-store.md` OPEN
7
- > QUESTION **C4** (federation conflict primitive) — review them together.
8
-
9
- ## Why
10
-
11
- The federation ambition: several human users, each running one or more
12
- agents, working on the same project from different machines. Today's
13
- identity tuple `{agent_name, agent_id, host_id, user}` is locally minted,
14
- unauthenticated, and name-keyed at every load-bearing point:
15
-
16
- - Cloud inbox routed by **bare agent name** (`/api/v1/inbox/claude-code`)
17
- — two humans' claude-codes are one identity to the bus.
18
- - Event cursors, claim ownership comparisons, reputation, trust: keyed by
19
- name or per-store registry doc; "Juan's claude-code" is **inexpressible**.
20
- - `FederationMessage.from` is self-asserted under a single API key; routing
21
- uses machine-local `project_path`.
22
- - ed25519 keypairs are generated per agent and **never used** (private keys
23
- currently under `~/.codex/brainclaw/keys`).
24
- - 2,000+ store files carry the OS username; worktree paths are absolute.
25
- - No write-authority model exists for same-project-multi-machine (the
26
- signaling-vs-execution decision covered cross-*project* only). C4 cannot
27
- be answered without one.
28
-
29
- Local incidents (2026-06-10) showing the same root: VS Code extension
30
- inherited agent env → impersonation + cursor consumption; directory
31
- presence (`~/.config/opencode`) minted a **trusted** identity; three
32
- simultaneous claude-code instances shared one agent_id (loop slot auth
33
- distinguishes nothing); a copilot worker committed as the human.
34
-
35
- ## The model
36
-
37
- ```
38
- principal prn_<id> a HUMAN (or service account), optionally under org_<id>
39
- (org = the premium billing/management unit)
40
- actor agt_<id> an AGENT INSTANCE = (principal, machine, agent software)
41
- keeps today's agt_ ids; gains principal_id, origin_id,
42
- public key
43
- origin orig_<id> a STORE REPLICA on a machine — minted once at
44
- init/materialize, stored next to project.identity.json.
45
- NOT os.hostname() (hostnames collide; one machine can
46
- hold several replicas). host_id stays as metadata.
47
- display name "claude-code" demoted to a label. Never a key.
48
- ```
49
-
50
- ### Write authority (answers C4's prerequisite)
51
-
52
- - **Execution entities** (claims, sessions, runs, locks): origin-scoped,
53
- **single-writer**. Other origins materialize them read-only, tagged with
54
- their origin. Claims get eager push-on-write to the bus (piggybacked on
55
- the existing claim mutation path — still no daemon) for advisory
56
- cross-machine visibility; true atomic arbitration is deferred to the
57
- cloud dispatcher (Durable Object, premium) and local-first never blocks
58
- on it. **Eager-push messages MUST carry the same record identity as
59
- journal slices** — `(origin_id, origin_epoch, seq, entity_rev)` plus the
60
- full post-image. Then arrival order vs the periodic pull is irrelevant:
61
- the receiver applies any copy through the same never-regress comparison
62
- (apply iff incoming rev > materialized rev for that origin), and a push
63
- that lands after the pull already delivered it is a no-op. A push lacking
64
- this identity would reintroduce ordering bugs; it is not optional.
65
- - **Memory entities** (decisions, traps, plans…): **highest-rev-wins with
66
- origin tiebreak** — not wall-clock LWW; timestamps never order anything
67
- (same rule as the event-log spec §2.2). On equal `entity_rev` from two
68
- origins, the winner for the materialized view is deterministic
69
- (lexicographic `origin_id`) and the loser is surfaced as a conflict
70
- candidate, never silently dropped.
71
- - **The scalar-rev hole, named and fixed**: a bare rev comparison cannot
72
- distinguish *descends-from* from *concurrent-with*. Origin A edits
73
- rev 5→6→7; origin B concurrently edits 5→6. A's 7 beats B's 6 and the
74
- comparison looks like a clean fast-forward — B's edit is lost with **no
75
- conflict surfaced**. Scalar `entity_rev` alone therefore cannot honor the
76
- "conflicts surface as candidates" promise. The minimal fix (still no
77
- vectors): exported memory-entity records carry **`base_rev`** — the rev
78
- the write was based on. Receiver rule: incoming wins only if
79
- `incoming.base_rev >= current.rev` (true fast-forward); otherwise the
80
- write was concurrent → materialize the winner by the tiebreak above AND
81
- emit a conflict candidate carrying both post-images. One extra integer
82
- per exported record; local writes don't need it (single store, the lock
83
- serializes). This is sufficient *because* payloads are full post-images
84
- (event-log §2.1) — no merge, just detect-and-surface.
85
- - **Restore-from-backup breaks `(origin_id, seq)` — origin epochs**: a
86
- store restored from backup at seq 700 will re-issue seqs 701… with
87
- different content under the same `origin_id`, while peers hold cursors
88
- at, say, 1000 — the divergent range is silently ignored and the
89
- addressing invariant (one `(origin_id, seq)` = one immutable record) is
90
- violated. Fix: slice headers carry **`origin_epoch`** (integer, starts
91
- at 0). Detection is cheap and reliable: the cloud keeps a per-origin
92
- high-water mark; an origin reconnecting with `local next_seq <=
93
- cloud watermark` has been restored → it bumps its epoch (recorded
94
- locally as a `seq_repair`-class event) before pushing anything. Peers
95
- treat a new epoch as a rebase: drop the cursor for that origin and
96
- re-pull from the origin's latest checkpoint. Never-regress stays intact
97
- *within* an epoch; epochs make the restore case explicit instead of
98
- silent.
99
- - **Consequence for C4**: with partitioned authority, **scalar
100
- `entity_rev` + origin tag + `base_rev` on exported memory records**
101
- suffices — no vector clocks. Federation transfers are
102
- `(origin_id, origin_epoch, seq)`-headed segment slices.
103
-
104
- ### Authn — the minimal dumb-bus guarantee
105
-
106
- Activate the dormant ed25519 keys. `brainclaw cloud register` (already
107
- specced in the cli-cloud plan) binds *actor public key → principal* once,
108
- under the principal's API token (one token per human). The cloud verifies
109
- each message signature once and stamps `verified_principal`; receivers
110
- trust the stamp and MUST reject unstamped messages on cloud transport. No
111
- PKI web. Routing moves from `project_path` to `project_id` + channel.
112
- (Local cross-project-link transport stays a separate, weaker trust domain:
113
- same machine, same OS user — signatures optional there.)
114
-
115
- #### FederationMessage v2 envelope & signature block (concrete)
116
-
117
- ```jsonc
118
- {
119
- "schema_version": 2,
120
- "id": "msg_...",
121
- "from": {
122
- "principal_id": "prn_...",
123
- "actor_id": "agt_...",
124
- "origin_id": "orig_...",
125
- "origin_epoch": 0,
126
- "origin_msg_seq": 412, // per-origin monotonic counter (replay watermark)
127
- "agent_name": "claude-code" // display only — never a key
128
- },
129
- "to": { "project_id": "proj_...", "channel": "signals", "actor_id": "agt_..."? },
130
- "type": "signal|handoff|candidate|runtime_note|board_snapshot|claim_advisory|slice",
131
- "payload_hash": "sha256:...", // hash of canonical payload bytes
132
- "payload": { ... }, // inline iff <= 64 KB, else payload_ref (see slices)
133
- "created_at": "...",
134
- "causal_parent": "msg_..."?,
135
- "idempotency_key": "...", // DEDUP ONLY — explicitly not a security control
136
- "sig": {
137
- "alg": "ed25519",
138
- "key_fingerprint": "...", // selects among the actor's registered keys
139
- "sig": "base64..."
140
- }
141
- }
142
- ```
143
-
144
- - **What is signed**: the ed25519 signature covers the **RFC 8785 (JCS)
145
- canonical JSON** of the envelope with `sig` removed and `payload`
146
- replaced by `payload_hash`. Today's `JSON.stringify` of a parsed object
147
- is insertion-order-dependent and NOT a stable signing base across
148
- writers/runtimes — canonicalization is required, not optional. Signing
149
- `payload_hash` instead of inline payload means oversized payloads can
150
- move out-of-band (blob transfer, below) without changing the signature
151
- scheme.
152
- - **Replay protection**: `idempotency_key` (content hash, today truncated
153
- to 64 bits) is **dedup, not replay defense** — it only helps if the
154
- receiver retains seen-keys forever, and it cannot distinguish a
155
- legitimate redelivery from a malicious replay. The actual defense is the
156
- **per-origin monotonic watermark**: every v2 message carries
157
- `(origin_id, origin_epoch, origin_msg_seq)`; receivers keep a high-water
158
- mark per origin and reject anything at-or-below it. This unifies signal
159
- messages with slice cursor semantics — one mechanism, no retention
160
- window to tune. `idempotency_key` survives for content-level dedup but
161
- its computation must move to canonical JSON (same JCS bytes as signing).
162
- - **Key rotation**: actor registration holds a **list** of keys with
163
- `created_at` / `retired_at` validity windows, not a single key. Rotation
164
- = registering a new key under the principal's API token (same act as
165
- initial registration — no cross-signing chain needed because the token,
166
- not the old key, is the root of the binding). The cloud verifies against
167
- whichever registered key the `key_fingerprint` selects, checking the
168
- validity window against `created_at`.
169
- - **Revocation (the fired-teammate case)**: revocation is enforced at the
170
- **single online verification point** — the org admin revokes the
171
- principal (dashboard / `members` projection); the cloud immediately stops
172
- stamping that principal's messages and rejects its API token. Because
173
- receivers trust the stamp rather than verifying raw signatures
174
- themselves, **revocation does not need to propagate to offline stores to
175
- stop new writes** — an offline store cannot receive new federation
176
- messages anyway. What offline stores DO hold: (a) previously
177
- materialized data from the revoked principal — that is history, not
178
- authority; it stays, attributed; (b) a stale `members` projection that
179
- still lists the principal — bounded by re-pull on reconnect. The
180
- residual product call: whether a store whose members projection exceeds
181
- a max staleness age should degrade to read-only federation (deny new
182
- grants by default) — see [JUAN] below.
183
-
184
- ### Authz
185
-
186
- Trust moves from per-local-agent-doc to **per (principal, project)**
187
- grants, distributed as a signed `members` projection (managed in the
188
- Phase-2 dashboard, materialized locally like everything else). Local-only
189
- operation is unchanged: principal defaults to an implicit `prn_local`.
190
-
191
- ### Slice transfer & event-log coupling
192
-
193
- The event-log spec is written for ONE store-global seq space assigned
194
- under the local lock. Foreign events cannot be appended into it (their
195
- seqs are foreign); the spec's own §2.2 scope boundary anticipates exactly
196
- this. The ingestion model, stated so the two documents stop hand-waving at
197
- each other:
198
-
199
- - **Export** is what the proposal already says: slices of the local
200
- journal, header `{origin_id, origin_epoch, from_seq, to_seq,
201
- slice_hash}` — zero local schema cost, origin appears only on export.
202
- - **Import — per-origin sidecar journals, not seq renumbering.** Ingested
203
- slices land in `events/foreign/<origin_id>/seg-*.jsonl`, preserving the
204
- remote `(epoch, seq)` addressing (verifiable against `slice_hash`,
205
- re-requestable). The receiver keeps **one cursor per foreign origin**
206
- (`{origin_epoch, last_seq}`) — a new mechanism; the spec's `meta.json`
207
- per-family watermarks don't cover it and must grow a `foreign_origins`
208
- map. Renumbering foreign events into the local seq space was rejected:
209
- it destroys the remote addressability that re-sync, audit, and
210
- `slice_hash` verification depend on.
211
- - **Materialization is a local journal event.** Applying a foreign batch
212
- appends a local `federation_apply` event (local seq, payload = the
213
- per-entity outcomes: applied / regressed-skip / conflict-candidate).
214
- Local rebuild therefore replays to the same state WITHOUT consulting
215
- foreign journals — checkpoints stay self-contained, and the spec's
216
- rebuild/gc/verify story is untouched. Foreign sidecars are cache-class:
217
- re-pullable, gc-able by their own retention, never part of local
218
- checkpoint verification.
219
- - **Remote gc floor**: the origin may have archived segments below a
220
- requested cursor (spec §2.3 retention). The slice protocol therefore has
221
- two response shapes, mirroring §2.5's `{gap: true}`: contiguous events,
222
- OR a checkpoint-derived snapshot slice (full post-images at the origin's
223
- latest checkpoint) from which the receiver re-bases its cursor.
224
- Snapshot slices and the epoch-rebase path (above) are the same code.
225
- - **Oversized payloads**: the phase-0 falsifier FIRED on handoffs (p50
226
- ~110 KB, 15-45× over the 64 KB record threshold). Whatever shape
227
- `payload_ref` takes in event-log phase 1, federation inherits it:
228
- slices reference blobs by sha256; receivers fetch
229
- content-addressed blobs (`blobs/<sha256>`) out-of-band and verify the
230
- hash. The envelope/signature design above already accommodates this
231
- (signatures cover `payload_hash`, never inline bytes). Note the
232
- composable alternative flagged in the measurements doc — a handoff
233
- "diet" externalizing `snapshot.diff` — also shrinks the federation
234
- privacy surface (the diff is the #1 secret-leak channel) and should be
235
- preferred where possible.
236
- - **Idempotency for slices**: the event-log spec says federation dedup
237
- uses `(seq, writer)`. At the transfer layer this becomes
238
- `(origin_id, origin_epoch, seq)` — the per-origin watermark — with
239
- `slice_hash` as the integrity check. The content-hash `idempotency_key`
240
- is for *signal* messages only; do not use it for slices.
241
-
242
- ### Privacy boundary
243
-
244
- The federation export strips or rewrites — verified against
245
- `src/core/schema.ts`, not just the obvious fields:
246
-
247
- - `user` (OS username — Claim, CurrentSessionState) → dropped;
248
- `principal_id` is the human attribution.
249
- - **`author` / `author_id`** (present on every memory entity; in practice
250
- the OS username or a human name, occasionally an email) → mapped to the
251
- principal's display handle. The original strip list missed this — it is
252
- the single most widespread PII field in the store (2,000+ files).
253
- - **Reviewer/actor name fields**: `HandoffReview.requester/reviewer/
254
- reviewed_by`, `Candidate.starred_by[]/resolved_by`,
255
- `ActionRequiredResponse.responded_by`, `assignee`, `Sequence.owner` →
256
- same principal-handle mapping.
257
- - **`host_id`** — today raw `os.hostname()` (src/core/host.ts), and
258
- hostnames routinely embed personal names ("juans-macbook"). Kept as
259
- *metadata* locally, but on export it is hashed (or dropped; `origin_id`
260
- already provides replica identity).
261
- - Absolute paths: `worktree_path`, `related_paths` → repo-relative.
262
- **Also**: `AgentRun.command` / `shell` (spawn command lines embed
263
- absolute paths, usernames, and potentially tokens) → stripped on export,
264
- not relativized — command lines are not reconstructable safely; and
265
- `verify_cmd` (decisions/traps) → redaction-pattern pass.
266
- - pid, session env internals (`CurrentSessionState.pid/user/
267
- active_project.path`) → dropped.
268
- - **Existing leak, fix independent of v2**: `FederationMessage.from.
269
- project_path` / `to.project_path` are REQUIRED absolute paths in v1 and
270
- are pushed to the cloud **today** whenever cloud_sync is enabled. v2
271
- routing by `project_id` removes them; until then this is a live PII
272
- exposure, not a future risk.
273
- - **Narrative content (`text`, `narrative`, `snapshot.diff`,
274
- `InboxMessage.payload`, `RuntimeEvent.metadata`) cannot be schema-
275
- stripped** — free text can quote anything, including secrets and names.
276
- This is policy + redaction territory only: the existing
277
- `RedactionConfig` patterns, the explicit `visibility: 'shared'` opt-in
278
- (already enforced for handoffs/candidates), and the J1 `doctor redact`
279
- erasure path. The proposal claims no schema-level guarantee here, by
280
- design.
281
-
282
- Pairs with the J1 `doctor redact` decision.
283
-
284
- ## Identity acquisition (local prerequisite, pln#562)
285
-
286
- Detection conflates three questions today; they split as follows:
287
-
288
- | Question | Owner | Legitimate evidence |
289
- |---|---|---|
290
- | What is installed on this machine? | `agent-inventory` (single source) | disk presence — an **origin** attribute; doubles as the capability advertisement the cloud dispatcher needs |
291
- | Who is calling? | `detectAiAgent` | process-scoped env markers ONLY — never directory presence; claimed identity sanity-checked *against* inventory |
292
- | What trust? | explicit acts (human at setup, dispatcher at spawn), capped at contributor | never a read-path side effect |
293
-
294
- ## Migration (additive where verified; two steps are NOT)
295
-
296
- Each claim re-checked against the actual zod schemas and call sites:
297
-
298
- 1. `AgentIdentityDocument` += optional `principal_id`, `origin_id` —
299
- **verified additive** (schema.ts keeps `version: z.literal(1)`; new
300
- fields zod-optional; old docs parse). Do NOT bump the version literal.
301
- 2. `origin_id` minted lazily at first federation use, stored next to
302
- `project.identity.json`; the v2 event envelope stays agnostic (origin
303
- added only to *exported* slices via segment headers — zero local
304
- *export* cost). **Import is not zero-cost**: per-origin cursors +
305
- foreign sidecars (see slice section) are new mechanisms, landing only
306
- with federation itself.
307
- 3. Cursors and cloud inboxes re-keyed name → instance. **Two corrections**:
308
- (a) local event cursors (`events/.cursors/<agent>.json`) are being
309
- migrated to seq watermarks by event-log phase 1 anyway — do the re-key
310
- *in that migration*, not as a separate rename done twice; (b) the cloud
311
- inbox re-key (`/api/v1/inbox/<agent_name>` → instance-keyed) is a
312
- **coordinated cloud API change, not a local rename** — an old client
313
- pulling the name-keyed endpoint against a re-keyed cloud silently gets
314
- an empty inbox (the pull error path returns `[]`). Needs an API
315
- version window or dual-key serving.
316
- 4. Wire `isDuplicate` + a persisted `since` watermark for cloud pulls now
317
- (pre-federation bugfix — **verified real**: session-start.ts pulls with
318
- `{limit: 100}`, no `since`, no dedup; every session re-materializes the
319
- same signals as new entities). Slightly more than "wire it":
320
- `materializeFederationSignal` mints fresh local ids and **discards the
321
- incoming `idempotency_key`**, so there is nothing on disk to dedup
322
- against — the fix must (a) persist a seen-keys watermark file and
323
- (b) stamp `provenance.kind: 'federation'` + `remote_id` on
324
- materialized entities (the provenance schema already has the slot).
325
- 5. `FederationMessage` v2 — **the secretly-breaking step**.
326
- `FederationMessageSchema` pins `schema_version: z.literal(1)` and
327
- `deserializeMessage` hard-parses; a v1-only receiver given a v2 message
328
- throws, and `pullSignals`' catch-skip **silently drops it forever**
329
- (local-transport inboxes are written by the *sender* into the target
330
- project). And v1's required `project_path` fields mean a v2 message
331
- that omits them fails v1 parsing by absence too. Mitigation: ship the
332
- v1|v2 **union reader first** (accept both, one release ahead), only
333
- then switch writers to v2; cloud negotiates per-client version during
334
- the window. `idempotency_key` survives as dedup-only, recomputed over
335
- canonical JSON (see envelope section).
336
-
337
- ## Open questions for this review
338
-
339
- Resolved by the 2026-06-10 schema review pass (claims now in the body
340
- above): envelope/signature concretization (JCS canonical form, payload_hash
341
- detachment, per-origin replay watermark, key rotation via token-rooted key
342
- list); the scalar-rev concurrency hole (`base_rev` fix); restore-from-backup
343
- (`origin_epoch`); eager-push ordering (envelope identity + never-regress);
344
- slice ingestion vs the event-log seq space (per-origin sidecars +
345
- `federation_apply`); remote-gc snapshot slices; the privacy strip-list gaps
346
- (author/reviewer fields, raw-hostname host_id, AgentRun.command, the live
347
- v1 project_path leak); the two non-additive migration steps (3b, 5).
348
-
349
- Residual — genuinely needs a second model's eye:
350
-
351
- - **[CODEX]** `members` projection wire format: exact signed structure
352
- (who signs — the cloud as trust root, or the org-admin principal key?),
353
- grant granularity (per-project role enum vs capability list), and the
354
- max-staleness/deny-by-default semantics interaction with local-only
355
- stores that NEVER reconnect. The revocation *enforcement point* is
356
- settled (cloud stamp); the projection's own authenticity chain is not.
357
- - **[CODEX]** `base_rev` adversarial check: can a malicious or buggy
358
- origin *forge* fast-forwards (claim `base_rev = current.rev` to suppress
359
- conflict surfacing)? Within-org actors are semi-trusted — decide whether
360
- conflict suppression by a hostile insider is in scope, or accepted as
361
- "history is signed and attributable, dispute via audit".
362
- - **[CODEX]** `federation_apply` event schema vs event-log C1 review (it
363
- joins the `checkpoint_ref`/`journal_note`/`seq_repair`/`backfill` set):
364
- payload shape for per-entity outcomes, and whether a huge foreign batch
365
- (10k entities) needs chunked apply events to respect the 256 KB record
366
- cap and §2.9 chunked-lock rule.
367
- - **[JUAN]** org model scope for v1 (implicit single-org? explicit org
368
- onboarding?); one API token per human vs per machine; whether claim
369
- eager-push is acceptable bus chatter on the free tier; members-projection
370
- max-staleness → read-only-federation degradation (availability vs
371
- security posture).