instar 1.0.13 → 1.1.1

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +52 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/init.d.ts +17 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +156 -64
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/server.d.ts.map +1 -1
  9. package/dist/commands/server.js +68 -4
  10. package/dist/commands/server.js.map +1 -1
  11. package/dist/commands/setup.d.ts +3 -1
  12. package/dist/commands/setup.d.ts.map +1 -1
  13. package/dist/commands/setup.js +67 -28
  14. package/dist/commands/setup.js.map +1 -1
  15. package/dist/commands/worktree.d.ts +17 -4
  16. package/dist/commands/worktree.d.ts.map +1 -1
  17. package/dist/commands/worktree.js +28 -4
  18. package/dist/commands/worktree.js.map +1 -1
  19. package/dist/core/AgentWorktreeDetector.d.ts +63 -0
  20. package/dist/core/AgentWorktreeDetector.d.ts.map +1 -0
  21. package/dist/core/AgentWorktreeDetector.js +329 -0
  22. package/dist/core/AgentWorktreeDetector.js.map +1 -0
  23. package/dist/core/InstarWorktreeManager.d.ts +99 -0
  24. package/dist/core/InstarWorktreeManager.d.ts.map +1 -0
  25. package/dist/core/InstarWorktreeManager.js +447 -0
  26. package/dist/core/InstarWorktreeManager.js.map +1 -0
  27. package/dist/core/MachineIdentity.d.ts.map +1 -1
  28. package/dist/core/MachineIdentity.js +2 -0
  29. package/dist/core/MachineIdentity.js.map +1 -1
  30. package/dist/core/PostUpdateMigrator.d.ts +25 -0
  31. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  32. package/dist/core/PostUpdateMigrator.js +258 -0
  33. package/dist/core/PostUpdateMigrator.js.map +1 -1
  34. package/dist/memory/NativeModuleHealer.d.ts +15 -0
  35. package/dist/memory/NativeModuleHealer.d.ts.map +1 -1
  36. package/dist/memory/NativeModuleHealer.js +22 -0
  37. package/dist/memory/NativeModuleHealer.js.map +1 -1
  38. package/dist/monitoring/NativeHealDegradationBridge.d.ts +32 -0
  39. package/dist/monitoring/NativeHealDegradationBridge.d.ts.map +1 -0
  40. package/dist/monitoring/NativeHealDegradationBridge.js +61 -0
  41. package/dist/monitoring/NativeHealDegradationBridge.js.map +1 -0
  42. package/dist/monitoring/PromptGate.d.ts +12 -0
  43. package/dist/monitoring/PromptGate.d.ts.map +1 -1
  44. package/dist/monitoring/PromptGate.js +44 -0
  45. package/dist/monitoring/PromptGate.js.map +1 -1
  46. package/dist/scaffold/templates.d.ts.map +1 -1
  47. package/dist/scaffold/templates.js +17 -0
  48. package/dist/scaffold/templates.js.map +1 -1
  49. package/dist/server/AgentServer.d.ts.map +1 -1
  50. package/dist/server/AgentServer.js +6 -0
  51. package/dist/server/AgentServer.js.map +1 -1
  52. package/package.json +1 -1
  53. package/scripts/lint-no-direct-destructive.js +7 -0
  54. package/scripts/resolve-release-tier.mjs +192 -0
  55. package/src/data/builtin-manifest.json +57 -49
  56. package/src/scaffold/templates.ts +17 -0
  57. package/src/templates/scripts/instar-watchdog.sh +169 -6
  58. package/src/templates/scripts/instar-worktree-create.sh +79 -0
  59. package/upgrades/1.1.0.md +320 -0
  60. package/upgrades/1.1.1.md +58 -0
  61. package/upgrades/side-effects/agent-worktree-convention-layer-1-2-5.md +280 -0
  62. package/upgrades/side-effects/agent-worktree-convention-layer-3-4.md +285 -0
  63. package/upgrades/side-effects/feat-fix-parent-framework-intercept.md +59 -0
  64. package/upgrades/side-effects/feat-install-claude-gating.md +67 -0
  65. package/upgrades/side-effects/feat-install-framework-flag.md +65 -0
  66. package/upgrades/side-effects/feat-portability-shadow-capabilities.md +63 -0
  67. package/upgrades/side-effects/feat-setup-framework-flag.md +65 -0
  68. package/upgrades/side-effects/lockdown-layer-2.md +123 -0
  69. package/upgrades/side-effects/native-heal-degradation-bridge.md +206 -0
  70. package/upgrades/side-effects/watchdog-bind-failure-probe.md +174 -0
@@ -0,0 +1,58 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ **fix(monitoring): native-module heal failures now surface on the DegradationReporter alert path.**
8
+ `NativeModuleHealer` already rebuilds better-sqlite3 when a NODE_MODULE_VERSION
9
+ mismatch breaks `SemanticMemory` / `TopicMemory` / `MemoryIndex` / `TokenLedger`,
10
+ but a *failed* rebuild only landed in `<stateDir>/native-module-heals.jsonl`
11
+ and a `console.error` line — the agent then silently ran with the affected
12
+ feature unavailable and no Telegram alert ever fired. The healer's own jsdoc
13
+ already named `DegradationReporter` as a consumer ("consumed by health checks
14
+ and DegradationReporter") but no consumer was wired. This PR adds the missing
15
+ bridge: a tiny `onHealEvent(listener)` registration surface on the healer
16
+ and a `NativeHealDegradationBridge` subscriber wired in `AgentServer` boot
17
+ that translates failure events into `DegradationReporter.report(...)` calls
18
+ with component-specific impact lines (per-component dedupe; success events
19
+ intentionally silent). No change to the heal path itself, no change to the
20
+ jsonl log, no new dependencies.
21
+
22
+ ## Evidence
23
+
24
+ Prior to this PR, `src/memory/NativeModuleHealer.ts` writes a `HealEvent`
25
+ row to `<stateDir>/native-module-heals.jsonl` and `console.error`s on
26
+ failure, but `grep -rn "DegradationReporter\|degradation"` against
27
+ `src/memory/NativeModuleHealer.ts` and
28
+ `src/remediation/runbooks/node-abi-mismatch.ts` returns zero call sites
29
+ — the heal-failure event never reached the existing Telegram alert path.
30
+ After this PR, nine new unit tests in
31
+ `tests/unit/NativeHealDegradationBridge.test.ts` pin the wiring (reports
32
+ on failure, silent on success, dedupes per component, returns an
33
+ unsubscribe handle that detaches the listener, swallows listener errors,
34
+ and exercises `NativeModuleHealer.onHealEvent` through the real
35
+ `healBetterSqlite3Sync` early-exit path with a mocked install-prefix
36
+ resolver) and `AgentServer` calls `bridgeNativeHealToDegradation()` next
37
+ to the existing `NativeModuleHealer.configure(...)` call so every
38
+ server boot gets the subscriber. Pre-existing `NativeModuleHealer`
39
+ (17 tests) and `degradation-reporter*` (22 tests) suites pass
40
+ unchanged.
41
+
42
+ Spec: `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` (DegradationReporter
43
+ is the v1 signal producer per §A28 / §A33; this PR adds the missing
44
+ emit-site for heal-failure events the W-1 runbook already implied).
45
+ Side-effects review:
46
+ `upgrades/side-effects/native-heal-degradation-bridge.md` with
47
+ second-pass independent audit appended (concurred).
48
+
49
+ ## What to Tell Your User
50
+
51
+ - "When the agent's database driver gets out of sync with your Node version and can't repair itself, you now get a Telegram alert telling you what broke. Previously the agent quietly kept running with persistent memory turned off and you only found out by noticing things weren't sticking."
52
+
53
+ ## Summary of New Capabilities
54
+
55
+ | Capability | How to Use |
56
+ |-----------|-----------|
57
+ | Native-module heal failures alert the user | Automatic. `AgentServer` wires `NativeHealDegradationBridge` at boot; any `NativeModuleHealer` rebuild that fails surfaces as a `DegradationReporter` event (Telegram alert via the agent-attention path, structured row in `<stateDir>/degradations.json`). Success heals stay silent. |
58
+ | `NativeModuleHealer.onHealEvent(listener)` | New public registration point on the healer singleton — listener fires after every persisted heal event. Returns an unsubscribe function. Errors thrown by a listener are swallowed so observability cannot break the heal path. |
@@ -0,0 +1,280 @@
1
+ # Side-Effects Review — Agent Worktree Convention (Layers 1, 2, 5)
2
+
3
+ **Version / slug:** `agent-worktree-convention-layer-1-2-5`
4
+ **Date:** `2026-05-19`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required`
7
+ (see §"Phase 5 trigger check" below — this change does not touch the
8
+ high-risk surfaces listed in the /instar-dev skill.)
9
+
10
+ ## Summary of the change
11
+
12
+ Implements three of the five layers of the
13
+ `docs/specs/AGENT-WORKTREE-CONVENTION-SPEC.md` (status: approved by
14
+ Justin 2026-05-17 22:35 UTC):
15
+
16
+ - **Layer 1 — `instar worktree create <branch>` CLI subcommand.** New
17
+ command under the existing `worktree` group. Creates a sandbox-safe
18
+ git worktree of the shared instar repo at
19
+ `~/.instar/agents/<agent>/.worktrees/<slug>/`. Refuses any other
20
+ destination. All validation logic lives in a new
21
+ `src/core/InstarWorktreeManager.ts` so it can be unit-tested without
22
+ spawning the CLI.
23
+ - **Layer 2 — Scaffold seed for new agents.** `generateClaudeMd()`
24
+ gains a top-level "Worktree Convention" section with the literal
25
+ spec text plus the `GIT_AUTHOR_NAME` / `GIT_COMMITTER_EMAIL`
26
+ env-override caveat. `generateMemoryMd()` seeds a feedback-style
27
+ entry under "Project Patterns". `MachineIdentity.ts`
28
+ `GITIGNORE_ENTRIES` adds `.worktrees/` (consumed by `ensureGitignore`
29
+ on `instar init`).
30
+ - **Layer 5 — Documentation & discoverability.**
31
+ `docs/self-knowledge/worktrees.md` is the authoritative reference
32
+ (linked from the seed CLAUDE.md). The `worktree` command group's
33
+ `.description()` is updated to distinguish `create` (this spec) from
34
+ the existing `register-keypair` (parallel-dev cryptography).
35
+
36
+ **Files touched:**
37
+ - `src/core/InstarWorktreeManager.ts` (new, ~430 lines)
38
+ - `src/commands/worktree.ts` (existing, +~22 lines for `createWorktree` shim)
39
+ - `src/cli.ts` (existing, +~28 lines for `create` subcommand and updated group description)
40
+ - `src/scaffold/templates.ts` (existing, +~25 lines for Worktree Convention section + MEMORY.md entry)
41
+ - `src/core/MachineIdentity.ts` (existing, +2 lines in `GITIGNORE_ENTRIES`)
42
+ - `docs/self-knowledge/worktrees.md` (new)
43
+ - `tests/unit/InstarWorktreeManager.test.ts` (new, 27 cases)
44
+ - `tests/unit/scaffold-worktree-convention.test.ts` (new, 5 cases)
45
+ - `tests/integration/instar-worktree-create.test.ts` (new, 6 cases)
46
+ - `upgrades/NEXT.md` (appended one section)
47
+
48
+ Layers 3 (PostUpdateMigrator step for existing agents) and 4 (lifeline
49
+ detector) are deliberately deferred to the next `/instar-dev` cycle —
50
+ PostUpdateMigrator changed substantially in the v1.0.x portability
51
+ work that just landed on main, and splitting keeps the diff readable.
52
+ Layer 3 is what propagates the convention to existing agents on
53
+ update; until it ships, existing agents continue to use the hand-rolled
54
+ bash helper that already exists in each agent's `.bin/`. New agents
55
+ created via `instar init` get the seed (Layer 2) immediately.
56
+
57
+ ## Decision-point inventory
58
+
59
+ - **CLI accept/refuse on worktree placement** — *add*. Refuses any
60
+ destination outside `<agent_home>/.worktrees/`. Structural
61
+ hard-invariant validation (path containment via `realpath`), not a
62
+ judgment call. Falls under the "safety guard on irreversible action"
63
+ carve-out in `docs/signal-vs-authority.md`.
64
+ - **CLI accept/refuse on agent-home resolution** — *add*. Refuses
65
+ unregistered homes, hostile-CWD-planted `AGENT.md`, and paths that
66
+ don't match the `^<instarHome>/agents/[a-z0-9_-]+/?$` regex.
67
+ Hard-invariant validation.
68
+ - **CLI accept/refuse on `INSTAR_REPO`** — *add*. Refuses repos whose
69
+ `remote.origin.url` is not in the bake-in allowlist (extendable via
70
+ `worktree.repoUrlAllowlist` in `~/.instar/config.json`) or whose
71
+ `core.hooksPath` resolves outside the repo. Hard-invariant.
72
+ - **Audit ledger writes** — *add*. Two append paths (local
73
+ `.worktrees/.ledger.jsonl` + audit mirror under
74
+ `<stateDir>/audit/worktree-ops.jsonl`). No block/allow surface;
75
+ signal-only for downstream consumers. `O_NOFOLLOW` + `fstat`
76
+ owner/mode gate is structural validation of a security invariant,
77
+ not a brittle filter.
78
+
79
+ No new authorities introduced. No detectors with blocking power.
80
+
81
+ ---
82
+
83
+ ## 1. Over-block
84
+
85
+ **What legitimate inputs does this change reject that it shouldn't?**
86
+
87
+ - **Calls from a directory that is not under any registered agent
88
+ home** are refused. *Intentional* — the whole point of the
89
+ convention is "from inside an agent home, into the agent home." If
90
+ an operator legitimately wants to create a worktree from a non-agent
91
+ shell, they use raw `git worktree add` (still works, but the Layer 4
92
+ detector — once it ships — will surface a one-time attention item).
93
+ - **`INSTAR_REPO` pointing at a fork** with a non-allowlisted remote
94
+ URL is refused. *Operator-controllable* via
95
+ `worktree.repoUrlAllowlist` in `~/.instar/config.json`. Default
96
+ closed.
97
+ - **Slugs containing legitimate Unicode** (anything outside
98
+ `[A-Za-z0-9._-]`) are refused. *Intentional*: the slug becomes a
99
+ directory name passed to `git worktree add`, and the threat model
100
+ (prompt-injection-driven slug pollution → path traversal back into
101
+ the shared checkout) outweighs the convenience of accepting Unicode
102
+ branch labels. Operators with Unicode branch names get the default
103
+ hyphenation of slashes; if they want a custom slug, they pass an
104
+ ASCII one explicitly.
105
+
106
+ ## 2. Under-block
107
+
108
+ **What failure modes does this still miss?**
109
+
110
+ - **Raw `git worktree add` into the shared checkout is not
111
+ intercepted.** Spec explicitly out-of-scope ("Non-goals"). Mitigation
112
+ is the Layer 4 detector (deferred to next `/instar-dev` cycle) +
113
+ scaffold seed memory + per-agent CLAUDE.md mention.
114
+ - **A compromised local user** (same uid, full execution privilege)
115
+ can rewrite `~/.instar/config.json` to widen the
116
+ `worktree.repoUrlAllowlist`, plant files in `~/.instar/agents/`, or
117
+ replace the binary. Explicitly out-of-scope per the spec's threat
118
+ model boundary in §Non-goals.
119
+ - **The agent-name segment can be any registered name**, not strictly
120
+ "the agent whose `INSTAR_AGENT_HOME` env var resolved here." This is
121
+ intentional — the env var IS the canonical identity transport set by
122
+ the launcher; if the env var is wrong, the launcher is broken and
123
+ the validation flags only structural problems (regex, registry
124
+ membership, realpath containment). The walk-up fallback only triggers
125
+ when the env var is unset and resolves against the real CWD, so
126
+ there's no path where a different agent's home is silently selected
127
+ while the current agent is running.
128
+ - **`GIT_AUTHOR_NAME` / `GIT_COMMITTER_EMAIL` env vars override** the
129
+ per-worktree identity the CLI sets. The seed CLAUDE.md and
130
+ self-knowledge doc both warn about this; we can't override env
131
+ precedence without breaking legitimate per-call attribution.
132
+
133
+ ## 3. Level-of-abstraction fit
134
+
135
+ **Is this at the right layer?**
136
+
137
+ - The CLI subcommand and the manager class live where every other
138
+ instar CLI tool lives: `src/cli.ts` → `src/commands/<group>.ts` →
139
+ `src/core/<Manager>.ts`. The split between the picocolors-printing
140
+ command shim and the manager class matches `WorktreeKeyVault`,
141
+ `BackupManager`, etc. — manager has no I/O on stdout, command shim
142
+ has no business logic.
143
+ - The scaffold seed change is one entry in `GITIGNORE_ENTRIES` and one
144
+ literal-text section in `generateClaudeMd`/`generateMemoryMd`. No
145
+ new code path — it rides on top of `ensureGitignore` which already
146
+ runs on every relevant `instar init` codepath.
147
+ - The self-knowledge doc lives in `docs/self-knowledge/` (new
148
+ directory). The TreeGenerator integration (a node sourced from this
149
+ file) is left as a follow-up — the runtime discoverability that
150
+ matters for v1 is the seed CLAUDE.md mention + the `--help` text,
151
+ both of which are wired here.
152
+
153
+ A lower-level primitive (`GITIGNORE_ENTRIES`) already exists and is
154
+ used; the new `.worktrees/` entry just feeds it. A higher-level gate
155
+ (the Layer 4 detector) is what the convention will eventually feed —
156
+ that's a follow-up, but the audit ledger this change writes is the
157
+ signal that detector will consume.
158
+
159
+ ## 4. Signal vs authority compliance
160
+
161
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
162
+
163
+ **Does this change hold blocking authority with brittle logic?**
164
+
165
+ - [x] No — this change has no judgment-call block/allow surface. The
166
+ blocks are all structural hard-invariant validation (`realpath`
167
+ containment, regex on agent-name segment, registry-membership lookup,
168
+ `git check-ref-format`, `O_NOFOLLOW` + `fstat`), which fall under the
169
+ "hard-invariant validation" and "safety guards on irreversible
170
+ actions" carve-outs documented at the bottom of
171
+ `docs/signal-vs-authority.md`.
172
+ - The audit ledger is explicitly a **signal**. The spec restates this
173
+ in §Design ("Ledger is signal, never authority") and the manager
174
+ comment at `worktreeDedupeKey` carries the same invariant for the
175
+ Layer 4 detector — the detector's authority rule is path-based, not
176
+ ledger-membership-based.
177
+ - No new LLM-backed gates introduced. No new authorities introduced.
178
+
179
+ ## 5. Interactions
180
+
181
+ - **Shadowing:** The `worktree` command group's existing
182
+ `register-keypair` subcommand is unaffected — different verb,
183
+ different action method, different command path. Group description
184
+ updated to distinguish them so `instar worktree --help` is honest
185
+ about both purposes.
186
+ - **Double-fire:** The hand-rolled bash helper in echo/bob's `.bin/`
187
+ (`instar-worktree-create.sh`) continues to exist and continues to
188
+ work. After Layer 3 ships, the wrapper refresh re-exports it to
189
+ `exec` into `instar worktree create` so the two paths converge; for
190
+ now, both are functional but the bash helper is the primary path on
191
+ echo's machine until Layer 3 lands.
192
+ - **Races:** The CLI runs `git worktree prune` before every `add`, and
193
+ on add-failure it does NOT remove any partial directory — git owns
194
+ rollback. Two agent homes targeting the same `INSTAR_REPO` cannot
195
+ produce partial state on either side: each path is contained inside
196
+ its own `.worktrees/`, and the `worktree-list`/`prune`/`add`
197
+ sequence is git's own atomicity contract.
198
+ - **Feedback loops:** None. The ledger feeds nothing in this PR (Layer
199
+ 4 is the consumer, deferred). The scaffold seed is one-shot at
200
+ `instar init`.
201
+
202
+ ## 6. External surfaces
203
+
204
+ - **Other agents on the same machine:** the seed change shows up only
205
+ in *newly initialized* agent homes (Layer 2 path). Existing agents
206
+ see nothing until Layer 3 ships.
207
+ - **Other users of the install base:** no — same as above. The new
208
+ CLI subcommand is available to anyone who upgrades, but it's purely
209
+ additive; no existing command's behaviour changes.
210
+ - **External systems:** none. No network calls. No Telegram. No
211
+ GitHub. The integration test creates a local bare git repo in `os.tmpdir()`.
212
+ - **Persistent state:** two new JSONL files per agent home
213
+ (`.worktrees/.ledger.jsonl` + `<stateDir>/audit/worktree-ops.jsonl`)
214
+ appended on every successful create. Both are bounded — the spec
215
+ defers ring-rotation to the PostUpdateMigrator (Layer 3) — and both
216
+ are git-ignored in the agent home. Rollback is `rm` of the two
217
+ files, no schema migration.
218
+ - **Timing / runtime conditions:** the manager calls out to `git`
219
+ shell-out (via `execFileSync`). The integration test exercises this
220
+ against a real `git init`. No long-running operations; no timeouts
221
+ needed.
222
+
223
+ ## 7. Rollback cost
224
+
225
+ - **Code:** revert these commits. The hand-rolled bash helper in
226
+ echo/bob `.bin/` already exists, has been in production today, and
227
+ continues to work. No agent is dependent on `instar worktree create`
228
+ yet because Layer 3 (migrator) hasn't shipped — until it does, no
229
+ existing agent has the wrapper that calls into Layer 1.
230
+ - **Persistent state:** the two JSONL files. `rm
231
+ <agent_home>/.worktrees/.ledger.jsonl` and
232
+ `rm <stateDir>/audit/worktree-ops.jsonl` are safe one-liners; both
233
+ are signal-only and consumed by nothing in this PR.
234
+ - **Agent state repair:** none required.
235
+ - **User visibility during rollback:** the only user-visible surface
236
+ is the `instar worktree create` subcommand, which simply disappears.
237
+ Anyone using it would fall back to the bash helper.
238
+
239
+ Total rollback time: under 5 minutes (one revert, two `rm`).
240
+
241
+ ## Conclusion
242
+
243
+ The change is purely additive at the CLI layer, additive in the
244
+ scaffold layer (new agents only), and one-shot at the `.gitignore`
245
+ seed. All decision points are structural validators in the spec's
246
+ hard-invariant / safety-guard carve-outs. No new authorities. No
247
+ detectors with blocking power. 38 new tests pass (27 unit + 5
248
+ scaffold-unit + 6 integration). Rollback is straightforward and the
249
+ hand-rolled bash helper continues to function as a fallback.
250
+
251
+ Phase 5 trigger check: the change does **not** touch outbound or
252
+ inbound messaging dispatch, session lifecycle (spawn/restart/kill),
253
+ compaction/respawn, coherence gates, idempotency at the transport
254
+ layer, trust levels, or anything named sentinel/guard/gate/watchdog.
255
+ No second-pass reviewer required.
256
+
257
+ Clear to ship.
258
+
259
+ ## Evidence pointers
260
+
261
+ - **Unit tests:** `tests/unit/InstarWorktreeManager.test.ts` (27
262
+ cases — agent-home resolution including the hostile-CWD path,
263
+ INSTAR_REPO validation including unallowlisted remote +
264
+ out-of-repo `core.hooksPath`, slug/branch validation including
265
+ path-traversal and `--upload-pack=` injection, ledger O_NOFOLLOW
266
+ + fstat owner/mode gate).
267
+ - **Scaffold tests:** `tests/unit/scaffold-worktree-convention.test.ts`
268
+ (5 cases — literal-text assertions on the seed sections + idempotent
269
+ `.gitignore` addition).
270
+ - **Integration tests:** `tests/integration/instar-worktree-create.test.ts`
271
+ (6 cases — happy path with identity verification + ledger + mirror,
272
+ node_modules default symlink, `--no-share-node-modules` opt-out,
273
+ slug collision without removing partial state, path-containment
274
+ refusal of a pre-planted symlink at `.worktrees/`, refusal of a
275
+ repo with unset remote).
276
+ - **Spec:** `docs/specs/AGENT-WORKTREE-CONVENTION-SPEC.md` (approved
277
+ 2026-05-17 22:35 UTC).
278
+ - **ELI16:** `docs/specs/AGENT-WORKTREE-CONVENTION-ELI16.md`.
279
+ - **Convergence report:**
280
+ `docs/specs/reports/agent-worktree-convention-convergence.md`.
@@ -0,0 +1,285 @@
1
+ # Side-Effects Review — Agent Worktree Convention (Layers 3 + 4)
2
+
3
+ **Version / slug:** `agent-worktree-convention-layer-3-4`
4
+ **Date:** `2026-05-19`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required` (see §"Phase 5 trigger check" below)
7
+
8
+ ## Summary of the change
9
+
10
+ Completes the agent worktree convention spec by adding the two layers
11
+ that propagate the convention to **existing** agents and observe
12
+ violations on every agent boot.
13
+
14
+ - **Layer 3 — `PostUpdateMigrator.migrateWorktreeConvention`.** Runs on
15
+ every agent update tick. Validates the agent home against Layer 1's
16
+ shape contract (registry membership + anchored regex), refuses if
17
+ `<agent_home>/.bin` is a symlink, then installs/refreshes the
18
+ `instar-worktree-create.sh` wrapper (always-overwrite per Migration
19
+ Parity Standard), idempotently adds `.worktrees/` to
20
+ `<agent_home>/.gitignore`, and ensures `<agent_home>/.worktrees/`
21
+ exists with mode `0700`. Plus a **Migration Parity Standard backfill**
22
+ in `migrateClaudeMd`: the "Worktree Convention" section is appended
23
+ to existing agents' CLAUDE.md (the scope-coherence checkpoint caught
24
+ this gap earlier — without it, only fresh `instar init` agents would
25
+ ever see the convention text). Mirrored to AGENTS.md / GEMINI.md via
26
+ the v1.0.15 shadow-capability mirror (added one entry to the markers
27
+ list).
28
+ - **Layer 4 — `AgentWorktreeDetector`.** Runs once per agent server
29
+ boot, from `startServer()`. Resolves the canonical instar repo
30
+ deterministically (config `worktree.repoPath` or default fallback
31
+ chain — explicitly NOT `INSTAR_REPO` env, because env vars can
32
+ differ between lifeline boot and interactive sessions and the
33
+ detector wants consistent results across both). Runs `git worktree
34
+ list --porcelain` with a 2-second timeout, skips the main checkout
35
+ and bare entries, and emits an AttentionItem-shaped record for every
36
+ remaining worktree whose path is not under any registered agent's
37
+ `.worktrees/`. **Signal-only**: never blocks, never moves, never
38
+ deletes. Dedupe via the documented `worktree-misplaced:sha256(path)`
39
+ key. When no Telegram adapter is configured, appends to a JSONL
40
+ fallback at `<stateDir>/audit/worktree-detector.jsonl` (O_NOFOLLOW +
41
+ fstat owner/mode gate, 24h rolling-window dedupe).
42
+
43
+ **Files touched:**
44
+
45
+ - `src/core/PostUpdateMigrator.ts` (+~130 lines: `migrateWorktreeConvention` method, registration in `migrate()`, marker addition in `migrateFrameworkShadowCapabilities`, "Worktree Convention" section in `migrateClaudeMd`, import of `resolveAgentHome` from Layer 1's manager).
46
+ - `src/core/AgentWorktreeDetector.ts` (new, ~330 lines).
47
+ - `src/commands/server.ts` (+~35 lines: detector invocation after `registerAgent`).
48
+ - `src/templates/scripts/instar-worktree-create.sh` (new — canonical wrapper template the migrator installs into `<agent_home>/.bin/`).
49
+ - `scripts/lint-no-direct-destructive.js` (+8 lines — adds the wrapper template to `SHELL_ALLOWLIST` with documented rationale).
50
+ - `tests/unit/migrateWorktreeConvention.test.ts` (new, 5 cases).
51
+ - `tests/unit/AgentWorktreeDetector.test.ts` (new, 10 cases).
52
+ - `upgrades/NEXT.md` (appended section + capabilities-table rows + updated deferred list).
53
+
54
+ ## Decision-point inventory
55
+
56
+ - **Migrator refusal on `.bin/` symlink** — *add*. Structural safety guard
57
+ on an irreversible action (would write a wrapper script via a symlink
58
+ that resolves outside the agent home, e.g., to `/usr/local/bin`).
59
+ Hard-invariant: `lstat`-based, no judgment call. Carve-out applies.
60
+ - **Migrator silent-skip on non-conforming agent home** — *add*. Refuses
61
+ to install the wrapper for project-bound agents whose home isn't
62
+ `~/.instar/agents/<name>/`. The convention only applies to
63
+ agent-home-living agents; project-bound agents have the project dir
64
+ as their primary working directory and the wrapper is meaningless
65
+ for them. Hard-invariant via the same regex+registry contract as
66
+ Layer 1.
67
+ - **Detector — AttentionItem emission** — *add*. Pure signal. Emits to
68
+ AttentionQueue (which already exists as the authority for what to do
69
+ with attention items). Never blocks; the agent boots regardless.
70
+ Compliant with signal-vs-authority per the spec's explicit
71
+ "signal only" framing.
72
+ - **Detector — path-based filter rule** — *add*. The detector's only
73
+ authoritative rule is `realpath(worktree_path) startsWith
74
+ realpath(<safe_root>)` for some registered agent. Pure structural
75
+ validator, no judgment.
76
+
77
+ No new authorities introduced. No detectors hold blocking power.
78
+
79
+ ---
80
+
81
+ ## 1. Over-block
82
+
83
+ **What legitimate inputs does this change reject that it shouldn't?**
84
+
85
+ - **Migrator** silently skips agents whose home doesn't match the
86
+ `<instarHome>/agents/<name>/` shape. *Intentional* — project-bound
87
+ agents would receive a wrapper that points at their project dir but
88
+ the convention's spec is specific to agent-home-living agents.
89
+ - **Migrator** refuses if `<agent_home>/.bin` is a symlink. Operators
90
+ who deliberately symlink `.bin/` to a shared dir would be refused.
91
+ This is documented in the spec adversarial-finding section; the
92
+ alternative (silently writing into the symlink target) was rejected
93
+ as a security regression.
94
+ - **Detector** flags every worktree of the canonical instar repo
95
+ outside the safe-root set. **First-run on echo's machine will emit
96
+ ~30 attention items** (one per pre-existing worktree). Each is
97
+ deduped for 24h. Operator must drain or relocate. This is the spec's
98
+ documented "one-time burst" and is in the Deferred list of NEXT.md.
99
+
100
+ ## 2. Under-block
101
+
102
+ **What failure modes does this still miss?**
103
+
104
+ - **Migrator** does not detect or repair drift in `.worktrees/` mode
105
+ beyond re-asserting 0700 on every run. If something changes the
106
+ permissions on individual worktree directories underneath, that's
107
+ not policed. *Acceptable* — those are operator-managed
108
+ per-worktree.
109
+ - **Detector** treats stale `git worktree list` entries (paths whose
110
+ worktree dir was rm'd outside `git worktree prune`) as not-misplaced
111
+ (they're skipped via the existsSync check). This is intentional —
112
+ the spec wants signal on real misplacements, not on stale
113
+ metadata. A separate detector for stale-metadata could be a
114
+ follow-up.
115
+ - **Detector** does not yet feed AttentionQueue when Telegram is
116
+ configured — it falls back to JSONL in v1 because TelegramAdapter
117
+ initializes later in `startServer` than the detector. NEXT.md
118
+ Deferred list documents this; the JSONL trail is the durable
119
+ observation surface for v1.
120
+ - **Compromised local user** (same uid, full execution privilege)
121
+ remains explicitly out-of-scope per the spec's threat-model
122
+ boundary.
123
+
124
+ ## 3. Level-of-abstraction fit
125
+
126
+ **Is this at the right layer?**
127
+
128
+ - **Migrator step** lives where every other `PostUpdateMigrator`
129
+ step lives, registered in `migrate()`. The bash-wrapper template is
130
+ in `src/templates/scripts/` alongside other agent-installed scripts
131
+ (`git-sync-gate.sh`, etc.) — same pattern as
132
+ `migrateConversationalCatalogPlaybookManifest`.
133
+ - **CLAUDE.md backfill** is a content-sniffing patch inside the
134
+ existing `migrateClaudeMd`, matching the pattern every other Capability
135
+ section already uses (`Self-Discovery`, `Cloudflare Tunnel`,
136
+ `Dashboard`, etc.). One marker added to
137
+ `migrateFrameworkShadowCapabilities` is the minimal touch needed for
138
+ Codex/Gemini shadow propagation.
139
+ - **Detector** is a standalone module under `src/core/` (alongside
140
+ Layer 1's `InstarWorktreeManager`). It's invoked once from
141
+ `startServer()` — a deliberate placement after `registerAgent` so
142
+ the registry is populated before `enumerateSafeRoots()` walks it.
143
+ - Reused primitives: `resolveAgentHome` and `resolveInstarRepo` (from
144
+ Layer 1's manager) — keeps the validation rules in one place so the
145
+ CLI and the migrator/detector can't disagree about "what an agent
146
+ home is" or "what a valid instar repo is."
147
+
148
+ A lower-level primitive (Layer 1's manager) is consumed; the higher
149
+ layer (AttentionQueue) is fed via signal-only emission. No new
150
+ authority introduced.
151
+
152
+ ## 4. Signal vs authority compliance
153
+
154
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
155
+
156
+ **Does this change hold blocking authority with brittle logic?**
157
+
158
+ - [x] No — the migrator's blocks are structural safety guards
159
+ (`.bin/` symlink refusal, agent-home regex+registry check) — both
160
+ carve-outs in `docs/signal-vs-authority.md` ("hard-invariant
161
+ validation" and "safety guards on irreversible actions").
162
+ - [x] No — the detector is **explicitly signal-only** per spec. It
163
+ emits AttentionItems / appends JSONL; the AttentionQueue is the
164
+ authority that decides what to do with them. The detector's filter
165
+ rule is path-based hard-invariant (no judgment).
166
+
167
+ The detector's invariant — "the rule lives here and only here, never
168
+ the audit ledger" — is restated in the file-level comment so future
169
+ maintainers can't drift toward ledger-membership.
170
+
171
+ ## 5. Interactions
172
+
173
+ - **Shadowing:** the migrator runs in the `migrate()` chain after
174
+ `migrateConversationalCatalogPlaybookManifest` and before the async
175
+ `migrateParityRenderings` (in `migrateAsync()`). It doesn't share
176
+ state with adjacent steps. The `migrateClaudeMd` backfill rides on
177
+ top of the existing patched-content flow, sharing `patched` with
178
+ earlier sections so the file is written once.
179
+ - **Double-fire:** the wrapper template the migrator installs is now
180
+ the canonical source. The hand-rolled wrappers in echo + bob's
181
+ `.bin/` will be refreshed by the migrator on their next update tick.
182
+ Until then they coexist (both work — they're functionally
183
+ equivalent).
184
+ - **Races:** the detector runs once per agent boot, before the server
185
+ starts listening. No concurrent invocations from the same agent.
186
+ Two agents booting concurrently could each enumerate the same
187
+ worktree list — each emits its own AttentionItem with the same
188
+ `worktree-misplaced:sha256(path)` id, which the AttentionQueue
189
+ collapses to a single topic per the existing dedupe contract (line
190
+ 2949 of `TelegramAdapter.ts`). The JSONL fallback dedup is per-file
191
+ (each agent has its own state dir).
192
+ - **Feedback loops:** the detector reads `git worktree list`. The
193
+ Layer 1 CLI writes to that list. There's no infinite-loop concern
194
+ because the detector emits to AttentionQueue (or JSONL), not back
195
+ into `git worktree`.
196
+ - **Migration Parity:** the Layer 2 changes (seed CLAUDE.md +
197
+ GITIGNORE_ENTRIES) are now also delivered to existing agents on
198
+ update — closing the parity gap. Without the Layer 3
199
+ `migrateClaudeMd` backfill, existing agents would never see the
200
+ convention text; with it, they pick it up on the next update tick.
201
+
202
+ ## 6. External surfaces
203
+
204
+ - **Other agents on the same machine:** every agent picks up the
205
+ wrapper, the `.gitignore` entry, the `.worktrees/` directory, and
206
+ the CLAUDE.md section on its own next update tick. Single-agent
207
+ scope per spec — no cross-agent writes.
208
+ - **Other users of the install base:** purely additive. Existing
209
+ workflows are unchanged. The wrapper just makes the safe path the
210
+ default; raw `git worktree add` still works (and gets flagged by
211
+ the detector).
212
+ - **External systems:** none. No network. The detector calls `git`
213
+ locally, with a 2-second timeout.
214
+ - **Persistent state:**
215
+ - `<agent_home>/.bin/instar-worktree-create.sh` (new file per agent
216
+ on update tick — always-overwrite).
217
+ - `<agent_home>/.gitignore` (idempotent line addition).
218
+ - `<agent_home>/.worktrees/` (directory creation, mode 0700).
219
+ - `<agent_home>/CLAUDE.md` (single section appended, idempotent
220
+ via content-sniff).
221
+ - `<agent_home>/AGENTS.md` / `GEMINI.md` (single section mirrored
222
+ when those shadows exist).
223
+ - `<stateDir>/audit/worktree-detector.jsonl` (append-only signal
224
+ log; bounded by 24h rolling-window dedupe before append).
225
+ - All git-ignored or per-machine. Rollback is `rm` of these files.
226
+ - **Timing:** the detector's 2-second timeout means a hung
227
+ `git worktree list` doesn't block agent boot beyond that window.
228
+
229
+ ## 7. Rollback cost
230
+
231
+ - **Code:** revert this commit. The Layer 1 CLI subcommand (PR #277)
232
+ continues to function for fresh `instar worktree create` calls.
233
+ Existing agents that already received the wrapper on a prior update
234
+ tick keep it (it works — it delegates to Layer 1).
235
+ - **Persistent state:**
236
+ - The wrapper, `.gitignore` entry, and `.worktrees/` directory in
237
+ each agent home are harmless leftovers. No removal required.
238
+ - JSONL fallback files are signal-only and consumed by nothing on
239
+ rollback. `rm` is a safe one-liner per agent.
240
+ - CLAUDE.md "Worktree Convention" section is documentation only —
241
+ leaving it in is benign.
242
+ - AGENTS.md / GEMINI.md mirrored sections — same.
243
+ - **Agent state repair:** none required.
244
+ - **User visibility during rollback:** none.
245
+
246
+ Total rollback time: under 5 minutes (one revert + optional cleanup
247
+ sweep).
248
+
249
+ ## Conclusion
250
+
251
+ Completes the agent worktree convention spec. Migrator backfills
252
+ existing agents with the on-disk surface (wrapper, gitignore, dir
253
+ permissions, CLAUDE.md section, AGENTS/GEMINI mirror) so the
254
+ convention applies fleet-wide on the next update tick — closing the
255
+ Migration Parity gap the scope-coherence checkpoint flagged during
256
+ Layer 1+2+5 implementation. Detector emits signal per misplaced
257
+ worktree without ever blocking. All structural validators are in the
258
+ signal-vs-authority carve-outs. 15 new tests pass (5 migrator + 10
259
+ detector). Pre-push gate green.
260
+
261
+ Phase 5 trigger check: the change does **not** touch outbound/inbound
262
+ messaging dispatch, session lifecycle, compaction, coherence gates,
263
+ idempotency at transport, trust levels, or anything named
264
+ sentinel/guard/gate/watchdog (the "Detector" naming is intentional —
265
+ it's a signal producer, not a gate). No second-pass reviewer required.
266
+
267
+ Clear to ship.
268
+
269
+ ## Evidence pointers
270
+
271
+ - **Migrator unit tests:** `tests/unit/migrateWorktreeConvention.test.ts`
272
+ (happy path with wrapper + .worktrees/0700 + .gitignore;
273
+ idempotency; always-overwrites tampered wrapper; refuses
274
+ `.bin/`-as-symlink; silently skips project-bound agents).
275
+ - **Detector unit tests:** `tests/unit/AgentWorktreeDetector.test.ts`
276
+ (silent when no misplaced; skips main checkout entry; emits one
277
+ AttentionItem per misplaced via injected emitter; skips when under
278
+ safe root; JSONL fallback writes; 24h JSONL dedupe; refuses
279
+ pre-planted symlink at the fallback path; resolveDetectorInstarRepo
280
+ honors config and returns null on no-repo).
281
+ - **Spec:** `docs/specs/AGENT-WORKTREE-CONVENTION-SPEC.md` (approved
282
+ 2026-05-17 22:35 UTC).
283
+ - **Sibling artifact:**
284
+ `upgrades/side-effects/agent-worktree-convention-layer-1-2-5.md`
285
+ (Layers 1+2+5, merged in PR #277 as commit `bdf8508f`).