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/dist/core/PostUpdateMigrator.d.ts +17 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +48 -1
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +20 -20
- package/src/templates/scripts/telegram-reply.sh +61 -0
- package/upgrades/1.3.985.md +109 -0
- package/upgrades/side-effects/misplaced-flag-sent-as-message-text.md +193 -0
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"instarVersion": "1.3.
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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.
|