instar 1.3.983 → 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/CredentialManualLevers.js +0 -0
- package/dist/core/CredentialManualLevers.js.map +1 -1
- package/dist/core/DeliverMessageHandler.js +0 -0
- package/dist/core/DeliverMessageHandler.js.map +1 -1
- package/dist/core/PeerEndpointResolver.d.ts +0 -0
- package/dist/core/PeerEndpointResolver.d.ts.map +1 -1
- package/dist/core/PeerEndpointResolver.js +0 -0
- package/dist/core/PeerEndpointResolver.js.map +1 -1
- 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/dist/core/QueueDrainLoop.js +0 -0
- package/dist/core/QueueDrainLoop.js.map +1 -1
- package/dist/core/ScopeAccretionSweep.js +1 -1
- package/dist/core/ScopeAccretionSweep.js.map +1 -1
- package/dist/core/SessionOwnership.js +1 -1
- package/dist/core/SessionOwnership.js.map +1 -1
- package/dist/core/StandardsEnforcementAuditor.js +1 -1
- package/dist/core/StandardsEnforcementAuditor.js.map +1 -1
- package/dist/core/StoreSnapshot.js +2 -2
- package/dist/core/StoreSnapshot.js.map +1 -1
- package/dist/core/UnionReader.js +0 -0
- package/dist/core/UnionReader.js.map +1 -1
- package/dist/core/benchmarkDivergenceCore.js +1 -1
- package/dist/core/benchmarkDivergenceCore.js.map +1 -1
- package/dist/core/cartographerSummary.js +0 -0
- package/dist/core/cartographerSummary.js.map +1 -1
- package/dist/core/routingPriceAuthority.d.ts +0 -0
- package/dist/core/routingPriceAuthority.d.ts.map +1 -1
- package/dist/core/routingPriceAuthority.js +0 -0
- package/dist/core/routingPriceAuthority.js.map +1 -1
- package/dist/messaging/relayContentDedup.js +0 -0
- package/dist/messaging/relayContentDedup.js.map +1 -1
- package/dist/monitoring/BenchmarkDivergenceAnalyzer.js +10 -10
- package/dist/monitoring/BenchmarkDivergenceAnalyzer.js.map +1 -1
- package/dist/monitoring/ExternalHogArmMarker.js +0 -0
- package/dist/monitoring/ExternalHogArmMarker.js.map +1 -1
- package/dist/monitoring/ExternalHogClassifier.js +0 -0
- package/dist/monitoring/ExternalHogClassifier.js.map +1 -1
- package/dist/monitoring/ExternalHogSampler.js +0 -0
- package/dist/monitoring/ExternalHogSampler.js.map +1 -1
- package/dist/monitoring/FeatureMetricsLedger.js +1 -1
- package/dist/monitoring/FeatureMetricsLedger.js.map +1 -1
- package/dist/monitoring/GreenPrAutoMerger.js +1 -1
- package/dist/monitoring/GreenPrAutoMerger.js.map +1 -1
- package/dist/monitoring/PermissionPromptAutoResolver.js +1 -1
- package/dist/monitoring/PermissionPromptAutoResolver.js.map +1 -1
- package/dist/monitoring/ProviderCostReportStore.js +0 -0
- package/dist/monitoring/ProviderCostReportStore.js.map +1 -1
- package/dist/monitoring/blockerSettleAuthority.js +0 -0
- package/dist/monitoring/blockerSettleAuthority.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.982.md +17 -2
- package/upgrades/1.3.984.md +116 -0
- package/upgrades/1.3.985.md +109 -0
- package/upgrades/side-effects/behavioural-promise-unverifiable.md +17 -2
- package/upgrades/side-effects/misplaced-flag-sent-as-message-text.md +193 -0
- package/upgrades/side-effects/raw-nul-bytes-hide-source-from-grep.md +199 -0
|
@@ -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.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Side-Effects Review — a raw NUL byte made 30 source files invisible to grep
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `raw-nul-bytes-hide-source-from-grep`
|
|
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
|
+
`grep` classifies a file as binary if it contains a NUL (0x00) byte, and on a binary
|
|
11
|
+
file it emits **nothing** — not a match, not a `Binary file X matches` line, not even a
|
|
12
|
+
`0` under `-c`. Thirty tracked text files each contained one or more raw NUL bytes, so
|
|
13
|
+
every grep-based audit over `src/` silently skipped them and the empty result read as
|
|
14
|
+
"absent".
|
|
15
|
+
|
|
16
|
+
The bytes were never corruption. Each was a deliberate composite-key or hash separator
|
|
17
|
+
written as a literal byte instead of the six-character escape:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const key = `${row.model}<literal 0x00>${row.framework}`; // now: \u0000
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The delimiter choice is correct — NUL cannot occur inside a model or framework name, so
|
|
24
|
+
it is collision-proof. Writing it raw is the whole defect.
|
|
25
|
+
|
|
26
|
+
**Measured:** 30 tracked files (22 under `src/`, 7 tests, 1 spec doc). Affected modules
|
|
27
|
+
include `blockerSettleAuthority.ts` (the true-blocker settle gate), `SessionOwnership.ts`,
|
|
28
|
+
`GreenPrAutoMerger.ts`, `PermissionPromptAutoResolver.ts` (an always-on safety floor),
|
|
29
|
+
all three `ExternalHog*` modules, and `StandardsEnforcementAuditor.ts` — the module that
|
|
30
|
+
audits whether standards carry structural guards was itself invisible to the standard
|
|
31
|
+
search instrument.
|
|
32
|
+
|
|
33
|
+
**Second consequence, worse in kind:** git applies the same rule but only sniffs the
|
|
34
|
+
first 8000 bytes. For the **11 files** whose NUL fell inside that window, `git diff`
|
|
35
|
+
rendered `Bin 5407 -> 5412 bytes` instead of a line diff — so pull requests touching
|
|
36
|
+
safety-critical authority code were reviewed **without the reviewer being shown the
|
|
37
|
+
changed lines**. For the other 19, git saw text while grep did not: the two instruments
|
|
38
|
+
disagreed, which is precisely why this survived so long.
|
|
39
|
+
|
|
40
|
+
Every raw byte is replaced with `\u0000` (identical runtime string; valid and unambiguous
|
|
41
|
+
in string, template and regex contexts — unlike `\0`, a legacy octal escape when followed
|
|
42
|
+
by a digit), and a new ratchet fails the build if one ever returns.
|
|
43
|
+
|
|
44
|
+
## Decision-point inventory
|
|
45
|
+
|
|
46
|
+
| point | classification | note |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `containsRawNul(file)` | `invariant` | A byte is present or it is not. No judgment, no model, no competing signals. |
|
|
49
|
+
| lint scope (extension allowlist) | `invariant` | Deterministic set of text-by-definition extensions. Binary fixtures are out of scope by construction, not by heuristic. |
|
|
50
|
+
| control bytes other than 0x00 | `invariant` (deliberate exclusion) | Empirically verified not to cause grep-skip; see §2. |
|
|
51
|
+
|
|
52
|
+
## 1. Over-block
|
|
53
|
+
|
|
54
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
55
|
+
|
|
56
|
+
The lint rejects a raw 0x00 byte in a file with a text extension. There is **no
|
|
57
|
+
legitimate case it blocks**, because there is no case that needs one: any runtime NUL a
|
|
58
|
+
program wants is expressible as an escape, with an identical result. The lint therefore
|
|
59
|
+
carries no exemption list — an exemption would only ever be used to reinstate the defect.
|
|
60
|
+
|
|
61
|
+
The nearest real cost: a genuinely binary fixture that someone gives a `.json`/`.md`
|
|
62
|
+
extension would be refused. That is a naming error the lint is right to surface, and no
|
|
63
|
+
such file exists in the tree today (verified by the sweep).
|
|
64
|
+
|
|
65
|
+
## 2. Under-block
|
|
66
|
+
|
|
67
|
+
**What failure modes does this still miss?**
|
|
68
|
+
|
|
69
|
+
- **Other raw control bytes remain.** ESC (0x1b), BEL (0x07), 0x1f and 0x7f appear in a
|
|
70
|
+
handful of hostile-input test fixtures (`RevertDetector.ts`, `liveOutputStream.ts`,
|
|
71
|
+
`process-health-render.test.ts`, and others). These are **deliberately not covered**:
|
|
72
|
+
I verified empirically that a file containing ESC/BEL is still `ASCII text` to `file`
|
|
73
|
+
and still fully searchable by `grep` — only 0x00 triggers the skip. Escaping them would
|
|
74
|
+
be cosmetic. A lint should enforce exactly the failure it is named for. Recorded rather
|
|
75
|
+
than silently widened. (A separate concern does exist — a raw ESC in a file that gets
|
|
76
|
+
`cat`'d is a terminal-escape vector — but that is a different defect with a different
|
|
77
|
+
argument, and folding it in here would smuggle it past review.)
|
|
78
|
+
- **Untracked and generated files are not scanned.** The lint walks `src`, `tests`,
|
|
79
|
+
`docs`, `scripts`, `.github`. Build output is excluded on purpose (it is regenerated
|
|
80
|
+
from the now-clean sources).
|
|
81
|
+
- **This fixes one silent instrument, not the class.** Other tools can also fail
|
|
82
|
+
closed-mouthed. Nothing here establishes that no others remain — see §5.
|
|
83
|
+
|
|
84
|
+
## 3. Level-of-abstraction fit
|
|
85
|
+
|
|
86
|
+
**Is this at the right layer?**
|
|
87
|
+
|
|
88
|
+
Yes, and at two layers deliberately:
|
|
89
|
+
|
|
90
|
+
1. **The data** — the bytes themselves are corrected at source, which is the only place
|
|
91
|
+
the fix can be complete. A tooling workaround (`grep -a` everywhere, a `.gitattributes`
|
|
92
|
+
override) would have left the files hostile to every *other* consumer, including
|
|
93
|
+
editors, reviewers, and any future agent's search.
|
|
94
|
+
2. **The guard** — a repo-level test, matching the existing tree-scanning ratchets
|
|
95
|
+
(`no-empty-catch-blocks`, `no-silent-fallbacks`). It reads bytes with `readFileSync`
|
|
96
|
+
rather than shelling out to a search tool, so the lint cannot be blinded by the very
|
|
97
|
+
defect it detects.
|
|
98
|
+
|
|
99
|
+
Notably a `.gitattributes` `*.ts text` entry was considered and **rejected**: it would
|
|
100
|
+
have fixed git's rendering while leaving `grep` — the instrument that actually failed —
|
|
101
|
+
just as blind, and would have converted a visible defect into a hidden one.
|
|
102
|
+
|
|
103
|
+
## 4. Signal vs authority compliance
|
|
104
|
+
|
|
105
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
106
|
+
|
|
107
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
108
|
+
|
|
109
|
+
It holds blocking authority (a failing test blocks a merge) on **maximally non-brittle**
|
|
110
|
+
logic: the presence of one specific byte in a file. There is no model, no heuristic, no
|
|
111
|
+
threshold, no parse. It cannot drift, cannot be rate-limited, and produces the same
|
|
112
|
+
verdict on every machine.
|
|
113
|
+
|
|
114
|
+
This is the correct place for hard authority — a deterministic, universally-checkable
|
|
115
|
+
fact — and precisely the shape the standard reserves it for.
|
|
116
|
+
|
|
117
|
+
## 4b. Judgment-point check (Judgment Within Floors standard)
|
|
118
|
+
|
|
119
|
+
No judgment point is introduced. Every decision in the lint is a byte comparison or a
|
|
120
|
+
set membership test. Nothing consults an LLM; nothing weighs competing signals.
|
|
121
|
+
|
|
122
|
+
## 5. Interactions
|
|
123
|
+
|
|
124
|
+
- **Runtime behaviour is unchanged.** `\u0000` denotes exactly the byte it replaces. The
|
|
125
|
+
lint asserts this explicitly (`JSON.parse` of the escape text `=== String.fromCharCode(0)`)
|
|
126
|
+
rather than leaving it as a claim in prose.
|
|
127
|
+
- **Persisted data is unaffected.** Several sites feed these separators into hashes
|
|
128
|
+
(`relayContentDedup`, `blockerSettleAuthority`, `UnionReader`, `ExternalHogArmMarker`).
|
|
129
|
+
Because the string is identical, every hash is identical — **no migration, no cache
|
|
130
|
+
invalidation, no re-keying**. This was the primary risk and it is checked, not assumed.
|
|
131
|
+
- **`git diff` becomes readable** for the 11 previously-binary files. A one-time
|
|
132
|
+
consequence: this PR shows them as `Bin` on the *old* side.
|
|
133
|
+
- **The empty-catch ratchet** scans `src/` as text via `readFileSync`, so it was NOT
|
|
134
|
+
blinded — verified before assuming a shared failure. Grep-based *ad hoc* audits were the
|
|
135
|
+
affected consumers, not the file-reading lints.
|
|
136
|
+
- **This session's own reasoning was affected.** A search for a retraction mechanism
|
|
137
|
+
across `src/` returned one unrelated subsystem, and I was one step from recording
|
|
138
|
+
"no such path exists" as a finding. That search had skipped 22 source files. The
|
|
139
|
+
correction is recorded in the run log rather than quietly dropped: **the finding I was
|
|
140
|
+
about to publish was manufactured by the defect this PR fixes.**
|
|
141
|
+
|
|
142
|
+
## 6. External surfaces
|
|
143
|
+
|
|
144
|
+
None. No route, no config key, no persisted state, no user-visible behaviour. This is a
|
|
145
|
+
source-text and build-time change only.
|
|
146
|
+
|
|
147
|
+
## 6b. Operator-surface quality
|
|
148
|
+
|
|
149
|
+
Nothing reaches the operator. The one surface is the failing-test message, which names
|
|
150
|
+
each offending file, states the consequence in plain terms ("INVISIBLE to grep — every
|
|
151
|
+
grep-based audit silently skips them and reports 'not found'"), and gives the exact fix.
|
|
152
|
+
A lint that only says "assertion failed" would have re-created the original problem at
|
|
153
|
+
the level of the guard.
|
|
154
|
+
|
|
155
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
156
|
+
|
|
157
|
+
**Posture: `unified` by construction — no state at all.** This change introduces no
|
|
158
|
+
feature, no field, no file, no surface. It edits source text and adds a build-time check
|
|
159
|
+
that runs identically wherever the repo is checked out. No replication path is required
|
|
160
|
+
and no `machine-local-justification` marker applies, because there is nothing machine-local
|
|
161
|
+
to justify.
|
|
162
|
+
|
|
163
|
+
## 8. Rollback cost
|
|
164
|
+
|
|
165
|
+
Trivial and total: one commit, 30 files, 33 single-line substitutions plus one new test
|
|
166
|
+
file. Reverting restores the raw bytes — and with them the grep-invisibility of 22 source
|
|
167
|
+
modules and the binary diffs on 11. There is no data to migrate back and no state to
|
|
168
|
+
unwind.
|
|
169
|
+
|
|
170
|
+
## Phase 5 — Second-pass review (independent reviewer subagent)
|
|
171
|
+
|
|
172
|
+
**Disclosure, per Truthful Provenance:** no independent reviewer subagent was spawned — a
|
|
173
|
+
standing instruction in this session prohibits it unless the operator requests it. The
|
|
174
|
+
review lenses were applied by the author. That is **reduced independence**, recorded as
|
|
175
|
+
such rather than presented as a concurring second pass.
|
|
176
|
+
|
|
177
|
+
What author-applied review caught and changed:
|
|
178
|
+
|
|
179
|
+
1. **The first draft of this very lint shipped five raw NUL bytes and hid itself from
|
|
180
|
+
grep.** I typed the escape inline; it was normalised into actual NUL bytes on write.
|
|
181
|
+
Caught only because I byte-checked the file I had just written instead of trusting it.
|
|
182
|
+
The lint now constructs both the raw byte and the escape *text* from char codes and
|
|
183
|
+
never types the escape inline — and the authoring hazard is documented in its header,
|
|
184
|
+
because the next author will hit exactly the same trap.
|
|
185
|
+
2. **The scope was nearly widened to all control bytes** for tidiness. I tested the
|
|
186
|
+
assumption instead and found ESC/BEL do not cause the skip — so widening would have
|
|
187
|
+
added churn and noise while claiming safety it does not provide. Narrowed back, with
|
|
188
|
+
the empirical result recorded in §2.
|
|
189
|
+
3. **A "scan is not silently empty" assertion was added.** Without it, a scan that walked
|
|
190
|
+
the wrong roots would find zero offenders and pass forever — the same absence-reads-as-
|
|
191
|
+
presence failure, reincarnated inside its own guard. The lint now asserts it examined
|
|
192
|
+
>500 files.
|
|
193
|
+
4. **The hash-stability question was checked, not assumed.** Four sites feed these
|
|
194
|
+
separators into digests; had the substitution altered the string, persisted dedup keys
|
|
195
|
+
and arm markers would have silently changed meaning. Verified identical before
|
|
196
|
+
committing.
|
|
197
|
+
5. **`.gitattributes` was rejected as the fix** (see §3) because it would have repaired
|
|
198
|
+
the *visible* symptom (git diffs) while leaving the *silent* one (grep) intact —
|
|
199
|
+
trading a detectable failure for an undetectable one.
|