instar 1.3.767 → 1.3.769
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.map +1 -1
- package/dist/core/PostUpdateMigrator.js +17 -2
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/scaffold/templates.js +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +19 -4
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lint-machine-local-justification.js +273 -0
- package/scripts/lint-self-heal-fields.js +259 -0
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +1 -1
- package/upgrades/1.3.768.md +38 -0
- package/upgrades/1.3.769.md +62 -0
- package/upgrades/eli16/act1174-lint-floors.eli16.md +75 -0
- package/upgrades/side-effects/act1174-lint-floors.md +124 -0
- package/upgrades/side-effects/outbound-advisory-offswitch-config-shape.eli16.md +41 -0
- package/upgrades/side-effects/outbound-advisory-offswitch-config-shape.md +117 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ELI16 — Two deterministic lint floors for Standards A and B
|
|
2
|
+
|
|
3
|
+
## The one-sentence version
|
|
4
|
+
|
|
5
|
+
Two tiny no-AI checkers now read a design spec's plain text and flag when it forgets
|
|
6
|
+
the "which machine does this live on?" tag (Standard A) or writes a broken
|
|
7
|
+
"try to fix it yourself before bugging the operator" plan (Standard B) — cheap
|
|
8
|
+
signals that back up the smart reviewer instead of replacing it.
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
Instar's constitution has two rules that used to live only in prose and reviewer
|
|
13
|
+
habit:
|
|
14
|
+
|
|
15
|
+
- **Standard A — "always multi-machine":** every piece of state a feature adds should
|
|
16
|
+
be SHARED across all the agent's machines by default. Keeping it on one machine is
|
|
17
|
+
only allowed for a short list of concrete reasons (a login/key that physically sits
|
|
18
|
+
on one disk, a piece of hardware, or something the operator explicitly signed off on).
|
|
19
|
+
This rule was ratified *because* a spec once wrongly kept its memory on one machine
|
|
20
|
+
and it survived SEVEN review rounds — only the operator caught it.
|
|
21
|
+
- **Standard B — "self-heal before notify":** a watchdog should try a bounded, logged
|
|
22
|
+
self-repair FIRST and only ping the operator when that repair genuinely runs out of
|
|
23
|
+
road. A repair plan that does nothing, or that can retry forever, is exactly the trap
|
|
24
|
+
the rule forbids.
|
|
25
|
+
|
|
26
|
+
The smart `/spec-converge` reviewer already reads for these. But "a reviewer will
|
|
27
|
+
notice" is willpower, and Instar's root rule is **structure beats willpower**. So each
|
|
28
|
+
rule gets a cheap deterministic floor that runs first and never needs to remember.
|
|
29
|
+
|
|
30
|
+
## What actually shipped
|
|
31
|
+
|
|
32
|
+
Two small parser scripts (no AI, just text rules) plus their tests:
|
|
33
|
+
|
|
34
|
+
- **`scripts/lint-machine-local-justification.js` (Standard A).** It looks in a spec's
|
|
35
|
+
`## Multi-machine posture` section. If the spec says a surface is "machine-local" but
|
|
36
|
+
carries no `machine-local-justification: <reason>` tag, that's a finding. It also
|
|
37
|
+
checks the OTHER direction: a tag with a made-up reason (not one of the three allowed),
|
|
38
|
+
or an "operator ratified it" claim that cites no checkable proof (a commit SHA, a URL,
|
|
39
|
+
or a registry key), is also a finding.
|
|
40
|
+
- **`scripts/lint-self-heal-fields.js` (Standard B).** When a spec declares a watchdog's
|
|
41
|
+
self-heal, it must list all its brakes (max attempts, time limit, backoff, dedupe key,
|
|
42
|
+
a breaker, a notification deadline with real units like "300s", an audit location, and
|
|
43
|
+
the concrete repair actions), plus the severity class. An empty repair-actions list
|
|
44
|
+
(the fake heal that does nothing) or a deadline written as a bare "300" with no units
|
|
45
|
+
is a finding.
|
|
46
|
+
|
|
47
|
+
Both are registered in `docs/STANDARDS-REGISTRY.md` so Instar's own conformance auditor
|
|
48
|
+
now grades Standards A and B as enforced by a real deterministic `lint` on disk, not just
|
|
49
|
+
"documented" (the auditor confirmed: 0 → 2 lint-enforced standards).
|
|
50
|
+
|
|
51
|
+
## The important nuance: report-first
|
|
52
|
+
|
|
53
|
+
These lints ship in **report mode** — they PRINT their findings but exit 0 (they do not
|
|
54
|
+
block anything yet). A `--strict` flag makes them exit non-zero; that is the FAIL
|
|
55
|
+
capability the tests exercise and the hook a future graduation can flip on. This is
|
|
56
|
+
deliberate: the marker convention is brand new, existing specs predate it, and the spec's
|
|
57
|
+
own honesty clause says the deterministic floor's grade is "inert until the registry
|
|
58
|
+
ship." Shipping a brand-new blocking gate over the whole spec corpus would be a wall of
|
|
59
|
+
false failures. Report-first is the graduated-rollout path Instar always takes with a new
|
|
60
|
+
enforcement.
|
|
61
|
+
|
|
62
|
+
## Signal vs. authority
|
|
63
|
+
|
|
64
|
+
Neither lint decides the hard question. Standard A's lint cannot tell whether a
|
|
65
|
+
justification is TRUE; Standard B's lint cannot tell whether a repair is genuinely
|
|
66
|
+
SUBSTANTIVE or a severity label is HONEST. Those calls stay with the smart `/spec-converge`
|
|
67
|
+
reviewer. The lint is the cheap body; the reviewer is the mind. Together they enforce the
|
|
68
|
+
rule; neither alone does.
|
|
69
|
+
|
|
70
|
+
## What did NOT change
|
|
71
|
+
|
|
72
|
+
No constitutional text was written or re-ratified — Standards A and B were already
|
|
73
|
+
ratified by the operator on 2026-07-03. This change is purely the enforcement machinery
|
|
74
|
+
(two lints + tests + the registry rows that register them as guards). It touches no
|
|
75
|
+
runtime server code and changes nothing for a user.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Side-Effects Review — ACT-1174: two deterministic lint floors (Standards A + B)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `act1174-lint-floors`
|
|
4
|
+
**Date:** `2026-07-04`
|
|
5
|
+
**Author:** `echo (build hand)`
|
|
6
|
+
**Second-pass reviewer:** `not required (Tier 1)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Ships the two DEFERRED deterministic lint floors named in
|
|
11
|
+
`docs/specs/three-standards-enforcement.md` (§178-202 for A, §256-289/§343-361 for B) —
|
|
12
|
+
the "registry ship" the spec hard-sequences against each standard's registered guard.
|
|
13
|
+
Two no-LLM parser scripts + their unit tests + fixtures, plus the STANDARDS-REGISTRY
|
|
14
|
+
guard-registration rows that let the conformance auditor grade Standards A and B as
|
|
15
|
+
enforced by a deterministic `lint`.
|
|
16
|
+
|
|
17
|
+
Files touched:
|
|
18
|
+
- `scripts/lint-machine-local-justification.js` — Standard A marker floor (no-LLM).
|
|
19
|
+
- `scripts/lint-self-heal-fields.js` — Standard B self-heal field-schema floor (no-LLM).
|
|
20
|
+
- `tests/unit/lint-machine-local-justification.test.ts`, `tests/unit/lint-self-heal-fields.test.ts` — self-tests.
|
|
21
|
+
- `tests/fixtures/spec-lint/*.md` — positive / negative / bidirectional / out-of-scope fixtures.
|
|
22
|
+
- `docs/STANDARDS-REGISTRY.md` — the two `**Applied through.**` rows now cite the lints (enforcement registration, NOT new ratification prose).
|
|
23
|
+
- `upgrades/eli16/act1174-lint-floors.eli16.md`, `upgrades/side-effects/act1174-lint-floors.md`, `upgrades/next/act1174-lint-floors.md` — gate artifacts.
|
|
24
|
+
|
|
25
|
+
No constitutional text is minted or re-ratified (the standard TEXTS were ratified by the operator 2026-07-03). No runtime `src/` code changes.
|
|
26
|
+
|
|
27
|
+
## Decision-point inventory
|
|
28
|
+
|
|
29
|
+
- `Standards Enforcement-Coverage auditor classification (StandardsEnforcementAuditor)` — pass-through — the audit reads the registry and now classifies A + B as `lint` because the two rows cite `scripts/lint-*.js` (verified: baseline lint 0 → 2, A/B out of the gaps list, dangling still 0).
|
|
30
|
+
- `/spec-converge integration reviewer` — pass-through — the semantic authority is unchanged; these lints are the deterministic SIGNAL beneath it.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. Over-block
|
|
35
|
+
|
|
36
|
+
The lints DO have a block/allow surface, but ONLY under `--strict`. In report mode (the shipped default) they never block. Under `--strict` the plausible over-block is: a spec that legitimately mentions "machine-local" inside its `## Multi-machine posture` section as prose discussion (not a real surface) would trip A1. This is mitigated by scoping A1's trigger to the posture section AND skipping `<placeholder>` marker values, and by the report-first default — a false positive is a printed line, never a blocked commit. `--strict` is opt-in (tests + a future graduation), so no live over-block exists today.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2. Under-block
|
|
41
|
+
|
|
42
|
+
The deterministic floor is PRESENCE + well-formedness only, by design (Signal vs. Authority). It misses: a marker whose key is valid but substantively WRONG (A), and a self-heal whose fields are all present but whose remediation is a plausible-but-ineffective no-op or whose severity class is dishonestly `recoverable` (B). Those are exactly the semantic calls left to the `/spec-converge` reviewer — the lint is not meant to catch them. A spec that omits a posture section entirely is also not flagged (that is the reviewer's §168 call).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 3. Level-of-abstraction fit
|
|
47
|
+
|
|
48
|
+
Correct layer: a cheap, brittle DETECTOR that produces a signal, explicitly paired with the existing high-context `/spec-converge` reviewer that owns the semantic authority. This is the constitutional Signal-vs-Authority / Body-and-Mind split the spec mandates (§87-95). The lint does NOT hold blocking authority over a merge in its shipped (report) mode.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 4. Signal vs authority compliance
|
|
53
|
+
|
|
54
|
+
**Required reference:** docs/signal-vs-authority.md
|
|
55
|
+
|
|
56
|
+
- [x] No — this change produces a signal consumed by an existing smart gate (the `/spec-converge` integration reviewer), and in its shipped mode it does not block at all.
|
|
57
|
+
|
|
58
|
+
The lints are deterministic signals. Their `--strict` FAIL capability exists for tests and a future, separately-decided CI graduation; it is not wired into the blocking `npm run lint` chain. The reviewer holds semantic authority.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 5. Interactions
|
|
63
|
+
|
|
64
|
+
- **Shadowing:** none. The lints are standalone scripts, not wired into the blocking lint `&&`-chain, so they cannot shadow an existing lint or be shadowed.
|
|
65
|
+
- **Double-fire:** none. No runtime path invokes them; they are dev/CI tooling run explicitly or by their tests.
|
|
66
|
+
- **Races:** none. Pure fs-read parsers, no shared state.
|
|
67
|
+
- **Feedback loops:** none.
|
|
68
|
+
|
|
69
|
+
The one real interaction is the Standards Enforcement-Coverage auditor: the registry rows now resolve to on-disk `lint` guards, which RAISES the enforced ratio (0.4429 → 0.4714). That is the intended effect, not a regression; the coverage ratchet (`standards-coverage.mjs --check`) passes (floors: ratio ≥ floor, dangling ≤ 0).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 6. External surfaces
|
|
74
|
+
|
|
75
|
+
- Other agents on the same machine? No.
|
|
76
|
+
- Install base? These are instar-repo dev/CI tooling; they do not ship into agent homes as runtime behavior.
|
|
77
|
+
- External systems? No.
|
|
78
|
+
- Persistent state? No (the coverage script's `.instar/standards-coverage.json` output is untracked runtime state, never a committed baseline).
|
|
79
|
+
- Operator surface (Mobile-Complete): No operator-facing actions — dev/CI tooling only.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 6b. Operator-surface quality
|
|
84
|
+
|
|
85
|
+
No operator surface — not applicable.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
90
|
+
|
|
91
|
+
**machine-local BY DESIGN — pure per-machine dev/CI tooling with no durable state.** These lint scripts + tests + fixtures are repo source, replicated to every machine via git like all source (so there is no per-machine divergence to reconcile). They emit no user-facing notices (no one-voice gating needed), hold no durable state (nothing to strand on a topic transfer), and generate no URLs. The registry rows they add are likewise git-replicated source. There is no runtime state surface introduced by this change, so the multi-machine posture question is satisfied structurally: source is uniform across machines by construction.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 8. Rollback cost
|
|
96
|
+
|
|
97
|
+
Pure additive dev/CI change — revert the commit and ship as the next patch. No persistent state, no data migration, no agent-state repair, no user-visible regression. The registry rows revert cleanly (the conformance auditor simply re-grades A/B back to their prior kind). Because the lints ship report-first and are not in the blocking chain, a rollback cannot un-block anything that was blocking.
|
|
98
|
+
|
|
99
|
+
## Conclusion
|
|
100
|
+
|
|
101
|
+
The review produced no design changes. The two lints are correctly scoped as deterministic signals beneath the existing reviewer authority, ship report-first per the spec's honesty / hard-sequencing clause, and are registered so the conformance auditor grades Standards A and B as `lint`-enforced. Clear to ship as a Tier-1 change.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Second-pass review (if required)
|
|
106
|
+
|
|
107
|
+
**Reviewer:** not required (Tier 1)
|
|
108
|
+
**Independent read of the artifact: [concur]**
|
|
109
|
+
|
|
110
|
+
Tier-1 low-risk additive tooling; no independent reviewer required per the tier policy.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Evidence pointers
|
|
115
|
+
|
|
116
|
+
- `npx vitest run tests/unit/lint-machine-local-justification.test.ts tests/unit/lint-self-heal-fields.test.ts` → 13 passed (7 A + 6 B).
|
|
117
|
+
- `node scripts/standards-coverage.mjs` → `lint 2` (baseline 0), A/B absent from the gaps list, dangling 0, enforced-ratio 0.4429 → 0.4714.
|
|
118
|
+
- Manual fixture runs: A good-defended/good-ratified pass `--strict`; A-bad-undefended (A1), A-bad-spurious-key (A2), A-bad-ratified-noref (A2) fail `--strict`; B-good-complete + B-out-of-scope pass; B-bad-missing-fields (B1), B-bad-noop-and-unitless (B2/B3/B4) fail `--strict`; report mode exits 0 on all bad fixtures.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Class-Closure Declaration (display-only mirror)
|
|
123
|
+
|
|
124
|
+
No agent-authored-artifact defect and no self-triggered controller — not applicable. This change adds enforcement lints; it does not fix a defect in an LLM prompt/hook/config/skill/standards-text, and it introduces no loop/monitor/sentinel/reaper/scheduler/recovery path.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ELI16 — the "off" button for the auto-message advisory didn't do anything
|
|
2
|
+
|
|
3
|
+
## The story
|
|
4
|
+
|
|
5
|
+
Instar has an "outbound advisory" — when a background job tries to send you a message, it first checks
|
|
6
|
+
the text for raw file paths, dev jargon, or localhost links and, if it finds any, holds the message so
|
|
7
|
+
the agent can fix it. It's on by default, and the docs told operators they could turn it off by
|
|
8
|
+
setting a switch: `messaging.outboundAdvisory.enabled: false`.
|
|
9
|
+
|
|
10
|
+
The problem: that switch never worked on a real setup. The config's "messaging" section is a **list**
|
|
11
|
+
of chat platforms, not a labeled box — so a switch addressed as if it were a box *inside* that list
|
|
12
|
+
can't be read. The program looked, found nothing, and used the default ("on"). So an operator who
|
|
13
|
+
tried to turn the advisory off would find it stubbornly still on, with no error explaining why.
|
|
14
|
+
|
|
15
|
+
This is the same shape of bug we fixed earlier for the promise follow-through feature (PR #1379). That
|
|
16
|
+
one was worse — a feature you could never turn *on*. This one is milder — a feature you can't turn
|
|
17
|
+
*off* — because it defaults to on, so it still works; you just can't disable it.
|
|
18
|
+
|
|
19
|
+
## What this change does
|
|
20
|
+
|
|
21
|
+
It moves the switch to a spot the program can actually read: a **top-level `outboundAdvisory`**
|
|
22
|
+
setting, right next to the other real settings instead of buried in the platform list. Setting
|
|
23
|
+
`outboundAdvisory.enabled: false` there now genuinely turns the advisory off. The old (broken) nested
|
|
24
|
+
key is still honored where it *can* be read (for setups that use a box-shaped config), so nothing that
|
|
25
|
+
worked before breaks. The docs are corrected to point at the new switch — both for new agents and, via
|
|
26
|
+
a small automatic update, for agents that already have the old wording in their notes.
|
|
27
|
+
|
|
28
|
+
## Why it's safe
|
|
29
|
+
|
|
30
|
+
- The advisory is inform-only — it never blocks your messages — so there's no risk of it wrongly
|
|
31
|
+
rejecting anything. The only change is that its off-button now works.
|
|
32
|
+
- Old configs keep working (the old key is a fallback), so it's backwards compatible.
|
|
33
|
+
- It's a plain code + docs change; if it were ever wrong, the fix is a simple revert with no leftover
|
|
34
|
+
data to clean up.
|
|
35
|
+
|
|
36
|
+
## How we know it works
|
|
37
|
+
|
|
38
|
+
A new test sets up the config the **real** way — "messaging" as a list — flips the new top-level
|
|
39
|
+
off-switch, and confirms the advisory reports itself disabled (which was impossible before the fix). It
|
|
40
|
+
also checks the default-on behavior when the switch isn't set, and that the old box-shaped config still
|
|
41
|
+
works. The 58 existing advisory tests all still pass.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Side-Effects Review — outbound-advisory off-switch reachable on array-shaped messaging
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `outbound-advisory-offswitch-config-shape`
|
|
4
|
+
**Date:** `2026-07-04`
|
|
5
|
+
**Author:** `Echo`
|
|
6
|
+
**Second-pass reviewer:** `not required (Tier-1)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
The outbound-advisory preflight route read its off-switch (and tuning knobs) at
|
|
11
|
+
`messaging.outboundAdvisory.*`. On a real install `messaging` is a JSON **array** of adapter configs,
|
|
12
|
+
so that dot-path resolves `undefined` → the read's default. For the `enabled` read (default `true`)
|
|
13
|
+
that meant the **documented off-switch (`messaging.outboundAdvisory.enabled: false`) had no effect —
|
|
14
|
+
an operator could not disable the advisory** (the un-DISABLABLE sub-class of the PR #1379 bug). Fix:
|
|
15
|
+
read from the reachable **top-level `outboundAdvisory`** block (canonical), honoring the legacy
|
|
16
|
+
`messaging.outboundAdvisory` as a back-compat fallback. Applied to all four reads
|
|
17
|
+
(`enabled`, `ignoreEscalationThreshold`, `ignoreEscalationSlugThreshold`, `timeClaim.enabled` dev-gate),
|
|
18
|
+
the documented off-switch key in both templates, and an existing-agent CLAUDE.md doc migration.
|
|
19
|
+
Files: `src/server/routes.ts`, `src/core/PostUpdateMigrator.ts`, `src/scaffold/templates.ts`, + test.
|
|
20
|
+
|
|
21
|
+
## Decision-point inventory
|
|
22
|
+
|
|
23
|
+
- `POST /messaging/preflight` `enabled` gate (`routes.ts`) — **modify** — top-level-first resolution.
|
|
24
|
+
- `OutboundAdvisoryAudit` threshold reads (`routes.ts`) — **modify** — top-level-first.
|
|
25
|
+
- `timeClaim.enabled` dev-gate value (`routes.ts`) — **pass-through** — sourced top-level-first;
|
|
26
|
+
`undefined → live-on-dev` semantics unchanged.
|
|
27
|
+
- CLAUDE template off-switch text (both `PostUpdateMigrator` content + `scaffold/templates`) —
|
|
28
|
+
**modify** — documents the reachable top-level key.
|
|
29
|
+
- `PostUpdateMigrator` TIME_CLAIM anchor marker — **modify** — matched on the stable `- Off-switch: \``
|
|
30
|
+
prefix so it still anchors regardless of which key the line carries.
|
|
31
|
+
- New `migrateClaudeMd` swap — **add** — updates an existing agent's stale nested-key off-switch line.
|
|
32
|
+
|
|
33
|
+
## 1. Over-block / 2. Under-block
|
|
34
|
+
|
|
35
|
+
No block/allow surface change of consequence: the advisory is inform-only and never blocks a message.
|
|
36
|
+
The only behavior change is that the **off-switch now actually works** — an operator who sets
|
|
37
|
+
`outboundAdvisory.enabled: false` disables the advisory (previously impossible). Over-block N/A;
|
|
38
|
+
under-block N/A.
|
|
39
|
+
|
|
40
|
+
## 3. Level-of-abstraction fit
|
|
41
|
+
|
|
42
|
+
Right layer — same top-level-first config-read pattern as PR #1379, applied at the read sites. No new
|
|
43
|
+
authority, no new machinery.
|
|
44
|
+
|
|
45
|
+
## 4. Signal vs authority compliance
|
|
46
|
+
|
|
47
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
48
|
+
|
|
49
|
+
- [x] No — no runtime block/allow surface changes; the advisory remains inform-only. This restores a
|
|
50
|
+
broken operator control (an off-switch), it does not add gating authority.
|
|
51
|
+
|
|
52
|
+
## 5. Interactions
|
|
53
|
+
|
|
54
|
+
- **Shadowing:** none — top-level-first read is a superset of prior behavior; on object-messaging the
|
|
55
|
+
legacy fallback resolves identically.
|
|
56
|
+
- **Double-fire / races / feedback loops:** none.
|
|
57
|
+
- **Migration marker:** the TIME_CLAIM anchor now matches the stable `- Off-switch: \`` prefix, so it
|
|
58
|
+
still finds the off-switch line whether CLAUDE.md carries the old nested or the new top-level key —
|
|
59
|
+
verified the substring is present in both.
|
|
60
|
+
|
|
61
|
+
## 6. External surfaces
|
|
62
|
+
|
|
63
|
+
- **Install base / agents:** existing agents get the corrected off-switch DOC via the new
|
|
64
|
+
`migrateClaudeMd` swap (content-sniff, idempotent) on their next update; new agents via the
|
|
65
|
+
templates. The CODE fix ships with the server. No config migration needed to keep working (top-level
|
|
66
|
+
is additive; legacy still honored).
|
|
67
|
+
- **Operator surface:** restores a documented operator control (the off-switch). No new UI/route.
|
|
68
|
+
- **Persistent state / external systems:** none.
|
|
69
|
+
|
|
70
|
+
## 6b. Operator-surface quality
|
|
71
|
+
|
|
72
|
+
No dashboard/approval/form surface — not applicable (the off-switch is a config edit, now at a
|
|
73
|
+
reachable key, documented for the operator).
|
|
74
|
+
|
|
75
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
76
|
+
|
|
77
|
+
**machine-local BY DESIGN** — config (`.instar/config.json`) is per-machine; the advisory runs
|
|
78
|
+
per-machine on the sending machine. This change only alters where in the local config the flag is
|
|
79
|
+
read. No cross-machine state, notice, durable state, or URL.
|
|
80
|
+
|
|
81
|
+
## 8. Rollback cost
|
|
82
|
+
|
|
83
|
+
Pure code + doc change — revert the reads + template text + the one migration block. No persistent
|
|
84
|
+
state; the top-level key is additive and the legacy fallback preserves prior behavior. Zero-cost
|
|
85
|
+
back-out.
|
|
86
|
+
|
|
87
|
+
## Conclusion
|
|
88
|
+
|
|
89
|
+
Restores the outbound-advisory off-switch that was silently broken on every real (array-`messaging`)
|
|
90
|
+
install — the un-DISABLABLE sibling of the PR #1379 un-ENABLABLE bug, found by the sibling audit
|
|
91
|
+
(`docs/investigations/messaging-config-unreachable-audit-2026-07-04.md`). Code + docs + existing-agent
|
|
92
|
+
migration + a real-LiveConfig array-shape test. Clear to ship.
|
|
93
|
+
|
|
94
|
+
## Second-pass review (if required)
|
|
95
|
+
|
|
96
|
+
**Reviewer:** not required (Tier-1)
|
|
97
|
+
|
|
98
|
+
## Evidence pointers
|
|
99
|
+
|
|
100
|
+
- `tests/unit/outbound-advisory-config-shape.test.ts` — real LiveConfig + real array-shaped config:
|
|
101
|
+
top-level off-switch disables (was impossible before the fix), default-on when unset, object-shape
|
|
102
|
+
back-compat.
|
|
103
|
+
- Existing `outbound-advisory-routes` / `outbound-advisory` / `telegram-reply-advisory-script` tests
|
|
104
|
+
(58) stay green; `tsc` clean; `lint-no-unreachable-messaging-gate` clean.
|
|
105
|
+
|
|
106
|
+
## Class-Closure Declaration (display-only mirror)
|
|
107
|
+
|
|
108
|
+
- **`defectClass`** — `config-unreachable-on-shape` (the PR #1379 class; this is its un-DISABLABLE
|
|
109
|
+
default-ON sub-class).
|
|
110
|
+
- **`closure`** — `gap` — the #1381 lint catches the default-OFF (un-enablable) sub-class; the
|
|
111
|
+
default-ON un-disablable sub-class (`.get('messaging.*.enabled', true)` with a documented off-switch)
|
|
112
|
+
is not reliably lint-detectable without false positives (documented in the #1381 side-effects
|
|
113
|
+
follow-ups). This fix ships the direct regression test + moves the config to the reachable
|
|
114
|
+
convention; the class-level guard for the un-disablable sub-class remains a tracked gap.
|
|
115
|
+
- **`guardEvidence`** — n/a for `closure: gap`.
|
|
116
|
+
- **`gap`** — tracked in `docs/investigations/messaging-config-unreachable-audit-2026-07-04.md`
|
|
117
|
+
(follow-up 2) + the #1381 side-effects artifact.
|