instar 1.3.984 → 1.3.985

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.984",
3
+ "version": "1.3.985",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-07-26T08:47:20.129Z",
5
- "instarVersion": "1.3.984",
4
+ "generatedAt": "2026-07-26T17:59:55.648Z",
5
+ "instarVersion": "1.3.985",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -11,7 +11,7 @@
11
11
  "domain": "identity",
12
12
  "sourcePath": "src/core/PostUpdateMigrator.ts",
13
13
  "installedPath": ".instar/hooks/instar/session-start.sh",
14
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
14
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
15
15
  "since": "2025-01-01"
16
16
  },
17
17
  "hook:dangerous-command-guard": {
@@ -20,7 +20,7 @@
20
20
  "domain": "safety",
21
21
  "sourcePath": "src/core/PostUpdateMigrator.ts",
22
22
  "installedPath": ".instar/hooks/instar/dangerous-command-guard.sh",
23
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
23
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
24
24
  "since": "2025-01-01"
25
25
  },
26
26
  "hook:grounding-before-messaging": {
@@ -29,7 +29,7 @@
29
29
  "domain": "safety",
30
30
  "sourcePath": "src/core/PostUpdateMigrator.ts",
31
31
  "installedPath": ".instar/hooks/instar/grounding-before-messaging.sh",
32
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
32
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
33
33
  "since": "2025-01-01"
34
34
  },
35
35
  "hook:compaction-recovery": {
@@ -38,7 +38,7 @@
38
38
  "domain": "identity",
39
39
  "sourcePath": "src/core/PostUpdateMigrator.ts",
40
40
  "installedPath": ".instar/hooks/instar/compaction-recovery.sh",
41
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
41
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
42
42
  "since": "2025-01-01"
43
43
  },
44
44
  "hook:external-operation-gate": {
@@ -47,7 +47,7 @@
47
47
  "domain": "safety",
48
48
  "sourcePath": "src/core/PostUpdateMigrator.ts",
49
49
  "installedPath": ".instar/hooks/instar/external-operation-gate.js",
50
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
50
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
51
51
  "since": "2025-01-01"
52
52
  },
53
53
  "hook:deferral-detector": {
@@ -56,7 +56,7 @@
56
56
  "domain": "safety",
57
57
  "sourcePath": "src/core/PostUpdateMigrator.ts",
58
58
  "installedPath": ".instar/hooks/instar/deferral-detector.js",
59
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
59
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
60
60
  "since": "2025-01-01"
61
61
  },
62
62
  "hook:self-stop-guard": {
@@ -65,7 +65,7 @@
65
65
  "domain": "coherence",
66
66
  "sourcePath": "src/core/PostUpdateMigrator.ts",
67
67
  "installedPath": ".instar/hooks/instar/self-stop-guard.js",
68
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
68
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
69
69
  "since": "2025-01-01"
70
70
  },
71
71
  "hook:post-action-reflection": {
@@ -74,7 +74,7 @@
74
74
  "domain": "evolution",
75
75
  "sourcePath": "src/core/PostUpdateMigrator.ts",
76
76
  "installedPath": ".instar/hooks/instar/post-action-reflection.js",
77
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
77
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
78
78
  "since": "2025-01-01"
79
79
  },
80
80
  "hook:external-communication-guard": {
@@ -83,7 +83,7 @@
83
83
  "domain": "safety",
84
84
  "sourcePath": "src/core/PostUpdateMigrator.ts",
85
85
  "installedPath": ".instar/hooks/instar/external-communication-guard.js",
86
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
86
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
87
87
  "since": "2025-01-01"
88
88
  },
89
89
  "hook:scope-coherence-collector": {
@@ -92,7 +92,7 @@
92
92
  "domain": "coherence",
93
93
  "sourcePath": "src/core/PostUpdateMigrator.ts",
94
94
  "installedPath": ".instar/hooks/instar/scope-coherence-collector.js",
95
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
95
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
96
96
  "since": "2025-01-01"
97
97
  },
98
98
  "hook:scope-coherence-checkpoint": {
@@ -101,7 +101,7 @@
101
101
  "domain": "coherence",
102
102
  "sourcePath": "src/core/PostUpdateMigrator.ts",
103
103
  "installedPath": ".instar/hooks/instar/scope-coherence-checkpoint.js",
104
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
104
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
105
105
  "since": "2025-01-01"
106
106
  },
107
107
  "hook:free-text-guard": {
@@ -110,7 +110,7 @@
110
110
  "domain": "safety",
111
111
  "sourcePath": "src/core/PostUpdateMigrator.ts",
112
112
  "installedPath": ".instar/hooks/instar/free-text-guard.sh",
113
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
113
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
114
114
  "since": "2025-01-01"
115
115
  },
116
116
  "hook:claim-intercept": {
@@ -119,7 +119,7 @@
119
119
  "domain": "coherence",
120
120
  "sourcePath": "src/core/PostUpdateMigrator.ts",
121
121
  "installedPath": ".instar/hooks/instar/claim-intercept.js",
122
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
122
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
123
123
  "since": "2025-01-01"
124
124
  },
125
125
  "hook:claim-intercept-response": {
@@ -128,7 +128,7 @@
128
128
  "domain": "coherence",
129
129
  "sourcePath": "src/core/PostUpdateMigrator.ts",
130
130
  "installedPath": ".instar/hooks/instar/claim-intercept-response.js",
131
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
131
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
132
132
  "since": "2025-01-01"
133
133
  },
134
134
  "hook:stop-gate-router": {
@@ -137,7 +137,7 @@
137
137
  "domain": "safety",
138
138
  "sourcePath": "src/core/PostUpdateMigrator.ts",
139
139
  "installedPath": ".instar/hooks/instar/stop-gate-router.js",
140
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
140
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
141
141
  "since": "2025-01-01"
142
142
  },
143
143
  "hook:auto-approve-permissions": {
@@ -146,7 +146,7 @@
146
146
  "domain": "safety",
147
147
  "sourcePath": "src/core/PostUpdateMigrator.ts",
148
148
  "installedPath": ".instar/hooks/instar/auto-approve-permissions.js",
149
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
149
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
150
150
  "since": "2025-01-01"
151
151
  },
152
152
  "job:health-check": {
@@ -1242,7 +1242,7 @@
1242
1242
  "type": "template",
1243
1243
  "domain": "operations",
1244
1244
  "sourcePath": "src/templates/scripts/telegram-reply.sh",
1245
- "contentHash": "a2cf02154a6023725f15480a575f54a5231278c70396cd12051b7d7055b72d98",
1245
+ "contentHash": "4464581188f5c736a62edac5e6a2edecfcfcd365557a18e514b741731bed6e0b",
1246
1246
  "since": "2025-01-01"
1247
1247
  },
1248
1248
  "template:whatsapp-reply.sh": {
@@ -1562,7 +1562,7 @@
1562
1562
  "type": "subsystem",
1563
1563
  "domain": "updates",
1564
1564
  "sourcePath": "src/core/PostUpdateMigrator.ts",
1565
- "contentHash": "0c703f879feac72668890b6db759ed3da3ecd33035f1d2896ccbf7e1ac59c7b5",
1565
+ "contentHash": "59f279be8e27e1d3f4a19c810f39645b9d63dacfbba3b1cc800a4ffeec67d57d",
1566
1566
  "since": "2025-01-01"
1567
1567
  },
1568
1568
  "subsystem:scheduler": {
@@ -4,6 +4,12 @@
4
4
  # Usage:
5
5
  # ./telegram-reply.sh TOPIC_ID "message text"
6
6
  # ./telegram-reply.sh --format markdown TOPIC_ID "**bold**"
7
+ #
8
+ # EVERY FLAG GOES BEFORE THE TOPIC ID. Flag parsing stops at the topic id, so
9
+ # anything after it is message text. A flag in the wrong position used to be
10
+ # sent to the user as literal text with its effect silently dropped; it is now
11
+ # refused. Correct:
12
+ # ./telegram-reply.sh --tone-ack B2_FILE_PATH --tone-reason "why" TOPIC_ID "msg"
7
13
  # echo "message text" | ./telegram-reply.sh TOPIC_ID
8
14
  # cat <<'EOF' | ./telegram-reply.sh TOPIC_ID
9
15
  # Multi-line message here
@@ -25,6 +31,24 @@
25
31
  # a blanket pre-ack that silently disables the inform
26
32
  # layer; spec outbound-jargon-filepath-gap §2.4(4)).
27
33
  #
34
+ # Tone-gate advisory reactions (answering a 422 `tone-gate-advisory`). BOTH
35
+ # forms are recorded as evidence that tunes the gate; neither is optional
36
+ # once you have been handed a decisionRef.
37
+ # --tone-complied <RULE> You agreed with the nudge and revised the text.
38
+ # Grades the check `right`.
39
+ # --tone-ack <RULE> You disagree and are sending unchanged. Grades
40
+ # the check `wrong`. REQUIRES --tone-reason: a
41
+ # reasonless ack is refused and nothing sends,
42
+ # because that reason IS the tuning evidence.
43
+ # --tone-reason "<why>" Why the nudge is wrong in this case.
44
+ # --tone-decision-ref <REF> The decisionRef from the 422, so the reaction
45
+ # joins the verdict it answers.
46
+ #
47
+ # Example (note the ordering — flags first, topic id last):
48
+ # ./telegram-reply.sh --tone-ack B2_FILE_PATH \
49
+ # --tone-reason "the operator asked for the path explicitly" \
50
+ # --tone-decision-ref DQ-1234 29723 "the message"
51
+ #
28
52
  # Outbound advisory preflight (inform-only — spec outbound-jargon-filepath-gap §2.4):
29
53
  # When this send comes from an automated LLM job session (the scheduler
30
54
  # stamps INSTAR_MESSAGE_KIND=automated + INSTAR_SENDER_CLASS=llm-session
@@ -133,6 +157,43 @@ if [ -z "$TOPIC_ID" ]; then
133
157
  exit 1
134
158
  fi
135
159
 
160
+ # A flag placed AFTER the topic id was silently swallowed into the message.
161
+ #
162
+ # The parse loop above stops at the first non-flag argument (the topic id), so
163
+ # everything from there on becomes message text via `MSG="$*"` below. A
164
+ # misplaced `--tone-ack` was therefore SENT TO THE USER as visible message body
165
+ # while the override it was meant to carry never reached the server — and,
166
+ # because the flags never applied, the tone gate re-reviewed the send and its
167
+ # verdict was then misread as absurd. That is how a CORRECT check came to be
168
+ # graded `wrong` in the decision-quality data on 2026-07-26. The record is
169
+ # durable; the cause was two characters of argument order.
170
+ #
171
+ # The asymmetry is the defect: a flag-shaped token BEFORE the topic id is fatal
172
+ # ("Unknown flag", above), but after it the script was maximally permissive.
173
+ # Refuse flag-shaped tokens in both positions. This also catches a TYPO'd flag
174
+ # (`--tone-akc`), which is the realistic case and was equally silent.
175
+ #
176
+ # Deliberately strict: any `--*` argument. A message that genuinely needs such a
177
+ # token as literal text goes through stdin, which is the documented primary path
178
+ # and is unaffected by this check ($# is 0 there).
179
+ for _arg in "$@"; do
180
+ case "$_arg" in
181
+ --*)
182
+ echo "Refused: '$_arg' appears AFTER the topic id." >&2
183
+ echo "" >&2
184
+ echo "Flags are only parsed BEFORE the topic id. Placed here it would have been" >&2
185
+ echo "sent to the user as literal message text, and its effect silently dropped." >&2
186
+ echo "" >&2
187
+ echo " Correct: telegram-reply.sh --tone-ack RULE --tone-reason \"why\" $TOPIC_ID \"message\"" >&2
188
+ echo " You ran: telegram-reply.sh $TOPIC_ID ... $_arg ..." >&2
189
+ echo "" >&2
190
+ echo "If '$_arg' is genuinely part of the message text, pipe the message on stdin:" >&2
191
+ echo " cat <<'EOF' | telegram-reply.sh $TOPIC_ID" >&2
192
+ exit 1
193
+ ;;
194
+ esac
195
+ done
196
+
136
197
  # Read message from args or stdin
137
198
  if [ $# -gt 0 ]; then
138
199
  MSG="$*"
@@ -0,0 +1,109 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ **A flag placed after the topic id was sent to the user as literal message text, and the
9
+ setting it carried was silently dropped.**
10
+
11
+ `telegram-reply.sh` parses flags in a loop that breaks at the first non-flag argument — the
12
+ topic id. Everything after that becomes the message via `MSG="$*"`. So
13
+ `telegram-reply.sh 29723 --tone-ack B15 --tone-reason "why"` sent the user the literal text
14
+ `--tone-ack B15 --tone-reason why`, while the tone-advisory override never reached the
15
+ server. Both failures were silent; the script exited 0.
16
+
17
+ This is measured, not inferred. The pre-fix template is captured verbatim as a test fixture
18
+ (at the same SHA now registered in the migrator's shipped-SHA allowlist) and run against a
19
+ stub of the reply route: exit 0, received text containing `--tone-ack`, and no
20
+ `toneAdvisoryAck` in the metadata.
21
+
22
+ **The cost was a corrupted measurement.** Because the override never applied, the tone gate
23
+ re-reviewed the send as an ordinary message whose text now began with option noise. The
24
+ verdict looked absurd, was read as a malfunction, and a **correct** check was graded `wrong`
25
+ in the decision-quality data — the data used to decide which checks to trust. That record is
26
+ durable and is not retracted by this change.
27
+
28
+ **The root cause was not the script.** These flags were documented nowhere agent-facing:
29
+ not in the script's own usage header (which covers `--format` and `--stdin-base64`), and not
30
+ in the agent instructions, which document the HTTP `metadata.*` fields while simultaneously
31
+ mandating "ALWAYS the relay script, never a hand-rolled curl". The bridge between the two
32
+ did not exist, so the invocation had to be guessed — and the script accepted the guess
33
+ without complaint. A capability shipped without instructions, plus a tool that stayed silent
34
+ on the only usage an uninstructed caller would try.
35
+
36
+ An inconsistency underneath made the fix obvious once seen: the script **already** treated an
37
+ unrecognised flag-shaped token as fatal when it appeared *before* the topic id. It was strict
38
+ about nonsense in one position and fully permissive about a real flag in the other. Both
39
+ positions are now treated the same.
40
+
41
+ Three parts, because the chain had three links:
42
+
43
+ 1. **The script refuses** a `--*` argument after the topic id, printing the correct ordering
44
+ and the corrected command. Nothing is sent.
45
+ 2. **A typo'd flag is caught too** (`--tone-akc`) — the realistic mistake, and equally silent
46
+ before. The guard matches the *shape*, not a list of known names, which is what makes it
47
+ useful rather than decorative.
48
+ 3. **The flags are documented** — in the script's usage header and in the agent instructions,
49
+ with a worked example showing flags before the topic id.
50
+
51
+ **What this does not do:** it does not retract the false grade from 2026-07-26. Whether a
52
+ mistaken grade can be corrected at all — and whether correcting one erases the evidence that
53
+ it was ever made, which would be the opposite defect — is a separate open question, and is
54
+ deliberately not answered here. It also fixes only this script; `slack-reply.sh` and
55
+ `whatsapp-reply.sh` share the parse-then-break shape but carry no tone flags today, so they
56
+ are named rather than swept in.
57
+
58
+ ## What to Tell Your User
59
+
60
+ If a stray double-dash option ever appeared in the middle of one of my messages to you, that
61
+ was this: a setting that belonged in the plumbing ended up in the text, and whatever it was
62
+ supposed to do quietly didn't happen. It can't reach you as text any more — the send is
63
+ refused and corrected instead.
64
+
65
+ The part worth knowing: one of those settings is how I record that I disagree with one of my
66
+ own safety checks. When it silently failed to apply, I misread the result and marked a check
67
+ as faulty when it had actually been right. So this wasn't only cosmetic — it put a wrong
68
+ entry in the records used to judge whether those checks are any good. That entry is still
69
+ there; this stops more from being created.
70
+
71
+ ## Summary of New Capabilities
72
+
73
+ - A flag placed after the topic id is refused with an actionable message naming the correct
74
+ ordering, instead of being sent to the user as literal text with its effect dropped.
75
+ - A misspelled flag after the topic id is caught by the same guard.
76
+ - The tone-advisory reaction flags (`--tone-ack`, `--tone-reason`, `--tone-complied`,
77
+ `--tone-decision-ref`) are documented in the script's usage header and in the agent
78
+ instructions, with a worked example.
79
+ - Existing agents receive both the new script and the new documentation — the doc block is
80
+ sniffed on its own marker so an agent that already carries the surrounding section is not
81
+ skipped.
82
+
83
+ ## Evidence
84
+
85
+ **Reproduction (before),** the pre-fix template against a stub reply route:
86
+
87
+ | invocation | exit | text received by the user | override applied |
88
+ |---|---|---|---|
89
+ | `… 4242 --tone-ack B15 --tone-reason because` | `0` | `--tone-ack B15 --tone-reason because` | no |
90
+
91
+ **Observed after,** same stub:
92
+
93
+ | invocation | before | after |
94
+ |---|---|---|
95
+ | flag after the topic id | sent as message text, exit 0 | refused, exit 1, **zero requests sent** |
96
+ | typo'd flag after the topic id | sent as message text, exit 0 | refused, exit 1, zero requests |
97
+ | flags before the topic id | worked | works — text clean, `toneAdvisoryAck` + reason present |
98
+ | message on stdin containing a flag-shaped token | sent verbatim | sent verbatim (stdin is not inspected) |
99
+ | unknown flag before the topic id | `Unknown flag`, exit 1 | unchanged |
100
+
101
+ **Tests:** `tests/integration/telegram-reply-misplaced-flag.test.ts` (6) runs the real
102
+ template — and the pre-fix fixture — against a stub route, so "was it sent as text?" is
103
+ answered by the received payload rather than by reading the script.
104
+ `tests/unit/PostUpdateMigrator-toneAdvisoryFlagPosition.test.ts` (3) covers migration parity,
105
+ including the load-bearing case: an agent that already carries the tone-advisory section must
106
+ still receive the invocation guidance. That test was verified to FAIL when the second
107
+ migration block is disabled, so it cannot pass whether or not the migration exists. The full
108
+ telegram-reply and template-SHA surface (10 files, 51 tests) passes unchanged;
109
+ `npx tsc --noEmit` clean.
@@ -0,0 +1,193 @@
1
+ # Side-Effects Review — a flag after the topic id was sent to the user as message text
2
+
3
+ **Version / slug:** `misplaced-flag-sent-as-message-text`
4
+ **Date:** `2026-07-26`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `author-applied lenses — see Phase 5 (reduced independence, disclosed)`
7
+
8
+ ## Summary of the change
9
+
10
+ `telegram-reply.sh` parses flags in a loop that BREAKS at the first non-flag argument —
11
+ the topic id. Everything after that becomes the message via `MSG="$*"`. So
12
+ `telegram-reply.sh 29723 --tone-ack B15 --tone-reason "why"` did two wrong things at once,
13
+ both silently: it sent the literal text `--tone-ack B15 --tone-reason why` to the user, and
14
+ the tone-advisory override never reached the server.
15
+
16
+ **Measured, not hypothesised.** The pre-fix template is captured verbatim as a fixture
17
+ (SHA `a2cf0215…`, the same SHA registered in the migrator's shipped-SHA allowlist) and the
18
+ test suite runs it against a stub of `/telegram/reply/:topicId`: it returns exit 0, the
19
+ received `text` contains `--tone-ack`, and `metadata.toneAdvisoryAck` is `undefined`.
20
+
21
+ **The consequence was a corrupted measurement.** On 2026-07-26 the swallowed flags meant a
22
+ tone-gate advisory was re-reviewed as an ordinary send whose text now began with option
23
+ noise. The resulting verdict looked absurd, I read it as a malfunction, and graded a
24
+ **correct** check `wrong` in the decision-quality data. That record is durable and is not
25
+ retracted by this change — see §2.
26
+
27
+ **The root cause is not the script.** The tone-reaction flags were documented NOWHERE
28
+ agent-facing: not in the script's own usage header (which documents `--format` and
29
+ `--stdin-base64`), not in the CLAUDE.md template. The template documents `metadata.*` — the
30
+ HTTP shape — while simultaneously mandating "ALWAYS the relay script, never a hand-rolled
31
+ curl". The bridge between the two did not exist, so the invocation had to be invented. The
32
+ script then accepted the invention without complaint. A capability shipped without
33
+ instructions, plus a tool silent on the only usage an uninstructed caller would try.
34
+
35
+ Three parts, because it took three to close the chain: the guard (refuses), the script's
36
+ usage header, and the CLAUDE.md template (documents the correct form).
37
+
38
+ ## Decision-point inventory
39
+
40
+ | point | classification | note |
41
+ |---|---|---|
42
+ | `--*` argument after the topic id → refuse | `invariant` | Deterministic glob on an argument. No judgment, no model, no competing signals. Symmetric with the pre-existing `-*` refusal before the topic id. |
43
+ | stdin path exempt | `invariant` | Structural, not a rule: `$#` is 0 when the message comes from stdin, so the loop has nothing to inspect. |
44
+ | CLAUDE.md content-sniff marker | `invariant` | String presence test. |
45
+
46
+ ## 1. Over-block
47
+
48
+ **What legitimate inputs does this change reject that it shouldn't?**
49
+
50
+ An argument-form message whose own token is exactly `--something`. For example
51
+ `telegram-reply.sh 29723 the flag --tone-ack means X` is now refused, where before it sent.
52
+
53
+ This is deliberate and I think correct, but it is a real narrowing and worth stating
54
+ plainly rather than waving away:
55
+
56
+ - The refusal is **loud, immediate, and actionable** — it prints the corrected ordering and
57
+ names the escape hatch. The failure mode it replaces was silent and unrecoverable.
58
+ - The escape hatch is **the documented primary path**: stdin (`cat <<'EOF' | …`). Every
59
+ multi-line message already uses it, and stdin is untouched by this check.
60
+ - The alternative — matching only the eight known flag names — would have let a TYPO'd
61
+ flag (`--tone-akc`) through silently. That is the realistic mistake, and the whole point
62
+ is to stop flag-shaped tokens being swallowed. Matching the shape, not the list, is what
63
+ makes the guard useful rather than decorative.
64
+
65
+ ## 2. Under-block
66
+
67
+ **What failure modes does this still miss?**
68
+
69
+ - **The false grade from 2026-07-26 is not retracted.** This stops the cause; it does not
70
+ undo the effect. Whether a mistaken grade can be corrected at all — and whether a
71
+ supersede erases the evidence the mistake was made, which would be the opposite defect —
72
+ is an open question deliberately NOT answered here. Recorded rather than quietly folded in.
73
+ - **A single-token message that is exactly a flag, passed as an argument, is now refused
74
+ rather than sent.** Correct behaviour, but it is a refusal, not a rescue.
75
+ - **Only this script is fixed.** `slack-reply.sh` and `whatsapp-reply.sh` share the
76
+ parse-then-break shape. They do not carry tone-reaction flags today, so the same mistake
77
+ has less to swallow — but the shape is the same. Named here rather than silently
78
+ extended: widening the change to two more shipped scripts, each needing its own
79
+ shipped-SHA registration, belongs in its own review.
80
+ - **Documentation coverage generally.** This fixes one undocumented capability. Nothing
81
+ here establishes that others are documented.
82
+
83
+ ## 3. Level-of-abstraction fit
84
+
85
+ **Is this at the right layer?**
86
+
87
+ Yes, and at three layers on purpose, because the defect had three links:
88
+
89
+ 1. **The script** — the only place a misplaced flag can be caught before it becomes text.
90
+ 2. **The script's usage header** — where someone reading the tool learns the ordering.
91
+ 3. **The CLAUDE.md template** — where the agent that is *mandated* to use the script learns
92
+ that these flags exist at all. Fixing only the script would have left the root cause
93
+ (undocumented capability) fully intact; the next agent would guess again, get a refusal
94
+ instead of a silent mis-send, and still not know the right form.
95
+
96
+ Rejected: making the parser accept flags in either position. It looks friendlier and is
97
+ worse — `MSG="$*"` means the parser cannot distinguish "a flag I should apply" from "a word
98
+ of the message" without guessing, and a guess here silently rewrites what the user reads.
99
+ Refusing is the only honest option.
100
+
101
+ ## 4. Signal vs authority compliance
102
+
103
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
104
+
105
+ **Does this change hold blocking authority with brittle logic?**
106
+
107
+ It holds blocking authority (the send is refused) on a deterministic glob over an
108
+ argument — no model, no heuristic, no network, no threshold. It cannot drift or degrade,
109
+ and it fails in the safe direction: it withholds a send rather than delivering a
110
+ misleading one.
111
+
112
+ It is also strictly *less* authority than it appears: it blocks nothing the caller cannot
113
+ immediately re-issue correctly, and the message text itself is never inspected — only
114
+ argument tokens.
115
+
116
+ ## 4b. Judgment-point check (Judgment Within Floors standard)
117
+
118
+ No judgment point is introduced. The guard is a `case` glob. Notably it *removes* an
119
+ implicit judgment: the old code silently decided that a flag-shaped token was message
120
+ content, which is precisely the kind of unexamined call this standard exists to surface.
121
+
122
+ ## 5. Interactions
123
+
124
+ - **stdin is structurally unaffected** — `$#` is 0 there, so the loop body never runs. This
125
+ is asserted by test, because "the primary path still works" is the claim most worth
126
+ checking.
127
+ - **Correct-order invocations are unchanged** — asserted end-to-end: message body clean,
128
+ `metadata.toneAdvisoryAck` and `toneAdvisoryAckReason` both present.
129
+ - **Pre-existing `Unknown flag` behaviour before the topic id is untouched** — asserted.
130
+ - **Migration parity for the SCRIPT**: the pre-change SHA `a2cf0215…` is registered in
131
+ `TELEGRAM_REPLY_PRIOR_SHIPPED_SHAS`. Without that entry the SHA-history migrator leaves
132
+ the swallowing version in place with a `.new` beside it and every deployed agent keeps
133
+ mis-sending — the failure mode the entry above it in that list was added to prevent.
134
+ - **Migration parity for the DOCS — the trap.** The tone-advisory CLAUDE.md block is
135
+ content-sniffed on `'Most checks are NUDGES you may override'`, a marker this change does
136
+ NOT alter. Appending guidance to that constant would therefore reach new installs ONLY;
137
+ every deployed agent short-circuits. Hence a second, independently-sniffed block. A test
138
+ simulates an agent that already carries the old section and proves it still receives the
139
+ new part — and I verified that test FAILS when the second block is disabled, so it cannot
140
+ become decorative.
141
+ - **No route, config key, persisted state, or schema change.**
142
+
143
+ ## 6. External surfaces
144
+
145
+ One user-visible change, and it is a removal of a bad one: a misplaced flag no longer
146
+ appears in the user's chat as literal option text. Nothing else about delivery changes.
147
+
148
+ ## 6b. Operator-surface quality
149
+
150
+ The refusal prints what was run, what the correct ordering is, and how to send the token
151
+ literally if it really is message text. An error that only said "bad arguments" would have
152
+ reproduced the original problem — a caller with no way to learn the right form — one level
153
+ up.
154
+
155
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
156
+
157
+ **Posture: `unified` by construction — no new state.** A shell-script argument guard plus
158
+ two documentation blocks. No new field, file, route, or surface; nothing to replicate and
159
+ no `machine-local-justification` marker applicable. The script is deployed per machine by
160
+ the existing SHA-history migrator, which is the established path and unchanged here.
161
+
162
+ ## 8. Rollback cost
163
+
164
+ Low and total: revert one commit. The guard disappears, the docs revert, and the
165
+ shipped-SHA entry becomes a harmless extra allowlist member (never remove old SHAs — they
166
+ remain valid migration sources). No data, no migration, no state to unwind.
167
+
168
+ ## Phase 5 — Second-pass review (independent reviewer subagent)
169
+
170
+ **Disclosure, per Truthful Provenance:** no independent reviewer subagent was spawned — a
171
+ standing instruction in this session prohibits it unless the operator requests it. The
172
+ review lenses were applied by the author. That is **reduced independence**, recorded as
173
+ such rather than presented as a concurring second pass.
174
+
175
+ What author-applied review caught and changed:
176
+
177
+ 1. **The first version fixed only the script.** That would have left the actual root cause
178
+ untouched: the flags are documented nowhere, so the next agent guesses again — it would
179
+ just get a refusal instead of a silent mis-send. Verified the absence repo-wide before
180
+ claiming it (the flags appear only in the script itself, an old upgrade guide, and the
181
+ migrator). Docs are now part of the change, not a follow-up.
182
+ 2. **The CLAUDE.md content-sniff trap was nearly walked into.** Appending to the existing
183
+ constant looked sufficient and would have shipped a doc fix that reached new installs
184
+ only — the exact "works for new agents only" shape the Migration Parity Standard names as
185
+ a broken feature. Caught by asking who actually *receives* the edit.
186
+ 3. **The guard was nearly scoped to the eight known flag names**, which reads as more
187
+ precise and is weaker: a typo'd flag — the realistic mistake — would still be swallowed
188
+ silently. Widened to the shape, with the over-block cost stated in §1 rather than hidden.
189
+ 4. **The negative case was run, not assumed.** I disabled the second migration block and
190
+ confirmed the load-bearing test fails, because a migration-parity test that passes
191
+ whether or not the migration exists is worse than none.
192
+ 5. **The sibling scripts were checked and deliberately left alone** (§2) rather than swept
193
+ in for tidiness — each needs its own shipped-SHA registration and review.