instar 1.3.324 → 1.3.325

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.324",
3
+ "version": "1.3.325",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-06T01:17:45.496Z",
5
- "instarVersion": "1.3.324",
4
+ "generatedAt": "2026-06-06T02:11:49.317Z",
5
+ "instarVersion": "1.3.325",
6
6
  "entryCount": 199,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,59 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The multi-machine live-tail streamer (the lease holder pushing recent conversation
9
+ content to the standby every 5s) rebuilt EVERY known topic's content on EVERY tick —
10
+ and the Telegram content provider resolved each rebuild by synchronously reading the
11
+ entire JSONL message log (up to 75,000 lines) per topic. On 2026-06-05 this blocked
12
+ the live echo server's event loop for 5–40s at a stretch (151 measured gaps >5s in
13
+ 10 minutes), which made outgoing mesh RPC timestamps stale, which made the standby
14
+ reject flushes (403), which the source hot-retried every tick — a self-amplifying
15
+ storm that read as "machine under extremely heavy load" and stuck Telegram delivery.
16
+ Four bounded mechanisms close it: (1) a per-topic monotonic content version in
17
+ `TelegramAdapter` lets `LiveTailSource` skip unchanged topics without building their
18
+ content; (2) `getTopicHistory` is served from an in-memory per-topic tail cache
19
+ (single-pass batch seed for all live topics, maintained on append, byte-equivalent
20
+ to a file scan — the handoff hash depends on that and it is test-pinned); (3) a
21
+ failed flush backs off exponentially (5s base doubling to a 5min cap) instead of
22
+ retrying at tick rate, while the handoff path explicitly forces past gate+backoff;
23
+ (4) a single flush's content is capped at 256KiB (matching the standby buffer's
24
+ own per-topic ceiling).
25
+
26
+ ## What to Tell Your User
27
+
28
+ If you run me on more than one machine: my server no longer freezes for seconds at
29
+ a time while keeping the other machine's copy of our conversations fresh, and the
30
+ two machines no longer talk themselves into retry storms when one of them stalls.
31
+ Messages stop getting stuck, and false "the other machine looks dead" machine-swaps
32
+ stop being triggered. Nothing about what the standby machine knows changes — it
33
+ just costs almost nothing to keep it current now.
34
+
35
+ ## Summary of New Capabilities
36
+
37
+ - Live-tail version gate: idle topics cost one Map lookup per tick instead of a
38
+ full message-log read (TelegramAdapter.getTopicContentVersion → LiveTailSource).
39
+ - Topic tail cache: getTopicHistory serves from memory after a one-time single-pass
40
+ seed — no per-call file reads (also speeds respawn history + handoff hashing).
41
+ - Flush failure backoff: a rejecting/unreachable standby is retried on an
42
+ exponential schedule (5s→5min), never hammered at tick rate; handoffs bypass it.
43
+ - Flush content cap: one flush ≤256KiB (freshest suffix kept) — matching what the
44
+ standby retains anyway.
45
+
46
+ ## Evidence
47
+
48
+ Live incident 2026-06-05 (topic "Resource Limitation Mitigation"): event loop gaps
49
+ measured via server.log timestamp deltas (151 gaps >5s in ~10min); `sample` traces
50
+ showed timer callbacks in giant live-tail payload serialization; Mini rejected
51
+ flushes `stale-timestamp` / live-tail flush 403s with "flush seq=1 not acknowledged
52
+ — will retry" + "content diverged — resending full tail" hot loops. Root cause in
53
+ code: `LiveTailSource.pushTick` → `getTopicContent` per topic per tick →
54
+ `TelegramAdapter.getTopicHistory` full `readFileSync` of the 75k-line JSONL per
55
+ call. Spec: `docs/specs/live-tail-event-loop-guards.md`. Tests: 32 green across
56
+ `LiveTailSource.test.ts` (gate/backoff/cap/force + all pre-existing delta pins),
57
+ `TelegramAdapter-topicTailCache.test.ts` (read-count spy pins + cache-vs-file byte
58
+ parity), `live-tail-version-gate-wiring.test.ts` (wiring integrity), updated
59
+ `handoff-sentinel-boot-wiring.test.ts` (force pin); tsc + full lint chain clean.
@@ -0,0 +1,77 @@
1
+ # Side-Effects Review — Live-Tail Event-Loop Guards
2
+
3
+ **Version / slug:** `live-tail-event-loop-guards`
4
+ **Date:** `2026-06-05`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `independent reviewer subagent — CONCUR (one non-blocking memory-retention fix + two doc-precision notes, all applied)`
7
+
8
+ ## Summary of the change
9
+
10
+ The multi-machine live-tail streamer (`LiveTailSource.pushTick`, every `liveTailPushRateMs`=5s while holding the lease) rebuilt EVERY known topic's content EVERY tick, and the Telegram content provider (`TelegramAdapter.getTopicHistory`) resolved each rebuild with a synchronous full read of the JSONL message log (≤75k lines) — measured on 2026-06-05 as 5–40s event-loop blocks that went on to stale mesh timestamps → standby 403s → tick-rate hot retries (a self-amplifying storm). This change adds: a per-topic monotonic content version (`TelegramAdapter.appendToLog` bump → `getTopicContentVersion`) consumed by `LiveTailSource` as an optional `getTopicVersion` dep so unchanged topics are skipped WITHOUT building content; an in-memory per-topic tail cache behind `getTopicHistory` (single-pass batch seed of all live topics, maintained on append, lazy per-topic fallback, reclaimed on `unregisterTopic`); exponential per-topic failure backoff (5s base ×2 → 5min cap) replacing tick-rate retries; a 256KiB per-flush content cap (freshest suffix); and a `{ force: true }` handoff path that bypasses gate+backoff (`handoffSentinelBootWiring`). Files: `LiveTailSource.ts`, `TelegramAdapter.ts`, `handoffSentinelBootWiring.ts`, one wiring line in `server.ts`, four test files.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `LiveTailSource.flushTopic` — **modify** — adds skip conditions (unchanged version / inside backoff window) and a size cap; the delta-accounting model (never advance `seq`/`streamed` on failure) is unchanged.
15
+ - `TelegramAdapter.getTopicHistory` — **modify (transparent)** — same results, served from memory; byte-parity with a fresh file scan is test-pinned (the handoff hash depends on it).
16
+ - `TelegramAdapter.appendToLog` — **modify (additive)** — bumps the version counter + maintains the cache before either persistence path; persistence behavior unchanged.
17
+ - `handoffSentinelBootWiring.pushTick` — **modify** — forces (gate/backoff bypass) so a handoff manifest can never silently drop a mid-backoff topic.
18
+ - `LiveTailSource` backoff ledger — **add** — a pure cadence suppressor; holds no authority over WHAT is eventually streamed.
19
+
20
+ ## 1. Over-block
21
+
22
+ **What legitimate inputs does this change reject that it shouldn't?**
23
+
24
+ Nothing is rejected — only deferred or trimmed, all bounded: (a) a topic mid-backoff delays its retry ≤5min (cleared instantly on any success; bypassed entirely by the handoff force path, so a planned handoff can never be starved by the window); (b) the version gate skips only topics whose version — bumped by the single funnel every logged message passes through — is unchanged, and an identical-content no-op records the version only after byte-comparing the real content; (c) the 256KiB cap trims a flush to its freshest suffix — the standby's `LiveTailBuffer` enforces its own independent per-topic byte cap, and the reviewer verified `getTail()` currently has no consumer (post-failover history is reconstructed from each machine's own message log, not the buffer), so trimmed middle bytes have no downstream reader today.
25
+
26
+ ## 2. Under-block
27
+
28
+ **What failure modes does this still miss?**
29
+
30
+ (a) The mesh-RPC clock-tolerance rejection itself is untouched (correct behavior; the defect was upstream). (b) Sync child-process spawns in OTHER timer ticks (reaper/backstop `execSync` paths) remain — separate subsystem, explicitly tracked as follow-up in the multi-machine loop-safety audit <!-- tracked: CMT-1109 --> (topic "Resource Limitation Mitigation"). (c) The backoff is per-topic, not per-peer: with many topics all failing against one dead peer, first-attempt cost is still N serializations once per window — bounded by the cap and far below tick-rate, acceptable. (d) `maxFlushBytes` counts UTF-16 chars, not UTF-8 bytes (≈3× under-count for heavily non-ASCII content) — sender-side cost bound only; the standby caps independently (doc-noted per reviewer).
31
+
32
+ ## 3. Level-of-abstraction fit
33
+
34
+ **Is this at the right layer?**
35
+
36
+ Yes. Each guard sits exactly where its cost is generated: the version signal lives in the adapter that owns message logging (the only component that KNOWS when content changes); the gate/backoff/cap live in the source that generates the serialization work; the cache lives behind the existing `getTopicHistory` contract (every consumer — respawn history, handoff hash, live-tail — speeds up without knowing). No new authority, no new config surface, no parallel mechanism duplicating the standby buffer's own caps. The backoff mirrors the established suppressor shape (`AgeKillBackoff`, `AttentionTopicGuard`): pure logic, injectable clock, bounded state.
37
+
38
+ ## 4. Signal vs authority compliance
39
+
40
+ **Required reference:** `docs/signal-vs-authority.md`
41
+
42
+ - [x] No — this change produces/consumes signals (a change counter, a "skip until" window, a size bound) inside an existing data-freshness pipeline; it holds NO authority over sessions, leases, kills, or message delivery.
43
+
44
+ The version counter cannot suppress a real change (every logged message bumps it; the reviewer traced all ingress/egress paths through `appendToLog`). The backoff cannot drop content (state never advances on failure; the owed delta sends when the window opens or a handoff forces). The cap cannot starve the standby (its own buffer cap is lower-or-equal in practice). Lease/handoff authority is untouched.
45
+
46
+ ## 5. Interactions
47
+
48
+ - **Handoff hash parity (the critical one):** both machines hash their OWN `getTopicHistory(topic, 500)`. The cache stores the same entry objects the file receives, in the same order (append updates both in one synchronous call; the seed reads file order). Reviewer confirmed `hashTopicHistory` reads only `timestamp` + `text` (primitive strings in every shape) and ran the real two-server `planned-handoff-e2e` — including the hash-mismatch abort path — green through the cache.
49
+ - **Version capture timing:** `version` is captured in `flushTopic` together with the content build (no `await` between them) and recorded only on success — a message arriving during the `await broadcast` bumps the live counter ABOVE the recorded snapshot, so the next tick correctly re-opens. (Reviewer probed this specifically.)
50
+ - **Double-fire / races:** the cache+counter are plain in-process Maps touched only from synchronous adapter calls; `seedTailCacheFromLog` is fully synchronous (no event-loop yield between read and `tailCacheSeeded=true`), so no append can fall between seed-read and cache-live. Appends during a broadcast `await` land in the seeded cache and bump the version consistently.
51
+ - **Feedback loops:** the change only REMOVES a feedback loop (frozen loop → stale timestamps → rejects → hot retries → more freeze). Skipping/deferring work cannot trigger more work.
52
+ - **Shadowing:** force on the handoff path ensures the new skip conditions can never shadow the handoff manifest's freshness requirement (test-pinned `pushTick:force`).
53
+
54
+ ## 6. External surfaces
55
+
56
+ - **Other agents / users:** behavior ships fleet-wide on update; observable change is fewer event-loop stalls, fewer mesh 403 storms, fewer log lines ("not acknowledged — will retry" now carries a backoff horizon, plus capped/truncation lines). No API, schema, or message-shape change.
57
+ - **External systems:** none beyond the existing machine-to-machine live-tail channel (same wire format, same encryption; only cadence + max size change).
58
+ - **Persistent state:** none. Counter + cache + backoff ledger are in-memory; nothing written to disk; no migration. (Migration Parity: nothing to migrate — defaults live in code.)
59
+ - **Timing:** standby copy freshness for a FAILING peer degrades from "retry every 5s" to "retry ≤5min" — the deliberate trade; a HEALTHY peer's freshness is unchanged (new content still flushes on the next tick).
60
+
61
+ ## 7. Rollback cost
62
+
63
+ Pure in-process code change. Revert the commit and ship a patch. No persistent state to clean, no config to unwind, no schema. Per-component soft-disable exists naturally for tests (omit `getTopicVersion` → pre-fix gating behavior); no operator dial is exposed deliberately — the pre-fix behavior is a measured pathology with no legitimate operating point.
64
+
65
+ ## Conclusion
66
+
67
+ The review confirmed the three invariants that matter: (a) the standby converges to the SAME content as before — only the cost/cadence of getting there changes (handoff-hash parity test-pinned and e2e-verified); (b) no authority moved — every new mechanism is a bounded cadence/size signal inside an existing pipeline; (c) rollback is a trivial revert with zero persistent state. One reviewer finding changed code: `unregisterTopic` now reclaims the topic's tail cache (long-lived servers churning topics must not retain ≤500 entries per topic forever) — the version counter is deliberately retained (~8 bytes) to avoid a re-registration version-collision against `lastSeenVersion` snapshots.
68
+
69
+ ---
70
+
71
+ ## Phase 5 — Second-pass review (cross-machine continuity change → performed)
72
+
73
+ An independent reviewer subagent audited the staged diff, the final files, the spec, and the wiring at line level, specifically probing: handoff-hash divergence through the cache (including entry order, JSON round-trip shape, and the `channelId` compat path), version-gate miss scenarios (other writers, rotation, multi-instance), cache coherence windows, backoff-vs-gate retry semantics and version-capture timing, content-cap interaction with the delta model and `LiveTailBuffer` semantics, memory bounds, the force chain, and ran the unit + neighbor + real two-server handoff e2e suites (all green, tsc clean).
74
+
75
+ **Verdict: CONCUR.** No blocking defects. Three non-blocking findings, all addressed: (1) `unregisterTopic` did not reclaim the tail cache → fixed (cache deleted on unregister; counter deliberately kept, with the collision rationale documented inline); (2) the `maxFlushBytes` chars-vs-bytes approximation overstated "matching the standby ceiling" → comment and this artifact corrected to "sender-side cost bound; standby caps independently"; (3) the cache-vs-file parity test is stricter than the hash requires and would read as a false regression if the shared-MessageLogger flag ever flips → scope-noted in the test.
76
+
77
+ **Post-CI note (same day):** shard-3 ratchet (no-silent-fallbacks) flagged the seed's per-line malformed-JSONL skip (460 > 459 baseline); annotated `@silent-fallback-ok` inside the catch per the report-or-exempt rule — identical deliberate behavior to the scan path's long-standing skip.