instar 1.3.983 → 1.3.984

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.
Files changed (54) hide show
  1. package/dist/core/CredentialManualLevers.js +0 -0
  2. package/dist/core/CredentialManualLevers.js.map +1 -1
  3. package/dist/core/DeliverMessageHandler.js +0 -0
  4. package/dist/core/DeliverMessageHandler.js.map +1 -1
  5. package/dist/core/PeerEndpointResolver.d.ts +0 -0
  6. package/dist/core/PeerEndpointResolver.d.ts.map +1 -1
  7. package/dist/core/PeerEndpointResolver.js +0 -0
  8. package/dist/core/PeerEndpointResolver.js.map +1 -1
  9. package/dist/core/QueueDrainLoop.js +0 -0
  10. package/dist/core/QueueDrainLoop.js.map +1 -1
  11. package/dist/core/ScopeAccretionSweep.js +1 -1
  12. package/dist/core/ScopeAccretionSweep.js.map +1 -1
  13. package/dist/core/SessionOwnership.js +1 -1
  14. package/dist/core/SessionOwnership.js.map +1 -1
  15. package/dist/core/StandardsEnforcementAuditor.js +1 -1
  16. package/dist/core/StandardsEnforcementAuditor.js.map +1 -1
  17. package/dist/core/StoreSnapshot.js +2 -2
  18. package/dist/core/StoreSnapshot.js.map +1 -1
  19. package/dist/core/UnionReader.js +0 -0
  20. package/dist/core/UnionReader.js.map +1 -1
  21. package/dist/core/benchmarkDivergenceCore.js +1 -1
  22. package/dist/core/benchmarkDivergenceCore.js.map +1 -1
  23. package/dist/core/cartographerSummary.js +0 -0
  24. package/dist/core/cartographerSummary.js.map +1 -1
  25. package/dist/core/routingPriceAuthority.d.ts +0 -0
  26. package/dist/core/routingPriceAuthority.d.ts.map +1 -1
  27. package/dist/core/routingPriceAuthority.js +0 -0
  28. package/dist/core/routingPriceAuthority.js.map +1 -1
  29. package/dist/messaging/relayContentDedup.js +0 -0
  30. package/dist/messaging/relayContentDedup.js.map +1 -1
  31. package/dist/monitoring/BenchmarkDivergenceAnalyzer.js +10 -10
  32. package/dist/monitoring/BenchmarkDivergenceAnalyzer.js.map +1 -1
  33. package/dist/monitoring/ExternalHogArmMarker.js +0 -0
  34. package/dist/monitoring/ExternalHogArmMarker.js.map +1 -1
  35. package/dist/monitoring/ExternalHogClassifier.js +0 -0
  36. package/dist/monitoring/ExternalHogClassifier.js.map +1 -1
  37. package/dist/monitoring/ExternalHogSampler.js +0 -0
  38. package/dist/monitoring/ExternalHogSampler.js.map +1 -1
  39. package/dist/monitoring/FeatureMetricsLedger.js +1 -1
  40. package/dist/monitoring/FeatureMetricsLedger.js.map +1 -1
  41. package/dist/monitoring/GreenPrAutoMerger.js +1 -1
  42. package/dist/monitoring/GreenPrAutoMerger.js.map +1 -1
  43. package/dist/monitoring/PermissionPromptAutoResolver.js +1 -1
  44. package/dist/monitoring/PermissionPromptAutoResolver.js.map +1 -1
  45. package/dist/monitoring/ProviderCostReportStore.js +0 -0
  46. package/dist/monitoring/ProviderCostReportStore.js.map +1 -1
  47. package/dist/monitoring/blockerSettleAuthority.js +0 -0
  48. package/dist/monitoring/blockerSettleAuthority.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/data/builtin-manifest.json +2 -2
  51. package/upgrades/1.3.982.md +17 -2
  52. package/upgrades/1.3.984.md +116 -0
  53. package/upgrades/side-effects/behavioural-promise-unverifiable.md +17 -2
  54. package/upgrades/side-effects/raw-nul-bytes-hide-source-from-grep.md +199 -0
@@ -0,0 +1,116 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ **Thirty source files were invisible to `grep`. A search across them returned nothing,
9
+ and nothing read as "there is nothing there."**
10
+
11
+ `grep` classifies a file as binary if it contains a NUL (0x00) byte, and on a binary file
12
+ it emits **nothing at all** — not a match, not a `Binary file X matches` line, not even a
13
+ `0` under `-c`. Thirty tracked text files each carried one, so every grep-based audit over
14
+ `src/` silently skipped them. A search that examined two-thirds of the tree was
15
+ byte-for-byte indistinguishable from one that examined all of it and found nothing wrong.
16
+
17
+ Twenty-two were live source. Among them: `blockerSettleAuthority.ts` (the gate that
18
+ decides whether a blocker is genuinely unresolvable), `SessionOwnership.ts`,
19
+ `GreenPrAutoMerger.ts`, `PermissionPromptAutoResolver.ts` (an always-on safety floor), all
20
+ three `ExternalHog*` modules — and `StandardsEnforcementAuditor.ts`, the module that
21
+ audits whether our standards carry structural guards. The auditor of guarantees was itself
22
+ invisible to the standard search instrument.
23
+
24
+ **A second consequence, worse in kind.** Git applies the same rule but only sniffs the
25
+ first 8000 bytes. For the **11 files** whose NUL fell inside that window, `git diff`
26
+ rendered `Bin 5407 -> 5412 bytes` instead of a line diff — so pull requests touching
27
+ safety-critical authority code were reviewed **without the reviewer being shown the
28
+ changed lines**. For the other 19, git saw text while `grep` did not. The two instruments
29
+ disagreed on the same tree, which is exactly why this survived so long: whichever one you
30
+ happened to reach for decided what you believed.
31
+
32
+ **None of it was corruption.** Every byte was a deliberate composite-key or hash
33
+ separator — the classic collision-proof choice, since a NUL cannot occur inside a model
34
+ name or a framework name:
35
+
36
+ ```ts
37
+ const key = `${row.model}<a literal 0x00 byte>${row.framework}`;
38
+ ```
39
+
40
+ The delimiter is correct. Writing it as the byte rather than its escape is the entire
41
+ defect. All thirty now use the six-character escape, which denotes the identical
42
+ character — so runtime behaviour is unchanged, and every hash derived from these
43
+ separators is byte-identical. Four sites feed them into digests
44
+ (`relayContentDedup`, `blockerSettleAuthority`, `UnionReader`, `ExternalHogArmMarker`);
45
+ this was the primary risk and it was verified rather than assumed. No migration, no cache
46
+ invalidation, no re-keying.
47
+
48
+ A new ratchet, `tests/unit/no-raw-nul-bytes-in-source.test.ts`, fails the build if one
49
+ returns. It reads bytes via `readFileSync` and never shells out to a search tool, so it
50
+ cannot be blinded by the defect it detects. It carries no exemption list, because no case
51
+ needs one: any runtime NUL a program wants is expressible as an escape.
52
+
53
+ **What this does not do:** it fixes one silent instrument, not the class. Other raw
54
+ control bytes (ESC, BEL) remain in a few hostile-input test fixtures — deliberately, after
55
+ verifying empirically that they do **not** cause the grep skip. Only 0x00 does. A lint
56
+ should enforce exactly the failure it is named for, and widening this one for tidiness
57
+ would have added churn while claiming safety it does not provide.
58
+
59
+ ## What to Tell Your User
60
+
61
+ Nothing changes in how the agent behaves — this is a source-text and build-time fix with
62
+ no route, setting, or visible surface.
63
+
64
+ What it does change is trustworthiness of process: for months, searching the codebase for
65
+ a given pattern could quietly skip thirty files and report a clean result, and for eleven
66
+ of those files a code review showed "binary file changed" instead of the actual lines. Any
67
+ past conclusion of the form "we don't do X anywhere" may have been drawn from an
68
+ instrument that wasn't looking. Those files are readable again, and a check now fails the
69
+ build if it recurs.
70
+
71
+ ## Summary of New Capabilities
72
+
73
+ - Thirty text files that were invisible to `grep` — twenty-two of them live source,
74
+ including several safety-critical authority modules — are searchable again.
75
+ - Eleven files that rendered as binary diffs in code review now show reviewable lines.
76
+ - A build-time ratchet refuses any text file containing a raw NUL byte, naming the file
77
+ and the consequence rather than emitting a bare assertion failure.
78
+ - The ratchet proves its own detector on every run, so a check that has gone dead is
79
+ distinguishable from one that has nothing to report.
80
+
81
+ ## Evidence
82
+
83
+ **Reproduction (before):** `grep -c export src/monitoring/blockerSettleAuthority.ts`
84
+ printed nothing — no count, no error, exit status indistinguishable from a clean miss.
85
+ `file` reported the same TypeScript source as `data`, not text. Three one-line fixtures
86
+ isolate the cause: a clean file and a file containing ESC + BEL both return 1 match; a
87
+ file differing only by one NUL byte returns nothing at all.
88
+
89
+ **Observed after,** same scenarios against the fixed tree:
90
+
91
+ | scenario | before | after |
92
+ |---|---|---|
93
+ | `grep -c export` on `blockerSettleAuthority.ts` | *(silent)* | `1` |
94
+ | `grep -c export` on `StandardsEnforcementAuditor.ts` | *(silent)* | `13` |
95
+ | `grep -c export` on `FeatureMetricsLedger.ts` | *(silent)* | `20` |
96
+ | `file` on those sources | `data` | `ASCII text` |
97
+ | `git diff` on the 11 early-NUL files | `Bin NNNN -> NNNN bytes` | reviewable line diff |
98
+ | runtime value of the separator | U+0000 | U+0000 (unchanged) |
99
+ | digests derived from the separator | — | byte-identical, verified |
100
+ | ratchet against a reintroduced NUL | *(no check existed)* | fails, names the file |
101
+
102
+ **Tests:** `tests/unit/no-raw-nul-bytes-in-source.test.ts` (4 new). One asserts the
103
+ detector flags a raw-byte file and clears an escaped one — a dead-check guard, since a
104
+ lint that has never objected is output-identical to a lint that cannot object. One asserts
105
+ the escape decodes to exactly the raw byte, so the behaviour-preserving claim is checked
106
+ rather than stated. One scans the tree. One asserts the scan examined more than 500 files,
107
+ so a scan that walked the wrong roots cannot pass forever by finding nothing.
108
+
109
+ Verified by reintroducing a raw NUL into `FeatureMetricsLedger.ts`: the lint exits 1,
110
+ names the file, and states the consequence. `npx tsc --noEmit` clean; the full unit suite
111
+ runs 39,046 tests with no failure attributable to this change.
112
+
113
+ **Provenance note.** This was found only because a *different* investigation produced a
114
+ suspiciously empty search result that was about to be written up as "no such mechanism
115
+ exists in the codebase." That search had skipped twenty-two files. The finding was
116
+ manufactured by the defect, and has been retracted and superseded rather than deleted.
@@ -100,8 +100,23 @@ explicit in code: absence of a verdict is reported as absence, never as either o
100
100
  between them causes no change in expiry behaviour — checked before editing.
101
101
  - **PromiseBeacon / overdue surfacing** operate on open commitments; `pending` keeps them
102
102
  visible, which is the stated intent of the precedent ("should NAG, not vanish").
103
- - No persistence change, no migration, no config key. Existing rows re-evaluate to
104
- `pending` on the next sweep without a data rewrite.
103
+ - No persistence change, no migration, no config key.
104
+ - ~~Existing rows re-evaluate to `pending` on the next sweep without a data rewrite.~~
105
+ **CORRECTED 2026-07-26 — this was FALSE and is struck rather than deleted.** The
106
+ behavioural branch returns BEFORE the `switch` in `verifyOne`, so `mutateSync` never runs
107
+ and a pre-existing row is never touched. **The change is FORWARD-ONLY.**
108
+ Verified on the live store after deploy: 98 behavioural rows still read **74 `verified` /
109
+ 24 `violated`**, unchanged. What the change DID do, measured over two reads 75s apart:
110
+ tick accumulation stopped dead (one row frozen at 163,135 where it had been climbing every
111
+ minute). What it did NOT do: clear the 98 stale verdicts — so the false comfort this change
112
+ set out to remove is still displayed for every row that predates it, and `getHealth`, which
113
+ now counts actual `verified` rows, will report those 74 stale stamps as verified.
114
+ Remedy is an open operator decision, not a reflex: either a migration resetting behavioural
115
+ rows to `pending` (a data rewrite this very section claimed was unnecessary) or a read
116
+ surface that refuses a verdict for a never-verifiable row whatever is stored.
117
+ **Found by reading the live surface for the exact case the change fixed** — no test caught
118
+ it, and the suite was green. An artifact that over-claims is the same defect class as the
119
+ bug it documents, which is why the wrong sentence stays visible above.
105
120
 
106
121
  ## 6. External surfaces
107
122
 
@@ -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.