instar 1.2.83 → 1.3.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.
- package/dist/commands/init.js +14 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +45 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +2 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +268 -3
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/installCodexHooks.d.ts.map +1 -1
- package/dist/core/installCodexHooks.js +3 -2
- package/dist/core/installCodexHooks.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +10 -2
- package/dist/server/routes.js.map +1 -1
- package/dist/server/stopGate.d.ts +8 -2
- package/dist/server/stopGate.d.ts.map +1 -1
- package/dist/server/stopGate.js +42 -2
- package/dist/server/stopGate.js.map +1 -1
- package/package.json +1 -1
- package/playbook-scripts/build-state.py +39 -1
- package/scripts/analyze-release.js +16 -8
- package/scripts/generate-builtin-manifest.cjs +2 -1
- package/src/data/builtin-manifest.json +74 -65
- package/src/templates/hooks/build-stop-hook.sh +62 -0
- package/src/templates/hooks/settings-template.json +10 -0
- package/upgrades/1.3.0.md +27 -0
- package/upgrades/side-effects/build-stop-hook-session-scoping.md +133 -0
- package/upgrades/side-effects/fresh-session-stop-gate-shadow-wiring.md +35 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Upgrade Guide — NEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
<!-- Valid values: patch, minor, major -->
|
|
5
|
+
<!-- patch = bug fixes, refactors, test additions, doc updates -->
|
|
6
|
+
<!-- minor = new features, new APIs, new capabilities (backwards-compatible) -->
|
|
7
|
+
<!-- major = breaking changes to existing APIs or behavior -->
|
|
8
|
+
|
|
9
|
+
## What Changed
|
|
10
|
+
|
|
11
|
+
**The `/build` stop-hook is now session-scoped — it only nags the session that actually owns the build.** Before, the hook that keeps a build from quitting half-done had no idea *which* of your concurrent sessions started the build, so it fired its "keep working" block into every session — trapping unrelated ones and, worse, spending the owning build's reinforcement budget on each misfire (when that budget hit its cap, the hook stopped protecting the real builder too).
|
|
12
|
+
|
|
13
|
+
Now `build-state.py` stamps the owning session (its tmux session name, and optionally the Claude session UUID) at build start, and the hook blocks **only** the proven owner. Every other session approve-exits without touching the owner's budget. A build with no owner stamp (legacy state) gets a conservative no-adopt: the hook goes quiet rather than guessing — it never traps a session and never claims ownership.
|
|
14
|
+
|
|
15
|
+
The hook ships via the always-overwrite path (the inline `getBuildStopHook()` twin in `PostUpdateMigrator`, kept byte-identical to `src/templates/hooks/build-stop-hook.sh` and asserted by a drift test), so every agent gets it on update.
|
|
16
|
+
|
|
17
|
+
## What to Tell Your User
|
|
18
|
+
|
|
19
|
+
- **No action needed; this just stops cross-talk between your sessions.** If you run more than one session at once, a build in one of them will no longer pester the others or drain its own "keep going" budget. You won't notice anything unless you run concurrent sessions, in which case it gets quieter.
|
|
20
|
+
|
|
21
|
+
## Summary of New Capabilities
|
|
22
|
+
|
|
23
|
+
| Capability | How to Use |
|
|
24
|
+
|-----------|-----------|
|
|
25
|
+
| Session-scoped build stop-hook | Automatic. `build-state.py init` stamps `owner.{tmux,session,stampedAt}`; the hook blocks only the owner. |
|
|
26
|
+
| Owner-stamp flags on `build-state.py init` | `--owner-session "$CLAUDE_CODE_SESSION_ID"` (precision; SKILL wiring is a fast-follow), `--owner-tmux` (override seam). Tmux name is auto-resolved by default. |
|
|
27
|
+
| Conservative no-adopt for un-stamped builds | Automatic. No owner stamp → hook approves without claiming ownership (never traps, never drains). |
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Side-Effects Review — Build Stop-Hook Session-Scoping
|
|
2
|
+
|
|
3
|
+
**Slug:** `build-stop-hook-session-scoping`
|
|
4
|
+
**Date:** `2026-05-26`
|
|
5
|
+
**Author:** Echo
|
|
6
|
+
**Spec:** `docs/specs/BUILD-STOP-HOOK-SESSION-SCOPING-SPEC.md` (converged round 1, approved by Justin via Telegram topic 13352)
|
|
7
|
+
**Second-pass reviewer:** independent general-purpose review agent (3 findings, all incorporated — see spec §"Review Findings Incorporated")
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
The `/build` Stop hook had no notion of which session owns a build. With one
|
|
12
|
+
shared `build-state.json` and one hook in a checkout, a build started by session
|
|
13
|
+
A fired its "keep working" block into every concurrent session of the same agent
|
|
14
|
+
— trapping unrelated sessions and, on every misfire, incrementing the shared
|
|
15
|
+
`reinforcementsUsed` counter, which drains the owning build's protection budget
|
|
16
|
+
to zero.
|
|
17
|
+
|
|
18
|
+
This change stamps the owning session at `/build` start (`build-state.py init`
|
|
19
|
+
writes `owner.{tmux,session,stampedAt}`) and teaches the hook to block **only**
|
|
20
|
+
the proven owner. Any other session approve-exits **without** incrementing the
|
|
21
|
+
counter. A build with no owner stamp gets a conservative no-adopt (approve,
|
|
22
|
+
never claim ownership) — it never traps a session and never inverts ownership.
|
|
23
|
+
|
|
24
|
+
## Files changed (in gate scope = behavior)
|
|
25
|
+
|
|
26
|
+
- `src/core/PostUpdateMigrator.ts` — the inline `getBuildStopHook()` (the
|
|
27
|
+
shipping artifact; written to `.instar/hooks/instar/build-stop-hook.sh` on every
|
|
28
|
+
migration via always-overwrite, and by `init.ts` via `getHookContent`). Added
|
|
29
|
+
the ownership block between the terminal-phase early-exit and the counter
|
|
30
|
+
mutation.
|
|
31
|
+
- `src/templates/hooks/build-stop-hook.sh` — the canonical reference template +
|
|
32
|
+
builtin-manifest fingerprint. Kept byte-identical to the inline twin (asserted
|
|
33
|
+
by a new drift test).
|
|
34
|
+
|
|
35
|
+
Out of gate scope but part of the change:
|
|
36
|
+
- `playbook-scripts/build-state.py` — `cmd_init` stamps `owner`; new
|
|
37
|
+
`--owner-session` / `--owner-tmux` flags; `resolve_owner_tmux()` helper.
|
|
38
|
+
- `tests/unit/build-stop-hook-session-scoping.test.ts` (new, 12 tests),
|
|
39
|
+
`tests/unit/PostUpdateMigrator-buildStopHook.test.ts` (+1 drift test).
|
|
40
|
+
- `docs/specs/*`, `upgrades/NEXT.md` (docs / release note).
|
|
41
|
+
|
|
42
|
+
## Decision-point inventory
|
|
43
|
+
|
|
44
|
+
- **Added**: hook ownership gate — between terminal-phase exit and counter
|
|
45
|
+
mutation. Decides block (owner) vs approve-no-increment (non-owner / unknown /
|
|
46
|
+
un-stamped). This is the new decision boundary.
|
|
47
|
+
- **Added**: `build-state.py` owner stamp at init (records identity; no runtime
|
|
48
|
+
decision, pure data).
|
|
49
|
+
- **Unchanged**: no-state-file exit, terminal-phase exit, and the
|
|
50
|
+
counter/reinforcement block logic itself (the owner path falls through to the
|
|
51
|
+
exact pre-existing code).
|
|
52
|
+
|
|
53
|
+
## Over-block / under-block analysis
|
|
54
|
+
|
|
55
|
+
- **Over-block risk (trapping a non-owner):** eliminated. A non-owner returns
|
|
56
|
+
`approve` before reaching the counter. The only block path requires a positive
|
|
57
|
+
owner match (tmux or session). Tested: non-owner tmux, non-owner session,
|
|
58
|
+
identity-unknown, and legacy/un-stamped all return approve.
|
|
59
|
+
- **Under-block risk (owner not protected):** bounded and acceptable. The owner
|
|
60
|
+
is protected whenever `owner.tmux` matches the live tmux (the load-bearing
|
|
61
|
+
path, proven live) or `owner.session` matches stdin `session_id`. The only
|
|
62
|
+
under-protection case is an **un-stamped** build (legacy state, or an
|
|
63
|
+
environment where stamping didn't run) — by deliberate design the hook goes
|
|
64
|
+
quiet there rather than guess. Forfeiting protection for a stale build is the
|
|
65
|
+
correct trade vs. trapping the wrong session (spec §"Why conservative-no-adopt").
|
|
66
|
+
- **Bootstrap inversion (the rejected alternative):** an earlier draft let the
|
|
67
|
+
first session to Stop adopt ownership. The independent review showed this
|
|
68
|
+
inverts ownership in the real incident pattern (busy owner never stops first).
|
|
69
|
+
Removed entirely; replaced with conservative no-adopt. Tested: un-stamped state
|
|
70
|
+
yields approve with `owner` NOT written.
|
|
71
|
+
|
|
72
|
+
## Level-of-abstraction fit
|
|
73
|
+
|
|
74
|
+
The fix lives at the same layer as the bug: the Stop hook and the state writer.
|
|
75
|
+
It mirrors the already-shipped autonomous stop-hook's session-scoping ladder
|
|
76
|
+
(tmux-name primary, session-UUID backstop, fail-open) without merging the two
|
|
77
|
+
(explicit non-goal — bash hooks don't share code cleanly; premature abstraction
|
|
78
|
+
avoided). No new module, no new service.
|
|
79
|
+
|
|
80
|
+
## Signal-vs-authority compliance
|
|
81
|
+
|
|
82
|
+
The hook is a low-context filter making a binary ownership decision from
|
|
83
|
+
locally-verifiable identifiers (tmux `#S`, stdin `session_id`). It does not
|
|
84
|
+
arrogate higher-level judgment — it only declines to block a session it cannot
|
|
85
|
+
prove it owns. Conservative-by-construction: every ambiguous case resolves to
|
|
86
|
+
`approve` (release), never to `block` (trap). It emits no user-facing messages.
|
|
87
|
+
|
|
88
|
+
## Interactions
|
|
89
|
+
|
|
90
|
+
- **Reinforcement counter:** the owner path is byte-for-byte the prior logic, so
|
|
91
|
+
graduated protection (3/5/10) is unchanged for the owner. Non-owners no longer
|
|
92
|
+
touch the counter at all.
|
|
93
|
+
- **Restart reconcile:** writes `owner.session` ONLY on a confirmed tmux-owner
|
|
94
|
+
match with a rotated UUID. Gated strictly behind the tmux match — a non-owner
|
|
95
|
+
can never clobber `owner.session` (tested explicitly).
|
|
96
|
+
- **stdin consumption:** the hook now reads stdin (`cat`). Stop hooks deliver and
|
|
97
|
+
close stdin in production (the autonomous hook relies on this), so no hang.
|
|
98
|
+
Even with no stdin/session, tmux-scoping alone is sufficient (proven live).
|
|
99
|
+
- **Worktree topology:** ownership is keyed on the cwd-independent tmux name, so
|
|
100
|
+
it is correct whether the owner launched at the main root and `cd`'d into a
|
|
101
|
+
worktree or launched rooted inside the worktree. The old, fragile `$PWD`-based
|
|
102
|
+
stopgap is NOT carried forward.
|
|
103
|
+
- **Migration parity:** inline twin is always-overwritten → every agent gets the
|
|
104
|
+
new hook on update. `build-state.py` rides the repo checkout (the only place
|
|
105
|
+
the bug occurs — see spec §Migration Parity 2). Drift test prevents
|
|
106
|
+
template/inline divergence.
|
|
107
|
+
|
|
108
|
+
## Rollback cost
|
|
109
|
+
|
|
110
|
+
Low and clean. Revert the two src files (and optionally build-state.py); the
|
|
111
|
+
always-overwrite migration restores the prior hook on next update. The added
|
|
112
|
+
`owner` block in state is additive JSON ignored by the old hook — no destructive
|
|
113
|
+
schema migration. No data loss path.
|
|
114
|
+
|
|
115
|
+
## Tracked deferral
|
|
116
|
+
|
|
117
|
+
The SKILL change to pass `--owner-session "$CLAUDE_CODE_SESSION_ID"` (session-UUID
|
|
118
|
+
precision; + its dedicated PostUpdateMigrator migration per Migration Parity §5)
|
|
119
|
+
is a deliberate fast-follow per the approved phasing (Justin approved one-PR-now +
|
|
120
|
+
tiny-follow-up). The flag is already plumbed and tested in `build-state.py`; only
|
|
121
|
+
the SKILL invocation + migration remain. This is tracked, not orphaned.
|
|
122
|
+
|
|
123
|
+
## Verification
|
|
124
|
+
|
|
125
|
+
- 3-tier behavior tests (12) drive the **real shipping hook** (from
|
|
126
|
+
`getHookContent`) against the **real `build-state.py`** with real stdin/tmux
|
|
127
|
+
seams — covering owner-block, non-owner-no-drain, repeated-non-owner,
|
|
128
|
+
session-only owner, identity-unknown fail-open, legacy no-adopt, restart
|
|
129
|
+
reconcile, anti-clobber, terminal-phase. Plus build-state stamp tests (3) and
|
|
130
|
+
the template/inline drift test (1).
|
|
131
|
+
- Live test-as-self: ran the shipping hook with **real** `tmux display-message`
|
|
132
|
+
resolution (no seam) in this session (`echo-build-stop-hook-session-scoping`) —
|
|
133
|
+
confirmed owner→block, non-owner→approve with zero counter drain.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Side-effects review — fresh-session stop-gate shadow wiring
|
|
2
|
+
|
|
3
|
+
**Scope**: Complete the conservative first rollout for the fresh-session stop-gate: wire the existing server authority/database, install a Stop-hook router, and default to observe-only shadow mode when the gate is healthy.
|
|
4
|
+
|
|
5
|
+
**Files touched**:
|
|
6
|
+
- `src/commands/server.ts` — constructs `StopGateDb` and `UnjustifiedStopGate`, persists mode state, passes both into `AgentServer`.
|
|
7
|
+
- `src/server/stopGate.ts` — persists mode flips to `server-data/stop-gate-mode.json`.
|
|
8
|
+
- `src/server/routes.ts` — records SessionStart rows in `StopGateDb` when hook events arrive.
|
|
9
|
+
- `src/core/PostUpdateMigrator.ts` — installs `stop-gate-router.js` and patches `.claude/settings.json` Stop hooks.
|
|
10
|
+
- `src/commands/init.ts` and `src/templates/hooks/settings-template.json` — include the router on fresh installs.
|
|
11
|
+
- `src/core/installCodexHooks.ts` — mirrors the Stop router into Codex hook config.
|
|
12
|
+
- Focused tests cover route mode persistence, hook behavior, Codex registration, and update migration.
|
|
13
|
+
|
|
14
|
+
**Under-block**: Intentional for this PR. Default mode is `shadow` only when the authority and SQLite log initialize successfully; otherwise the gate is `off`. In shadow mode the hook submits evaluations but always lets the agent exit. Enforcement is reserved for a later explicit operator flip.
|
|
15
|
+
|
|
16
|
+
**Over-block**: Minimal. The router only emits `{decision:"block"}` when the server is already in `enforce` mode and the server authority returns `continue` with a reminder. All network errors, malformed hook payloads, missing config, hot-path failures, compaction-in-flight, kill-switch, and degraded initialization paths fail open.
|
|
17
|
+
|
|
18
|
+
**Signal vs authority**: Compliant. The hook collects evidence metadata and simple signals, but never decides whether a Stop is unjustified. The server-side `UnjustifiedStopGate` remains the sole authority for `continue`; the hook is a transport/router.
|
|
19
|
+
|
|
20
|
+
**External surfaces**:
|
|
21
|
+
- New installed hook file: `.instar/hooks/instar/stop-gate-router.js`.
|
|
22
|
+
- Existing routes become live for real Stop events: `GET /internal/stop-gate/hot-path` and `POST /internal/stop-gate/evaluate`.
|
|
23
|
+
- New persisted mode file: `server-data/stop-gate-mode.json`.
|
|
24
|
+
- Existing SQLite event log: `server-data/stop-gate.db`.
|
|
25
|
+
|
|
26
|
+
**Migration parity**:
|
|
27
|
+
- Post-update migration writes the router and patches existing Claude settings.
|
|
28
|
+
- Fresh `instar init` installs the router template.
|
|
29
|
+
- Codex hook installer places the router first in the Stop chain before the existing review trio.
|
|
30
|
+
|
|
31
|
+
**Rollback cost**: Revert this change set. Existing `stop-gate-mode.json` and `stop-gate.db` files can remain on disk; without the router/server wiring they are inert. Emergency runtime rollback without code revert: `instar gate mode off` or set the kill-switch.
|
|
32
|
+
|
|
33
|
+
**Tests**:
|
|
34
|
+
- `npm test -- --run tests/unit/routes-stopGate.test.ts tests/unit/stop-gate-router-hook.test.ts tests/unit/installCodexHooks.test.ts tests/unit/PostUpdateMigrator-codexHooks.test.ts`
|
|
35
|
+
- `npx tsc --noEmit`
|