instar 1.3.980 → 1.3.981

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.
@@ -1 +1 @@
1
- {"version":3,"file":"dashboardInsightCollectors.d.ts","sourceRoot":"","sources":["../../src/monitoring/dashboardInsightCollectors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAiC,MAAM,6BAA6B,CAAC;AAQhH;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,gBAAgB,CA0C9F;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,oBAAoB,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACpD,GAAG,WAAW,EAAE,CAWhB"}
1
+ {"version":3,"file":"dashboardInsightCollectors.d.ts","sourceRoot":"","sources":["../../src/monitoring/dashboardInsightCollectors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAiC,MAAM,6BAA6B,CAAC;AAQhH;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,gBAAgB,CAwE9F;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,oBAAoB,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACpD,GAAG,WAAW,EAAE,CAWhB"}
@@ -48,19 +48,47 @@ export function buildLlmActivityCollector(ledger) {
48
48
  }
49
49
  }
50
50
  anomalies.sort((a, b) => (b.severity === 'alert' ? 1 : 0) - (a.severity === 'alert' ? 1 : 0));
51
- const overallErrorRate = totalReal > 0 ? totalErrors / totalReal : 0;
51
+ // MIN_REAL_CALLS is declared at the top of this file as "minimum real calls
52
+ // before an error-rate is statistically meaningful" and the per-feature loop
53
+ // above honours it. The AGGREGATE did not: with zero calls it rendered
54
+ // `Error rate: 0%` and asserted "Routing is healthy — 0 checks ran", so an
55
+ // empty 24 hours was indistinguishable from a clean one. Same file, same
56
+ // constant, same reasoning — just applied to the aggregate too.
57
+ const overallErrorRate = totalReal > 0 ? totalErrors / totalReal : null;
52
58
  const metrics = [
53
59
  { label: 'LLM calls (24h)', value: String(totalReal) },
54
60
  { label: 'Errors (24h)', value: String(totalErrors) },
55
- { label: 'Error rate', value: `${Math.round(overallErrorRate * 100)}%` },
61
+ {
62
+ label: 'Error rate',
63
+ // A rate over zero calls is not a rate. Show why it is absent, not a "0%"
64
+ // a reader cannot tell from a measured zero.
65
+ value: overallErrorRate === null ? 'no calls yet' : `${Math.round(overallErrorRate * 100)}%`,
66
+ },
56
67
  { label: 'Checks active', value: String(features.length) },
57
68
  ];
58
69
  const facts = [];
59
- if (anomalies.length === 0) {
60
- facts.push(`Routing is healthy ${features.length} checks ran with no check failing a meaningful share of its calls.`);
70
+ // Order matters, and BAD NEWS COMES FIRST unconditionally. Today an anomaly
71
+ // cannot occur below the floor (a feature needs >= MIN_REAL_CALLS to flag, so
72
+ // totalReal < MIN_REAL_CALLS implies none), but that is an arithmetic
73
+ // coincidence rather than a guarantee — if the constants ever diverge, a
74
+ // low-volume window must still surface a real failure. The floor gates only
75
+ // the reassuring conclusion, never a warning.
76
+ if (anomalies.length > 0) {
77
+ facts.push(`${anomalies.length} check${anomalies.length === 1 ? '' : 's'} ${anomalies.length === 1 ? 'is' : 'are'} failing more than usual and worth a look.`);
78
+ }
79
+ else if (totalReal === 0) {
80
+ // Nothing ran. Say so, rather than claiming health from an empty window.
81
+ facts.push(features.length === 0
82
+ ? 'No checks have reported in the last 24 hours, so there is nothing to judge yet.'
83
+ : `${features.length} check${features.length === 1 ? '' : 's'} are configured but none made a call in the last 24 hours, so there is nothing to judge yet.`);
84
+ }
85
+ else if (totalReal < MIN_REAL_CALLS) {
86
+ // The rate above is real data, but the volume is too low to call it healthy —
87
+ // the same asymmetry the per-feature loop already applies.
88
+ facts.push(`Only ${totalReal} call${totalReal === 1 ? '' : 's'} in the last 24 hours — too few to say whether routing is healthy.`);
61
89
  }
62
90
  else {
63
- facts.push(`${anomalies.length} check${anomalies.length === 1 ? '' : 's'} ${anomalies.length === 1 ? 'is' : 'are'} failing more than usual and worth a look.`);
91
+ facts.push(`Routing is healthy ${features.length} checks ran with no check failing a meaningful share of its calls.`);
64
92
  }
65
93
  if (busiest && busiest.realCalls > 0)
66
94
  facts.push(`The busiest check is ${busiest.feature} (${busiest.realCalls} calls in 24h).`);
@@ -1 +1 @@
1
- {"version":3,"file":"dashboardInsightCollectors.js","sourceRoot":"","sources":["../../src/monitoring/dashboardInsightCollectors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dashboardInsightCollectors.js","sourceRoot":"","sources":["../../src/monitoring/dashboardInsightCollectors.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.980",
3
+ "version": "1.3.981",
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-07-26T04:26:53.883Z",
5
- "instarVersion": "1.3.980",
4
+ "generatedAt": "2026-07-26T04:54:53.613Z",
5
+ "instarVersion": "1.3.981",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,81 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ **The LLM Activity dashboard panel claimed "Routing is healthy" when nothing had run — and in one
9
+ case displayed `Error rate: 50%` directly above that claim.**
10
+
11
+ `dashboardInsightCollectors.buildLlmActivityCollector` computed
12
+ `overallErrorRate = totalReal > 0 ? totalErrors / totalReal : 0`, rendered it as `Error rate: 0%`, and
13
+ pushed a health fact whenever the anomaly list was empty:
14
+
15
+ "Routing is healthy — 0 checks ran with no check failing a meaningful share of its calls."
16
+
17
+ With an empty 24-hour window that sentence claimed health from no observations, its own denominator
18
+ ("0 checks ran") contradicting its claim. The sharper failure: the per-feature anomaly loop correctly
19
+ requires `f.realCalls >= MIN_REAL_CALLS` before flagging a check, but the health sentence had no such
20
+ condition — so a window with 2 calls where 1 errored rendered `Error rate: 50%` beside "Routing is
21
+ healthy". Both halves were individually defensible; the verdict was simply never told about the
22
+ threshold the alarm was already using.
23
+
24
+ `MIN_REAL_CALLS = 5` is declared at the top of that same file, commented "minimum real calls before an
25
+ error-rate is statistically meaningful". This applies it to the aggregate:
26
+
27
+ - `overallErrorRate` is `null` at zero calls; the metric renders **`no calls yet`** rather than a `0%`
28
+ indistinguishable from a measured zero. The call count and check count still travel beside it.
29
+ - Zero calls → a fact saying there is nothing to judge yet (distinguishing "no checks reported at all"
30
+ from "checks configured but idle").
31
+ - Below the floor → the real rate is still shown, but the verdict is withheld: "Only N calls in the
32
+ last 24 hours — too few to say whether routing is healthy."
33
+ - **Anomalies are evaluated FIRST and unconditionally**, so no evidence caveat can ever displace a
34
+ warning. Today an anomaly cannot occur below the floor, but that is an arithmetic coincidence
35
+ between two constants rather than a guarantee, and the ordering makes it structural.
36
+
37
+ Verified before/after on the built collector: nothing-ran went from `0%` + "Routing is healthy" to
38
+ `no calls yet` + "nothing to judge yet"; 2 calls with 1 error went from `50%` + "Routing is healthy" to
39
+ `50%` + "too few to say"; 10 clean calls still reads "Routing is healthy" unchanged; 6 calls with 3
40
+ errors still surfaces the failing check.
41
+
42
+ A grammar fix falls out of the plural handling: "1 checks are failing" → "1 check is failing".
43
+
44
+ ## What to Tell Your User
45
+
46
+ Nothing to do. On most installs this panel ships dark, so there is nothing visible.
47
+
48
+ Where it runs, the panel is now honest about a quiet window: it says there is nothing to judge yet
49
+ rather than reporting a flawless zero. Read that as "nothing is wrong and nothing is known" — it is
50
+ deliberately not an alarm. Genuine failures are still reported exactly as promptly as before, and are
51
+ now always reported ahead of any note about low call volume.
52
+
53
+ ## Summary of New Capabilities
54
+
55
+ - The LLM Activity panel distinguishes "no calls observed" from "no errors observed": the error rate
56
+ reads `no calls yet` instead of `0%`, with the call count beside it.
57
+ - A health verdict now requires enough calls to support it, reusing the file's own existing
58
+ `MIN_REAL_CALLS` threshold rather than a new one.
59
+ - A real failure is surfaced ahead of any low-evidence caveat, by branch order rather than by luck.
60
+
61
+ ## Evidence
62
+
63
+ **Reproduction (before):** build the collector over an empty ledger and read the snapshot. Observed:
64
+ `Error rate: 0%` and the fact `"Routing is healthy — 0 checks ran with no check failing a meaningful
65
+ share of its calls."` Then seed 2 calls with 1 error: observed `Error rate: 50%` together with
66
+ `"Routing is healthy — 1 checks ran…"`.
67
+
68
+ **Observed after,** same four scenarios against the built collector:
69
+
70
+ | scenario | before | after |
71
+ |---|---|---|
72
+ | nothing ran (0 calls, 0 checks) | `0%` + "Routing is healthy — 0 checks ran…" | `no calls yet` + "No checks have reported in the last 24 hours, so there is nothing to judge yet." |
73
+ | 2 calls, 1 error (below floor) | `50%` + "Routing is healthy — 1 checks ran…" | `50%` + "Only 2 calls in the last 24 hours — too few to say whether routing is healthy." |
74
+ | 10 clean calls | `0%` + "Routing is healthy" | `0%` + "Routing is healthy" — unchanged |
75
+ | 6 calls, 3 errors | `50%` + "1 checks are failing…" | `50%` + "1 check is failing more than usual and worth a look." |
76
+
77
+ **Tests:** `tests/unit/dashboard-insight-collectors-unobserved.test.ts` — four cases covering
78
+ no-rate-and-no-health-claim at zero calls, real-rate-with-verdict-withheld below the floor,
79
+ health-claimed-once-supported, and a real failure surfacing ahead of any caveat. The existing
80
+ `tests/e2e/insights-alive.test.ts` seeds 6 calls above the floor and passes unchanged. 7 tests pass
81
+ across both files; `npx tsc --noEmit` clean.
@@ -0,0 +1,141 @@
1
+ # Side-Effects Review — the dashboard showed "Error rate: 50%" beside "Routing is healthy"
2
+
3
+ **Version / slug:** `dashboard-error-rate-unobserved`
4
+ **Date:** `2026-07-26`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `author-applied lenses — see Phase 5 (reduced independence, disclosed)`
7
+
8
+ ## Summary of the change
9
+
10
+ The LLM Activity insight collector computed `overallErrorRate = totalReal > 0 ? totalErrors/totalReal : 0`
11
+ and rendered it as `Error rate: 0%`, then asserted a health fact whenever no anomaly had been flagged:
12
+
13
+ "Routing is healthy — 0 checks ran with no check failing a meaningful share of its calls."
14
+
15
+ Two failures fell out of that. With an empty 24-hour window the panel claimed health from no data —
16
+ and the sentence's own denominator ("0 checks ran") contradicted its claim. Worse, with a genuine
17
+ failure below the anomaly floor the panel showed **`Error rate: 50%` beside "Routing is healthy"**,
18
+ because the per-feature loop correctly required `f.realCalls >= MIN_REAL_CALLS` before flagging while
19
+ the health sentence had no such condition.
20
+
21
+ `MIN_REAL_CALLS = 5` is declared at the top of this same file, commented "minimum real calls before
22
+ an error-rate is statistically meaningful". The fix applies that existing, already-honoured constant
23
+ to the aggregate: the rate is `null` (rendered "no calls yet") at zero calls, a sub-floor window says
24
+ so plainly, and **a real failure is surfaced ahead of any evidence caveat**.
25
+
26
+ ## Decision-point inventory
27
+
28
+ | point | classification | note |
29
+ |---|---|---|
30
+ | the `facts` branch order | `invariant` | Deterministic. Anomalies are checked FIRST and unconditionally, so no caveat can displace a warning. |
31
+ | the sub-floor caveat | `invariant` | A comparison against the file's existing `MIN_REAL_CALLS`. Gates only the reassuring conclusion. |
32
+ | `Error rate` rendering | `invariant` | `null` at zero calls → a string saying why, never a computed default. |
33
+
34
+ ## 1. Over-block
35
+
36
+ **What legitimate inputs does this change reject that it shouldn't?**
37
+
38
+ Nothing is blocked — this is a read-only dashboard collector with no gating authority. The analogue
39
+ is **withholding a health claim from a genuinely healthy window**: a window with 1–4 clean calls now
40
+ reads "too few to say" instead of "healthy". Bounded, and the real rate is still displayed beside the
41
+ call count so the reader can judge for themselves. At 5+ calls the claim returns unchanged
42
+ (demonstrated: 10 clean calls → "Routing is healthy", byte-identical to before).
43
+
44
+ ## 2. Under-block
45
+
46
+ **What failure modes does this still miss?**
47
+
48
+ - The floor is a call count, not a confidence interval. 5 clean calls yields "healthy" though a real
49
+ interval would still be wide. Deliberate: this reuses the file's existing constant rather than
50
+ inventing a second, differently-tuned threshold in the same file.
51
+ - `busiest` is still reported whenever `realCalls > 0`, which is honest (it is a count, not a rate).
52
+ - The 24h window itself is not surfaced as a caveat when the process started minutes ago — a
53
+ short-uptime window looks the same as a genuinely quiet one. Out of scope here; noted rather than
54
+ silently skipped.
55
+
56
+ ## 3. Level-of-abstraction fit
57
+
58
+ **Is this at the right layer?**
59
+
60
+ Yes. The defect is in how this collector renders its own aggregate, so the fix belongs in it. No
61
+ shared helper was extracted: the sibling instance in `CoherenceGate` (PR #1648) has different types
62
+ and a different output shape, and abstracting over two instances would be the premature-generalisation
63
+ mistake this project's own spec review caught elsewhere.
64
+
65
+ ## 4. Signal vs authority compliance
66
+
67
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
68
+
69
+ **Does this change hold blocking authority with brittle logic?**
70
+
71
+ No. Pure presentation, zero authority: no branch blocks, delays or alters anything, and the collector
72
+ is consumed only by the insights read surface. There is no misclassification path that can deny
73
+ something, because nothing is denied. The change strictly reduces the panel's capacity to mislead.
74
+
75
+ ## 4b. Judgment-point check (Judgment Within Floors standard)
76
+
77
+ No judgment point is introduced. Every branch is a deterministic comparison on counted calls against
78
+ a constant already present in the file. The one asymmetry is deliberate and stated in a code comment:
79
+ anomalies are evaluated first and unconditionally, so thin evidence can never suppress a warning —
80
+ only the reassuring conclusion requires evidence behind it.
81
+
82
+ ## 5. Interactions
83
+
84
+ - `overallErrorRate` is a local; verified by grep that nothing outside this file reads it.
85
+ - `InsightMetric.value` is typed `string`, so "no calls yet" needs no type change and no consumer
86
+ performs arithmetic on it.
87
+ - The existing E2E (`tests/e2e/insights-alive.test.ts`) seeds 6 calls with 3 errors — above the floor,
88
+ so it still routes to the anomaly branch and still asserts `/50%/`. Re-run: passes unchanged.
89
+ - A visible grammar fix falls out of the plural handling: "1 checks are failing" → "1 check is failing".
90
+ - No persistence, no config, no migration: every value is computed per call from the ledger.
91
+
92
+ ## 6. External surfaces
93
+
94
+ The `/insights` payload for the `llm-activity` page changes in two places: `Error rate` may read
95
+ "no calls yet" instead of "0%", and the first fact may be a caveat rather than a health claim. Both
96
+ are display strings already typed as free text. The feature ships dark on the fleet (dev-agent gated,
97
+ dryRun), so exposure is limited to development agents today.
98
+
99
+ ## 6b. Operator-surface quality
100
+
101
+ This is the whole point of the change. An operator glancing at the panel during a quiet window
102
+ previously read "Error rate: 0% · Routing is healthy" and would reasonably conclude the routing layer
103
+ was fine. They now read "no calls yet" and "nothing to judge yet". And in the sub-floor case they no
104
+ longer see a 50% error rate sitting next to the word "healthy" — the panel stops contradicting
105
+ itself. None of the new wording is alarming: "nothing to judge yet" says nothing is wrong AND nothing
106
+ is known.
107
+
108
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
109
+
110
+ **Posture: `unified` by construction — no new state.** The collector derives everything, per call,
111
+ from the per-process metrics ledger that already existed. Each machine reports its own LLM activity,
112
+ which is correct: a call was made by a specific process. No replication path is needed because nothing
113
+ is stored, and no `machine-local-justification` marker is required because no new machine-local state
114
+ is introduced.
115
+
116
+ ## 8. Rollback cost
117
+
118
+ Trivial: revert one commit touching one source file plus its new test. No migration, no persisted
119
+ state, no config key. A consumer written against the new strings sees the old ones again, and the old
120
+ strings' failure mode is the one documented above.
121
+
122
+ ## Phase 5 — Second-pass review (independent reviewer subagent)
123
+
124
+ **Disclosure, per Truthful Provenance:** no independent reviewer subagent was spawned — a standing
125
+ instruction in this session prohibits spawning subagents unless the operator requests it. The review
126
+ lenses were applied by the author. That is **reduced independence**, recorded as such rather than
127
+ presented as a concurring second pass.
128
+
129
+ What author-applied review actually caught and changed:
130
+
131
+ 1. **The first draft ordered the branches wrongly**, putting the sub-floor caveat ahead of the
132
+ anomaly message. Today no anomaly can occur below the floor (a feature needs `>= MIN_REAL_CALLS`
133
+ to flag, so `totalReal < MIN_REAL_CALLS` implies none) — but that is an arithmetic coincidence,
134
+ not a guarantee. Reordered so anomalies win unconditionally, and the reasoning is in a comment so
135
+ a future edit to either constant cannot silently reintroduce suppression.
136
+ 2. **My own first test asserted the wrong thing** and failed on correct output: it matched
137
+ `/routing is healthy/i`, which the caveat legitimately contains ("too few to say whether routing
138
+ is healthy"). Corrected to assert the absence of the AFFIRMATIVE claim. Worth recording because
139
+ this project's Tier 1 is about instruments that mis-measure — including my tests.
140
+ 3. The consumer check was run rather than assumed, establishing that `overallErrorRate` is local and
141
+ that `InsightMetric.value` is already a string.