instar 1.3.767 → 1.3.768

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.
@@ -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.