instar 1.3.449 → 1.3.451

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.
Files changed (45) hide show
  1. package/.claude/skills/autonomous/SKILL.md +42 -2
  2. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  3. package/dist/config/ConfigDefaults.js +26 -0
  4. package/dist/config/ConfigDefaults.js.map +1 -1
  5. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  6. package/dist/core/PostUpdateMigrator.js +17 -6
  7. package/dist/core/PostUpdateMigrator.js.map +1 -1
  8. package/dist/core/types.d.ts +47 -0
  9. package/dist/core/types.d.ts.map +1 -1
  10. package/dist/core/types.js.map +1 -1
  11. package/dist/messaging/slack/SlackAdapter.d.ts +8 -0
  12. package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
  13. package/dist/messaging/slack/SlackAdapter.js +10 -0
  14. package/dist/messaging/slack/SlackAdapter.js.map +1 -1
  15. package/dist/monitoring/GrowthMilestoneAnalyst.d.ts +169 -0
  16. package/dist/monitoring/GrowthMilestoneAnalyst.d.ts.map +1 -0
  17. package/dist/monitoring/GrowthMilestoneAnalyst.js +500 -0
  18. package/dist/monitoring/GrowthMilestoneAnalyst.js.map +1 -0
  19. package/dist/permissions/AmbientContributionGate.d.ts +100 -0
  20. package/dist/permissions/AmbientContributionGate.d.ts.map +1 -1
  21. package/dist/permissions/AmbientContributionGate.js +85 -3
  22. package/dist/permissions/AmbientContributionGate.js.map +1 -1
  23. package/dist/scaffold/templates.d.ts.map +1 -1
  24. package/dist/scaffold/templates.js +5 -0
  25. package/dist/scaffold/templates.js.map +1 -1
  26. package/dist/server/AgentServer.d.ts +1 -0
  27. package/dist/server/AgentServer.d.ts.map +1 -1
  28. package/dist/server/AgentServer.js +41 -0
  29. package/dist/server/AgentServer.js.map +1 -1
  30. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  31. package/dist/server/CapabilityIndex.js +14 -0
  32. package/dist/server/CapabilityIndex.js.map +1 -1
  33. package/dist/server/routes.d.ts +5 -0
  34. package/dist/server/routes.d.ts.map +1 -1
  35. package/dist/server/routes.js +77 -0
  36. package/dist/server/routes.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/data/builtin-manifest.json +65 -65
  39. package/src/scaffold/templates.ts +5 -0
  40. package/upgrades/1.3.450.md +24 -0
  41. package/upgrades/1.3.451.md +57 -0
  42. package/upgrades/side-effects/autonomous-legitimate-stops.md +108 -0
  43. package/upgrades/side-effects/growth-milestone-analyst-dev-gate.md +99 -0
  44. package/upgrades/side-effects/growth-milestone-analyst.md +92 -0
  45. package/upgrades/side-effects/slack-livetest-cleanups.md +75 -0
@@ -0,0 +1,500 @@
1
+ // GrowthMilestoneAnalyst — the proactive "growth & milestone" analyst.
2
+ //
3
+ // WHY THIS EXISTS (grounded requirement, Justin 2026-06-06, topic 21624):
4
+ // Instar built excellent *sensors* (InitiativeTracker, FeatureRolloutReconciler,
5
+ // ApprovalLedger, CorrectionLedger) and excellent *anti-flood plumbing* — but
6
+ // never the *analyst layer* in between that reads the tracked data, decides what
7
+ // crosses from noise into "a concrete milestone or realization worth telling the
8
+ // operator," and proactively surfaces it. The result was total silence on the
9
+ // three questions the operator actually asked:
10
+ // 1. Are initiatives being left behind?
11
+ // 2. Are features earning their way through the maturity path (dark → enabled)?
12
+ // 3. Are patterns being extracted from conversation data (approve-vs-change-spec
13
+ // rate, correction rate)?
14
+ //
15
+ // THE KEY DESIGN LEVER (Justin, same conversation): the incubation/maturation
16
+ // window must stay TIGHT — a week MAX, a few days for low-risk features — and
17
+ // **the window EXPIRING is the trigger itself**. "Left behind" becomes
18
+ // structurally impossible because every incubating feature carries a deadline
19
+ // that drags it in front of the operator: either it proved itself (→ promote?)
20
+ // or it never did (→ extend / fix / kill?).
21
+ //
22
+ // MATURITY HONESTY (ref keystone-dormancy #905): promotion requires real
23
+ // proof-of-life (the feature actually fired), NOT merely elapsed time. A feature
24
+ // that sat dark and never ran is a *kill/fix* candidate, never a *promote* one.
25
+ //
26
+ // THIS COMPONENT ADDS NO NEW SENSORS. It composes the existing read surfaces and
27
+ // keeps ONE piece of minimal internal bookkeeping: a stage-observation journal,
28
+ // because the rollout engines do not cleanly stamp "entered stage X at time T"
29
+ // for every stage (notably `dark`). The journal records the first time we
30
+ // observed a feature in its current stage so "days in stage" is robust.
31
+ //
32
+ // Spec: docs/specs/PROACTIVE-GROWTH-MILESTONE-ANALYST-SPEC.md
33
+ // Ships DARK (monitoring.growthAnalyst.enabled defaults false). This slice
34
+ // COMPUTES findings + exposes them via read routes; it does NOT send to Telegram.
35
+ // Sending / cadence / enabling the muted analyzers ride later slices, behind
36
+ // their own review (they are the flood-sensitive part).
37
+ import fs from 'node:fs';
38
+ import path from 'node:path';
39
+ export const DEFAULT_GROWTH_WINDOWS = {
40
+ lowRisk: 3,
41
+ standard: 7,
42
+ highRisk: 7,
43
+ };
44
+ export const DEFAULT_GROWTH_SETTINGS = {
45
+ enabled: false,
46
+ incubationWindows: { ...DEFAULT_GROWTH_WINDOWS },
47
+ proofOfLifeMinActivations: 1,
48
+ rules: {
49
+ promotionReady: true,
50
+ incubationExpired: true,
51
+ initiativeStalling: true,
52
+ specPattern: true,
53
+ correctionPattern: true,
54
+ },
55
+ specPatternMinTotal: 3,
56
+ specPatternMinChangeRatio: 0.6,
57
+ correctionPatternMinOccurrences: 3,
58
+ digestEvenWhenCalm: true,
59
+ };
60
+ /** Resolve raw config (possibly partial / undefined) into fully-defaulted
61
+ * settings. Pure — safe to unit test, and used by both the class and routes. */
62
+ export function resolveGrowthSettings(raw) {
63
+ const r = (raw ?? {});
64
+ const windows = (r.incubationWindows ?? {});
65
+ const rules = (r.rules ?? {});
66
+ return {
67
+ enabled: r.enabled === true,
68
+ incubationWindows: {
69
+ lowRisk: numOr(windows.lowRisk, DEFAULT_GROWTH_WINDOWS.lowRisk),
70
+ standard: numOr(windows.standard, DEFAULT_GROWTH_WINDOWS.standard),
71
+ highRisk: numOr(windows.highRisk, DEFAULT_GROWTH_WINDOWS.highRisk),
72
+ },
73
+ proofOfLifeMinActivations: numOr(r.proofOfLifeMinActivations, DEFAULT_GROWTH_SETTINGS.proofOfLifeMinActivations),
74
+ rules: {
75
+ promotionReady: rules.promotionReady !== false,
76
+ incubationExpired: rules.incubationExpired !== false,
77
+ initiativeStalling: rules.initiativeStalling !== false,
78
+ specPattern: rules.specPattern !== false,
79
+ correctionPattern: rules.correctionPattern !== false,
80
+ },
81
+ specPatternMinTotal: numOr(r.specPatternMinTotal, DEFAULT_GROWTH_SETTINGS.specPatternMinTotal),
82
+ specPatternMinChangeRatio: numOr(r.specPatternMinChangeRatio, DEFAULT_GROWTH_SETTINGS.specPatternMinChangeRatio),
83
+ correctionPatternMinOccurrences: numOr(r.correctionPatternMinOccurrences, DEFAULT_GROWTH_SETTINGS.correctionPatternMinOccurrences),
84
+ digestEvenWhenCalm: r.digestEvenWhenCalm !== false,
85
+ };
86
+ }
87
+ function numOr(v, fallback) {
88
+ return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
89
+ }
90
+ /** Whole calendar-ish days between an ISO timestamp and `now` (fractional). */
91
+ export function daysSince(iso, now) {
92
+ const then = new Date(iso).getTime();
93
+ if (!Number.isFinite(then))
94
+ return 0;
95
+ return (now.getTime() - then) / 86_400_000;
96
+ }
97
+ /** Map a feature to a risk tier. Heuristic for now (overridable later via an
98
+ * explicit per-feature tag). default-on/live features that already shipped are
99
+ * treated 'standard'; everything dark is 'standard' unless flagged lowRisk. */
100
+ export function riskTierForInitiative(init) {
101
+ // An explicit tag on the rollout's promotionCriteria wins if present.
102
+ const crit = init.rollout?.promotionCriteria?.toLowerCase() ?? '';
103
+ if (crit.includes('low-risk') || crit.includes('lowrisk'))
104
+ return 'lowRisk';
105
+ if (crit.includes('high-risk') || crit.includes('highrisk'))
106
+ return 'highRisk';
107
+ return 'standard';
108
+ }
109
+ /** The core verdict. PURE. `proofCount === undefined` ⇒ no evidence source wired
110
+ * ⇒ proved:'unknown' ⇒ cannot be promotion-ready (honest: never promote a
111
+ * feature we can't prove ran). */
112
+ export function classifyRollout(stage, daysInStage, windowDays, proofCount, minProof) {
113
+ const proved = proofCount === undefined ? 'unknown' : proofCount >= minProof;
114
+ if (stage === 'default-on') {
115
+ return { classification: 'terminal', stage, daysInStage, windowDays, proved };
116
+ }
117
+ const expired = daysInStage >= windowDays;
118
+ if (!expired) {
119
+ return { classification: 'incubating', stage, daysInStage, windowDays, proved };
120
+ }
121
+ if (proved === true) {
122
+ return { classification: 'promotion-ready', stage, daysInStage, windowDays, proved };
123
+ }
124
+ return { classification: 'expired-unproven', stage, daysInStage, windowDays, proved };
125
+ }
126
+ export class GrowthMilestoneAnalyst {
127
+ stateDir;
128
+ journalPath;
129
+ deps;
130
+ settings;
131
+ constructor(deps) {
132
+ this.deps = deps;
133
+ this.settings = deps.settings;
134
+ this.stateDir = path.join(deps.stateDir, 'state', 'growth-milestone-analyst');
135
+ this.journalPath = path.join(this.stateDir, 'stage-journal.json');
136
+ try {
137
+ fs.mkdirSync(this.stateDir, { recursive: true });
138
+ }
139
+ catch (err) {
140
+ this.deps.onError?.('mkdir', err);
141
+ }
142
+ }
143
+ now() {
144
+ return this.deps.now ? this.deps.now() : new Date();
145
+ }
146
+ isEnabled() {
147
+ return this.settings.enabled === true;
148
+ }
149
+ // ── Stage journal persistence ──────────────────────────────────────────
150
+ loadJournal() {
151
+ try {
152
+ const raw = fs.readFileSync(this.journalPath, 'utf-8');
153
+ const parsed = JSON.parse(raw);
154
+ return parsed && typeof parsed === 'object' ? parsed : {};
155
+ }
156
+ catch {
157
+ // @silent-fallback-ok: a missing/corrupt journal → empty, then self-heals
158
+ // on the next observeStages write. Observe-only analyst — never gates an
159
+ // action, so an empty journal only means "recompute stage clocks", not a
160
+ // wrong decision. (No DegradationReporter: this is expected first-run state.)
161
+ return {};
162
+ }
163
+ }
164
+ saveJournal(j) {
165
+ try {
166
+ fs.writeFileSync(this.journalPath, JSON.stringify(j, null, 2));
167
+ }
168
+ catch (err) {
169
+ this.deps.onError?.('saveJournal', err);
170
+ }
171
+ }
172
+ /**
173
+ * Observe the current rollout stage of every staged feature and reconcile the
174
+ * journal: new feature → record; stage changed → reset firstObservedAt; gone →
175
+ * prune. Returns the updated journal (also persisted). PUBLIC so a tick can run
176
+ * the observation step independently of finding computation.
177
+ */
178
+ observeStages(now = this.now()) {
179
+ const journal = this.loadJournal();
180
+ const live = new Set();
181
+ for (const init of this.stagedInitiatives()) {
182
+ const stage = init.rollout.stage;
183
+ live.add(init.id);
184
+ const prev = journal[init.id];
185
+ if (!prev || prev.stage !== stage) {
186
+ journal[init.id] = { stage, firstObservedAt: now.toISOString() };
187
+ }
188
+ }
189
+ // Prune features no longer staged (deleted / archived away from rollout).
190
+ for (const id of Object.keys(journal)) {
191
+ if (!live.has(id))
192
+ delete journal[id];
193
+ }
194
+ this.saveJournal(journal);
195
+ return journal;
196
+ }
197
+ stagedInitiatives() {
198
+ let list;
199
+ try {
200
+ list = this.deps.tracker.list();
201
+ }
202
+ catch (err) {
203
+ // @silent-fallback-ok: input-read failure surfaced via onError; returning
204
+ // [] yields a smaller digest (the safe direction) for this observe-only,
205
+ // never-acting component — it can never cause a wrong action.
206
+ this.deps.onError?.('tracker.list', err);
207
+ return [];
208
+ }
209
+ return list.filter((i) => i.rollout && i.rollout.stage);
210
+ }
211
+ // ── Notify-rule computation ─────────────────────────────────────────────
212
+ /** R1 + R2: feature maturity via the tight incubation window. */
213
+ computeRolloutFindings(now, journal) {
214
+ const findings = [];
215
+ const s = this.settings;
216
+ for (const init of this.stagedInitiatives()) {
217
+ const obs = journal[init.id];
218
+ if (!obs)
219
+ continue; // observeStages must run first
220
+ const tier = riskTierForInitiative(init);
221
+ const windowDays = s.incubationWindows[tier];
222
+ const daysInStage = daysSince(obs.firstObservedAt, now);
223
+ let proofCount;
224
+ try {
225
+ proofCount = this.deps.evidenceCounter?.(init);
226
+ }
227
+ catch (err) {
228
+ // @silent-fallback-ok: surfaced via onError; undefined → proved:'unknown',
229
+ // the HONEST conservative result (a feature we can't prove ran can never
230
+ // be promotion-ready). Never a wrong action — observe-only.
231
+ this.deps.onError?.('evidenceCounter', err);
232
+ proofCount = undefined;
233
+ }
234
+ const v = classifyRollout(init.rollout.stage, daysInStage, windowDays, proofCount, s.proofOfLifeMinActivations);
235
+ const rounded = Math.round(daysInStage * 10) / 10;
236
+ if (v.classification === 'promotion-ready' && s.rules.promotionReady) {
237
+ findings.push({
238
+ rule: 'R1',
239
+ priority: 'normal',
240
+ subjectId: init.id,
241
+ title: `${init.title} — ready to promote`,
242
+ detail: `Incubated ${rounded}d in '${v.stage}' (window ${windowDays}d) with ${proofCount} real activation(s) and no issues. Promote to the next stage?`,
243
+ suggestedAction: 'promote',
244
+ stage: v.stage,
245
+ daysInStage: rounded,
246
+ windowDays,
247
+ proved: v.proved,
248
+ });
249
+ }
250
+ else if (v.classification === 'expired-unproven' && s.rules.incubationExpired) {
251
+ const provedNote = v.proved === 'unknown'
252
+ ? 'no evidence source is wired, so it cannot prove it ran'
253
+ : `it has not reached ${s.proofOfLifeMinActivations} activation(s)`;
254
+ findings.push({
255
+ rule: 'R2',
256
+ priority: 'normal',
257
+ subjectId: init.id,
258
+ title: `${init.title} — incubation expired, unproven`,
259
+ detail: `Sat in '${v.stage}' ${rounded}d (window ${windowDays}d) but ${provedNote}. Extend, fix, or kill?`,
260
+ suggestedAction: 'extend-fix-kill',
261
+ stage: v.stage,
262
+ daysInStage: rounded,
263
+ windowDays,
264
+ proved: v.proved,
265
+ });
266
+ }
267
+ }
268
+ return findings;
269
+ }
270
+ /** R3: initiatives left behind — reuse the tracker's own staleness digest. */
271
+ computeStallingFindings(now) {
272
+ if (!this.settings.rules.initiativeStalling)
273
+ return [];
274
+ let items;
275
+ try {
276
+ items = this.deps.tracker.digest(now).items;
277
+ }
278
+ catch (err) {
279
+ // @silent-fallback-ok: input-read failure surfaced via onError; [] yields a
280
+ // smaller digest (safe direction) for this observe-only component.
281
+ this.deps.onError?.('tracker.digest', err);
282
+ return [];
283
+ }
284
+ const findings = [];
285
+ for (const it of items) {
286
+ if (it.reason !== 'stale' && it.reason !== 'needs-user')
287
+ continue;
288
+ findings.push({
289
+ rule: 'R3',
290
+ priority: 'normal',
291
+ subjectId: it.initiativeId,
292
+ title: `${it.title} — ${it.reason === 'needs-user' ? 'waiting on you' : 'drifting'}`,
293
+ detail: it.detail || (it.reason === 'needs-user' ? 'An open decision is waiting on you.' : 'No update in a while.'),
294
+ suggestedAction: 'review',
295
+ });
296
+ }
297
+ return findings;
298
+ }
299
+ /** R4: spec approve-vs-change pattern — "you keep changing X the same way." */
300
+ computeSpecPatternFindings() {
301
+ if (!this.settings.rules.specPattern || !this.deps.approvalLedger)
302
+ return [];
303
+ let summaries;
304
+ try {
305
+ summaries = this.deps.approvalLedger.summarize();
306
+ }
307
+ catch (err) {
308
+ // @silent-fallback-ok: input-read failure surfaced via onError; [] yields a
309
+ // smaller digest (safe direction) for this observe-only component.
310
+ this.deps.onError?.('approvalLedger.summarize', err);
311
+ return [];
312
+ }
313
+ const s = this.settings;
314
+ const findings = [];
315
+ for (const cs of summaries) {
316
+ if (cs.total < s.specPatternMinTotal)
317
+ continue;
318
+ const changeRatio = cs.total > 0 ? cs.approvedWithChange / cs.total : 0;
319
+ if (changeRatio < s.specPatternMinChangeRatio)
320
+ continue;
321
+ const dominant = dominantDivergence(cs.divergenceCounts);
322
+ if (!dominant)
323
+ continue;
324
+ findings.push({
325
+ rule: 'R4',
326
+ priority: 'low',
327
+ subjectId: cs.decisionClass,
328
+ title: `Spec pattern: '${cs.decisionClass}' keeps getting changed`,
329
+ detail: `${cs.approvedWithChange}/${cs.total} '${cs.decisionClass}' decisions were approved-with-change, mostly '${dominant.category}' (${dominant.count}). Bake this into the default?`,
330
+ suggestedAction: 'bake-in-default',
331
+ });
332
+ }
333
+ return findings;
334
+ }
335
+ /** R5: correction pattern — a recurring correction worth surfacing. The
336
+ * CORRECTION-PREFERENCE spec owns the routing; this only SURFACES it. */
337
+ computeCorrectionFindings(now) {
338
+ if (!this.settings.rules.correctionPattern || !this.deps.correctionLedger)
339
+ return [];
340
+ let records;
341
+ try {
342
+ records = this.deps.correctionLedger.list({});
343
+ }
344
+ catch (err) {
345
+ // @silent-fallback-ok: input-read failure surfaced via onError; [] yields a
346
+ // smaller digest (safe direction) for this observe-only component.
347
+ this.deps.onError?.('correctionLedger.list', err);
348
+ return [];
349
+ }
350
+ const min = this.settings.correctionPatternMinOccurrences;
351
+ const findings = [];
352
+ for (const r of records) {
353
+ // Only surface still-open recurring patterns (acted-on/verified are owned
354
+ // by the correction loop's own lifecycle).
355
+ if (r.status !== 'open' && r.status !== 'reopened')
356
+ continue;
357
+ if (r.occurrenceCount < min)
358
+ continue;
359
+ findings.push({
360
+ rule: 'R5',
361
+ priority: 'low',
362
+ subjectId: r.dedupeKey,
363
+ title: `Recurring correction (${r.occurrenceCount}×)`,
364
+ // scrubbedSummary is the only HTTP-safe text on a correction record.
365
+ detail: `${r.scrubbedSummary} — recurred ${r.occurrenceCount}×. Worth a durable preference or infra fix?`,
366
+ suggestedAction: 'route-correction',
367
+ });
368
+ }
369
+ return findings;
370
+ }
371
+ /** Run the full observation + computation pass. Returns all findings. */
372
+ computeFindings(now = this.now()) {
373
+ const journal = this.observeStages(now);
374
+ return [
375
+ ...this.computeRolloutFindings(now, journal),
376
+ ...this.computeStallingFindings(now),
377
+ ...this.computeSpecPatternFindings(),
378
+ ...this.computeCorrectionFindings(now),
379
+ ];
380
+ }
381
+ /** Build the operator-facing digest. Calm digests still render so the operator
382
+ * knows the analyst ran (fixes the old "near-silent → never speaks" failure). */
383
+ buildDigest(now = this.now()) {
384
+ const journal = this.observeStages(now);
385
+ const rollout = this.computeRolloutFindings(now, journal);
386
+ const stalling = this.computeStallingFindings(now);
387
+ const spec = this.computeSpecPatternFindings();
388
+ const corr = this.computeCorrectionFindings(now);
389
+ const findings = [...rollout, ...stalling, ...spec, ...corr];
390
+ const counts = {
391
+ incubating: this.countIncubating(now, journal),
392
+ promotionReady: rollout.filter((f) => f.rule === 'R1').length,
393
+ expiredUnproven: rollout.filter((f) => f.rule === 'R2').length,
394
+ stalling: stalling.length,
395
+ specPatterns: spec.length,
396
+ correctionPatterns: corr.length,
397
+ };
398
+ const nextWindowClosesInDays = this.nextWindowClose(now, journal);
399
+ const calm = findings.length === 0;
400
+ const summary = calm
401
+ ? this.calmSummary(counts, nextWindowClosesInDays)
402
+ : this.activeSummary(counts);
403
+ return {
404
+ generatedAt: now.toISOString(),
405
+ calm,
406
+ summary,
407
+ findings,
408
+ counts,
409
+ nextWindowClosesInDays,
410
+ };
411
+ }
412
+ /** Lightweight status for a GET route / dashboard. */
413
+ getStatus(now = this.now()) {
414
+ const digest = this.buildDigest(now);
415
+ return {
416
+ enabled: this.isEnabled(),
417
+ settings: this.settings,
418
+ counts: digest.counts,
419
+ nextWindowClosesInDays: digest.nextWindowClosesInDays,
420
+ };
421
+ }
422
+ // ── digest helpers ──────────────────────────────────────────────────────
423
+ countIncubating(now, journal) {
424
+ let n = 0;
425
+ for (const init of this.stagedInitiatives()) {
426
+ const obs = journal[init.id];
427
+ if (!obs)
428
+ continue;
429
+ const tier = riskTierForInitiative(init);
430
+ const windowDays = this.settings.incubationWindows[tier];
431
+ const v = classifyRollout(init.rollout.stage, daysSince(obs.firstObservedAt, now), windowDays, this.safeProof(init), this.settings.proofOfLifeMinActivations);
432
+ if (v.classification === 'incubating')
433
+ n++;
434
+ }
435
+ return n;
436
+ }
437
+ nextWindowClose(now, journal) {
438
+ let soonest;
439
+ for (const init of this.stagedInitiatives()) {
440
+ const obs = journal[init.id];
441
+ if (!obs)
442
+ continue;
443
+ const tier = riskTierForInitiative(init);
444
+ const windowDays = this.settings.incubationWindows[tier];
445
+ const daysInStage = daysSince(obs.firstObservedAt, now);
446
+ if (init.rollout.stage === 'default-on')
447
+ continue;
448
+ if (daysInStage >= windowDays)
449
+ continue; // already expired
450
+ const remaining = windowDays - daysInStage;
451
+ if (soonest === undefined || remaining < soonest)
452
+ soonest = remaining;
453
+ }
454
+ return soonest === undefined ? undefined : Math.round(soonest * 10) / 10;
455
+ }
456
+ safeProof(init) {
457
+ try {
458
+ return this.deps.evidenceCounter?.(init);
459
+ }
460
+ catch {
461
+ // @silent-fallback-ok: count-only helper for the calm-digest incubating
462
+ // tally; undefined → proved:'unknown' (the honest conservative result).
463
+ // Observe-only — never gates an action.
464
+ return undefined;
465
+ }
466
+ }
467
+ calmSummary(counts, nextClose) {
468
+ const base = `All healthy — ${counts.incubating} feature(s) incubating, nothing past its window.`;
469
+ if (nextClose !== undefined)
470
+ return `${base} Next window closes in ${nextClose}d.`;
471
+ return base;
472
+ }
473
+ activeSummary(counts) {
474
+ const parts = [];
475
+ if (counts.promotionReady)
476
+ parts.push(`${counts.promotionReady} ready to promote`);
477
+ if (counts.expiredUnproven)
478
+ parts.push(`${counts.expiredUnproven} expired-unproven`);
479
+ if (counts.stalling)
480
+ parts.push(`${counts.stalling} stalling`);
481
+ if (counts.specPatterns)
482
+ parts.push(`${counts.specPatterns} spec pattern(s)`);
483
+ if (counts.correctionPatterns)
484
+ parts.push(`${counts.correctionPatterns} correction pattern(s)`);
485
+ return `Growth digest: ${parts.join(', ')}.`;
486
+ }
487
+ }
488
+ /** The divergence category with the highest count (ties → first by declaration),
489
+ * or null when there are no divergences at all. PURE. */
490
+ export function dominantDivergence(counts) {
491
+ let best = null;
492
+ for (const [category, count] of Object.entries(counts)) {
493
+ if (count <= 0)
494
+ continue;
495
+ if (!best || count > best.count)
496
+ best = { category, count };
497
+ }
498
+ return best;
499
+ }
500
+ //# sourceMappingURL=GrowthMilestoneAnalyst.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrowthMilestoneAnalyst.js","sourceRoot":"","sources":["../../src/monitoring/GrowthMilestoneAnalyst.ts"],"names":[],"mappings":"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"}
@@ -54,6 +54,65 @@ export interface AmbientDecision {
54
54
  /** Optional human-readable detail (e.g. the LLM's named contribution). */
55
55
  detail?: string;
56
56
  }
57
+ /**
58
+ * Observability — a bounded, in-memory record of a SINGLE silence the gate is
59
+ * about to return. Used only to populate the near-miss ring; never affects the
60
+ * decision. No message text is stored — just the channel, reason, and how close
61
+ * the (clamped) confidence was to the threshold.
62
+ */
63
+ export interface AmbientSilenceSample {
64
+ /** The Slack channel the silence occurred in. */
65
+ channelId: string;
66
+ /** Why the gate stayed silent. */
67
+ reason: AmbientDecisionReason;
68
+ /**
69
+ * The LLM's clamped confidence for this evaluation, when one was produced
70
+ * (only the LLM paths carry it; the channel-not-opted-in / rate-limited /
71
+ * no-intelligence / llm-error / llm-unparseable paths leave it undefined).
72
+ */
73
+ confidence?: number;
74
+ /** Whether this silence was within `nearMissDelta` below the speak threshold. */
75
+ nearMiss: boolean;
76
+ /** When the sample was taken (gate clock). */
77
+ at: number;
78
+ }
79
+ /**
80
+ * Per-channel ambient observability counters + a bounded ring of recent near-miss
81
+ * silences. Pure aggregate — NO message content, NO per-event Telegram/topic
82
+ * side-effect (so it can never flood). This is the read surface the observe-only
83
+ * live test polls to measure the ambient gate's false-positive (wrongful-silence)
84
+ * and false-negative (wrongful-speak) rates. Signal-only: reading or computing it
85
+ * NEVER changes the speak/silence verdict.
86
+ */
87
+ export interface AmbientChannelStats {
88
+ channelId: string;
89
+ /** Every shouldSpeak() call for this channel (spoke + silent). */
90
+ evaluated: number;
91
+ /** Decisions that returned speak=true. */
92
+ spoke: number;
93
+ /** Decisions that returned speak=false (the FP candidates). */
94
+ silent: number;
95
+ /**
96
+ * Silences where the LLM's confidence was within `nearMissDelta` BELOW the
97
+ * speak threshold — i.e. it nearly spoke. The highest-signal subset for tuning
98
+ * the confidence floor.
99
+ */
100
+ nearMissSilent: number;
101
+ /** Per-reason silence breakdown (channel-not-opted-in, rate-limited, …). */
102
+ silentByReason: Partial<Record<AmbientDecisionReason, number>>;
103
+ }
104
+ export interface AmbientStats {
105
+ /** Per-channel aggregate counters. */
106
+ channels: AmbientChannelStats[];
107
+ /** Bounded ring (most-recent-first) of recent near-miss silences for spot-inspection. */
108
+ recentNearMisses: AmbientSilenceSample[];
109
+ /** The near-miss delta in use (confidence within this far below the threshold). */
110
+ nearMissDelta: number;
111
+ /** The confidence floor a "speak" verdict must clear. */
112
+ minConfidence: number;
113
+ /** Cap on the recentNearMisses ring (bounded — no unbounded growth). */
114
+ ringCapacity: number;
115
+ }
57
116
  export type AmbientDecisionReason = 'channel-not-opted-in' | 'rate-limited' | 'no-intelligence' | 'llm-error' | 'llm-unparseable' | 'llm-declined' | 'low-confidence' | 'speak';
58
117
  /** Per-channel ambient configuration. Default: ambient OFF. */
59
118
  export interface AmbientChannelConfig {
@@ -72,6 +131,18 @@ export interface AmbientChannelConfig {
72
131
  * this, the gate stays silent even if the LLM said speak. Default: 0.85 (high bar).
73
132
  */
74
133
  minConfidence?: number;
134
+ /**
135
+ * Observability only — a silence whose confidence lands within this far BELOW
136
+ * `minConfidence` is flagged a "near-miss" (it nearly spoke). Default: 0.1. Has
137
+ * NO effect on the verdict; it only classifies silences for the stats surface.
138
+ */
139
+ nearMissDelta?: number;
140
+ /**
141
+ * Observability only — cap on the in-memory ring of recent near-miss silence
142
+ * samples kept for spot-inspection. Bounded so the gate can never grow unbounded.
143
+ * Default: 50.
144
+ */
145
+ nearMissRingCapacity?: number;
75
146
  }
76
147
  export interface AmbientContributionGateDeps {
77
148
  /** Per-channel ambient configuration. Absent/empty ⇒ ambient OFF everywhere. */
@@ -110,6 +181,20 @@ export declare class AmbientContributionGate {
110
181
  private readonly timeoutMs;
111
182
  private readonly onDecision?;
112
183
  private readonly now;
184
+ private readonly nearMissDelta;
185
+ private readonly nearMissRingCapacity;
186
+ /**
187
+ * Observability — bounded, in-memory aggregate of every decision, per channel.
188
+ * Populated in recordDecisionStats() (called from decide() AFTER the verdict is
189
+ * formed), so it can never change the verdict. No message content is stored. A
190
+ * restart resets it; this is acceptable for an FP-rate measurement surface (the
191
+ * durable per-decision ledger is the file-backed /permissions/decisions). The map
192
+ * is keyed by channelId — bounded by the set of opted-in channels, which is small
193
+ * and config-controlled.
194
+ */
195
+ private readonly channelStats;
196
+ /** Bounded ring (newest-last) of recent near-miss silences for spot-inspection. */
197
+ private readonly nearMissRing;
113
198
  /**
114
199
  * Rate-limit state lives HERE — a per-channel in-memory ring of the timestamps at
115
200
  * which the gate last returned speak=true. It is recorded by recordSpoke() (called
@@ -145,5 +230,20 @@ export declare class AmbientContributionGate {
145
230
  /** Count of proactive sends within the rolling window; prunes expired entries. */
146
231
  private recentCount;
147
232
  private decide;
233
+ /**
234
+ * Update the per-channel counters and (for a near-miss silence) the bounded ring.
235
+ * A SILENCE is a "near-miss" when the LLM produced a confidence that fell within
236
+ * `nearMissDelta` BELOW the speak threshold — i.e. it nearly spoke. Paths with no
237
+ * confidence (not-opted-in, rate-limited, no-intelligence, llm-error, unparseable)
238
+ * are never near-misses. Pure in-memory; no content stored; no side-effect.
239
+ */
240
+ private recordDecisionStats;
241
+ /**
242
+ * Read-only snapshot of the bounded observability aggregate. Safe to call any time
243
+ * (returns copies — the caller can't mutate internal state). With no opted-in
244
+ * channel the gate is never consulted, so `channels` stays empty and nothing is
245
+ * recorded. This is the surface the observe-only live test polls.
246
+ */
247
+ getStats(): AmbientStats;
148
248
  }
149
249
  //# sourceMappingURL=AmbientContributionGate.d.ts.map