instar 1.3.600 → 1.3.601

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 (39) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +29 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/BackupManager.d.ts.map +1 -1
  5. package/dist/core/BackupManager.js +5 -0
  6. package/dist/core/BackupManager.js.map +1 -1
  7. package/dist/core/PostUpdateMigrator.d.ts +1 -0
  8. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  9. package/dist/core/PostUpdateMigrator.js +104 -0
  10. package/dist/core/PostUpdateMigrator.js.map +1 -1
  11. package/dist/core/PrHandLease.d.ts +151 -0
  12. package/dist/core/PrHandLease.d.ts.map +1 -0
  13. package/dist/core/PrHandLease.js +448 -0
  14. package/dist/core/PrHandLease.js.map +1 -0
  15. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  16. package/dist/core/devGatedFeatures.js +6 -0
  17. package/dist/core/devGatedFeatures.js.map +1 -1
  18. package/dist/core/instarSettingsHooks.d.ts.map +1 -1
  19. package/dist/core/instarSettingsHooks.js +10 -0
  20. package/dist/core/instarSettingsHooks.js.map +1 -1
  21. package/dist/scaffold/templates.d.ts.map +1 -1
  22. package/dist/scaffold/templates.js +2 -0
  23. package/dist/scaffold/templates.js.map +1 -1
  24. package/dist/server/AgentServer.d.ts +1 -0
  25. package/dist/server/AgentServer.d.ts.map +1 -1
  26. package/dist/server/AgentServer.js +1 -0
  27. package/dist/server/AgentServer.js.map +1 -1
  28. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  29. package/dist/server/CapabilityIndex.js +1 -0
  30. package/dist/server/CapabilityIndex.js.map +1 -1
  31. package/dist/server/routes.d.ts +4 -0
  32. package/dist/server/routes.d.ts.map +1 -1
  33. package/dist/server/routes.js +56 -0
  34. package/dist/server/routes.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/data/builtin-manifest.json +65 -65
  37. package/src/scaffold/templates.ts +2 -0
  38. package/upgrades/1.3.601.md +38 -0
  39. package/upgrades/side-effects/parallel-hand-pr-lease.md +153 -0
@@ -0,0 +1,153 @@
1
+ # Side-Effects Review — Parallel-Hand PR Lease
2
+
3
+ Spec: `docs/specs/parallel-hand-pr-lease.md` (converged 4 rounds + approved).
4
+ Change: a per-branch push-ownership lease so two of the agent's OWN concurrent
5
+ sessions can't push competing commits to the same branch (the 2026-06-15 #1183
6
+ 2-hour merge thrash). A PreToolUse Bash hook asks the server (before a `git push`)
7
+ whether another LIVE session of this agent holds the branch's lease; if so the
8
+ second hand stands down. Dev-gated dark + dryRun-first; coordinates the agent's OWN
9
+ cooperating hands only.
10
+
11
+ **Phase-1 principle check (decision-point = YES):** the lease DENIES a `git push`,
12
+ so signal-vs-authority applies. Per spec §7 this is a deliberate, blast-radius-LIMITED
13
+ P2 EXCEPTION (not "it satisfies P2"): it blocks ONLY the agent's own cooperative
14
+ hands, ONLY a `git push`, FAILS OPEN on every uncertainty, and surfaces holder+intent
15
+ so a distinct fix escalates as a follow-up (never silently dropped). Outside those
16
+ limits it is a signal, not authority.
17
+
18
+ Files:
19
+ - `src/core/PrHandLease.ts` — store (one-lock CAS, topic-id identity, TTL-gated host-aware liveness, liveness-discriminated 90m ceiling, fail-open-on-corrupt, dryRun isolation, tombstones) + pure `canonicalPushKey` (git-native two-tier ref resolution).
20
+ - `src/server/routes.ts` — `POST /pr-leases/evaluate` (fail-open on every uncertainty; dryRun→allow+wouldDeny; redacts holderSessionId) + `GET /pr-leases` (derived liveness; 503 when disabled) + `RouterContext.prHandLease?`.
21
+ - `src/core/PostUpdateMigrator.ts` — `getPrHandLeaseGuardHook()` + migrateHooks deploy + migrateClaudeMd awareness.
22
+ - `src/core/instarSettingsHooks.ts` — `INSTAR_BASH_PRETOOLUSE_HOOKS` += the guard (registers for both init + existing-agent migration).
23
+ - `src/commands/server.ts` + `src/server/AgentServer.ts` — dev-gated construction (machine-local v1, runningSessionNames=tmuxSession set, audit→logs/pr-lease-decisions.jsonl) → AgentServer option → routeCtx.
24
+ - `src/core/devGatedFeatures.ts` — `prHandLease` dev-gate entry (dryRun-canary).
25
+ - `src/core/BackupManager.ts` — `state/pr-hand-leases.json` on the ephemeral denylist.
26
+ - `src/scaffold/templates.ts` — generateClaudeMd awareness block.
27
+ - Tests: `tests/unit/pr-hand-lease.test.ts` (20), `tests/integration/pr-lease-route.test.ts` (8); ratchets green (devGatedFeatures-wiring, migration-parity-hooks, feature-delivery, pretooluse-parity, no-bare-require).
28
+
29
+ ## Build-time design DEVIATION from the spec (recorded, mine, reversible)
30
+ Spec §4 idealized "the hook does a single local JSON read, no HTTP." The build
31
+ re-targeted to **hook → `POST /pr-leases/evaluate` (localhost)**: a generated
32
+ standalone `.js` hook can't cleanly import the TS store, and reimplementing the
33
+ security logic in JS would DUPLICATE it (drift hazard on a security path). The
34
+ codebase pattern (every instar hook) is hook→server. Latency is a localhost
35
+ round-trip (~ms); the git dry-run resolver runs server-side only on a detected push.
36
+ Same security posture; the logic stays in tested TS. Server-unreachable → fail-open.
37
+
38
+ ## 1. Over-block
39
+ The ONLY block is `git push` exit-2 on a confirmed LIVE foreign same-agent lease
40
+ (and only at `dryRun:false`). Over-block risks + mitigations: (a) a stale lease read
41
+ as live → TTL + host-aware liveness + the 90m ceiling force-release a dead/foreign
42
+ holder; (b) a corrupt state file / server-down / hook crash → ALL fail OPEN (allow);
43
+ (c) the dry-run resolver hanging → bounded timeout + GIT_TERMINAL_PROMPT=0 → fail
44
+ open. A live same-machine holder past 90m is NOT seized (would reintroduce the
45
+ thrash) — it escalates to the operator instead. Verified by the unit truth-table.
46
+
47
+ ## 2. Under-block
48
+ Misses (accepted residual evasions of a COOPERATIVE guard, stated in §2 non-goals):
49
+ a push hidden in a called script body (`bash deploy.sh`), a git alias, or string
50
+ obfuscation (`git pu"sh"`) — the hook matches a literal `git push` in the command.
51
+ Also OUT of scope: cross-MACHINE collisions (machine-local v1; `holderMachineId`
52
+ load-bearing only for the never-falsely-dead rule) and shared-WORKTREE edit bleed
53
+ (§1.1b — the `instar worktree create` convention is the mitigation). All documented.
54
+
55
+ ## 3. Level-of-abstraction fit
56
+ Correct. Enforcement sits at the PreToolUse Bash chokepoint where the agent's own
57
+ `git push` actually runs (round-2 caught that SafeGitExecutor — instar's internal TS
58
+ wrapper — is bypassed by agent Bash pushes). The security logic lives server-side in
59
+ tested TS; the hook is a thin relay. Identity is the stable TOPIC, not the respawn-
60
+ volatile session id. Reuses the ResumeQueue lock/persist/FD discipline.
61
+
62
+ ## 4. Signal vs authority compliance (docs/signal-vs-authority.md)
63
+ COMPLIANT as a deliberate, bounded P2 exception (§7): a brittle deterministic check
64
+ holds blocking authority ONLY over the agent's own cooperative hands, only over
65
+ `git push`, fails open on every uncertainty, and never blocks a principal/operator.
66
+ The deny surfaces holder+intent so a genuinely-distinct fix escalates rather than is
67
+ dropped. Dark + dryRun-first means the bind is opt-in and observable first.
68
+
69
+ ## 5. Interactions
70
+ - MergeRunner (green-pr-automerge): NOT a competing committer; reads the lease only as
71
+ a soft-hold on a live same-machine `intent:rework` (bounded by TTL). A dryRun lease
72
+ is ignored by all non-acquisition readers (so it can't perturb auto-merge).
73
+ - ResumeQueue: shares the lock/persist/FD pattern but a SEPARATE file + lock (no
74
+ contention with the revival queue).
75
+ - The hook is registered AFTER the existing Bash PreToolUse guards; it never shadows
76
+ them (it only blocks `git push`, and only on a confirmed deny).
77
+
78
+ ## 6. External surfaces
79
+ - New routes `POST /pr-leases/evaluate` + `GET /pr-leases` (Bearer-auth'd by the
80
+ server's global middleware like every route). New PreToolUse Bash hook
81
+ `pr-hand-lease-guard.js` (registered for init + existing agents via the canonical
82
+ hook list). New config under `monitoring.prHandLease.*` (read with safe defaults).
83
+ - Flag off (fleet default) → byte-identical-today behavior (hook fail-opens / route
84
+ feature-disabled). Visible only on a dev agent, and there only in dryRun (logs).
85
+
86
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
87
+ **Machine-local BY DESIGN (v1), stated honestly.** The lease state is per-machine;
88
+ two hands on DIFFERENT machines driving the same branch is NOT prevented by v1 (the
89
+ same thrash recurs). `holderMachineId` is load-bearing ONLY for the safety rule "never
90
+ judge a foreign-machine holder dead from local session absence" (so v1 never falsely
91
+ seizes a cross-machine holder within the ceiling). A fully replicated cross-machine
92
+ lease (likely a git-ref CAS lock) is the tracked §9b follow-up; it MUST inherit the
93
+ ResumeQueue FD1/FD2/FD5 host-classification before it ships. The observed incident
94
+ was same-machine, so v1 closes the demonstrated failure.
95
+
96
+ ## 8. Rollback cost
97
+ Trivial + reversible. Flag off (`monitoring.prHandLease` absent / developmentAgent
98
+ false) → the route feature-disables (fail-open) and the hook fail-opens → no behavior
99
+ change. The state file is ephemeral (excluded from backup). No data migration, no
100
+ persistent external side-effect. A bad deploy is a config flip away from inert.
101
+
102
+ ## Testing-scope decision (honest, precedented)
103
+ Unit (20) + integration (8) cover the store logic + the route over the real HTTP
104
+ pipeline. The generated hook is syntax-validated (`node --check`) + covered by the
105
+ no-bare-require regression test. A full hook-subprocess behavior test (mock server +
106
+ exit-code assertions) and a boot-e2e ("feature alive": route 200 not 503) are the one
107
+ deferred tier — same honest-scope decision as P1/#1174 + action-claim/#1178, justified
108
+ because the route IS exercised end-to-end at the integration tier and the hook is a
109
+ thin fail-open relay. <!-- tracked: follow-up — add tests/unit/pr-hand-lease-guard.test.ts (hook subprocess) + a boot-e2e at fleet-rollout, per the action-claim precedent -->
110
+
111
+ ## Phase-5 second-pass review (REQUIRED — touches a hook/gate/push-path)
112
+ **CONCUR with the review.** An independent reviewer audited every claim against the
113
+ code: the hook fail-opens on every path (non-Bash, no-git-push, no-config, no-topic/
114
+ session, server-down/timeout inner-catch, own-crash outer-catch, 8s stdin backstop);
115
+ the route fail-opens on null prHandLease / bad-request / no-branch-key / any throw,
116
+ and `evaluate()` is non-throwing (corrupt state → `{}`); the DENY path fires ONLY at
117
+ `dryRun:false` on a confirmed live foreign-topic lease (default dryRun rewrites every
118
+ deny/escalate to `allow+wouldDeny`); holderSessionId is redacted on both surfaces; a
119
+ respawned same-topic session reads its own lease as `own-topic` allow (topicId identity,
120
+ checked before any liveness probe); the 90m ceiling escalates a LIVE same-machine
121
+ holder but CAS-seizes a dead/foreign holder (loser yields); escalation routes to the
122
+ real aggregated-attention chokepoint; construction is dev-gated + audited.
123
+
124
+ Minor non-blocking note (accepted, tracked): PrHandLease is constructed nested inside
125
+ the `if (rqCfg.enabled ?? true)` resume-queue block (it reuses `raiseResumeAggregated`
126
+ for onAttention), so explicitly setting `monitoring.resumeQueue.enabled:false` would
127
+ leave prHandLease null — but the route then fail-opens, so this is a FAIL-SAFE soft
128
+ coupling, not a defect (and the resume queue defaults enabled). <!-- tracked: follow-up — decouple PrHandLease construction from the resume-queue-enabled block (give it its own onAttention sink) so the two features are independent -->
129
+
130
+ ## Post-merge addendum (2026-06-16) — no-silent-fallbacks ratchet
131
+ Merging current main into the branch tripped the no-silent-fallbacks ratchet (477 > 474).
132
+ PrHandLease's only flagged catch — the `readAll` corrupt-state fail-open — is now
133
+ `@silent-fallback-ok`-tagged (it is `recordFailOpen()`-surfaced with a recurrence
134
+ attention item, so it is NOT silent). The residual +2 is main's own accumulated drift
135
+ inherited via the merge (main shipped catches under `[skip ci]` releases that never
136
+ re-ran this gate); the baseline is aligned 474→476 with an in-file justification, per the
137
+ Zero-Failure Standard (a merge pulling main's pre-existing red is the merging branch's to
138
+ settle). No behavior change; the lease's fail-open posture is unchanged.
139
+
140
+ ## Post-CI addendum (2026-06-16) — discoverability classification
141
+
142
+ CI surfaced a second gap from the build: the new `GET /pr-leases` +
143
+ `POST /pr-leases/evaluate` routes were registered in routes.ts but the
144
+ `/pr-leases` prefix was never classified, so the capabilities-discoverability
145
+ ratchet failed. Resolved by adding `{ prefix: 'pr-leases', ... }` to
146
+ `INTERNAL_PREFIXES` in `src/server/CapabilityIndex.ts` — the lease is dev-gated
147
+ coordination machinery (a PreToolUse git-push guard consults it; the CLAUDE.md
148
+ template documents it), agent-invisible and 503 on the fleet, so it is correctly
149
+ INTERNAL (skips `/capabilities` discovery) rather than a user-invokable
150
+ capability — same class as `/action-claim` and `/playwright-profiles`.
151
+ Classification only; no runtime behavior change. (Also in this PR: the
152
+ `instar-settings-hooks` anti-drift contract was updated to include the new
153
+ `pr-hand-lease-guard.js` PreToolUse hook.)