instar 1.3.758 → 1.3.759

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,105 @@
1
+ # Side-Effects Review — machine-coherence-guard §5b: lease-live `awakeMachineCount`
2
+
3
+ **Version / slug:** `machine-coherence-guard-5b-awake-count`
4
+ **Date:** `2026-07-04`
5
+ **Author:** Echo (autonomous)
6
+ **Spec:** `docs/specs/machine-coherence-guard.md` §5b (converged 2026-07-03, approved)
7
+ **Second-pass reviewer:** not-required (Tier 2; a read-only telemetry-derivation fix — no gate, no demotion authority, no mutation)
8
+
9
+ ## Summary of the change
10
+
11
+ `/health.multiMachine.syncStatus.awakeMachineCount` reported **0** while both the
12
+ Laptop and the Mac Mini were online and the Mini correctly held the lease
13
+ (`leaseHolder` named the Mini). Root cause, confirmed in code: `getSyncStatus()`
14
+ derived the count SOLELY by counting `registry.machines[].role === 'awake'` rows
15
+ (`MultiMachineCoordinator.ts`) — a git-synced SYMBOL that lags the authoritative
16
+ lease and that a dead Cloudflare rope or a slow registry push can leave stale — so
17
+ it read 0 even though the lease (the same signal `leaseHolder` uses) named the
18
+ holder. The multi-transport hedge was NOT the wedge: it carries the lease over the
19
+ healthy Tailscale/LAN ropes (that is why `leaseHolder` was correct). The count
20
+ simply consulted the wrong source. This implements the spec's already-designed
21
+ §5b remedy: derive the count from LIVE lease observations, source-tagged and
22
+ honest.
23
+
24
+ Files modified:
25
+ - `src/core/LeaseCoordinator.ts` — new `deriveLiveAwakeCount(staleMs)`: `(self
26
+ holds ? 1 : 0)` + distinct peers whose most-recent observation is FRESH, LIVE
27
+ (not expired on our clock), and a SELF-CLAIM (`lease.holder === peerId`). The
28
+ per-peer observation seam (`peerLeaseObservations()` / transport `observedByPeer()`)
29
+ already existed; this wires the counting rule onto it. Advisory only.
30
+ - `src/core/MultiMachineCoordinator.ts` — `getSyncStatus()` rewritten to prefer
31
+ the lease-live basis (when a lease coordinator with pull capability is attached),
32
+ degrade to the registry-role basis on a git-only mesh, and yield `null` on a read
33
+ failure; a new `leaseObservationStaleMs()` (3× lease-pull interval, floor 30s).
34
+ `MultiMachineSyncStatus.awakeMachineCount` becomes `number | null` and gains
35
+ `awakeMachineCountSource: 'lease-live' | 'registry-roles' | 'unavailable'`.
36
+ `splitBrainState` now null-safely reads `count > 1` (degrades to the pull-contest
37
+ latch alone when the count is null).
38
+ - `src/server/routes.ts` — `/pool` router block carries `awakeMachineCountSource`
39
+ (`/health` spreads the whole object, so it flows automatically).
40
+ - `src/commands/machine.ts` — `instar doctor` labels the registry-role check as a
41
+ "may-lag" VIEW and, when the server is reachable, adds a **Live lease view**
42
+ check that prints the authoritative count + source and names any divergence.
43
+ - `src/scaffold/templates.ts` + `src/core/PostUpdateMigrator.ts` — the two deployed
44
+ CLAUDE.md mentions updated to the new shape (Agent Awareness + Migration Parity).
45
+
46
+ Tests: `tests/unit/multimachine-awake-count-lease-live.test.ts` (new; the
47
+ load-bearing "Cloudflare dead, Tailscale/LAN alive, Mini holds lease → count 1
48
+ lease-live, NOT 0" case plus stale/expired/hearsay/no-lease/registry-fallback/
49
+ unavailable), updated `tests/unit/multimachine-syncstatus.test.ts`,
50
+ `tests/integration/pool-routes.test.ts` (+ two other mocks), and an extended
51
+ `tests/e2e/multi-machine-lease-split-brain.test.ts` (lease-live convergence).
52
+
53
+ ## Roll-up across the seven review dimensions
54
+
55
+ 1. **Over-block**: none. Nothing is blocked, delayed, or rewritten — this is a
56
+ read-only observability derivation. It NEVER drives a demotion (demotion
57
+ authority stays exclusively with the strictly-higher-epoch supersede gate and
58
+ the operator flow), so it cannot mis-fence a healthy holder.
59
+ 2. **Under-block**: none. Genuine split-brain is still detected: `count > 1` OR
60
+ the pull-contest latch → `splitBrainState: 'contested'`. The freshness (3×
61
+ pull-interval), liveness (not-expired), and self-claim (no third-machine
62
+ hearsay) gates prevent both over- and under-count.
63
+ 3. **Level-of-abstraction fit**: correct. The count derivation lives in
64
+ `LeaseCoordinator` (where the lease/clock/expiry logic already lives) behind a
65
+ pure method; `getSyncStatus()` only chooses the basis and tags it.
66
+ 4. **Signal-vs-authority compliance**: fully signal-only. Peer lease claims are
67
+ self-asserted advisory data (L4/SEC-4); the count feeds dashboards + the
68
+ existing human-decision attention flow, never an automatic action. No silent
69
+ fallback: each `catch` yields an honest `null`+`'unavailable'` (tagged
70
+ `@silent-fallback-ok` as a read-only health field), never a fabricated 0.
71
+ 5. **Interactions**: the `MultiMachineSyncStatus` shape change (`number → number |
72
+ null` + source tag) is swept across every consumer — `/health`, `/pool`, the
73
+ two other route callers (leaseHolder-only, unaffected), the unit/integration/e2e
74
+ fixtures, `instar doctor`, and both CLAUDE.md templates. `splitBrainState`'s
75
+ three in-code consumers (ingress suppression, speaker-election, rope-health
76
+ `splitBrainItemOpen`) keep their exact semantics.
77
+ 6. **External surfaces**: none added. No new network path, no new egress. `instar
78
+ doctor` adds a Bearer'd localhost `/health` read (the same host it already
79
+ contacts) to fetch the live count.
80
+ 7. **Rollback cost**: low. Additive method + a localized `getSyncStatus()` rewrite;
81
+ revertable in one commit. No persistent state, no migration data. The
82
+ `registry-roles` basis remains as the automatic git-only-mesh degrade (there is
83
+ deliberately no config lever back to the old always-registry behavior — that
84
+ would preserve the documented defect).
85
+
86
+ ## Clock assumption
87
+
88
+ Lease liveness (`expiresAt` vs now) is judged on the OBSERVER's clock. A
89
+ clock-drifted machine could misjudge a peer lease's expiry and skew its own
90
+ published count; the mesh's existing per-peer clock-skew gate
91
+ (`MachinePoolRegistry`) marks a divergent machine placement-ineligible, and the
92
+ freshness bound caps how long any one misjudged observation can distort the count.
93
+ The count inherits the mesh's clock-sanity envelope rather than adding its own.
94
+
95
+ ## Evidence pointers
96
+
97
+ - `npx tsc --noEmit` — clean.
98
+ - `npm run lint` — exit 0 (all ratchets green, including no-silent-fallbacks).
99
+ - `npm run build` — exit 0.
100
+ - `npx vitest run tests/unit/multimachine-awake-count-lease-live.test.ts` — 8/8
101
+ pass (load-bearing Cloudflare-dead case asserts count 1 + `lease-live`, NOT 0).
102
+ - `npx vitest run` across the mesh/lease suites (MultiMachineCoordinator-leasePull,
103
+ multi-machine-coordinator, HttpLeaseTransport, multimachine-syncstatus,
104
+ lease-contested-resolution, pool-routes, multi-machine-lease-split-brain) — all
105
+ green (59 + 9 pass).
@@ -0,0 +1,83 @@
1
+ # Side-Effects Review — Telegram Conservatism Pass
2
+
3
+ **Version / slug:** `telegram-conservatism-pass`
4
+ **Date:** `2026-07-04`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `echo (guard-adjacent; independent re-read appended)`
7
+
8
+ ## Summary of the change
9
+
10
+ Closes the `origin:'system'`/`'user'` bypass in the auto-topic flood ceiling. `TelegramAdapter.createForumTopic` previously enforced its last-resort budget ONLY for `origin:'auto'`; a bare `origin:'system'` skipped it entirely, and `createAttentionItem` used `origin:'system'` for every HIGH/URGENT item (never coalesced) → an unbounded per-item topic path. The change: (1) the ceiling now applies to every origin except an explicit human request (`origin:'user'`) or a caller that declares its topic cardinality-bounded (`opts.bounded === true`); (2) the genuine create-once system topics are marked `bounded:true`; (3) critical attention topics get a distinct budget label and coalesce (never drop) on ceiling-refusal. Files: `src/messaging/TelegramAdapter.ts`, `src/commands/server.ts`, `tests/integration/notification-flood-burst-invariant.test.ts`. Decision point touched: the topic-creation flood ceiling (a delivery shaper).
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `TelegramAdapter.createForumTopic` flood-ceiling exemption — **modify** — exemption narrowed from `origin ∈ {user,system}` to `origin===user || bounded===true`.
15
+ - `TelegramAdapter.createAttentionItem` critical-topic path — **modify** — critical items now ride a distinct budget label and coalesce on refusal instead of degrading.
16
+
17
+ ---
18
+
19
+ ## 1. Over-block
20
+
21
+ A create-once system topic caller that a FUTURE author forgets to mark `bounded:true` would be budgeted against the ceiling (12/10min global). Because these topics are low-volume create-once, the ceiling is not approached in normal operation; a refusal self-heals on the next window (the topic recreates). No user message and no attention item is ever rejected — only a redundant per-item topic is withheld, and even then the item is coalesced into the single notices topic. Conservative-direction residual.
22
+
23
+ ---
24
+
25
+ ## 2. Under-block
26
+
27
+ The raw-API lifeline topic creation (`lifeline/TelegramLifeline.ts:2685`) bypasses the adapter chokepoint entirely (separate process, create-once) and is NOT covered by this change — tracked follow-up `<!-- tracked: CMT-1901 -->`. It is create-once and cannot flood, so the miss is bounded. `origin:'user'` remains fully exempt by design (a human explicitly asking for a topic is the sanctioned exception).
28
+
29
+ ---
30
+
31
+ ## 3. Level-of-abstraction fit
32
+
33
+ Correct layer. The change lives in the ONE function where topics are born (`createForumTopic`) and reuses the existing `topicCreationGuard` (an `AttentionTopicGuard` instance) and `routeToFloodNotice` — it does not add a parallel guard or a new primitive. The ceiling is exactly the chokepoint the *Bounded Notification Surface* standard designates; this change fixes a hole in it rather than bolting on a new layer.
34
+
35
+ ---
36
+
37
+ ## 4. Signal vs authority compliance
38
+
39
+ - [x] No — this change has no NEW block/allow authority over agent behavior or information flow; it tunes an existing delivery shaper.
40
+
41
+ The `topicCreationBudget` ceiling and `AttentionTopicGuard` are DELIVERY SHAPERS (the rate-counter carve-out in `docs/signal-vs-authority.md`): they change the FORM of delivery (one coalesced topic + a log line instead of a new topic per item) and never withhold a critical notice or drop an item. This change narrows an over-broad exemption and adds a coalesce branch — strictly more conservative, no new brittle blocking logic.
42
+
43
+ ---
44
+
45
+ ## 5. Interactions
46
+
47
+ - **Shadowing:** the critical path now uses a distinct budget label (`attention-item-critical`) so LOW/NORMAL attention topics (`attention-item`) do not starve the critical budget and vice-versa. No shadowing.
48
+ - **Double-fire:** none — one `decide()` call per creation attempt (unchanged cardinality).
49
+ - **Races:** the guard is a per-process in-memory counter (no shared state); concurrent creations share the existing `floodNoticePending` in-flight map so the coalesce topic is created once. Unchanged.
50
+ - **Feedback loops:** the overflow surfaces (flood-notice, agent-health lane) are marked `bounded:true`, so the ceiling can NEVER refuse the very topic the coalesce path needs — no infinite regress. Verified in the design and by the passing 1,000-item critical-flood test (which creates exactly one coalesced topic).
51
+
52
+ ---
53
+
54
+ ## 6. External surfaces
55
+
56
+ - **Telegram:** fewer topics under a flood — the intended user-visible effect. A genuine lone emergency still gets its own topic (verified by the retained lone-emergency test).
57
+ - **Persistent state:** none changed. Coalesced items are still written to the attention store + `state/attention-suppressed.jsonl` (existing paths).
58
+ - **Other agents / users:** none — internal delivery shaping.
59
+ - **Operator surface (Mobile-Complete):** no operator-facing actions added or touched. Not applicable.
60
+
61
+ ---
62
+
63
+ ## 6b. Operator-surface quality
64
+
65
+ No operator surface (no `dashboard/*`, approval, or grant/secret form touched) — not applicable.
66
+
67
+ ---
68
+
69
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
70
+
71
+ **machine-local BY DESIGN.** The topic-creation budget is a per-`TelegramAdapter`-instance rate counter, and only the machine fronting Telegram polling creates topics. A per-machine budget is correct: it bounds the topics THAT machine creates, which is the only machine creating any. No durable state is introduced (nothing to strand on a topic transfer), no user-facing notice needs one-voice gating beyond what already exists (the change reduces notices), and no URLs are generated. Same posture as the existing `AttentionTopicGuard`.
72
+
73
+ ---
74
+
75
+ ## 8. Rollback cost
76
+
77
+ Pure code, no migration, no durable-state change. Back-out = revert the diff in a single hot-fix release. The new `bounded` opt is falsy-by-default, so an un-reverted partial state can only be MORE conservative, never a flood. Lowest-cost rollback class.
78
+
79
+ ---
80
+
81
+ ## Second-pass review (guard-adjacent — independent re-read)
82
+
83
+ Re-read the diff cold against the artifact. The one real behavior change is that a FLOOD (8+ in 10min per label, or 12+ global) of HIGH/URGENT attention items now coalesces instead of each spawning a topic. This contradicts the OLD "critical never coalesced" invariant — but that invariant was itself the unbounded hole (it made "mark it HIGH" a flood bypass), and the operator directive explicitly prefers a single alert topic under flood. The item is never dropped (coalesced + stored + audited). The generous per-label ceiling means a normal handful of genuine emergencies is unaffected. **Concur with the review.** No concern raised.