instar 1.3.782 → 1.3.784
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 +41 -15
- package/dist/commands/server.js.map +1 -1
- package/dist/core/MachineIdentity.d.ts +13 -0
- package/dist/core/MachineIdentity.d.ts.map +1 -1
- package/dist/core/MachineIdentity.js +31 -0
- package/dist/core/MachineIdentity.js.map +1 -1
- package/dist/core/MeteredSpendGate.d.ts +127 -0
- package/dist/core/MeteredSpendGate.d.ts.map +1 -0
- package/dist/core/MeteredSpendGate.js +248 -0
- package/dist/core/MeteredSpendGate.js.map +1 -0
- package/dist/core/MeteredSpendLedger.d.ts +154 -0
- package/dist/core/MeteredSpendLedger.d.ts.map +1 -0
- package/dist/core/MeteredSpendLedger.js +434 -0
- package/dist/core/MeteredSpendLedger.js.map +1 -0
- package/dist/core/PinAttemptStore.d.ts +35 -0
- package/dist/core/PinAttemptStore.d.ts.map +1 -0
- package/dist/core/PinAttemptStore.js +85 -0
- package/dist/core/PinAttemptStore.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts +7 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +24 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/RenderedPlanStore.d.ts +61 -0
- package/dist/core/RenderedPlanStore.d.ts.map +1 -0
- package/dist/core/RenderedPlanStore.js +92 -0
- package/dist/core/RenderedPlanStore.js.map +1 -0
- package/dist/core/RoutingSpendCapsStore.d.ts +113 -0
- package/dist/core/RoutingSpendCapsStore.d.ts.map +1 -0
- package/dist/core/RoutingSpendCapsStore.js +241 -0
- package/dist/core/RoutingSpendCapsStore.js.map +1 -0
- package/dist/core/SpendAlertDispatcher.d.ts +94 -0
- package/dist/core/SpendAlertDispatcher.d.ts.map +1 -0
- package/dist/core/SpendAlertDispatcher.js +168 -0
- package/dist/core/SpendAlertDispatcher.js.map +1 -0
- package/dist/core/SpendAlertEmitters.d.ts +118 -0
- package/dist/core/SpendAlertEmitters.d.ts.map +1 -0
- package/dist/core/SpendAlertEmitters.js +220 -0
- package/dist/core/SpendAlertEmitters.js.map +1 -0
- package/dist/core/SpendAlertResolver.d.ts +89 -0
- package/dist/core/SpendAlertResolver.d.ts.map +1 -0
- package/dist/core/SpendAlertResolver.js +175 -0
- package/dist/core/SpendAlertResolver.js.map +1 -0
- package/dist/core/TelegramSpendTopicChannel.d.ts +50 -0
- package/dist/core/TelegramSpendTopicChannel.d.ts.map +1 -0
- package/dist/core/TelegramSpendTopicChannel.js +106 -0
- package/dist/core/TelegramSpendTopicChannel.js.map +1 -0
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
- package/dist/core/WriteDomainRegistry.js +17 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +9 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/routingSpendView.d.ts +10 -2
- package/dist/core/routingSpendView.d.ts.map +1 -1
- package/dist/core/routingSpendView.js +18 -10
- package/dist/core/routingSpendView.js.map +1 -1
- package/dist/core/types.d.ts +45 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/server/AgentServer.d.ts +25 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +304 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +10 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +196 -4
- package/dist/server/routes.js.map +1 -1
- package/dist/testing/selfActionRegistry.d.ts.map +1 -1
- package/dist/testing/selfActionRegistry.js +206 -0
- package/dist/testing/selfActionRegistry.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/upgrades/1.3.783.md +66 -0
- package/upgrades/1.3.784.md +59 -0
- package/upgrades/side-effects/routing-spend-increment-b.md +44 -0
- package/upgrades/side-effects/routing-spend-increment-c.md +44 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
- **`SpendAlertDispatcher`** (`src/core/SpendAlertDispatcher.ts`): lane-scoped dedup (money-critical
|
|
9
|
+
cap-hit/holder-dead vs informational — S-F8) + informational coalescing into ONE digest per window
|
|
10
|
+
+ edge latch on CONFIRMED delivery only + dryRun-first + the scrubbed
|
|
11
|
+
`logs/routing-spend-alerts.jsonl` audit (metadata only — S-F7).
|
|
12
|
+
- **`TelegramSpendTopicChannel`** (`src/core/TelegramSpendTopicChannel.ts`): message-INTO the ONE
|
|
13
|
+
"💰 Routing & Spend Alerts" topic; money-critical kinds prefer the DURABLE relay
|
|
14
|
+
(`PendingRelayStore` → `DeliveryFailureSentinel`, retry-until-delivered); lifeline fallback on ANY
|
|
15
|
+
failure; a repoint of `alerts.telegramTopicId` is audible in BOTH topics (G5).
|
|
16
|
+
- **`SpendAlertEmitters`** (`src/core/SpendAlertEmitters.ts`): cap-approach 50/80% on BOTH caps (G4),
|
|
17
|
+
cap-hit on gate refusal (A-Min13 wording), door-dark with P19 brakes (episode budget = chain
|
|
18
|
+
length, widening backoff, flapping escalation), fallback-spike only at the hourly ceiling crossing
|
|
19
|
+
(Near-Silent), holder-dead surviving-voice (A2-2, stable pool-wide key), recon-drift surface
|
|
20
|
+
(fed by the Layer-1c sweep, next PR).
|
|
21
|
+
- **Resolver rung 2 pool half (FD-6):** the created topic id is published as a content-free
|
|
22
|
+
`routingSpendAlertTopicId` field on the replicated machine registry
|
|
23
|
+
(`MachineIdentityManager.updateRoutingSpendAlertTopic` / `readAnyRoutingSpendAlertTopic`) — a
|
|
24
|
+
future serving-lease holder INHERITS the id instead of re-creating; a degraded registry read
|
|
25
|
+
falls through the lease-fenced ladder (never a duplicate mint).
|
|
26
|
+
- **Router fan-out (I-9):** `onNatureRoutePlan` now fans to the env-gated console breadcrumb + the
|
|
27
|
+
emitters with per-subscriber throw-swallow; served fallbacks are counted from the existing
|
|
28
|
+
`onDegrade` seam.
|
|
29
|
+
- **Gate observer:** `MeteredSpendGate` gains an OPTIONAL signal-only `onGateEvent` (admit +
|
|
30
|
+
cap-exceeded refusal), throw-swallowed — the admit/refuse path is unchanged (unit-pinned).
|
|
31
|
+
- Self-action convergence models for the three new notifiers; config keys
|
|
32
|
+
`routingSpend.alerts.enabled` / `alerts.dryRun`.
|
|
33
|
+
|
|
34
|
+
## What to Tell Your User
|
|
35
|
+
|
|
36
|
+
Nothing yet — the alert layer ships in dry-run soak: it decides and audits but delivers nothing.
|
|
37
|
+
Once flipped live (after the soak), every routing/spend notice — cap warnings at 50/80%, cap hits,
|
|
38
|
+
a routing chain going fully dark, fallback-rate spikes — will land in exactly ONE "💰 Routing &
|
|
39
|
+
Spend Alerts" Telegram topic (never a new topic per event), with only genuinely money-critical
|
|
40
|
+
items escalating immediately and everything else coalesced into a quiet digest.
|
|
41
|
+
|
|
42
|
+
## Summary of New Capabilities
|
|
43
|
+
|
|
44
|
+
- (⚗️ Experimental, dryRun soak) The one-topic spend/routing alert layer: lane-deduped, digest-
|
|
45
|
+
coalesced, durable-delivery for money-critical kinds, pool-durable topic identity. Layer 1c
|
|
46
|
+
provider reconciliation (which feeds the drift alert) and the operator's amortized-subscription
|
|
47
|
+
display land next in this train (tracked: CMT-1929).
|
|
48
|
+
|
|
49
|
+
## Evidence
|
|
50
|
+
|
|
51
|
+
- `tests/unit/spend-alert-dispatcher.test.ts` — lanes, dryRun default, coalescing, edge latch
|
|
52
|
+
(confirmed-only), digest-failure un-latch, channel isolation, durable-relay preference, lifeline
|
|
53
|
+
fallback, G5 repoint, rung-2 pool inherit/publish/degraded-read.
|
|
54
|
+
- `tests/unit/spend-alert-emitters.test.ts` — the full trigger matrix + P19 brakes + observer isolation.
|
|
55
|
+
- `tests/integration/spend-alert-pipeline.test.ts` — the assembled pipeline: dryRun soak, durable
|
|
56
|
+
relay into the resolver-created topic, digest delivery, lifeline degradation.
|
|
57
|
+
- `tests/e2e/routing-spend-lifecycle.test.ts` — the alert layer constructs on the REAL AgentServer
|
|
58
|
+
boot path on a dev agent (dryRun-first pinned) and stays DARK on the fleet.
|
|
59
|
+
- `tests/unit/self-action-convergence.test.ts` — 47/47 with the three new models.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Side-Effects Review — Routing Control Room Increment B (money layer)
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/routing-control-room-spend-alerts.md` (review-convergence r7, approved 2026-07-07)
|
|
4
|
+
**Worktree:** `echo/money-increment-b` off `JKHeadley/main` @ `4811c74bb` (v1.3.782) — remotes verified (`JKHeadley` = canonical, push target), version recorded per Phase 2.
|
|
5
|
+
**Scope of this PR:** Increment B money core — `MeteredSpendLedger` (booking-priced, append-only, fold-is-canon), the O(1) fail-closed money gate, the PIN-only caps/go-live store (`state/routing-spend-caps.json`, versioned), rendered-plan machinery (nonce + TTL + version-pin), durable PIN-attempt store, Bearer freeze (set-true-only), caps-view wiring to real committed totals, and the stale-price/observed-drift alert emission on the minimal topic-resolver foundation (lifeline-fallback rails). Layer 1c provider_cost_report/reconciliation and the full Increment C channel abstraction land as the NEXT PRs in this sequence (tracked: CMT-1929).
|
|
6
|
+
|
|
7
|
+
## Phase 1 — Principle check (signal-vs-authority)
|
|
8
|
+
|
|
9
|
+
**Does this change involve a decision point?** YES — the money gate blocks metered LLM calls at cap.
|
|
10
|
+
|
|
11
|
+
**Compliance:** The gate holds blocking authority with DETERMINISTIC ARITHMETIC over booked dollars (`committed + estCost > cap`, strict `>`), never brittle pattern-matching over meaning — the same class as the fork-bomb spawn cap and the test-runner semaphore, where deterministic authority is the CORRECT shape (docs/signal-vs-authority.md draws the line at brittle/semantic checks holding authority; arithmetic over money is neither brittle nor semantic). Authority is additionally BOUNDED: a cap refusal is a swap-tail ADVANCE (the chain falls to a free door), never a chain kill; `RouterFailClosedError` remains reserved for whole-chain exhaustion. Everything reporting-side (provider reports, drift, subsidies, credits) is SIGNAL-ONLY by structural exclusion — the gate's read set cannot reach those stores (unit-tested).
|
|
12
|
+
|
|
13
|
+
## Phase 2 — Plan
|
|
14
|
+
|
|
15
|
+
- **Decision points touched:** metered-call admission (new gate, fail-closed on every uncertainty); PIN plan-commit (S2-3 rendered-plan, single-use nonce, version drift refusal); Bearer freeze (set-true-only — halting money is cheap, releasing is the operator's).
|
|
16
|
+
- **Existing detectors/authorities interacted with:** `checkMandatePin` (gains a durable attempt write-through, semantics unchanged); `PATCHABLE_CONFIG_KEYS` (regression-tested to NEVER reach the caps store or any gate-consumed price value — S-F2); `RoutingPriceAuthority` (gate consumes canonical-validated points ONLY, with code-defined per-provider plausibility floors); serving-lease staleness window (self-fence read); `DARK_GATE_EXCLUSIONS` (money authority documented as the action-bearing exclusion — dark everywhere until an explicit operator enable, FD-16).
|
|
17
|
+
- **Rollback path:** every surface is additive + dark. `routingSpend.money.enabled` absent ⇒ routes 503, gate never constructed, ledger never written. Disabling reverts to Increment A behavior byte-for-byte. The caps store and ledger files are inert data at rest; no migration touches existing state.
|
|
18
|
+
|
|
19
|
+
## Phase 4 — Side-effects review
|
|
20
|
+
|
|
21
|
+
1. **Over-block:** The gate fails closed on unknown price / stale lease / unbounded reservation — this can refuse legitimate metered calls when the manifest is stale or the pool is partitioned. Accepted BY DESIGN (money safety over availability); the refusal is a swap-tail advance to free doors, so the JOB still completes — only the paid door is withheld. Stale-price default is `book-conservative-max` (spend continues, over-booked), so manifest staleness alone does not halt paid routing.
|
|
22
|
+
2. **Under-block:** A crash between fsync'd row-append and totals update leaves totals STALE-LOW by at most one booking; the next gate read runs the row-count/high-water check and re-folds — bounded, tested both torn directions. Reserve sizing uses `max_tokens` worst-case; a provider that bills MORE than `max_tokens` output would under-reserve — mitigated by settle-books-actual (absolute row) and the billed-token per-door mapping erring HIGH. **Build-time finding worth recording:** the first gate implementation compared committed-vs-cap OUTSIDE the ledger's booking mutex; the two-concurrent-reserves test caught two $8 admits passing against a $12 cap. Fixed by moving the comparison INSIDE the per-key critical section (`admitOnlyUnderCaps` on `reserve()`), which is now the pinned shape — the check-and-reserve is atomic by construction.
|
|
23
|
+
3. **Level-of-abstraction fit:** The ledger/gate live in `src/core/` beside `DriftSpendLedger` (whose write-discipline they adopt); the caps store is a dedicated PIN-only store OUTSIDE config, exactly where the spec pins it (S-F2). The gate is a library consumed at the (future) metered dispatch seam — it does NOT modify `IntelligenceRouter` selection (the seam is declared, not wired; metered doors still skip — FD-11).
|
|
24
|
+
4. **Signal vs authority:** See Phase 1 — compliant; deterministic authority at the right layer, reporting side structurally excluded from the gate.
|
|
25
|
+
5. **Interactions:** Freeze vs go-live: freeze halts NEW admissions only (in-flight settles land — A-Min11). The reserve-expiry sweep takes the per-key lock; the (future) provider-reconciliation sweep never does — named distinctly to keep them un-conflated. Plan commits refuse on store-version drift, so a concurrent PIN action or background change re-renders instead of silently composing. The alert resolver is serving-lease-holder-only for creation; everyone else falls back to the lifeline — no duplicate-topic race with the attention queue (which is never used for spend alerts — Amendment 2).
|
|
26
|
+
6. **External surfaces:** New Bearer routes (plan render, freeze, caps log) and PIN routes (adjust, go-live, price promotion) — all 503 while dark. One new Telegram surface: the "💰 Routing & Spend Alerts" topic, created at most once by the serving-lease holder as a bounded create-once system topic; stale-price/drift alerts are edge-triggered + deduped. No third-party egress beyond Telegram.
|
|
27
|
+
7. **Multi-machine posture:** Money truth is SINGLE-WRITER by design in B (the whole cap on one PIN-designated metered-lease machine; FD-20); the gate self-fences (`lease-liveness-unconfirmed`) on a staleness window pinned shorter than the mesh-death threshold, so a partitioned old holder is $0-fenced before any reclaim can be offered — dual money authority structurally impossible. Caps view: committed figures are holder-known; non-holder reads proxy to the holder (wired when the pool read lands; in B-single-machine the local read IS the holder read, stated in the response). Topic-creation authority follows the SERVING lease (exists in every topology). The ledger/caps files are machine-local BY DESIGN on the designated holder; Increment D replicates slices, not files.
|
|
28
|
+
8. **Rollback cost:** Config-flag revert (dark) with zero data migration; ledger/caps files remain as inert audit history. A wrong booking can never be silently edited (append-only) — corrective action is a new row + the audit trail, never a rewrite.
|
|
29
|
+
|
|
30
|
+
## Phase 5 — Second-pass review
|
|
31
|
+
|
|
32
|
+
Required (money gate = "gate" class). Independent audit performed via a CROSS-MODEL reviewer — the gemini CLI (this build ran as a fork that cannot spawn subagents, so the skill's reviewer-subagent step was satisfied with a cross-model independent audit instead, which is stronger independence than a same-model subagent; codex is not installed on this machine and pi's non-interactive mode wedged on file attachments). Review scope: this artifact + `MeteredSpendGate.ts` in full, with the ledger/caps-store/plan-store contracts summarized; questions posed: fail-closed completeness, reporting-side reachability into the gate, Bearer-level money authority, signal-vs-authority compliance.
|
|
33
|
+
|
|
34
|
+
**Reviewer verdict:** `VERDICT: Concur with the review` (gemini, 2026-07-08)
|
|
35
|
+
|
|
36
|
+
## Self-action convergence (unbounded-self-action — closure: guard)
|
|
37
|
+
|
|
38
|
+
This change adds two self-triggered cadences, both registered as convergence models in `src/testing/selfActionRegistry.ts` and proven to settle by `tests/unit/self-action-convergence.test.ts` (the ratchet — enforcement: ratchet):
|
|
39
|
+
- **Reserve-expiry sweep** (`metered-reserve-expiry-sweep`): the idempotent terminal state machine IS the brake — an expired reserve can never re-expire and the sweep creates no reserves, so the steady-state emission bound is the finite stale pool (per-target 1, ever).
|
|
40
|
+
- **Stale-price alert** (`spend-stale-price-alert`): the 24h edge latch is the brake — a declared **Eternal Sentinel** with a 24h rate floor (stale pricing changes money ADMISSION behavior, so the alarm must re-arm daily while the condition persists; never a flood).
|
|
41
|
+
|
|
42
|
+
## No-deferrals accounting
|
|
43
|
+
|
|
44
|
+
Work sequenced to later PRs in this increment train (Layer 1c capture/reconciliation, Increment C channel abstraction + full emitter set, amortized-subscription display, scheduled web-research price checks) is tracked under the durable commitment CMT-1929 <!-- tracked: CMT-1929 --> and enumerated in `.instar/plans/money-increment-b-brief.md`; nothing in THIS PR's claimed scope is partial.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Side-Effects Review — Routing Control Room Increment C (alerts)
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/routing-control-room-spend-alerts.md` (review-convergence r7, approved, parent-principle: Token-Audit Completeness)
|
|
4
|
+
**Worktree:** `echo/money-increment-c` off `JKHeadley/main` @ `220ebeb0b` (v1.3.783 — contains the merged Increment B) — remotes + version verified per Phase 2.
|
|
5
|
+
**Scope of this PR:** Increment C — the channel-abstracted alert layer: `SpendAlertDispatcher` (lane-scoped dedup + coalescing BEFORE any channel send, edge latch on CONFIRMED delivery, dryRun-first, scrubbed jsonl audit), `TelegramSpendTopicChannel` (message-INTO the ONE dedicated topic; durable-relay path for money-critical kinds; lifeline fallback on ANY failure; audible repoint), the emitter set (cap-approach 50/80% on BOTH caps, cap-hit, door-dark with P19 brakes, fallback-spike rate detection, recon-drift surface, holder-dead surviving-voice), the router-signal fan-out (I-9, observer isolation), the gate event hook, and the POOL-PUBLISHED rung-2 topic record on the machine registry (FD-6). Layer 1c reconciliation (which feeds recon-drift) and the operator additions are the NEXT PRs (tracked: CMT-1929).
|
|
6
|
+
|
|
7
|
+
## Phase 1 — Principle check (signal-vs-authority)
|
|
8
|
+
|
|
9
|
+
**Does this change involve a decision point?** The alert layer decides WHETHER TO NOTIFY, never whether to act: it blocks no call, gates no money, and constrains no agent behavior. The only authority-shaped logic is the dispatcher's dedup/coalescing (bounded-notification-surface protection) — deterministic counters and latches whose worst failure is a suppressed or extra NOTIFICATION, never a suppressed action. The gate hook is signal-only (throw-swallowed; the gate's admit/refuse path is unchanged — unit-pinned). Compliant: signals feeding a notification surface, no brittle logic holding blocking authority.
|
|
10
|
+
|
|
11
|
+
## Phase 2 — Plan
|
|
12
|
+
|
|
13
|
+
- **Decision points touched:** none with blocking authority. The dispatcher's lanes (money-critical vs informational) govern coalescing only — both lanes land in the SAME dedicated topic (Amendment 2).
|
|
14
|
+
- **Existing detectors/authorities interacted with:** `SpendAlertResolver` (extended with the pool-published rung-2 read/publish — creation stays serving-lease-fenced); `MeteredSpendGate` (gains an OPTIONAL `onGateEvent` observer — throw-swallowed, never on the refusal path); `onNatureRoutePlan` (single callback → small fan-out preserving observer isolation, I-9); `PendingRelayStore` (money-critical alerts ride the existing durable relay via injected enqueue); `MachineIdentity` registry (a content-free numeric `routingSpendAlertTopicId` field riding the SAME authenticated registry-sync path as `lastKnownUrl`/`endpoints`).
|
|
15
|
+
- **Rollout (FD-16):** Increment C ships **dryRun-first live-on-dev** — `routingSpend.alerts.enabled` rides `resolveDevAgentGate` (dark on the fleet), and `alerts.dryRun` defaults TRUE even on dev (would-send lines to the scrubbed jsonl, nothing delivered) until a deliberate flip.
|
|
16
|
+
- **Rollback path:** additive + dev-gated; disabling `alerts.enabled` (or leaving dryRun) reverts to Increment-B behavior byte-for-byte (the B stale-price cadence keeps its direct resolver path when the dispatcher is absent). No state migration; the registry field is inert data.
|
|
17
|
+
|
|
18
|
+
## Phase 4 — Side-effects review
|
|
19
|
+
|
|
20
|
+
1. **Over-block:** N/A for actions. For notifications: the per-kind edge latch + coalescing can suppress a repeat alert while a condition persists — by design (Bounded Notification Surface / Near-Silent Notifications); money-critical kinds ride a DISTINCT dedupe lane so a flapping door's volume can never coalesce a cap-hit into a digest (S-F8).
|
|
21
|
+
2. **Under-block (missed notifications):** a transient send failure does NOT latch (stays eligible); money-critical kinds additionally fall back to the lifeline on ANY failure and ride the durable relay when wired — the failure mode is a DELAYED alert, never a silent drop. dryRun is the deliberate exception: it delivers nothing and says so in the jsonl (the FD-16 soak posture).
|
|
22
|
+
3. **Level-of-abstraction fit:** dispatcher/channel/emitters live beside the B modules in `src/core/`; the fan-out lives at the single existing `onNatureRoutePlan` wiring point; NO new routes (the read surface is the jsonl + existing /guards posture). The `/attention` queue is deliberately NOT used (Amendment 2 — topic-per-item is the flood the operator forbids).
|
|
23
|
+
4. **Signal vs authority:** compliant (Phase 1) — everything here is a signal consumer/notifier.
|
|
24
|
+
5. **Interactions:** the dispatcher subsumes B's resolver-direct emission for stale-price/observed-drift (single voice — no double-fire: the cadence now emits THROUGH the dispatcher when present); the fan-out preserves the env-gated console breadcrumb; the gate hook never fires on the ledger path (only post-verdict); holder-dead is the ONE named exception to holder-single-voice (A2-2) and is keyed `spend-holder-dead:<keyEpoch>` pool-wide so two survivors dedupe.
|
|
25
|
+
6. **External surfaces:** Telegram messages into the ONE dedicated topic (or lifeline). The registry field is content-free (a topic id number). No other egress.
|
|
26
|
+
7. **Multi-machine posture:** topic identity becomes genuinely pool-durable this increment (FD-6 rung 2): the auto-created id is persisted machine-locally AND published as a content-free field on the replicated machine registry, so a future serving-lease holder INHERITS the id instead of re-creating; creation stays serving-lease-holder-only + single-flight + fenced. holder-dead emission is explicitly a surviving-machine act (single-machine: strict no-op). Emitter state (latches, backoff, spike baselines) is machine-local BY DESIGN — each machine notifies for its own observations; the dedupe keys carry machineId where cross-machine duplication is possible (door-dark), and pool-wide keys where it is not acceptable (holder-dead).
|
|
27
|
+
8. **Rollback cost:** config-flag revert; no data migration; the jsonl and registry field are inert at rest.
|
|
28
|
+
|
|
29
|
+
## Phase 5 — Second-pass review
|
|
30
|
+
|
|
31
|
+
Required (touches messaging decisions — the dispatcher decides notification delivery). Cross-model independent audit; verdict appended below.
|
|
32
|
+
|
|
33
|
+
**Reviewer verdict:** `VERDICT: Concur with the review. The provided artifact and code demonstrate robust mechanisms against blocking actions, silent drops of money-critical alerts, and notification floods, while adhering to signal-vs-authority principles.` (gemini cross-model, 2026-07-08 — scope: this artifact + SpendAlertDispatcher + TelegramSpendTopicChannel in full; questions: blocking-vs-notify, silent-drop of money-critical, flood protection, signal-vs-authority)
|
|
34
|
+
|
|
35
|
+
## Self-action convergence (unbounded-self-action — closure: guard)
|
|
36
|
+
|
|
37
|
+
New self-triggered notifiers are registered as convergence models in `src/testing/selfActionRegistry.ts` and proven to settle by `tests/unit/self-action-convergence.test.ts` (enforcement: ratchet):
|
|
38
|
+
- **door-dark episode brakes** (`spend-door-dark-brakes`): max-attempts = chain length per episode bucket, widening backoff, flapping breaker → converges to the breaker bound under a permanently-dark chain.
|
|
39
|
+
- **fallback-spike digest** (`spend-fallback-spike`): steady-state fallback churn is jsonl-only; a digest line fires only on a rate-spike edge with a per-window latch → ≤1 digest per window under sustained churn (eternal-sentinel rate floor).
|
|
40
|
+
- **cap-approach thresholds** (`spend-cap-approach`): edge-triggered per (capKind, threshold, window) — at most 4 emissions per key per window (2 kinds × 2 thresholds), re-armed only by the window rolling.
|
|
41
|
+
|
|
42
|
+
## No-deferrals accounting
|
|
43
|
+
|
|
44
|
+
Layer 1c reconciliation (feeds recon-drift), the amortized-subscription display, and the web-research price checks are the next PRs of this increment train, tracked under CMT-1929 <!-- tracked: CMT-1929 --> and enumerated in `.instar/plans/money-increment-b-brief.md`; nothing in THIS PR's claimed scope is partial.
|