instar 1.3.110 → 1.3.111
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 +20 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/SessionWatchdog.d.ts +27 -4
- package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
- package/dist/monitoring/SessionWatchdog.js +52 -30
- package/dist/monitoring/SessionWatchdog.js.map +1 -1
- package/dist/monitoring/rateLimitDetection.d.ts +51 -3
- package/dist/monitoring/rateLimitDetection.d.ts.map +1 -1
- package/dist/monitoring/rateLimitDetection.js +61 -4
- package/dist/monitoring/rateLimitDetection.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.111.md +129 -0
- package/upgrades/NEXT.eli16.md +69 -60
- package/upgrades/side-effects/real-world-state-fixture-framework.md +137 -0
- package/upgrades/side-effects/throttle-settled-detection.md +70 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Side-effects review — Real-World-State fixture framework + first scenario (post-mortem lever B)
|
|
2
|
+
|
|
3
|
+
## What changed
|
|
4
|
+
|
|
5
|
+
A new test category `tests/real-world-state/` for scenarios that exercise
|
|
6
|
+
instar against state that LOOKS LIKE a real production agent — not the
|
|
7
|
+
small fresh-fixture state the rest of the suite uses.
|
|
8
|
+
|
|
9
|
+
- **`tests/real-world-state/_framework.ts`** — fixture-loader helpers,
|
|
10
|
+
tier system, agent-fixture builder.
|
|
11
|
+
- **`tests/real-world-state/externalized-config-boot.test.ts`** — the
|
|
12
|
+
first scenario (5 tests). Targets the post-mortem's #542 incident
|
|
13
|
+
class: `loadConfig()` MUST merge the externalized authToken back from
|
|
14
|
+
the secret store, or every downstream consumer that doesn't go
|
|
15
|
+
through `loadConfig()` silently 403s.
|
|
16
|
+
- **`vitest.config.ts`** — the new directory wired into the
|
|
17
|
+
`include` array (alongside unit / integration / e2e).
|
|
18
|
+
|
|
19
|
+
The framework introduces a **two-tier system**:
|
|
20
|
+
|
|
21
|
+
- **'pr' tier** — small fixtures (< ~5 MB, < ~30s setup). Runs on
|
|
22
|
+
every PR / every CI shard. The first scenario lives here.
|
|
23
|
+
- **'nightly' tier** — large fixtures (multi-100MB DBs, generated
|
|
24
|
+
JSONL volumes, environment-specific shapes like wrong-ABI binaries).
|
|
25
|
+
Gated on `INSTAR_REAL_WORLD_BIG=1` env. Default OFF; default behavior
|
|
26
|
+
is `describe.skip` with a clear "skipped (set ... to run)" message so
|
|
27
|
+
the coverage gap is visible, not silently absent.
|
|
28
|
+
|
|
29
|
+
Helper API:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { describeAtTier, makeAgentFixture } from './_framework.js';
|
|
33
|
+
|
|
34
|
+
describeAtTier('pr', 'scenario name', () => {
|
|
35
|
+
let fx;
|
|
36
|
+
afterEach(() => fx?.cleanup());
|
|
37
|
+
it('asserts X about Y', () => {
|
|
38
|
+
fx = makeAgentFixture();
|
|
39
|
+
// fx.projectDir + fx.stateDir are a real on-disk agent home shape
|
|
40
|
+
// … set up scenario, run code-under-test, assert
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Why
|
|
46
|
+
|
|
47
|
+
Per the 2026-05-29 pipeline post-mortem (PR #545), pattern #1 — "Tested
|
|
48
|
+
on fresh state, not real-world state" — was the **largest** of the five
|
|
49
|
+
named bug classes. Bugs in this class shipped in the last 2 weeks:
|
|
50
|
+
- #534 (TokenLedger backfill bricked boot at 202MB; fixture was tiny).
|
|
51
|
+
- #512 (TokenLedger schema-order died on pre-existing DB but passed on
|
|
52
|
+
fresh).
|
|
53
|
+
- #509 (heal-then-heal failed; heal-once passed).
|
|
54
|
+
- #501 (SleepWakeDetector false-wake under load).
|
|
55
|
+
- #503 (PresenceProxy redundant after ack).
|
|
56
|
+
- And the originator of this whole arc — #542 (silent-403 secret
|
|
57
|
+
externalization). The in-process Node side's `loadConfig()` wasn't
|
|
58
|
+
exercised against the externalized shape; only the SHELL hook/script
|
|
59
|
+
side was, by PR #542's tests.
|
|
60
|
+
|
|
61
|
+
Lever B is the only post-mortem lever that closes the broader pattern
|
|
62
|
+
rather than a specific incident shape. The other four (#542, #545,
|
|
63
|
+
#550, #551, #552) each closed individual classes; this one builds the
|
|
64
|
+
infrastructure to catch the class FAMILY going forward.
|
|
65
|
+
|
|
66
|
+
## Risk surface
|
|
67
|
+
|
|
68
|
+
- **Test-only change.** No runtime code modified. Existing test suites
|
|
69
|
+
unchanged.
|
|
70
|
+
- **vitest.config.ts include list extended** — additive only; the three
|
|
71
|
+
existing entries (unit / integration / e2e) remain unchanged. CI
|
|
72
|
+
shards will pick up the new path automatically.
|
|
73
|
+
- **PR-tier fixture is tiny.** The externalized-config-boot scenario
|
|
74
|
+
writes a ~1KB JSON file and runs the SecretMigrator + SecretStore
|
|
75
|
+
paths. Test runtime: ~200ms. CI impact: negligible.
|
|
76
|
+
- **Nightly tier defaults OFF.** No big-fixture cost on PR runs until
|
|
77
|
+
someone deliberately opts in. The skip is loud (`describe.skip` with
|
|
78
|
+
a message), not silent — the coverage gap is visible in every CI
|
|
79
|
+
report.
|
|
80
|
+
- **No fixture generation in this PR.** The first scenario uses
|
|
81
|
+
in-test fixture builders (write JSON file, run SecretMigrator). When
|
|
82
|
+
the nightly tier ships its first scenario (multi-100MB DB → catches
|
|
83
|
+
#534), it will need a `scripts/build-real-world-fixtures.mjs`
|
|
84
|
+
generator + a git-ignored `tests/fixtures/real-world-state/` cache.
|
|
85
|
+
Deferred to that PR.
|
|
86
|
+
|
|
87
|
+
## Bug surfaces eliminated
|
|
88
|
+
|
|
89
|
+
- A future regression in `mergeConfigWithSecrets` (or the call from
|
|
90
|
+
`Config.ts:loadConfig`) that re-introduces the placeholder-leak shape
|
|
91
|
+
fails the 5 new tests with clear messages. Verified destructive-
|
|
92
|
+
negative: disabling the merge call in `Config.ts` trips 4 of the 5
|
|
93
|
+
scenario tests with the exact failure modes the bug class produced.
|
|
94
|
+
- The full-shape sub-test asserts that telegram token, telegram chatId,
|
|
95
|
+
dashboard PIN, AND tunnel token all merge — not just authToken. A
|
|
96
|
+
future externalization that adds a new secret field without wiring
|
|
97
|
+
the merge fails immediately.
|
|
98
|
+
- The idempotency sub-test asserts that re-running pairing on an
|
|
99
|
+
already-externalized agent doesn't double-extract or lose state.
|
|
100
|
+
|
|
101
|
+
## Testing
|
|
102
|
+
|
|
103
|
+
- **5 tests, all green** on the current main:
|
|
104
|
+
- Disk shape sanity (post-pairing layout).
|
|
105
|
+
- `loadConfig()` returns merged real authToken (the headline check).
|
|
106
|
+
- `loadConfig()` never leaks the placeholder shape (regression).
|
|
107
|
+
- Full-shape merge (telegram + dashboard + tunnel + auth).
|
|
108
|
+
- Idempotency on re-run.
|
|
109
|
+
- **Tier-gating verified both directions**. With `INSTAR_REAL_WORLD_BIG`
|
|
110
|
+
unset, `nightly`-tier blocks skip with a visible message; with it set
|
|
111
|
+
to `1`, they execute. Verified with a sentinel test that
|
|
112
|
+
intentionally fails — proves the gating respects the env.
|
|
113
|
+
- **Destructive-negative verified**. Disabling
|
|
114
|
+
`mergeConfigWithSecrets()` in `Config.ts` fails 4 of 5 scenario tests
|
|
115
|
+
with the failure messages the bug class produced
|
|
116
|
+
(`{ secret: true }` returned as authToken, telegram token leaks the
|
|
117
|
+
placeholder, etc.).
|
|
118
|
+
- **No regression in existing related tests** —
|
|
119
|
+
`secret-migrator.test.ts` (40), `secret-store.test.ts` (presumed
|
|
120
|
+
green), `config-secret-merge.test.ts` (4) all still pass.
|
|
121
|
+
- `tsc --noEmit` clean.
|
|
122
|
+
|
|
123
|
+
## Follow-ups
|
|
124
|
+
|
|
125
|
+
- **First nightly-tier scenario** — multi-100MB token-ledger.db boot
|
|
126
|
+
test that would have caught #534. Needs a fixture generator
|
|
127
|
+
(`scripts/build-real-world-fixtures.mjs`) and a git-ignored cache
|
|
128
|
+
(`tests/fixtures/real-world-state/`). Separate PR.
|
|
129
|
+
- **Wrong-ABI better-sqlite3 scenario** — catches #539's class. Needs
|
|
130
|
+
the framework to support "environment fixtures" (specific Node + ABI
|
|
131
|
+
combos), which is a bigger design step.
|
|
132
|
+
- **Concurrency-at-scale scenarios** — multi-job + multi-session under
|
|
133
|
+
restart-during-tick. Bigger design step (probably needs a tmux
|
|
134
|
+
harness).
|
|
135
|
+
|
|
136
|
+
This PR ships the FRAMEWORK + ONE scenario as a working backstop.
|
|
137
|
+
Subsequent PRs add scenarios at the same incremental pace.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Side-Effects Review — settled-throttle detection (RateLimitSentinel actually fires)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `throttle-settled-detection`
|
|
4
|
+
**Date:** `2026-05-30`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Driving spec:** `docs/specs/rate-limit-sentinel.md` (§Settled-throttle detection — the live fix)
|
|
7
|
+
**Second-pass reviewer:** `not required` (single-author behavioral fix to an already-shipped sentinel; no new external surface)
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
The RateLimitSentinel (shipped PR #528, live at v1.3.108) was built, wired, and
|
|
12
|
+
enabled but fired **zero** times in the field — throttled sessions hung 5–10
|
|
13
|
+
minutes until the 15-minute silence fallback. Root cause was the detection
|
|
14
|
+
preconditions in `SessionWatchdog.checkRateLimited`, not the recovery machinery:
|
|
15
|
+
it required a session be "cleanly idle, no active child processes, at a prompt,
|
|
16
|
+
throttle string in the last 20 lines." Busy dev sessions never satisfy that —
|
|
17
|
+
they have a live background shell/MCP process, and Claude Code's input box pushes
|
|
18
|
+
the `API Error:` line out of the 20-line window.
|
|
19
|
+
|
|
20
|
+
The fix replaces those gates with a **settled-output signal**: throttle string in
|
|
21
|
+
a widened 45-line window + pane byte-identical across two consecutive watchdog
|
|
22
|
+
polls (an actively-working session animates its spinner/elapsed-timer every tick,
|
|
23
|
+
so a frozen pane is a reliable "turn ended, stuck" signal). No process-tree
|
|
24
|
+
inspection, no at-prompt heuristic. After a recovery cycle escalates and clears
|
|
25
|
+
(~30s), a still-stuck pane re-emits past the 60s cooldown → **unbounded retry**
|
|
26
|
+
until the throttle lifts (the "never hang forever" guarantee Justin asked for).
|
|
27
|
+
|
|
28
|
+
## Files touched
|
|
29
|
+
|
|
30
|
+
- `src/monitoring/rateLimitDetection.ts` — `detectRateLimited` gains an optional
|
|
31
|
+
`captureLines` param (default 20, unchanged for existing callers); new
|
|
32
|
+
`RATE_LIMIT_SETTLED_CAPTURE_LINES=45`, `RATE_LIMIT_DEFAULT_SETTLE_MS=20000`,
|
|
33
|
+
`throttleSignature()`, and the pure clock-injected `evaluateThrottleSettle()`.
|
|
34
|
+
- `src/monitoring/SessionWatchdog.ts` — `checkRateLimited` rewritten to the
|
|
35
|
+
settled-output decision; removed the active-child-process and at-prompt gates;
|
|
36
|
+
added `throttleSettle` tracking map + `rateLimitSettleMs` (config-read).
|
|
37
|
+
- `src/core/types.ts` — `monitoring.watchdog.rateLimitSettleMs?: number`.
|
|
38
|
+
- `src/commands/server.ts` — wire RateLimitSentinel lifecycle events
|
|
39
|
+
(detected/resuming/recovered/escalated) into `logs/sentinel-events.jsonl`.
|
|
40
|
+
- `tests/unit/rate-limit-detection.test.ts` — +16 cases (widened-window
|
|
41
|
+
regression reproducing the incident; throttleSignature; evaluateThrottleSettle
|
|
42
|
+
both sides of every branch).
|
|
43
|
+
- `tests/unit/SessionWatchdog-rate-limit-settle.test.ts` — new, 8 wiring cases.
|
|
44
|
+
- `upgrades/NEXT.md`, `docs/specs/rate-limit-sentinel.md` (+eli16) — docs.
|
|
45
|
+
|
|
46
|
+
## Decision-point inventory
|
|
47
|
+
|
|
48
|
+
- **Stuck-vs-working discriminator** — *modify*. Was process-tree + at-prompt +
|
|
49
|
+
20-line window; is now byte-identical-pane across polls + 45-line window. Pure
|
|
50
|
+
function (`evaluateThrottleSettle`), no judgment, fully unit-tested.
|
|
51
|
+
- **Widened capture window (45 lines)** — *new, bounded*. Only the watchdog
|
|
52
|
+
settled path uses it; `detectRateLimited`'s default stays 20 for every other
|
|
53
|
+
caller (SessionManager idle path unchanged). Old-throttle-scrolled-away and
|
|
54
|
+
usage-limit/mid-retry exclusions still hold at 45 lines (tested).
|
|
55
|
+
- **Unbounded re-emit after escalation** — *behavioral, intentional*. Bounded by
|
|
56
|
+
the 60s watchdog cooldown + the sentinel's own dedupe; the throttle is
|
|
57
|
+
Anthropic's short-lived shared-capacity signal, so cycles are few in practice.
|
|
58
|
+
This is the load-bearing "never hang forever" property.
|
|
59
|
+
- **Audit-log wiring** — *additive observability*. Best-effort `appendFileSync`
|
|
60
|
+
to the existing sentinel-events.jsonl; annotated `@silent-fallback-ok` (never
|
|
61
|
+
crashes the monitor path). No new route, no new external surface.
|
|
62
|
+
- **`rateLimitSettleMs` config** — *additive, back-compat*. Optional, code
|
|
63
|
+
default 20000; configs without it are unaffected → no migration required.
|
|
64
|
+
|
|
65
|
+
## Blast radius / rollback
|
|
66
|
+
|
|
67
|
+
Pure monitoring-path change; no API routes, no schema, no state-file format
|
|
68
|
+
change. The `RateLimitSentinel.enabled` master switch (existing) disables the
|
|
69
|
+
whole path. Reverting this commit restores the prior gates verbatim. Existing
|
|
70
|
+
121 rate-limit/watchdog tests pass unchanged (event contract preserved).
|