instar 1.3.1036 → 1.3.1037
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/data/standards-registry.meta.json +1 -1
- package/package.json +1 -1
- package/skills/spec-converge/scripts/publish-spec-review.mjs +70 -1
- package/src/data/builtin-manifest.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/upgrades/1.3.1037.md +53 -0
- package/upgrades/side-effects/publish-spec-review-honest-send.md +96 -0
package/package.json
CHANGED
|
@@ -33,6 +33,56 @@ import { checkEli16Overview } from '../../../scripts/eli16-overview-check.mjs';
|
|
|
33
33
|
|
|
34
34
|
export const API_PORT = Number(process.env.INSTAR_PORT) || 4042;
|
|
35
35
|
|
|
36
|
+
/** Relay locations, in resolution order (the second is the documented older-install path). */
|
|
37
|
+
const RELAY_RELATIVE_PATHS = ['.instar/scripts/telegram-reply.sh', '.claude/scripts/telegram-reply.sh'];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Locate telegram-reply.sh by walking UP from `startDir`.
|
|
41
|
+
*
|
|
42
|
+
* It used to be spawned as the bare relative path `.instar/scripts/telegram-reply.sh`,
|
|
43
|
+
* which only resolves when cwd IS the agent home. instar-dev work is REQUIRED to happen
|
|
44
|
+
* in a worktree (docs/specs/AGENT-WORKTREE-CONVENTION-SPEC.md), and a worktree has no
|
|
45
|
+
* `.instar/scripts/`, so every send from one failed with ENOENT. Walking up from a
|
|
46
|
+
* worktree at `<agent-home>/.worktrees/<slug>` reaches `<agent-home>`, which has it.
|
|
47
|
+
*
|
|
48
|
+
* Returns an absolute path, or null when no relay exists above `startDir`.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveRelayScript(startDir = process.cwd(), existsSync = fs.existsSync) {
|
|
51
|
+
const envHome = process.env.INSTAR_AGENT_HOME;
|
|
52
|
+
const roots = [];
|
|
53
|
+
if (envHome) roots.push(path.resolve(envHome));
|
|
54
|
+
let dir = path.resolve(startDir);
|
|
55
|
+
for (;;) {
|
|
56
|
+
roots.push(dir);
|
|
57
|
+
const parent = path.dirname(dir);
|
|
58
|
+
if (parent === dir) break;
|
|
59
|
+
dir = parent;
|
|
60
|
+
}
|
|
61
|
+
for (const root of roots) {
|
|
62
|
+
for (const rel of RELAY_RELATIVE_PATHS) {
|
|
63
|
+
const candidate = path.join(root, rel);
|
|
64
|
+
if (existsSync(candidate)) return candidate;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Did the relay actually deliver?
|
|
72
|
+
*
|
|
73
|
+
* The relay prints `Sent <n> chars to topic <id>` on a real send. Requiring that
|
|
74
|
+
* marker — not merely a zero exit — is deliberate: this script's whole job is
|
|
75
|
+
* handing the operator something to approve, and it previously printed
|
|
76
|
+
* "[published] … delivered" straight after an ENOENT, so a dropped approval ask
|
|
77
|
+
* was indistinguishable from a delivered one. Filed three times in fifteen days
|
|
78
|
+
* (ACT-616 2026-07-13, ACT-1390 2026-07-27, ACT-1517 2026-07-28) before this fix.
|
|
79
|
+
*/
|
|
80
|
+
export function relayDelivered(result) {
|
|
81
|
+
if (!result || result.error) return false;
|
|
82
|
+
if (result.status !== 0) return false;
|
|
83
|
+
return /Sent\s+\d+\s+chars?\s+to\s+topic/i.test(`${result.stdout || ''}${result.stderr || ''}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
36
86
|
/** Split a spec file into its frontmatter body and the rest. */
|
|
37
87
|
export function extractFrontmatter(specText) {
|
|
38
88
|
const m = specText.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -144,11 +194,30 @@ async function main() {
|
|
|
144
194
|
console.error('--send requires --topic <id>');
|
|
145
195
|
process.exit(2);
|
|
146
196
|
}
|
|
147
|
-
const
|
|
197
|
+
const relay = resolveRelayScript();
|
|
198
|
+
if (!relay) {
|
|
199
|
+
console.error(
|
|
200
|
+
'REFUSING to claim delivery: no telegram-reply.sh found at or above this directory ' +
|
|
201
|
+
`(looked for ${RELAY_RELATIVE_PATHS.join(' / ')}). The spec was NOT sent.`,
|
|
202
|
+
);
|
|
203
|
+
console.error('The composed message follows so the ask is not lost — send it yourself.');
|
|
204
|
+
console.log(message);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
const reply = spawnSync('bash', [relay, String(args.topic)], {
|
|
148
208
|
input: message, encoding: 'utf8',
|
|
149
209
|
});
|
|
150
210
|
process.stderr.write(reply.stdout || '');
|
|
151
211
|
process.stderr.write(reply.stderr || '');
|
|
212
|
+
if (!relayDelivered(reply)) {
|
|
213
|
+
const why = reply.error
|
|
214
|
+
? reply.error.message
|
|
215
|
+
: `relay exited ${reply.status} without a send confirmation`;
|
|
216
|
+
console.error(`\n[NOT DELIVERED] the spec was NOT sent to topic ${args.topic}: ${why}`);
|
|
217
|
+
console.error('The composed message follows so the ask is not lost — send it yourself.');
|
|
218
|
+
console.log(message);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
152
221
|
console.error(`\n[published] ELI16 link verified (HTTP 200) and delivered to topic ${args.topic}.`);
|
|
153
222
|
} else {
|
|
154
223
|
// Print the message + the verified link for the caller to send.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-07-28T18:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-07-28T18:42:16.061Z",
|
|
5
|
+
"instarVersion": "1.3.1037",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
`skills/spec-converge/scripts/publish-spec-review.mjs` spawned the relay script as a bare
|
|
9
|
+
relative path (`.instar/scripts/telegram-reply.sh`) and then printed
|
|
10
|
+
`[published] … delivered to topic N` without inspecting the spawn result.
|
|
11
|
+
|
|
12
|
+
instar-dev work is required to happen in a worktree, and a worktree has no
|
|
13
|
+
`.instar/scripts/`, so every send from one failed with ENOENT and was reported as
|
|
14
|
+
delivered. Reproduced live: the ENOENT line and the "delivered" line printed one after the
|
|
15
|
+
other, and the operator received nothing.
|
|
16
|
+
|
|
17
|
+
This matters because the script sits on a hook-enforced path — `grounding-before-messaging`
|
|
18
|
+
blocks a hand-written spec-review message and directs the agent here — so the one tool
|
|
19
|
+
whose job is delivering approval asks could silently drop them, leaving work "awaiting
|
|
20
|
+
operator approval" that the operator was never actually asked for.
|
|
21
|
+
|
|
22
|
+
Two changes:
|
|
23
|
+
|
|
24
|
+
- `resolveRelayScript()` walks up from cwd until it finds the relay, so a worktree reaches
|
|
25
|
+
the agent home. Honors `INSTAR_AGENT_HOME` first and falls back to the documented
|
|
26
|
+
`.claude/scripts/` path for older installs. Returns null rather than a guessed path.
|
|
27
|
+
- `relayDelivered()` requires the relay's own `Sent <n> chars to topic <id>` confirmation,
|
|
28
|
+
not merely exit 0. On any failure the script prints `[NOT DELIVERED]` with the reason,
|
|
29
|
+
emits the composed message so the ask is not lost, and exits 1 — it fails closed instead
|
|
30
|
+
of failing silent.
|
|
31
|
+
|
|
32
|
+
The script already refused a broken link, a missing overview, and a stub overview — and
|
|
33
|
+
then did not check whether the message sent. Filed three times in fifteen days (ACT-616
|
|
34
|
+
2026-07-13, ACT-1390 2026-07-27, ACT-1517 2026-07-28) before being fixed.
|
|
35
|
+
|
|
36
|
+
## What to Tell Your User
|
|
37
|
+
|
|
38
|
+
None — internal change (no user-facing surface).
|
|
39
|
+
|
|
40
|
+
## Summary of New Capabilities
|
|
41
|
+
|
|
42
|
+
None — internal change (no user-facing surface).
|
|
43
|
+
|
|
44
|
+
## Evidence
|
|
45
|
+
|
|
46
|
+
- 11 unit tests over both exported functions, including the exact production failure
|
|
47
|
+
(ENOENT → not delivered), the worktree walk-up that shipped broken, the `.claude`
|
|
48
|
+
fallback, precedence when both exist, and exit-0-with-no-confirmation.
|
|
49
|
+
- Proven against the real filesystem from the actual worktree: `resolveRelayScript`
|
|
50
|
+
returns the agent home's relay and it exists on disk.
|
|
51
|
+
- The fix direction was confirmed before the code was written — the unmodified script run
|
|
52
|
+
from the agent home printed `Sent 408 chars to topic 29723` and genuinely delivered,
|
|
53
|
+
while the same command from the worktree printed ENOENT followed by "delivered".
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Side-effects review — publish-spec-review honest send
|
|
2
|
+
|
|
3
|
+
**Change.** `publish-spec-review.mjs` spawned `.instar/scripts/telegram-reply.sh` as a
|
|
4
|
+
bare relative path and then printed `[published] … delivered to topic N` without
|
|
5
|
+
inspecting the spawn result. Now the relay is resolved by walking up from cwd (with the
|
|
6
|
+
documented `.claude/scripts/` fallback), and delivery is only claimed when the relay's own
|
|
7
|
+
`Sent <n> chars to topic <id>` confirmation is present.
|
|
8
|
+
|
|
9
|
+
**This bug was filed three times in fifteen days before being fixed:** ACT-616
|
|
10
|
+
(2026-07-13, medium), ACT-1390 (2026-07-27, high), ACT-1517 (2026-07-28, high).
|
|
11
|
+
Reproduced live at 18:09Z: the ENOENT line and the "delivered" line printed one after the
|
|
12
|
+
other, and the operator received nothing.
|
|
13
|
+
|
|
14
|
+
## 1. Over-block — what legitimate inputs does this reject that it shouldn't?
|
|
15
|
+
|
|
16
|
+
The delivery check requires the relay's `Sent … chars to topic …` marker, so a future
|
|
17
|
+
relay that succeeds *silently* would be reported as NOT delivered — a false negative.
|
|
18
|
+
Accepted deliberately: this script's only job is handing the operator something to
|
|
19
|
+
approve, so a false "didn't send" (which prints the composed message for manual sending
|
|
20
|
+
and exits 1) is strictly safer than a false "sent". If the relay's output contract
|
|
21
|
+
changes, this test fails loudly rather than the ask disappearing quietly.
|
|
22
|
+
|
|
23
|
+
The path walk can in principle find a relay in an unrelated ancestor directory. In the
|
|
24
|
+
agent-home/worktree layout the first match walking up is the agent's own relay, and
|
|
25
|
+
`INSTAR_AGENT_HOME` takes precedence when set.
|
|
26
|
+
|
|
27
|
+
## 2. Under-block — what failure modes does this still miss?
|
|
28
|
+
|
|
29
|
+
- **The relay can confirm a send that Telegram later drops.** `Sent N chars` means the
|
|
30
|
+
relay accepted and posted it; downstream delivery failure is the relay's own retry
|
|
31
|
+
domain (PendingRelayStore / DeliveryFailureSentinel), not this script's.
|
|
32
|
+
- **A wrong topic id still "delivers"** — to the wrong place. Not addressed here.
|
|
33
|
+
- **Non-`--send` callers are unchanged**: they print the message and the verified link for
|
|
34
|
+
the caller to send, and can still drop it themselves. That path already exits 0
|
|
35
|
+
honestly because it never claims delivery.
|
|
36
|
+
|
|
37
|
+
## 3. Level-of-abstraction fit
|
|
38
|
+
|
|
39
|
+
Correct layer. "Where does the relay live" is knowledge the spawning process must have,
|
|
40
|
+
and "did the spawn work" is a question only the spawner can ask. Neither belongs to the
|
|
41
|
+
relay or the caller. The walk-up specifically encodes the worktree convention
|
|
42
|
+
(`docs/specs/AGENT-WORKTREE-CONVENTION-SPEC.md`), which is *why* cwd is reliably not the
|
|
43
|
+
agent home.
|
|
44
|
+
|
|
45
|
+
## 4. Signal vs authority compliance
|
|
46
|
+
|
|
47
|
+
Compliant, and this is the crux. The script sits on a hook-ENFORCED path: the
|
|
48
|
+
`grounding-before-messaging` hook BLOCKS a hand-written spec-review message and directs
|
|
49
|
+
the agent here. So this script holds real authority over whether an operator ever sees an
|
|
50
|
+
approval ask — and it was reporting success without checking. A brittle, unchecked step
|
|
51
|
+
was granted the last word on a human decision point.
|
|
52
|
+
|
|
53
|
+
The fix adds no new blocking authority. It makes an existing authority *honest*: it now
|
|
54
|
+
fails closed (exit 1, message printed for manual sending) instead of failing silent.
|
|
55
|
+
|
|
56
|
+
## 5. Interactions
|
|
57
|
+
|
|
58
|
+
- **The grounding hook** is the upstream producer of traffic here; unchanged.
|
|
59
|
+
- **telegram-reply.sh** is spawned identically, only via an absolute resolved path. Its
|
|
60
|
+
own duplicate-suppression and tone-gate behaviour are untouched.
|
|
61
|
+
- **No double-send risk:** the delivery check is read-only on the result; it never retries.
|
|
62
|
+
A false negative prints the message for a human to send, which could produce a duplicate
|
|
63
|
+
if the relay *had* silently sent it — bounded by the relay's own exact-duplicate
|
|
64
|
+
suppression window.
|
|
65
|
+
- No shadowing, no race: one synchronous spawn, one verdict.
|
|
66
|
+
|
|
67
|
+
## 6. External surfaces
|
|
68
|
+
|
|
69
|
+
Developer/agent tooling under `skills/`. No runtime agent behaviour, route, config, job,
|
|
70
|
+
message schema, or persisted state changes. The only externally visible difference is that
|
|
71
|
+
a failed send is now reported as failed — previously it was reported as success.
|
|
72
|
+
|
|
73
|
+
## 7. Multi-machine posture
|
|
74
|
+
|
|
75
|
+
**Machine-local BY DESIGN.** The question answered is "where on THIS disk is this agent's
|
|
76
|
+
relay script" — a per-machine filesystem fact. Replicating or proxying it would be
|
|
77
|
+
incorrect; a peer's relay path says nothing about this machine's. No durable state, no
|
|
78
|
+
generated URL, no user-facing notice, so nothing strands on a topic transfer. The rendered
|
|
79
|
+
private-view link the script produces is a separate concern and already tunnel-backed.
|
|
80
|
+
|
|
81
|
+
## 8. Rollback cost
|
|
82
|
+
|
|
83
|
+
Revert the commit. Two pure exported functions and one call site; no persisted state, no
|
|
84
|
+
migration, no config. Reverting restores the false-success behaviour, which is the reason
|
|
85
|
+
not to.
|
|
86
|
+
|
|
87
|
+
## Verification
|
|
88
|
+
|
|
89
|
+
- 11 unit tests over both exported functions, including the exact production failure
|
|
90
|
+
(ENOENT → `relayDelivered` false) and the worktree walk-up that shipped broken.
|
|
91
|
+
- Proven against the real filesystem from the actual worktree: `resolveRelayScript`
|
|
92
|
+
returns the agent home's relay and `fs.existsSync` on it is true.
|
|
93
|
+
- The end-to-end fix direction was confirmed before writing the code — running the
|
|
94
|
+
unmodified script from the agent home printed `Sent 408 chars to topic 29723` and
|
|
95
|
+
genuinely delivered, while running it from the worktree printed ENOENT followed by
|
|
96
|
+
"delivered".
|