instar 1.3.653 → 1.3.655
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +124 -4
- package/dist/commands/server.js.map +1 -1
- package/dist/core/configCoherence.d.ts +46 -5
- package/dist/core/configCoherence.d.ts.map +1 -1
- package/dist/core/configCoherence.js +101 -12
- package/dist/core/configCoherence.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +12 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/types.d.ts +24 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/PresenceProxy.d.ts +14 -0
- package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
- package/dist/monitoring/PresenceProxy.js +103 -11
- package/dist/monitoring/PresenceProxy.js.map +1 -1
- package/dist/monitoring/ResumeQueue.d.ts +17 -2
- package/dist/monitoring/ResumeQueue.d.ts.map +1 -1
- package/dist/monitoring/ResumeQueue.js +19 -2
- package/dist/monitoring/ResumeQueue.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.654.md +26 -0
- package/upgrades/1.3.655.md +29 -0
- package/upgrades/side-effects/honest-session-state-surfaces.md +185 -0
- package/upgrades/side-effects/mesh-coherence-live-state-honesty.md +53 -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-06-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-24T16:39:12.372Z",
|
|
5
|
+
"instarVersion": "1.3.655",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The multi-machine config-coherence checker (`src/core/configCoherence.ts`) is a signal-only startup advisory — it logs WARNINGS about incoherent mesh config, never rejecting boot. Two honesty gaps are fixed. **Fix (c) (ships unflagged):** the mesh-rope priority check validated a `.priorities` dict that NO type or default ever populates — dead code that always skipped, so a real operator mistake (a `priorityLan` colliding with `priorityTailscale`, or a negative value) sailed through unwarned. It now validates the REAL flat keys (`priorityTailscale`/`priorityLan`/`priorityCloudflare`) for distinct-positive-integers, reusing the existing warning codes, and the local interface is retyped against the canonical `MeshTransportConfig` so the phantom dict can't be reintroduced. **Fix (b) (dev-gated dark behind `monitoring.meshCoherenceLiveCheck`, LIVE on a development agent):** the existing verdict read CONFIG ONLY, so it would report "all clear" even when the LIVE mesh state contradicts the config. A new periodic, signal-only recheck (wired onto the existing 30s peer-presence timer — no new timer) now compares config intent against live state and surfaces two real divergences: a runtime `meshTransport.enabled` flip WITHOUT a restart (config says off, the live server is still bound non-loopback → `mesh-config-off-but-live-on`), and a half-activated boot (config says on, but this machine advertises zero mesh endpoints past a warmup grace → `mesh-config-on-but-live-inert`). It emits transition-only (one line per divergence episode, not one-per-tick), carries a per-feature metric (`mesh-coherence-live`) for the dev soak, never throws into the tick (a corrupt registry read fails toward silence with a capped backoff), and never leaks a peer-writable endpoint value into a warning string. Single-machine agents are a strict no-op.
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
If you run me across more than one machine, my "is the mesh config coherent?" check no longer lies to you. Before, if you flipped the mesh switch off in config without restarting me, the check would cheerfully say "all clear" while the mesh was still demonstrably up — an advisory that lies is worse than silence. Now it reads my actual running state and tells you honestly when the config and the live mesh disagree (the disable hasn't taken effect yet — restart to apply; or the mesh is on but this machine isn't advertising any routes). It also now catches a real config mistake — duplicate or non-positive mesh rope priorities — that previously slipped through unwarned. It's still purely advisory (it never blocks my startup), and the live-state half is on only on this development agent while it soaks.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- The mesh rope-priority coherence check validates the REAL flat config keys (`priorityTailscale`/`priorityLan`/`priorityCloudflare`) for distinct-positive-integers, replacing a dead check that validated a never-populated dict. Ships unflagged (a pure improvement to an existing boot-time warning).
|
|
17
|
+
- A new periodic, signal-only `checkMeshLiveStateCoherence` surfaces config-vs-live mesh divergences — a runtime disable that hasn't taken effect (`mesh-config-off-but-live-on`) and a config-on machine advertising no routes (`mesh-config-on-but-live-inert`) — with transition-only logging + a `mesh-coherence-live` per-feature metric. Dev-gated dark behind `monitoring.meshCoherenceLiveCheck` (live-on-dev / dark-on-fleet); flag off is byte-identical to today.
|
|
18
|
+
- The live check never blocks boot, never throws into the periodic tick (capped half-open-breaker backoff on a corrupt registry read), and never interpolates a peer-writable endpoint value into a warning string (the no-leak invariant). Single-machine agents are a strict no-op.
|
|
19
|
+
|
|
20
|
+
## Evidence
|
|
21
|
+
|
|
22
|
+
The two divergences are dead-state bugs in the EXISTING checker, demonstrated by the new tests rather than reproduced as a live outage (signal-only, dev-gated dark — no production incident to repro):
|
|
23
|
+
|
|
24
|
+
- **Fix (c) — the dead priority check.** Before: a config with `priorityTailscale: 10, priorityLan: 10` produced ZERO warnings, because the checker validated `mm.meshTransport.priorities` — a dict key that no shipped type or `ConfigDefaults` ever populates (always `undefined`, so the guard `pri && typeof pri === 'object'` always skipped). After: the same collision raises `mesh-priority-collision`, and a `priorityTailscale: 0` raises `mesh-priority-nonpositive`. Proven by `tests/unit/configCoherence.test.ts` (collision / zero / negative / float / Infinity all fire on the FLAT keys; the dead-dict regression case asserts an old-style `priorities` dict now produces no warning AND is rejected by the retyped interface at compile time).
|
|
25
|
+
- **Fix (b) — the config-only verdict.** Before: with `meshTransport.enabled:false` in config but the live server still bound `0.0.0.0` (the flip-without-restart case), a fresh coherence read reported "all clear." After: `checkMeshLiveStateCoherence` raises `mesh-config-off-but-live-on` for a non-loopback live bind (incl. a specific `bindHost` override like `192.168.1.50`, the R2-M10 regression), and `mesh-config-on-but-live-inert` for a config-on machine advertising zero endpoints past the warmup grace. Proven both directions in `tests/unit/configCoherence.test.ts`; the transition-only emit, emitCap ceiling, dev-gate no-op, and corrupt-registry backoff/auto-recover are proven in `tests/integration/mesh-coherence-live-check.test.ts`; the inline boot wiring is pinned in `tests/unit/mesh-coherence-wiring.test.ts`.
|
|
26
|
+
- **Pre-existing failure fixed (Zero-Failure Standard):** `tests/unit/notification-spam-prevention.test.ts > sends ONE mismatch notification` was failing on the base branch (`expected +0 to be 1`) because the AutoUpdater mock lacked `getShadowInstalledVersion`, which the loop-breaker's strand detection now calls every tick — the mock threw before reaching the notify path. Before: 1 failed / 14 passed. After: 15 passed. Full unit suite green (31523 passing).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Two signal-only honesty fixes to the standby/reap user-facing surfaces (spec: `docs/specs/honest-session-state-surfaces.md`).
|
|
9
|
+
|
|
10
|
+
**Fix (b) — Tier1/Tier2 standby honesty (dev-gated dark behind `monitoring.standbyHonestyTiers.enabled`, LIVE on a development agent, dark on the fleet).** `PresenceProxy` already had an honest stuck-state classifier (`classifyStuckSignature` — rate-limited / policy-wedge / context-wedge / context-too-long) but invoked it ONLY at Tier 3 (the 5-minute check); at Tier 1 (~20s) and Tier 2 (~2min) the fallback paths hardcoded "is actively working" / "is still working". So for the first several minutes a genuinely rate-limited or permanently-wedged live session was reported to the user as "actively working" — the exact lie the honest-turn-receipts work set out to kill, fixed at Tier 3 but not the earlier, more-frequently-seen tiers. A new private `PresenceProxy.maybeStuckMessage(snapshot, sessionName, tierLabel)` helper reuses the SAME tail-gated classifier on the SAME sanitized snapshot and returns one of three outcomes: a string (stuck, no recovery owner → send the honest line), a `SUPPRESS` sentinel (stuck, but a recovery sentinel owns the voice → send nothing this fire, mirroring Tier 3's silent return — NOT a fall-through to the "working" copy), or `null` (not stuck → unchanged LLM path). It is inserted into `fireTier1`/`fireTier2` AFTER the existing quota and idle short-circuits and BEFORE the LLM block. Scheduling is NEVER gated — the next tier is scheduled in every branch (Tier 2 after Tier 1, Tier 3 after Tier 2), so the Tier-3 context-too-long auto-recovery still fires. It emits ONLY the verbatim classifier message (no pane-derived text). The flag is OMITTED from `ConfigDefaults` so it resolves via `resolveDevAgentGate`; a `DEV_GATED_FEATURES` entry was added. Flag-OFF (the whole fleet) is byte-identical to today.
|
|
11
|
+
|
|
12
|
+
**Fix (c) — paused-aware "restart is queued" claim (ships unflagged).** `ResumeQueue.hasLiveQueuedEntryFor` fed `ReapNotifier`'s "A restart is queued … I'll bring it back" line, but it gated on `dryRun`/`disabledReason`/`enabled` — NOT on whether the queue was PAUSED (e.g. by an emergency stop). When paused, a queued entry will NOT revive until the queue resumes, yet the notice still promised revival. The fix is a SPLIT predicate, not a paused-blind edit of the shared one: `hasLiveQueuedEntryFor` (OWNERSHIP, deliberately paused-BLIND) is left UNCHANGED because the PromiseBeacon I2 double-spawn coordination guard (`server.ts`) reads it and must keep deferring to the queue while paused — a paused-blind flip would re-open a double-spawn and revive emergency-stopped work. A NEW sibling `ResumeQueue.hasClaimableQueuedEntryFor` (= `hasLiveQueuedEntryFor && !isPaused()`) is added and ONLY the ReapNotifier copy consumer (`server.ts`) is re-pointed at it. The entries remain queued and revive normally on unpause.
|
|
13
|
+
|
|
14
|
+
## What to Tell Your User
|
|
15
|
+
|
|
16
|
+
(These changes are mostly internal honesty fixes — the bigger one ships only on the development agent for now, so a regular agent's behavior is unchanged.) When one of your sessions goes quiet, I post short "standby" updates while you wait. Previously, in the first couple of minutes, those updates would say I was "actively working" even when my session was actually stuck — rate-limited, or wedged in a way that needs a fresh start. On the development agent (⚗️ experimental, dark on everyone else for now), I now tell you the REAL reason at the 20-second and 2-minute marks instead, the same way I already did at the 5-minute mark. Separately (and live for everyone): if a session's restart is on hold because work was paused (for example by an emergency stop), I no longer falsely promise "a restart is queued — I'll bring it back" — I only say that when a restart will actually happen. Your queued work is never dropped; it still comes back once the pause is lifted.
|
|
17
|
+
|
|
18
|
+
## Summary of New Capabilities
|
|
19
|
+
|
|
20
|
+
- `PresenceProxy` Tier 1 / Tier 2 standby now surface the honest stuck reason (rate-limited / policy-wedge / context-wedge / context-too-long) instead of "actively working", via the new `maybeStuckMessage` helper reusing the existing tail-gated `classifyStuckSignature`. Dev-gated dark behind `monitoring.standbyHonestyTiers.enabled` (live-on-dev / dark-on-fleet); flag-OFF is byte-identical to today. Signal-only: it changes the standby MESSAGE TEXT (or suppresses it under one-voice ownership), never the tier schedule, recovery, spend, or egress.
|
|
21
|
+
- `ResumeQueue.hasClaimableQueuedEntryFor` (= live entry AND not paused) is a new claimability accessor that feeds the user-facing "restart is queued" copy, so the claim is suppressed while the queue is paused. Ships unflagged (pure correctness).
|
|
22
|
+
- The shared `ResumeQueue.hasLiveQueuedEntryFor` (ownership, paused-blind) is left unchanged so the PromiseBeacon I2 double-spawn coordination guard keeps deferring to the queue while paused — preserved byte-for-byte.
|
|
23
|
+
|
|
24
|
+
## Evidence
|
|
25
|
+
|
|
26
|
+
Both are honesty defects in EXISTING surfaces, demonstrated by new 3-tier tests (signal-only / dev-gated dark — no production outage to repro). Phase-5 independent diff review concurred (no blockers).
|
|
27
|
+
|
|
28
|
+
- **Fix (b) — the Tier1/Tier2 lie.** Before: a live-but-stuck pane (policy-wedge / context-wedge / context-too-long) at Tier 1 or Tier 2 produced "is actively working" / "is still working". After: with the flag ON it produces the honest classifier reason and NOT the "working" copy, and a stuck pane where a recovery sentinel owns the voice produces NO message at all (one-voice silent-suppress) — while the tier schedule is unchanged in every branch. Proven in `tests/unit/PresenceProxy-standby-honesty.test.ts` (Tier 1 + Tier 2 both-sides incl. the SUPPRESS-still-schedules and the no-leak assertions), the integration test `tests/integration/standby-honesty-reap-notice.test.ts` (real PresenceProxy fire path), and the e2e `tests/e2e/standby-honesty-flag-default.test.ts` (dev-gate both-sides + wiring integrity: resolved-TRUE threads into `classifyStuckSignature`, resolved-FALSE is a strict no-op byte-identical to today). The `devGatedFeatures-wiring.test.ts` both-sides assertion covers the new registry entry; `lint-dev-agent-dark-gate` confirms the flag is OMITTED from `ConfigDefaults`.
|
|
29
|
+
- **Fix (c) — the false "restart is queued" claim + the I2 regression guard.** Before: with a queued entry and the queue paused, the reap notice claimed "A restart is queued". After: `hasClaimableQueuedEntryFor` returns false while paused (copy suppressed) while `hasLiveQueuedEntryFor` stays true (ownership preserved → the PromiseBeacon I2 double-spawn guard keeps deferring). Proven both directions in `tests/unit/ResumeQueue-paused-claimable.test.ts` (incl. the I2-guard ownership-survives-pause assertion and the unpause-restores case) and the integration test (the ReapNotifier copy path suppresses the line while paused and emits it after unpause; the I2-guard path still reports ownership while paused). No entry is dropped, cancelled, or double-spawned.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Side-Effects Review — Honest Session-State Surfaces (Tier1/Tier2 standby honesty + paused-queue notice correctness)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `honest-session-state-surfaces`
|
|
4
|
+
**Date:** `2026-06-24`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `general-purpose (independent Phase-5 diff review)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Two small honesty fixes to the standby/reap user-facing surfaces, both signal-only.
|
|
11
|
+
**Finding (b)** lifts the existing Tier-3 honest stuck-state classifier
|
|
12
|
+
(`classifyStuckSignature` — rate-limited / policy-wedge / context-wedge /
|
|
13
|
+
context-too-long) into PresenceProxy **Tier 1** and **Tier 2** so a live-but-failing
|
|
14
|
+
session is reported with its REAL reason at the 20s / 2-minute marks instead of
|
|
15
|
+
"actively working". It is gated behind a dev-dark flag
|
|
16
|
+
`monitoring.standbyHonestyTiers.enabled` (OMITTED from ConfigDefaults → resolved via
|
|
17
|
+
`resolveDevAgentGate`; live on a dev agent, dark on the fleet). **Finding (c)** fixes a
|
|
18
|
+
false "A restart is queued — I'll bring it back" claim when the ResumeQueue is paused:
|
|
19
|
+
it adds a NEW `ResumeQueue.hasClaimableQueuedEntryFor` (= `hasLiveQueuedEntryFor && !isPaused()`)
|
|
20
|
+
and re-points ONLY the ReapNotifier copy consumer at it, leaving the paused-blind
|
|
21
|
+
`hasLiveQueuedEntryFor` (ownership) for the PromiseBeacon I2 double-spawn guard. Files:
|
|
22
|
+
`src/monitoring/PresenceProxy.ts`, `src/commands/server.ts`, `src/core/types.ts`,
|
|
23
|
+
`src/core/devGatedFeatures.ts`, `src/monitoring/ResumeQueue.ts`, plus 4 test files.
|
|
24
|
+
|
|
25
|
+
## Decision-point inventory
|
|
26
|
+
|
|
27
|
+
- `PresenceProxy Tier 1 / Tier 2 standby copy` — modify — flag-ON substitutes the honest stuck reason (or suppresses the message under one-voice) for "working" copy; flag-OFF byte-identical.
|
|
28
|
+
- `ReapNotifier "restart is queued" line (via ResumeQueue predicate)` — modify — now reads claimability (false while paused) instead of ownership; no flag, pure correctness.
|
|
29
|
+
- `PromiseBeacon I2 double-spawn coordination guard (server.ts:11980)` — pass-through — still reads the unchanged paused-blind `hasLiveQueuedEntryFor` (ownership). Deliberately UNCHANGED.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 1. Over-block
|
|
34
|
+
|
|
35
|
+
The change has no block/allow surface — it changes message TEXT and suppresses a
|
|
36
|
+
specific false claim. (b) The honest pre-check can only substitute the standby message
|
|
37
|
+
string or suppress it under one-voice ownership; it never blocks a session, a send, or
|
|
38
|
+
a tier. (c) Suppressing the "restart is queued" copy while paused omits one notice LINE,
|
|
39
|
+
never a whole notice and never a queue entry. "No block/allow surface — over-block not applicable."
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. Under-block
|
|
44
|
+
|
|
45
|
+
Not applicable (no block surface). The honest classifier is inherited as-is (tail-gated,
|
|
46
|
+
same false-positive profile as the established Tier-3 usage — the early tiers do not
|
|
47
|
+
widen its input surface). The claimability predicate is a strict conjunction over the
|
|
48
|
+
already-shipped ownership predicate, so it cannot pass a case the ownership predicate
|
|
49
|
+
already rejects. "No block/allow surface — under-block not applicable."
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 3. Level-of-abstraction fit
|
|
54
|
+
|
|
55
|
+
Right layer in both. (b) reuses the existing classifier (a detector) and feeds the
|
|
56
|
+
existing standby-message emission path — it adds NO new detector and NO new parse
|
|
57
|
+
surface; recovery stays with the Tier-3 block and the sentinels (the early tiers only
|
|
58
|
+
REPORT or stay silent). (c) adds a sibling read accessor on the store that already owns
|
|
59
|
+
the queue state (`ResumeQueue`) rather than re-deriving pause state in the caller — the
|
|
60
|
+
authority over "is this claimable?" lives with the queue.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 4. Signal vs authority compliance
|
|
65
|
+
|
|
66
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
67
|
+
|
|
68
|
+
- [x] No — this change produces a signal (a message string / a boolean read) consumed by existing smart gates and emission paths; it holds NO new block authority.
|
|
69
|
+
|
|
70
|
+
(b) only alters which message string is sent, or sends none when a recovery sentinel
|
|
71
|
+
owns the voice — it NEVER gates scheduling (`scheduleTier` runs in every branch),
|
|
72
|
+
initiates recovery, spends, or egresses. Every uncertainty (no snapshot, classifier
|
|
73
|
+
returns null/throws, recovery owner present) fails toward today's behavior. (c) is a
|
|
74
|
+
read-accessor correctness fix; the I2 guard's blocking decision still reads the
|
|
75
|
+
unchanged ownership predicate, so the only coordination authority in play is untouched.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 5. Interactions
|
|
80
|
+
|
|
81
|
+
- **Shadowing:** (b) The honest pre-check is placed AFTER the existing quota
|
|
82
|
+
short-circuit (`detectQuotaExhaustion`) and AFTER the idle/finished checks in both
|
|
83
|
+
fireTier1 and fireTier2, so it never shadows the quota or "agent finished" paths
|
|
84
|
+
(quota panes — the rate-limit form — are still handled by the pre-existing quota
|
|
85
|
+
short-circuit, which the lift sits below; the lift's incremental value is the wedge
|
|
86
|
+
set quota does not catch). It is placed BEFORE the LLM block, so a stuck session never
|
|
87
|
+
reaches the LLM "working" summary.
|
|
88
|
+
- **Double-fire:** (c) The whole point — the I2 guard keeps deferring to the queue while
|
|
89
|
+
paused (reads ownership, still true), so a PromiseBeacon escalation cannot double-spawn
|
|
90
|
+
a revive for a paused-frozen topic. A naïve single-predicate paused-aware edit would
|
|
91
|
+
have re-opened exactly that double-spawn; the split predicate prevents it.
|
|
92
|
+
- **Races:** No new shared state. (b) reuses the existing per-topic PresenceState and the
|
|
93
|
+
same persist/schedule tail. (c) reads `ResumeQueue` state through its existing accessors.
|
|
94
|
+
- **Feedback loops:** None — message text and a boolean read have no feedback into the systems they describe.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 6. External surfaces
|
|
99
|
+
|
|
100
|
+
- Other agents: none.
|
|
101
|
+
- Install base: (b) ships dark on the fleet (dev-gate) → no fleet behavior change until a
|
|
102
|
+
deliberate flip; (c) ships live but only removes a false claim line.
|
|
103
|
+
- External systems: the honest standby message and the reap notice both flow through the
|
|
104
|
+
EXISTING `sendMessage` / Telegram formatter+escape path — no new outbound surface, no raw send.
|
|
105
|
+
- Persistent state: none changed. ResumeQueue entries are untouched (paused entries
|
|
106
|
+
remain queued and revive normally on unpause).
|
|
107
|
+
- **Operator surface (Mobile-Complete Operator Actions):** No operator-facing actions —
|
|
108
|
+
both surfaces are passive notifications the agent emits; there is nothing for the
|
|
109
|
+
operator to do/tap. Not applicable.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
114
|
+
|
|
115
|
+
No operator surface — not applicable. This change touches no dashboard renderer/markup
|
|
116
|
+
file, no approval page, and no grant/revoke/secret-drop form. It alters two passive
|
|
117
|
+
agent-emitted notification strings (a 🔭 standby line and a 🪦 reap-notice line); there
|
|
118
|
+
is no operator form, button, or page in the diff.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
123
|
+
|
|
124
|
+
**machine-local BY DESIGN** — both surfaces are per-machine-local honesty surfaces with a
|
|
125
|
+
concrete reason each:
|
|
126
|
+
|
|
127
|
+
- **(b)** `PresenceProxy` already runs under the WS3 one-voice speaker election (only the
|
|
128
|
+
topic's OWNER machine speaks 🔭). The honest classifier reads the LIVE LOCAL tmux pane of
|
|
129
|
+
a session running on THIS machine — there is no remote session to classify, so it is
|
|
130
|
+
correctly machine-local: the machine serving the topic reports honestly about the session
|
|
131
|
+
it is actually running. User-facing notice → one-voice gating is already inherited from
|
|
132
|
+
the existing PresenceProxy speaker election (the pre-check sits inside that gate; the
|
|
133
|
+
SUPPRESS branch also honors the recovery-owner one-voice rule).
|
|
134
|
+
- **(c)** `ResumeQueue` is a durable PER-MACHINE queue (it holds a host-local lock
|
|
135
|
+
precisely to forbid two machines sharing its state). Its `paused` flag and entries are
|
|
136
|
+
machine-local, and the reap notice is emitted by the machine that reaped the session, so
|
|
137
|
+
the paused-guard is correctly local — it suppresses the claim on the only machine that
|
|
138
|
+
could (or could not) honor it. No durable state strands on transfer (entries are
|
|
139
|
+
untouched); no URLs are generated.
|
|
140
|
+
|
|
141
|
+
No `multiMachine.*` config, no replicated-store work, no pool routes touched.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 8. Rollback cost
|
|
146
|
+
|
|
147
|
+
- **Hot-fix release:** pure code change — revert and ship a patch. (b) is additionally
|
|
148
|
+
flag-gated, so disabling `monitoring.standbyHonestyTiers.enabled` (or running on the
|
|
149
|
+
fleet, where it is already dark) reverts Tier 1/2 to byte-identical-to-today wording
|
|
150
|
+
without a code change.
|
|
151
|
+
- **Data migration:** none — no persistent state added or changed.
|
|
152
|
+
- **Agent state repair:** none — no agent needs notification or reset.
|
|
153
|
+
- **User visibility:** none during rollback — reverting (b) restores "actively working";
|
|
154
|
+
reverting (c) restores the (incorrect) "restart is queued" claim, a cosmetic regression
|
|
155
|
+
only, with no behavioral loss.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Conclusion
|
|
160
|
+
|
|
161
|
+
This review produced no design changes — the spec's convergence had already resolved the
|
|
162
|
+
two load-bearing constraints this review re-verifies: (b) scheduling is never gated (the
|
|
163
|
+
honest pre-check substitutes the message string or suppresses it, then falls through to
|
|
164
|
+
the existing scheduling tail in every branch, including SUPPRESS), and (c) the shared
|
|
165
|
+
`hasLiveQueuedEntryFor` stays paused-blind for the I2 double-spawn guard while a NEW
|
|
166
|
+
claimability predicate feeds only the user-facing copy. The 3-tier test suite pins both
|
|
167
|
+
sides of each boundary, including the I2-guard regression assertion (ownership survives a
|
|
168
|
+
pause) and the no-leak contract at the new Tier1/2 callsite. Clear to ship.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Second-pass review (if required)
|
|
173
|
+
|
|
174
|
+
**Reviewer:** general-purpose (independent Phase-5 diff review)
|
|
175
|
+
**Independent read of the artifact: concur**
|
|
176
|
+
|
|
177
|
+
The independent reviewer read the real staged diff and the surrounding source (the full
|
|
178
|
+
`fireTier1`/`fireTier2` methods, `maybeStuckMessage`, `ResumeQueue.hasLiveQueuedEntryFor`
|
|
179
|
+
/ `hasClaimableQueuedEntryFor` / `isPaused`, and server.ts lines 7219/7244/7501/11980),
|
|
180
|
+
checking specifically: (1) the next tier is scheduled in EVERY branch of the new
|
|
181
|
+
pre-check (string / SUPPRESS / null); (2) the SUPPRESS branch sends no "working"
|
|
182
|
+
fallback; (3) the pre-check sits after the quota/idle short-circuits; (4)
|
|
183
|
+
`hasLiveQueuedEntryFor` is left paused-blind for the I2 guard; (5) only the ReapNotifier
|
|
184
|
+
copy reads the claimable predicate; (6) the no-leak contract holds; (7) no control-flow
|
|
185
|
+
bug from the fireTier2 brace restructuring. Findings are recorded below.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Side-Effects Review — Mesh Coherence: Live-State Honesty
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/mesh-coherence-live-state-honesty.md (converged + approved — standing operator pre-authorization, Justin topic 27515, 2026-06-22 mandate). **Parent principle:** Signal vs. Authority.
|
|
4
|
+
**Fix (c) ships UNFLAGGED** (pure improvement to an existing boot-time warning). **Fix (b) ships DARK** behind `monitoring.meshCoherenceLiveCheck.enabled` (dev-gated: live-on-dev / dark-fleet). Single-machine agents are a strict no-op (the `isMultiMachine` guard + the multi-machine-only `peerPresenceTimer`).
|
|
5
|
+
**Files:** src/core/configCoherence.ts, src/commands/server.ts, src/core/types.ts, src/core/devGatedFeatures.ts
|
|
6
|
+
|
|
7
|
+
## What changed
|
|
8
|
+
|
|
9
|
+
1. **configCoherence.ts — Fix (c):** DROPPED the dead `.priorities`-dict check (it validated a key no type or default ever populates) and replaced it with a flat-key check (`priorityTailscale`/`priorityLan`/`priorityCloudflare`) for distinct-positive-integers, reusing the EXISTING codes `mesh-priority-nonpositive` / `mesh-priority-collision`. RETYPED `MultiMachineLike.meshTransport` as `Pick<MeshTransportConfig, 'enabled'|'priorityTailscale'|'priorityLan'|'priorityCloudflare'|'bindHost'>` (imported the canonical type) so the phantom dict cannot be reintroduced by a hand-edit (compile-time proof).
|
|
10
|
+
2. **configCoherence.ts — Fix (b):** NEW exported pure function `checkMeshLiveStateCoherence(mm, isMultiMachine, live, warmupGraceMs?)` + the `MeshLiveState` interface + the exported `MESH_WARMUP_GRACE_MS` const. b.1 fires `mesh-config-off-but-live-on` on config-off + a NOT-LOOPBACK live bind (boundWide, corroborated-only by self-entry presence); b.2 fires `mesh-config-on-but-live-inert` on config-on + zero advertised endpoints + uptime past the warmup grace. NO-LEAK invariant: never interpolates a peer endpoint VALUE (counts + the process-local bound-host only); `selfEndpoints` is a boolean presence signal.
|
|
11
|
+
3. **server.ts — Fix (b) wiring:** `let meshResolvedBindHost: string | undefined` at OUTER function scope (beside `let getSelfMeshEndpoints`), ASSIGNED inside the mesh-init block via `resolveMeshBindHost` from the SAME `meshBindActive` inputs the AgentServer bind callsite uses (boot constant). The existing one-line `peerPresenceTimer` arrow was promoted to a named `peerPresenceTick` callback; the live-coherence recheck is APPENDED to it AFTER `pullOnce()`, gated `resolveDevAgentGate(config.monitoring?.meshCoherenceLiveCheck?.enabled, config)`. Transition-only emit (`_meshCoherenceLastCodes` Set + level-triggered reset) + emitCap (`_meshCoherenceEmitCounts`) + half-open-breaker backoff (`_meshCoherenceConsecFailures` / `_meshCoherenceTicksSinceAttempt`, MAX_BACKOFF_TICKS=20) + a healthy→failing latch (`_meshCoherenceFailing`) that transition-gates the `error` metric. Per-feature metric `mesh-coherence-live` (kind:'event', fired/noop/error) via `getFeatureMetricsRecorder()`.
|
|
12
|
+
4. **types.ts:** added `meshCoherenceLiveCheck?: { enabled?; warmupGraceMs?; emitCap? }` to `MonitoringConfig` (next to `growthAnalyst`).
|
|
13
|
+
5. **devGatedFeatures.ts:** added the `meshCoherenceLiveCheck` DEV_GATED_FEATURES entry (configPath `monitoring.meshCoherenceLiveCheck.enabled`). OMITTED from ConfigDefaults per the dev-gate convention → no `migrateConfig` needed.
|
|
14
|
+
|
|
15
|
+
## Blast radius
|
|
16
|
+
|
|
17
|
+
- **Fix (c) is behavior-narrowing then -widening, never wrong.** The dict check was ALWAYS dead (the dict is `undefined` for every shipped config), so removing it changes nothing for any real config; the new flat-key check only ADDS a warning for a genuine operator mistake (collision / non-positive) that previously sailed through unwarned. Both surfaces are advisory log lines — never a boot reject.
|
|
18
|
+
- **Fix (b) is config-gated, not wiring-gated.** With `meshCoherenceLiveCheck.enabled` resolving DARK (fleet), the `peerPresenceTick` coherence branch is a strict no-op — the tick does exactly what it did before (`pullOnce()`). `meshResolvedBindHost` is computed at boot regardless (cheap, pure), but it is read only inside the gated branch.
|
|
19
|
+
- **No new HTTP route, no new outbound surface.** Per spec, the warnings ride the existing `console.log(pc.yellow(...))` log path + the per-feature metric in `/metrics/features`. No Attention queue, no Telegram, no new route.
|
|
20
|
+
- **Machine-local.** Each machine evaluates its OWN config vs its OWN live bind/endpoints. No cross-machine fan-out, no pool-scope read.
|
|
21
|
+
- **The peerPresenceTimer refactor is behavior-preserving.** The named `peerPresenceTick` calls `pullOnce()` first (unchanged), then the gated coherence branch; the timer is still `setInterval(..., 30_000)` + `unref()`. The peer-presence-wiring test was updated to pin the pull inside the named tick fn.
|
|
22
|
+
|
|
23
|
+
## Risk + mitigation
|
|
24
|
+
|
|
25
|
+
- **Risk:** a corrupt/mid-write registry read (`getSelfMeshEndpoints()` → `loadRegistry`) throws and crashes the 30s tick. **Mitigation:** the live read is wrapped in try/catch; on failure it emits a TRANSITION-GATED `error` metric (one row per failure episode, not per tick), advances a capped backoff (≤1 attempt per ~10 min), and leaves the transition state untouched (fail toward silence). A successful read auto-resets the backoff and the latch. Proven by the sustained-failure integration test.
|
|
26
|
+
- **Risk:** a hostile/garbage peer-written self-entry steers the warning text (the registry is git-synced + peer-writable). **Mitigation:** the no-leak invariant — `selfEndpoints` is consumed as a boolean `length > 0` presence signal ONLY; warning strings interpolate only counts + the process-local operator-derived bound-host. Proven by the no-leak unit test (a hostile `url` never renders).
|
|
27
|
+
- **Risk:** b.1 false-silenced on a specific `meshTransport.bindHost` override (the original is-wildcard bug). **Mitigation:** `boundWide ≡ NOT-loopback` (mirrors `resolveMeshBindHost`'s `isLoopback`), so a specific non-loopback bind (e.g. 192.168.1.50) still fires. Proven by the R2-M10 regression test.
|
|
28
|
+
- **Risk:** b.2 false-fires during the legitimate boot-warmup window. **Mitigation:** a monotonic `process.uptime()` gate (default 2 min, overridable via `warmupGraceMs`); a permanently-inert mesh (first advertise never lands) still warns ~2 min after boot. Proven by the below/past-grace + param-override tests.
|
|
29
|
+
- **Risk:** the divergence line re-logs every 30s tick (repeated-true-line anti-pattern). **Mitigation:** transition-only emit with a level-triggered reset + an optional `emitCap` ceiling. Proven by the transition-only + emitCap integration tests.
|
|
30
|
+
|
|
31
|
+
## Migration parity
|
|
32
|
+
|
|
33
|
+
- `meshCoherenceLiveCheck` is OMITTED from ConfigDefaults (the dev-gate convention) → `resolveDevAgentGate` reads the absence directly, so NO `migrateConfig` change is needed (a backfilled `enabled:false` would defeat the gate). Stated explicitly in the spec (Decision #2).
|
|
34
|
+
- No CLAUDE.md template change: this is a signal-only honesty fix to an existing internal boot check with no user-facing conversational surface (no route, no proactive trigger). The Agent Awareness Standard targets capabilities an agent surfaces to users; a more-honest internal log line is not one. (The docs-coverage ratchet for the new exported helper is handled in site/src/content/docs if it trips — see PR.)
|
|
35
|
+
|
|
36
|
+
## Dark-gate line-map
|
|
37
|
+
|
|
38
|
+
- UNCHANGED. `meshCoherenceLiveCheck` is OMITTED from ConfigDefaults.ts (no inline `enabled:` literal added there), so no `enabled:` line shifted. Verified: `node scripts/lint-dev-agent-dark-gate.js` → clean; `tests/unit/devGatedFeatures-wiring.test.ts` → green (the new entry resolves live-on-dev / dark-fleet against real ConfigDefaults).
|
|
39
|
+
|
|
40
|
+
## Rollback
|
|
41
|
+
|
|
42
|
+
- Fix (b): set `monitoring.meshCoherenceLiveCheck.enabled: false` (or leave dark on the fleet) → strict no-op. Full revert: remove the DEV_GATED_FEATURES entry + the types field + the server.ts wiring (restore the one-line arrow timer) + `checkMeshLiveStateCoherence`. Fix (c) is a pure improvement to an existing check; reverting it restores the dead dict check (not recommended).
|
|
43
|
+
|
|
44
|
+
## Tests
|
|
45
|
+
|
|
46
|
+
- `tests/unit/configCoherence.test.ts` — extended: Fix (c) flat-key validation (collision / zero / negative / float / Infinity / default-no-warn / single-key / dead-dict-ignored), Fix (b) both directions (b.1 wildcards + bindHost-override-flip MUST-fire + stale-self-entry-must-not-fire + corroboration-count + disabled-took-effect; b.2 below/past grace + on-and-advertising + warmupGraceMs param override; single-machine no-op; undefined no-throw; no-leak assertion).
|
|
47
|
+
- `tests/integration/mesh-coherence-live-check.test.ts` — the wiring STATE MACHINE: transition-only emit over 3 ticks + level-triggered reset, flag-DARK no-op, emitCap:2 ceiling, sustained-failure (one error row, never crashes, bounded re-probes, auto-recovery).
|
|
48
|
+
- `tests/unit/mesh-coherence-wiring.test.ts` — e2e/wiring tier: source-pins the inline wiring in server.ts (import, outer-scope let, resolveMeshBindHost assignment from the bind inputs, nested-?.enabled dev-gate, real live signals, both tuning knobs, the metric, the throw-safety try/catch, the existing boot call intact).
|
|
49
|
+
- `tests/unit/peer-presence-wiring.test.ts` — updated for the named-tick refactor (the pull now lives in `peerPresenceTick`).
|
|
50
|
+
|
|
51
|
+
## Agent awareness
|
|
52
|
+
|
|
53
|
+
- No CLAUDE.md template entry (signal-only internal honesty fix, no user-facing surface). <!-- tracked: mesh-coherence-live-state-honesty -->
|