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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +29 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/BackupManager.d.ts.map +1 -1
- package/dist/core/BackupManager.js +5 -0
- package/dist/core/BackupManager.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +104 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/PrHandLease.d.ts +151 -0
- package/dist/core/PrHandLease.d.ts.map +1 -0
- package/dist/core/PrHandLease.js +448 -0
- package/dist/core/PrHandLease.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/instarSettingsHooks.d.ts.map +1 -1
- package/dist/core/instarSettingsHooks.js +10 -0
- package/dist/core/instarSettingsHooks.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +2 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +4 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +56 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +2 -0
- package/upgrades/1.3.601.md +38 -0
- 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.)
|