instar 0.28.49 → 0.28.51
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 +93 -93
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +61 -31
- package/dist/commands/server.js.map +1 -1
- package/dist/core/InputGuard.d.ts +29 -3
- package/dist/core/InputGuard.d.ts.map +1 -1
- package/dist/core/InputGuard.js +73 -45
- package/dist/core/InputGuard.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +14 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +46 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/messaging/shared/isSystemOrProxyMessage.d.ts +41 -0
- package/dist/messaging/shared/isSystemOrProxyMessage.d.ts.map +1 -0
- package/dist/messaging/shared/isSystemOrProxyMessage.js +64 -0
- package/dist/messaging/shared/isSystemOrProxyMessage.js.map +1 -0
- package/dist/monitoring/PresenceProxy.d.ts +3 -1
- package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
- package/dist/monitoring/PresenceProxy.js +5 -16
- package/dist/monitoring/PresenceProxy.js.map +1 -1
- package/package.json +1 -1
- package/scripts/pre-push-gate.js +6 -3
- package/src/data/builtin-manifest.json +43 -43
- package/upgrades/0.28.50.md +59 -0
- package/upgrades/0.28.51.md +31 -0
- package/upgrades/0.28.52.md +82 -0
- package/upgrades/side-effects/0.28.49.md +90 -0
- package/upgrades/side-effects/0.28.50.md +104 -0
- package/upgrades/side-effects/0.28.51.md +145 -0
- package/upgrades/side-effects/0.28.52.md +276 -0
- package/upgrades/side-effects/pre-push-gate-ci-scope.md +104 -0
- package/upgrades/side-effects/skill-port-dynamic-resolution.md +104 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Side-Effects Review — Compaction-recovery proxy-filter fix
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `0.28.52`
|
|
4
|
+
**Date:** `2026-04-17`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `(this is a classifier-dedup fix with no Guard
|
|
7
|
+
surface — second pass not required per skill Phase 5)`
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
Closes the topic-6795 compaction stall: `recoverCompactedSession` was
|
|
12
|
+
deciding "is there pending work?" by looking at the last message in the
|
|
13
|
+
topic without filtering out PresenceProxy standby messages or
|
|
14
|
+
server-emitted delivery/lifecycle acks. Those are `fromUser: false` but
|
|
15
|
+
they are NOT real agent responses — treating them as "agent answered" is
|
|
16
|
+
what let the compaction-recovery safety net decline three consecutive
|
|
17
|
+
re-inject attempts while the user sat with an unanswered question.
|
|
18
|
+
|
|
19
|
+
The fix hoists the classifier that `PresenceProxy.isSystemMessage()` and
|
|
20
|
+
`checkLogForAgentResponse()` already used into a shared module, adds a
|
|
21
|
+
thin `findLastRealMessage(history)` walk-back helper on top, and routes
|
|
22
|
+
`recoverCompactedSession` through it. Three scattered copies of the
|
|
23
|
+
prefix list are now one.
|
|
24
|
+
|
|
25
|
+
Files touched:
|
|
26
|
+
|
|
27
|
+
- `src/messaging/shared/isSystemOrProxyMessage.ts` — new. Exports
|
|
28
|
+
`isSystemOrProxyMessage(text)` (the classifier) and `findLastRealMessage(history)`
|
|
29
|
+
(the walk-back). Thorough header comment documenting which subsystems
|
|
30
|
+
consume it and the regression anchor.
|
|
31
|
+
- `src/commands/server.ts` — `recoverCompactedSession` now uses
|
|
32
|
+
`findLastRealMessage` instead of `history[history.length - 1]`. History
|
|
33
|
+
window widened 5 → 20. `checkLogForAgentResponse` now delegates to
|
|
34
|
+
`isSystemOrProxyMessage` instead of its inlined prefix list.
|
|
35
|
+
- `src/monitoring/PresenceProxy.ts` — `isSystemMessage()` is now a thin
|
|
36
|
+
wrapper over `isSystemOrProxyMessage` (instance-method signature
|
|
37
|
+
preserved so existing callsites are unchanged).
|
|
38
|
+
- `tests/unit/isSystemOrProxyMessage.test.ts` — new, 25 tests.
|
|
39
|
+
|
|
40
|
+
## Decision-point inventory
|
|
41
|
+
|
|
42
|
+
- `recoverCompactedSession` unanswered-message check — **modify** — the
|
|
43
|
+
authority that decides whether a compaction re-injection fires. The
|
|
44
|
+
decision predicate changed from "look at last message only" to "walk
|
|
45
|
+
backward skipping system/proxy, check first real message". Role
|
|
46
|
+
(authority) unchanged; correctness of the predicate improved.
|
|
47
|
+
- `checkLogForAgentResponse` — **modify** — signal producer ("has the
|
|
48
|
+
agent responded since X?"). Logic unchanged, just dedup against shared
|
|
49
|
+
classifier.
|
|
50
|
+
- `PresenceProxy.isSystemMessage()` — **modify** — signal producer for
|
|
51
|
+
the race guard. Logic unchanged, just dedup against shared classifier.
|
|
52
|
+
- `isSystemOrProxyMessage` / `findLastRealMessage` — **new helpers** —
|
|
53
|
+
pure functions, no state, no side effects, no blocking authority of
|
|
54
|
+
their own. They are detectors; callers decide what to do with the
|
|
55
|
+
verdict.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 1. Over-block
|
|
60
|
+
|
|
61
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
62
|
+
|
|
63
|
+
None new. The classifier's prefix list is identical to what
|
|
64
|
+
`PresenceProxy.isSystemMessage` and the pre-fix inlined copy in
|
|
65
|
+
`checkLogForAgentResponse` already used. Unit tests
|
|
66
|
+
(`does NOT classify a checkmark used in narrative as a delivery ack`,
|
|
67
|
+
`does NOT classify a message merely CONTAINING 🔭 later as proxy`) pin
|
|
68
|
+
down the leading-prefix contract — an agent reply that *mentions* 🔭 or
|
|
69
|
+
uses ✓ later in a sentence is correctly treated as a real response.
|
|
70
|
+
|
|
71
|
+
The only behavior change affecting "the agent answered" detection is that
|
|
72
|
+
`recoverCompactedSession` now walks PAST system/proxy entries instead of
|
|
73
|
+
tripping on them. The pre-fix behavior was strictly more trigger-happy
|
|
74
|
+
with declines; post-fix is strictly more trigger-happy with re-injections.
|
|
75
|
+
A re-injection on an already-answered topic costs one
|
|
76
|
+
`COMPACTION_RESUME_PROMPT` message plus a small session-wake — the same
|
|
77
|
+
path that fires on legitimate recoveries, and well-tested. Not comparable
|
|
78
|
+
to the silent 15-minute user-facing stall from the old behavior.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 2. Under-block
|
|
83
|
+
|
|
84
|
+
**What failure modes does this still miss?**
|
|
85
|
+
|
|
86
|
+
1. **Pre-change under-block (the one this fixes):** recoverCompactedSession
|
|
87
|
+
accepting standby-as-answer and declining recovery. Closed.
|
|
88
|
+
2. **History horizon:** `telegram.getTopicHistory(topicId, 20)` reads the
|
|
89
|
+
last 20 topic entries. If the user's unanswered question is 21+ entries
|
|
90
|
+
back AND every message in between is system/proxy, the walk returns
|
|
91
|
+
null and declines. Pre-fix used a window of 5, so this is a
|
|
92
|
+
strict improvement; but it's still finite. In practice, 20 messages of
|
|
93
|
+
pure standby/ack traffic without a single real agent reply would itself
|
|
94
|
+
be a distinct pathology (agent is completely wedged, not just
|
|
95
|
+
compacting), and the right response is escalation to stall triage —
|
|
96
|
+
not a bigger history window. Documented in the module header.
|
|
97
|
+
3. **New from-agent message formats:** if a future subsystem starts
|
|
98
|
+
emitting a new kind of "not really a response" message (say, a new
|
|
99
|
+
telemetry prefix), the classifier won't know about it and recovery
|
|
100
|
+
will decline. Mitigation: the classifier is now in one place, so
|
|
101
|
+
adding the new prefix is one edit and gets all three callsites for
|
|
102
|
+
free — versus the pre-fix state where you'd need to update three
|
|
103
|
+
files and remember which ones.
|
|
104
|
+
4. **Trimmed-whitespace input:** classifier trims before matching, so
|
|
105
|
+
indentation/CRLF variations are covered. Pinned by
|
|
106
|
+
`trim handling` tests.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 3. Level-of-abstraction fit
|
|
111
|
+
|
|
112
|
+
The classifier sits at the *data-shape* layer: "given a line of text,
|
|
113
|
+
is it a real agent response?" It has no knowledge of topics, sessions,
|
|
114
|
+
timestamps, or recovery policy. Callers (recoverCompactedSession,
|
|
115
|
+
PresenceProxy, checkLogForAgentResponse) own the *policy* — what to do
|
|
116
|
+
when a message is or isn't a real response.
|
|
117
|
+
|
|
118
|
+
The walk-back helper `findLastRealMessage` sits one layer up: "given a
|
|
119
|
+
chronological history, find the latest real entry." Still no policy. Its
|
|
120
|
+
only knowledge is that histories are chronologically ordered and that the
|
|
121
|
+
classifier distinguishes real from not-real.
|
|
122
|
+
|
|
123
|
+
This is the same shape as other shared classifiers in the repo
|
|
124
|
+
(`detectContextExhaustion`, `isHttpIdempotent`). Appropriate fit.
|
|
125
|
+
No authority is being packed into a detector, no detector is being
|
|
126
|
+
smeared across three files.
|
|
127
|
+
|
|
128
|
+
Alternatives considered and rejected:
|
|
129
|
+
|
|
130
|
+
- **Inline the walk-back in `recoverCompactedSession` only** — would
|
|
131
|
+
re-introduce the three-copy duplication problem on the next change.
|
|
132
|
+
Rejected.
|
|
133
|
+
- **Make the classifier itself decide "should recovery fire?"** — would
|
|
134
|
+
pack policy into the detector. Callers need different behaviors
|
|
135
|
+
(PresenceProxy cares about self-cancellation, recoverFn cares about
|
|
136
|
+
user turns, checkLog cares about any real agent message). Rejected.
|
|
137
|
+
- **Move the walk-back into TelegramAdapter as `getLastRealMessage`** —
|
|
138
|
+
would couple a generic message-shape filter to the Telegram adapter.
|
|
139
|
+
The same logic needs to apply to Slack histories later. Rejected in
|
|
140
|
+
favor of the transport-agnostic helper.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 4. Signal vs authority compliance
|
|
145
|
+
|
|
146
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
147
|
+
|
|
148
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
149
|
+
|
|
150
|
+
- [x] No — this change moves in the correct direction on the principle.
|
|
151
|
+
|
|
152
|
+
Narrative breakdown:
|
|
153
|
+
|
|
154
|
+
- `isSystemOrProxyMessage` is a **signal producer** — a detector on
|
|
155
|
+
textual shape. No blocking authority, no side effects, no state. It
|
|
156
|
+
emits a classification; callers choose what to do.
|
|
157
|
+
- `findLastRealMessage` is also a signal producer — it transforms a
|
|
158
|
+
history into "which entry is the latest real one?" It doesn't decide
|
|
159
|
+
whether anything should fire.
|
|
160
|
+
- The blocking/non-blocking authority lives in `recoverCompactedSession`
|
|
161
|
+
(which decides to inject or not) and in the CompactionSentinel
|
|
162
|
+
(which decides to retry or finalize). Neither gained new authority;
|
|
163
|
+
the existing authority now consumes a less-brittle signal.
|
|
164
|
+
|
|
165
|
+
Signal strength: the classifier is prefix-based, which is fragile for
|
|
166
|
+
general-purpose intent classification — but here the "signal" is a
|
|
167
|
+
structural marker emitted by known server-code paths (PresenceProxy
|
|
168
|
+
emits `🔭`, the server emits `✓ Delivered` via a specific code path, etc.).
|
|
169
|
+
It's not inferring intent from free-text user input. Prefix matching is
|
|
170
|
+
the right tool for this specific job.
|
|
171
|
+
|
|
172
|
+
No brittle check is acquiring blocking authority. One authority
|
|
173
|
+
(recoverCompactedSession) is migrating from a brittle predicate ("last
|
|
174
|
+
message is from user?") to a robust one ("last REAL message is from
|
|
175
|
+
user?"). Principle held.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 5. Interactions
|
|
180
|
+
|
|
181
|
+
- **With CompactionSentinel:** the Sentinel observes `recoverFn`'s
|
|
182
|
+
accept/reject verdict unchanged. Dedupe, retry, verify-window, and
|
|
183
|
+
finalize semantics are untouched. The fix changes WHEN recoverFn says
|
|
184
|
+
`true`, not the Sentinel's reaction to it.
|
|
185
|
+
- **With PresenceProxy race guard:** PresenceProxy already used the
|
|
186
|
+
(now shared) classifier to decide whether to self-cancel on a sibling
|
|
187
|
+
standby. That logic is byte-equivalent pre and post — only the source
|
|
188
|
+
of the prefix list changed. Verified by running
|
|
189
|
+
`presence-proxy-cancel-race.test.ts` (5 tests) green.
|
|
190
|
+
- **With checkLogForAgentResponse:** delegates the classifier call.
|
|
191
|
+
Function-level behavior unchanged. No new callers; same call sites
|
|
192
|
+
(stall-triage idle check, PresenceProxy "has the agent responded since
|
|
193
|
+
X"). Verified by running the broader suite green.
|
|
194
|
+
- **History-window widening (5 → 20):** only affects
|
|
195
|
+
`recoverCompactedSession`. `telegram.getTopicHistory` is already
|
|
196
|
+
safe up to 50+ entries in other call sites. No performance concern —
|
|
197
|
+
reading 20 log entries is ~1-2ms.
|
|
198
|
+
- **Races:** the walk is over a snapshot of the history read once per
|
|
199
|
+
call. No multi-step read, no TOCTOU window. Sentinel-level deduplication
|
|
200
|
+
already prevents concurrent recoverFn calls for the same session.
|
|
201
|
+
- **Feedback loops:** recovery inject → session wakes → agent emits real
|
|
202
|
+
response → future `checkLogForAgentResponse`/walk-back sees the real
|
|
203
|
+
response, not the proxy standby that preceded it. Correctly breaks the
|
|
204
|
+
loop that previously kept the session "stuck-looking."
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 6. External surfaces
|
|
209
|
+
|
|
210
|
+
- **Other agents on the same machine:** none. The classifier lives in
|
|
211
|
+
the per-agent server process.
|
|
212
|
+
- **Other users of the install base:** on upgrade, agents whose
|
|
213
|
+
compaction recovery was declining due to this bug will begin
|
|
214
|
+
successfully recovering. This is the intended, silent, good behavior
|
|
215
|
+
change. Users should not notice anything except that compaction stalls
|
|
216
|
+
become shorter.
|
|
217
|
+
- **External systems:** none. No new egress.
|
|
218
|
+
- **Persistent state:** none. No schema changes. No agent-state migration.
|
|
219
|
+
- **Log format:** `[CompactionResume]` log lines are unchanged.
|
|
220
|
+
`[Sentinel]` lifecycle lines are unchanged. A re-injection that
|
|
221
|
+
previously would have logged `recoverFn declined (no pending work or
|
|
222
|
+
session gone)` will now log the normal
|
|
223
|
+
`direct re-inject OK for topic <N>` path.
|
|
224
|
+
- **Timing/runtime:** walking 20 log entries through a string-prefix
|
|
225
|
+
classifier adds <1ms to the recoverFn hot path. Sentinel timers
|
|
226
|
+
unchanged.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 7. Rollback cost
|
|
231
|
+
|
|
232
|
+
Pure code change. Revert the commit, ship a patch. No persistent state
|
|
233
|
+
migration. No agent state repair. On rollback, compaction recovery
|
|
234
|
+
returns to the pre-fix behavior — declining when the last message is a
|
|
235
|
+
PresenceProxy standby. No user-visible regression beyond re-exposing the
|
|
236
|
+
original bug.
|
|
237
|
+
|
|
238
|
+
Estimated rollback effort: one revert commit + one release bump, <10 minutes.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Conclusion
|
|
243
|
+
|
|
244
|
+
Small-surface bug fix. The change:
|
|
245
|
+
|
|
246
|
+
1. Closes a concrete user-visible failure (topic-6795 compaction stall,
|
|
247
|
+
repro-ed three times).
|
|
248
|
+
2. Dedups three copies of a prefix list that were drifting.
|
|
249
|
+
3. Adds regression coverage (25 unit tests including the exact
|
|
250
|
+
repro sequence).
|
|
251
|
+
4. Moves `recoverCompactedSession` from a brittle last-message-only
|
|
252
|
+
predicate to a walk-back over filtered history.
|
|
253
|
+
5. Holds to signal-vs-authority: no detector gains blocking power; one
|
|
254
|
+
authority now consumes a robust signal instead of a brittle one.
|
|
255
|
+
|
|
256
|
+
Clear to ship.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Evidence pointers
|
|
261
|
+
|
|
262
|
+
- Reproduction: `.instar/shared-state.jsonl` entries around topic 6795 at
|
|
263
|
+
16:08:14 / 16:13:xx / 16:18:xx logged the three
|
|
264
|
+
`recoverFn declined (no pending work or session gone)` lines; topic
|
|
265
|
+
history in `.instar/telegram-messages.jsonl` at each decline point
|
|
266
|
+
shows the preceding message was a `🔭 Echo is currently updating the
|
|
267
|
+
ledger spec…` PresenceProxy standby.
|
|
268
|
+
- Pre-fix behavior: `git show <pre-fix>:src/commands/server.ts` around
|
|
269
|
+
the `recoverCompactedSession` definition shows
|
|
270
|
+
`const lastMsg = history[history.length - 1]; if (lastMsg?.fromUser) { ... }`.
|
|
271
|
+
- Post-fix behavior: `findLastRealMessage(history)` returns the last
|
|
272
|
+
non-system/non-proxy entry; the decision predicate sees the real user
|
|
273
|
+
turn.
|
|
274
|
+
- Tests: `tests/unit/isSystemOrProxyMessage.test.ts` — 25 passing tests.
|
|
275
|
+
Explicit `topic-6795 repro` case asserts the helper finds the user
|
|
276
|
+
question past trailing proxy + ack entries.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Side-Effects Review — pre-push gate: CI scope fix
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `pre-push-gate-ci-scope`
|
|
4
|
+
**Date:** `2026-04-17`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Modifies `scripts/pre-push-gate.js` in two ways: (1) wraps section 5 (side-effects artifact check) in `if (!process.env.CI)`, so the check runs only when developers push locally and is skipped in GitHub Actions; (2) adds `2>/dev/null` to the `HEAD~1` stderr fallback in section 3's git diff command, stopping stderr from leaking through the try/catch into the test output in shallow-clone CI environments. No `src/` files are touched — only the gate script itself.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `scripts/pre-push-gate.js` section 5 — **modify** — narrows the scope of the side-effects artifact check from "always" to "not in CI". The check itself is unchanged; only its execution context is restricted.
|
|
15
|
+
- `scripts/pre-push-gate.js` section 3 — **modify** — cosmetic: suppresses stderr noise from a git fallback command. No decision logic involved.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Over-block
|
|
20
|
+
|
|
21
|
+
No block/allow surface for messages or agent actions — not applicable in the traditional sense.
|
|
22
|
+
|
|
23
|
+
Within the gate's own domain: the change *reduces* over-block. Previously the gate would reject any CI run on a contributor branch that was cut before the side-effects artifact for the current version was added to main. That's a false positive — the contributor didn't violate the process, the artifact simply hadn't been added to main yet when they branched. The fix stops those legitimate branches from being blocked.
|
|
24
|
+
|
|
25
|
+
No new rejection surface is introduced.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Under-block
|
|
30
|
+
|
|
31
|
+
The gate now allows CI runs that are missing the side-effects artifact. This is an intentional scope reduction: CI is not the enforcement point. The enforcement points are:
|
|
32
|
+
|
|
33
|
+
1. The pre-commit hook (`scripts/instar-dev-precommit.js`) — runs per-commit on the developer's machine.
|
|
34
|
+
2. The pre-push hook (this gate, section 5) — runs on the developer's machine at push time.
|
|
35
|
+
|
|
36
|
+
Both hooks run before code reaches CI. If a developer bypasses them (e.g., `--no-verify`), section 5 in CI would have caught it — and now it won't. This is a real reduction in defense depth for the `--no-verify` bypass case.
|
|
37
|
+
|
|
38
|
+
Mitigation: `--no-verify` bypasses are visible in git history (the commit won't have the artifact). The pre-push gate also re-checks at the release-cut step when NEXT.md is renamed to a versioned file — which does happen locally, not in CI. The net under-block exposure is: a developer who uses `--no-verify` and then somehow gets their branch merged without a local push. This is a narrow path that the review process (PR review, merge gating) is expected to catch.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. Level-of-abstraction fit
|
|
43
|
+
|
|
44
|
+
The gate is a structural process-enforcement check, not a message-content gate. Section 5 is explicitly scoped to "push time" in its own comment. CI is not push time — it's post-push. Running a push-time check in CI creates a category mismatch that produces false failures on valid contributor branches.
|
|
45
|
+
|
|
46
|
+
The fix is at the correct layer: the `if (!process.env.CI)` guard is a simple execution-context discriminator applied directly to the check that's miscategorized for CI. No rearchitecting needed.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 4. Signal vs authority compliance
|
|
51
|
+
|
|
52
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
53
|
+
|
|
54
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
55
|
+
|
|
56
|
+
- [x] No — this change has no block/allow surface.
|
|
57
|
+
|
|
58
|
+
The gate operates on developer process compliance (file existence, git metadata), not on message content or agent behavior. The signal-vs-authority principle applies to decision points that evaluate messages or constrain agent information flow. A CI scope guard on a developer process check is outside that domain.
|
|
59
|
+
|
|
60
|
+
The change itself is a pure scope restriction — it *removes* an execution context from an existing check. No new brittle logic is added. No new authority is claimed.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 5. Interactions
|
|
65
|
+
|
|
66
|
+
**Shadowing:** The pre-commit hook and the local pre-push hook both enforce section 5's requirement. This change scopes section 5 to local-only. The pre-commit hook is unchanged — it still runs on every commit. No shadowing occurs.
|
|
67
|
+
|
|
68
|
+
**Double-fire:** Section 5 currently runs both locally (pre-push) and in CI (via the test that invokes the gate script). After this change it only runs locally. No double-fire; in fact we're eliminating the accidental double-enforcement.
|
|
69
|
+
|
|
70
|
+
**Races:** No shared state involved. The check reads filesystem files (upgrade guides, side-effects dir). No concurrent access concern.
|
|
71
|
+
|
|
72
|
+
**Feedback loops:** None. The gate is a one-way exit check with no input to any system that feeds back.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 6. External surfaces
|
|
77
|
+
|
|
78
|
+
- **Other agents:** No effect. The gate runs only in the instar repo's CI and in developer environments.
|
|
79
|
+
- **Install base users:** No effect. This is a developer tooling change, not a runtime change. `instar` as installed by users has no pre-push gate.
|
|
80
|
+
- **External systems:** No effect.
|
|
81
|
+
- **Persistent state:** No effect.
|
|
82
|
+
- **Timing/runtime:** The `CI` env var is set by GitHub Actions automatically for all runs. No timing dependency — it's present or absent at process start.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 7. Rollback cost
|
|
87
|
+
|
|
88
|
+
Pure code change in `scripts/pre-push-gate.js`. Revert and ship a patch. No persistent state, no migration, no agent state repair. The only user-visible effect during the rollback window would be contributor PR CI runs again failing on missing side-effects artifacts — which is the exact condition we're fixing, not a new regression.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Conclusion
|
|
93
|
+
|
|
94
|
+
The change is narrow and correct. It scopes section 5 of the pre-push gate to local developer contexts only, which matches the intent stated in the gate's own comment ("at push time"). The under-block exposure (a developer using `--no-verify` evading CI detection) is real but narrow: it requires bypassing two local enforcement hooks AND getting a PR merged without review catching the missing artifact. The pre-commit hook and PR review process are the remaining guards. The fix is clear to ship.
|
|
95
|
+
|
|
96
|
+
No design changes were made as a result of the review.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Evidence pointers
|
|
101
|
+
|
|
102
|
+
- `tests/unit/pre-push-gate.test.ts` — all 6 tests pass locally after the change.
|
|
103
|
+
- `CI=true node scripts/pre-push-gate.js` — exits 0 on the current branch (which has the 0.28.49 versioned guide with fix/feature language but no fresh side-effects artifact for that version in CI context).
|
|
104
|
+
- Without `CI`, the gate still enforces section 5 (verified by the existing passing local test that runs the gate in a non-CI shell).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Side-Effects Review — default skills: dynamic localhost port
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `skill-port-dynamic-resolution`
|
|
4
|
+
**Date:** `2026-04-17`
|
|
5
|
+
**Author:** `dawn`
|
|
6
|
+
**Second-pass reviewer:** `not required`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Two source changes. In `src/commands/init.ts`, every `http://localhost:${port}/...` URL inside `installBuiltinSkills` (and adjacent helpers that share the same file) is rewritten to emit `http://localhost:\${INSTAR_PORT:-${port}}/...`, so the generated `.claude/skills/*/SKILL.md` files contain a shell-expandable port reference instead of a number baked in at install time. In `src/core/PostUpdateMigrator.ts`, a new `migrateSkillPortHardcoding()` scans existing default-skill files for bare `http://localhost:NNNN/` URLs and rewrites them to `http://localhost:${INSTAR_PORT:-NNNN}/`, preserving the original port as the fallback default. The migration is scoped to the 14 known-default skill names and is idempotent. Test coverage: `tests/unit/PostUpdateMigrator-skillPortHardcoding.test.ts` — 6 cases.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `src/commands/init.ts` `installBuiltinSkills` — **modify** — replaces hardcoded port templating with runtime-expandable pattern. 93 occurrences, mechanical find/replace, all inside backtick template strings for shell-executed content.
|
|
15
|
+
- `src/core/PostUpdateMigrator.ts` `migrateSkillPortHardcoding` — **add** — new migration method. Called from `migrate()` between `migrateBuiltinSkills` and `migrateSelfKnowledgeTree`. Scoped to a fixed allowlist of 14 default skill names.
|
|
16
|
+
- `tests/unit/PostUpdateMigrator-skillPortHardcoding.test.ts` — **add** — regression coverage for the migration.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Over-block
|
|
21
|
+
|
|
22
|
+
No block/allow surface. The change is runtime port resolution in user-project skill files. No message content or agent action is gated.
|
|
23
|
+
|
|
24
|
+
Within the migration's own domain: the scan matches `/http:\/\/localhost:(\d+)\//g` in the default-skill set. This pattern is narrow enough that it will not false-positive on natural-language references ("localhost:4040" mentioned in prose without the URL form is untouched). Files outside the 14-name allowlist are never read, so custom skills are never modified — a principle the test suite asserts explicitly.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. Under-block
|
|
29
|
+
|
|
30
|
+
No block surface existed before this change. The migration adds no new enforcement — it is a one-way content rewrite. There is nothing to under-block.
|
|
31
|
+
|
|
32
|
+
Edge case: if a user had a default-skill file with a mix of the new dynamic pattern and stray hardcoded ports (e.g., partial manual edits), the idempotency guard (`includes('${INSTAR_PORT:-')`) will cause the migration to skip the file entirely rather than finish the rewrite. That is the safe direction — migrating a partially-edited file risks corrupting the user's edits. Users in that state can manually finish the rewrite or delete the file and let `installBuiltinSkills` regenerate it.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 3. Level-of-abstraction fit
|
|
37
|
+
|
|
38
|
+
The change is at the correct layer. The root cause was install-time templating of a value that should have been runtime-resolved. Fixing the template is the direct fix; fixing existing user files via migration is the correct catch-up mechanism. Neither change rearchitects the skill system — skills remain static markdown files, the only change is that a value inside them resolves later.
|
|
39
|
+
|
|
40
|
+
The dynamic pattern `${INSTAR_PORT:-PORT}` uses POSIX shell parameter expansion, the same primitive the rest of the Instar shell surface depends on. It is a recognized idiom inside curl-heavy bash content, not a novel construct the user has to learn.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 4. Signal vs authority compliance
|
|
45
|
+
|
|
46
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
47
|
+
|
|
48
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
49
|
+
|
|
50
|
+
- [x] No — this change has no block/allow surface.
|
|
51
|
+
|
|
52
|
+
The change is a content rewrite inside skill files. It does not evaluate messages, gate agent actions, or constrain information flow. Signal-vs-authority applies to decision points that judge messages or block work. A port-expansion template does neither.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 5. Interactions
|
|
57
|
+
|
|
58
|
+
**Shadowing:** `installBuiltinSkills` and `migrateSkillPortHardcoding` target overlapping surface. Order matters: `migrateBuiltinSkills` runs first (non-destructive, writes only missing files), then `migrateSkillPortHardcoding` runs (rewrites existing files). A skill newly written by `installBuiltinSkills` in the same migration pass already uses the dynamic pattern, so `migrateSkillPortHardcoding` will see the `${INSTAR_PORT:-` marker and no-op. No double-processing.
|
|
59
|
+
|
|
60
|
+
**Double-fire:** `migrateSkillPortHardcoding` is idempotent — once a file contains the dynamic marker, it is skipped. Test case `is idempotent on a second run after migration` covers this explicitly.
|
|
61
|
+
|
|
62
|
+
**Races:** `PostUpdateMigrator.migrate()` is sequential and runs once per `instar` update. No concurrent access to the same skill file is expected. If two updaters ran simultaneously, they would both read the hardcoded content, both rewrite it, and the second write would overwrite the first with identical content — no corruption.
|
|
63
|
+
|
|
64
|
+
**Feedback loops:** None. The migration is a one-shot rewrite; the rewritten content does not feed back into any system.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 6. External surfaces
|
|
69
|
+
|
|
70
|
+
- **Other agents:** Each agent running instar will get the migration on next `instar` upgrade. Agents on non-default ports gain working skills; agents on port 4040 see no behavioral change (the fallback matches their previous hardcoded value).
|
|
71
|
+
- **Install base users:** Users with customized skill files (renamed default skills, heavily edited content) are protected by the allowlist and the dynamic-marker idempotency check. The migration touches only the 14 canonical default-skill files, and only if they still contain the bare-port pattern.
|
|
72
|
+
- **External systems:** None. The URL targets are all `localhost` — no external traffic shape changes.
|
|
73
|
+
- **Persistent state:** Skill files on disk are rewritten in place. No database, no config, no registry is touched. Rollback = `git checkout` of the skill file or `rm` and re-run `installBuiltinSkills`.
|
|
74
|
+
- **Timing/runtime:** The `${INSTAR_PORT:-NNNN}` expansion runs at shell invocation time. An agent with `INSTAR_PORT` unset gets the fallback; with it set, gets the override. Zero-cost at skill-read time; one environment variable lookup per curl.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 7. Rollback cost
|
|
79
|
+
|
|
80
|
+
Low. Revert: `git revert` the two source commits; the emitted skills would return to hardcoded ports, matching pre-fix behavior. Users who already ran the migration would keep their dynamic-pattern skills, which continue to work (the fallback equals the previous hardcoded value). No persistent state to undo, no agent state to repair, no user communication required.
|
|
81
|
+
|
|
82
|
+
Narrow risk: if a user's `INSTAR_PORT` env var is set to an invalid value (e.g., a port the server isn't listening on), curls will fail after this change where they would have succeeded before on the hardcoded default. Mitigation: the variable is only consulted if the user explicitly exported it. The intersection of "exported `INSTAR_PORT`" and "set it wrong" is small and self-inflicted; the fix for that case is `unset INSTAR_PORT` or set it correctly.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Conclusion
|
|
87
|
+
|
|
88
|
+
The change is narrow, well-scoped, and covered by regression tests. The template fix is mechanical and safe. The migration is scoped to a known allowlist, idempotent, and respects user customizations. The under-block surface is zero; the over-block surface is zero. The worst case in rollback is a return to the original bug, which affected only users on non-default ports and is already worked around today by hand-sed. Ship.
|
|
89
|
+
|
|
90
|
+
No design changes were made as a result of the review.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Evidence pointers
|
|
95
|
+
|
|
96
|
+
- `tests/unit/PostUpdateMigrator-skillPortHardcoding.test.ts` — 6 tests pass:
|
|
97
|
+
- rewrites hardcoded ports in a default skill
|
|
98
|
+
- leaves already-dynamic skills untouched (idempotent)
|
|
99
|
+
- does not touch custom (non-default) skills
|
|
100
|
+
- is idempotent on a second run after migration
|
|
101
|
+
- skips when the skill file does not exist
|
|
102
|
+
- preserves the original port number in the fallback
|
|
103
|
+
- Live template verification: `node -e "const {installBuiltinSkills}=require('./dist/commands/init.js'); ..."` against a temp dir shows 13 of 14 default skills emit `localhost:${INSTAR_PORT:-4040}` and zero emit bare `localhost:4040` (the 14th skill, `autonomous`, is a stub that deploys separately and has no localhost URLs).
|
|
104
|
+
- Source-side verification: `grep -c 'localhost:${port}' src/commands/init.ts` = 0 after the rewrite (was 93).
|