instar 1.3.450 → 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.
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +26 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/types.d.ts +47 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +8 -0
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +10 -0
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/monitoring/GrowthMilestoneAnalyst.d.ts +169 -0
- package/dist/monitoring/GrowthMilestoneAnalyst.d.ts.map +1 -0
- package/dist/monitoring/GrowthMilestoneAnalyst.js +500 -0
- package/dist/monitoring/GrowthMilestoneAnalyst.js.map +1 -0
- package/dist/permissions/AmbientContributionGate.d.ts +100 -0
- package/dist/permissions/AmbientContributionGate.d.ts.map +1 -1
- package/dist/permissions/AmbientContributionGate.js +85 -3
- package/dist/permissions/AmbientContributionGate.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +5 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +41 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +14 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +5 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +77 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +47 -47
- package/src/scaffold/templates.ts +5 -0
- package/upgrades/1.3.451.md +57 -0
- package/upgrades/side-effects/growth-milestone-analyst-dev-gate.md +99 -0
- package/upgrades/side-effects/growth-milestone-analyst.md +92 -0
- 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
|