@indigoai-us/hq-cli 5.263.0 → 5.265.0

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.
@@ -0,0 +1,412 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { DEFAULT_KR_FRESHNESS_HOURS, loadLanesConfig } from "./config.js";
5
+ import { LanesError } from "./errors.js";
6
+ import { resolveManagerStatusTarget } from "./manager-status-gate.js";
7
+ import { readLanesRegistryFlag } from "./registry-flag.js";
8
+ import { assertNoSecretText } from "./text-scan.js";
9
+ export const KR_FRESHNESS_FLAG = "lanes.kr-freshness";
10
+ export const KR_FRESHNESS_DEFAULT_HOURS = DEFAULT_KR_FRESHNESS_HOURS;
11
+ export const KR_FRESHNESS_LEDGER_REL = path.join("workspace", "lanes", "kpi-ledger");
12
+ export const KR_FRESHNESS_LEDGER_SCHEMA = "kr-freshness-ledger.v1";
13
+ export const KR_FRESHNESS_HISTORY_LIMIT = 50;
14
+ const MAX_STATUS_BYTES = 1024 * 1024;
15
+ const MAX_LEDGER_BYTES = 1024 * 1024;
16
+ const MAX_KPIS = 1000;
17
+ const REMOVED_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
18
+ function isObject(value) {
19
+ return value !== null && typeof value === "object" && !Array.isArray(value);
20
+ }
21
+ function fail(code, message, details = {}) {
22
+ throw new LanesError(code, message, details);
23
+ }
24
+ function errorCode(error) {
25
+ return error instanceof LanesError ? error.error : "kr_freshness_unavailable";
26
+ }
27
+ function sha256(value) {
28
+ return createHash("sha256").update(value, "utf8").digest("hex");
29
+ }
30
+ function displayName(name) {
31
+ const chars = Array.from(name);
32
+ return chars.length <= 120 ? name : `${chars.slice(0, 119).join("")}…`;
33
+ }
34
+ function safeLaneId(laneId) {
35
+ return /^[A-Za-z0-9._-]{1,200}$/.test(laneId) && laneId !== "." && laneId !== "..";
36
+ }
37
+ function ensureLedgerDirectory(root, create) {
38
+ let current = path.resolve(root);
39
+ const parts = [current];
40
+ for (const segment of ["workspace", "lanes", "kpi-ledger"]) {
41
+ current = path.join(current, segment);
42
+ parts.push(current);
43
+ }
44
+ for (const [index, directory] of parts.entries()) {
45
+ if (create && index > 0) {
46
+ try {
47
+ fs.mkdirSync(directory, { mode: 0o700 });
48
+ }
49
+ catch (error) {
50
+ if (error.code !== "EEXIST") {
51
+ fail("kr_freshness_ledger_write_failed", `Cannot create KR freshness ledger directory: ${directory}`, { path: directory });
52
+ }
53
+ }
54
+ }
55
+ let stat;
56
+ try {
57
+ stat = fs.lstatSync(directory);
58
+ }
59
+ catch (error) {
60
+ if (!create && error.code === "ENOENT")
61
+ return false;
62
+ fail("kr_freshness_ledger_unavailable", `Cannot inspect KR freshness ledger directory: ${directory}`, { path: directory });
63
+ }
64
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
65
+ fail("kr_freshness_ledger_invalid", `KR freshness ledger path must not be a symlink: ${directory}`, { path: directory });
66
+ }
67
+ }
68
+ return true;
69
+ }
70
+ function ledgerFilePath(root, laneId) {
71
+ if (!safeLaneId(laneId))
72
+ fail("kr_freshness_lane_id_invalid", "Lane id cannot be used as a KR freshness ledger filename");
73
+ return path.join(root, KR_FRESHNESS_LEDGER_REL, `${laneId}.json`);
74
+ }
75
+ function validDate(value) {
76
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
77
+ }
78
+ function validateLedger(value, laneId, filePath) {
79
+ if (!isObject(value) || value.schema_version !== KR_FRESHNESS_LEDGER_SCHEMA || value.lane_id !== laneId ||
80
+ !validDate(value.updated_at) || !isObject(value.kpis)) {
81
+ fail("kr_freshness_ledger_invalid", `KR freshness ledger has an invalid shape: ${filePath}`, { path: filePath });
82
+ }
83
+ const keys = Object.keys(value.kpis);
84
+ if (keys.length > MAX_KPIS)
85
+ fail("kr_freshness_ledger_invalid", `KR freshness ledger exceeds ${MAX_KPIS} entries`, { path: filePath });
86
+ const kpis = {};
87
+ for (const key of keys) {
88
+ const row = value.kpis[key];
89
+ if (!/^[a-f0-9]{64}$/.test(key) || !isObject(row) || typeof row.name !== "string" || row.name.trim() === "" ||
90
+ !(typeof row.value === "string" || (typeof row.value === "number" && Number.isFinite(row.value))) ||
91
+ !(row.entry_measured_at === null || typeof row.entry_measured_at === "string") ||
92
+ !validDate(row.first_seen_at) || !validDate(row.measured_at) || !validDate(row.value_changed_at) ||
93
+ (row.removed_at !== undefined && !validDate(row.removed_at))) {
94
+ fail("kr_freshness_ledger_invalid", `KR freshness ledger contains an invalid KPI record: ${filePath}`, { path: filePath });
95
+ }
96
+ assertNoSecretText("KR name", row.name);
97
+ if (typeof row.value === "string")
98
+ assertNoSecretText("KR value", row.value);
99
+ if (typeof row.entry_measured_at === "string")
100
+ assertNoSecretText("KR measured_at", row.entry_measured_at);
101
+ kpis[key] = {
102
+ name: row.name,
103
+ value: row.value,
104
+ entry_measured_at: row.entry_measured_at,
105
+ first_seen_at: row.first_seen_at,
106
+ measured_at: row.measured_at,
107
+ value_changed_at: row.value_changed_at,
108
+ ...(typeof row.removed_at === "string" ? { removed_at: row.removed_at } : {}),
109
+ };
110
+ }
111
+ return {
112
+ schema_version: KR_FRESHNESS_LEDGER_SCHEMA,
113
+ lane_id: laneId,
114
+ updated_at: value.updated_at,
115
+ kpis,
116
+ };
117
+ }
118
+ export function readKrFreshnessStatus(statusPath) {
119
+ let stat;
120
+ try {
121
+ stat = fs.lstatSync(statusPath);
122
+ }
123
+ catch (error) {
124
+ fail("kr_freshness_status_unreadable", `Cannot inspect manager status for KR freshness: ${statusPath}`, {
125
+ path: statusPath,
126
+ cause: error.code ?? "unknown",
127
+ });
128
+ }
129
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_STATUS_BYTES) {
130
+ fail("kr_freshness_status_invalid", `Manager status is not a bounded regular file: ${statusPath}`, { path: statusPath });
131
+ }
132
+ let text;
133
+ try {
134
+ text = fs.readFileSync(statusPath, "utf8");
135
+ }
136
+ catch (error) {
137
+ fail("kr_freshness_status_unreadable", `Cannot read manager status for KR freshness: ${statusPath}`, {
138
+ path: statusPath,
139
+ cause: error.code ?? "unknown",
140
+ });
141
+ }
142
+ if (Buffer.byteLength(text, "utf8") > MAX_STATUS_BYTES) {
143
+ fail("kr_freshness_status_invalid", `Manager status exceeds the KR freshness size limit: ${statusPath}`, { path: statusPath });
144
+ }
145
+ let parsed;
146
+ try {
147
+ parsed = JSON.parse(text);
148
+ }
149
+ catch {
150
+ fail("kr_freshness_status_invalid", `Manager status contains invalid JSON: ${statusPath}`, { path: statusPath });
151
+ }
152
+ if (!isObject(parsed) || !Array.isArray(parsed.kpis)) {
153
+ fail("kr_freshness_status_invalid", `Manager status must contain a top-level kpis array: ${statusPath}`, { path: statusPath });
154
+ }
155
+ if (parsed.kpis.length > MAX_KPIS) {
156
+ fail("kr_freshness_status_invalid", `Manager status exceeds ${MAX_KPIS} KPIs: ${statusPath}`, { path: statusPath });
157
+ }
158
+ const found = new Map();
159
+ for (const [index, candidate] of parsed.kpis.entries()) {
160
+ if (!isObject(candidate) || typeof candidate.name !== "string" || candidate.name.trim() === "")
161
+ continue;
162
+ const fullName = candidate.name.trim();
163
+ if (Array.from(fullName).some((char) => {
164
+ const codePoint = char.codePointAt(0) ?? 0;
165
+ return codePoint <= 0x1f || codePoint === 0x7f;
166
+ })) {
167
+ fail("kr_freshness_status_invalid", `Manager KPI ${index + 1} name contains control characters: ${statusPath}`, { path: statusPath, index });
168
+ }
169
+ if (!(typeof candidate.value === "string" || (typeof candidate.value === "number" && Number.isFinite(candidate.value)))) {
170
+ fail("kr_freshness_status_invalid", `Manager KPI ${index + 1} needs a string or finite number value: ${statusPath}`, { path: statusPath, index });
171
+ }
172
+ if (candidate.measured_at !== undefined && typeof candidate.measured_at !== "string") {
173
+ fail("kr_freshness_status_invalid", `Manager KPI ${index + 1} measured_at must be a string when present: ${statusPath}`, { path: statusPath, index });
174
+ }
175
+ assertNoSecretText("KR name", fullName);
176
+ if (typeof candidate.value === "string")
177
+ assertNoSecretText("KR value", candidate.value);
178
+ if (typeof candidate.measured_at === "string")
179
+ assertNoSecretText("KR measured_at", candidate.measured_at);
180
+ const key = sha256(fullName);
181
+ if (found.has(key))
182
+ fail("kr_freshness_status_invalid", `Manager status contains duplicate KPI names: ${statusPath}`, { path: statusPath, index });
183
+ found.set(key, {
184
+ key,
185
+ fullName,
186
+ name: displayName(fullName),
187
+ value: candidate.value,
188
+ entryMeasuredAt: typeof candidate.measured_at === "string" ? candidate.measured_at : null,
189
+ });
190
+ }
191
+ return [...found.values()];
192
+ }
193
+ export function krFreshnessLedgerPath(root, laneId) {
194
+ return ledgerFilePath(root, laneId);
195
+ }
196
+ export function readKrFreshnessLedger(root, laneId) {
197
+ const filePath = ledgerFilePath(root, laneId);
198
+ if (!ensureLedgerDirectory(root, false))
199
+ return null;
200
+ let stat;
201
+ try {
202
+ stat = fs.lstatSync(filePath);
203
+ }
204
+ catch (error) {
205
+ if (error.code === "ENOENT")
206
+ return null;
207
+ fail("kr_freshness_ledger_unreadable", `Cannot inspect KR freshness ledger: ${filePath}`, { path: filePath });
208
+ }
209
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_LEDGER_BYTES) {
210
+ fail("kr_freshness_ledger_invalid", `KR freshness ledger is not a bounded regular file: ${filePath}`, { path: filePath });
211
+ }
212
+ let parsed;
213
+ try {
214
+ parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
215
+ }
216
+ catch {
217
+ fail("kr_freshness_ledger_invalid", `KR freshness ledger contains invalid JSON: ${filePath}`, { path: filePath });
218
+ }
219
+ return validateLedger(parsed, laneId, filePath);
220
+ }
221
+ export function writeKrFreshnessLedger(root, ledger) {
222
+ const filePath = ledgerFilePath(root, ledger.lane_id);
223
+ const directory = path.dirname(filePath);
224
+ let tempPath;
225
+ try {
226
+ ensureLedgerDirectory(root, true);
227
+ const data = `${JSON.stringify(ledger, null, 2)}\n`;
228
+ if (Buffer.byteLength(data, "utf8") > MAX_LEDGER_BYTES) {
229
+ fail("kr_freshness_ledger_invalid", `KR freshness ledger exceeds ${MAX_LEDGER_BYTES} bytes`, { path: filePath });
230
+ }
231
+ tempPath = path.join(directory, `.${ledger.lane_id}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
232
+ fs.writeFileSync(tempPath, data, { encoding: "utf8", flag: "wx", mode: 0o600 });
233
+ fs.renameSync(tempPath, filePath);
234
+ }
235
+ catch (error) {
236
+ if (tempPath !== undefined) {
237
+ try {
238
+ fs.unlinkSync(tempPath);
239
+ }
240
+ catch { /* preserve the original write failure */ }
241
+ }
242
+ if (error instanceof LanesError)
243
+ throw error;
244
+ fail("kr_freshness_ledger_write_failed", `Cannot write KR freshness ledger: ${filePath}`, { path: filePath });
245
+ }
246
+ }
247
+ export function updateKrFreshnessLedger(laneId, prior, statusKpis, now) {
248
+ const at = new Date(now).toISOString();
249
+ const next = {};
250
+ const seen = new Set();
251
+ for (const item of statusKpis) {
252
+ const { key, fullName } = item;
253
+ seen.add(key);
254
+ const old = prior?.kpis[key];
255
+ const changed = old === undefined || JSON.stringify(old.value) !== JSON.stringify(item.value);
256
+ const remeasured = old === undefined || old.removed_at !== undefined || old.entry_measured_at !== item.entryMeasuredAt;
257
+ next[key] = {
258
+ name: item.name,
259
+ value: item.value,
260
+ entry_measured_at: item.entryMeasuredAt,
261
+ first_seen_at: old?.first_seen_at ?? at,
262
+ measured_at: changed || remeasured ? at : old.measured_at,
263
+ value_changed_at: changed ? at : old.value_changed_at,
264
+ };
265
+ assertNoSecretText("KR name", fullName);
266
+ }
267
+ for (const [key, old] of Object.entries(prior?.kpis ?? {})) {
268
+ if (seen.has(key))
269
+ continue;
270
+ const removedAt = old.removed_at ?? at;
271
+ if (now - Date.parse(removedAt) >= REMOVED_RETENTION_MS)
272
+ continue;
273
+ next[key] = { ...old, removed_at: removedAt };
274
+ }
275
+ if (Object.keys(next).length > MAX_KPIS) {
276
+ fail("kr_freshness_ledger_invalid", `KR freshness ledger exceeds ${MAX_KPIS} retained entries`);
277
+ }
278
+ return {
279
+ schema_version: KR_FRESHNESS_LEDGER_SCHEMA,
280
+ lane_id: laneId,
281
+ updated_at: at,
282
+ kpis: next,
283
+ };
284
+ }
285
+ export function krFreshnessVerdict(kpi, now, windowHours) {
286
+ const cutoff = now - windowHours * 60 * 60 * 1000;
287
+ if (Date.parse(kpi.value_changed_at) >= cutoff)
288
+ return "moving";
289
+ if (Date.parse(kpi.measured_at) >= cutoff)
290
+ return "flat";
291
+ return "stalled";
292
+ }
293
+ export function krFreshnessFingerprint(keys) {
294
+ return sha256(JSON.stringify([...keys].sort()));
295
+ }
296
+ function viewsFromLedger(ledger, now, windowHours) {
297
+ if (!ledger)
298
+ return [];
299
+ return Object.entries(ledger.kpis)
300
+ .filter(([, kpi]) => kpi.removed_at === undefined)
301
+ .sort(([left], [right]) => left.localeCompare(right))
302
+ .map(([, kpi]) => ({
303
+ name: kpi.name,
304
+ value: kpi.value,
305
+ verdict: krFreshnessVerdict(kpi, now, windowHours),
306
+ measured_at: kpi.measured_at,
307
+ value_changed_at: kpi.value_changed_at,
308
+ }));
309
+ }
310
+ function reportFailure(lane, error) {
311
+ process.stderr.write(`hq lanes: KR freshness disabled for ${lane.lane_id}: ${errorCode(error)}\n`);
312
+ }
313
+ function failedResolution(error, onError) {
314
+ onError(error);
315
+ return {
316
+ flagEnabled: false,
317
+ enabled: false,
318
+ windowHours: KR_FRESHNESS_DEFAULT_HOURS,
319
+ kpis: [],
320
+ stalledKeys: [],
321
+ error: errorCode(error),
322
+ };
323
+ }
324
+ /** Read the flag before manager mapping, status, configuration or ledger files. */
325
+ export async function resolveKrFreshnessForLane(root, lane, options = {}) {
326
+ const onError = options.onError ?? ((error) => reportFailure(lane, error));
327
+ let flagEnabled;
328
+ try {
329
+ flagEnabled = await (options.readFlag ?? readLanesRegistryFlag)(KR_FRESHNESS_FLAG, {
330
+ company: lane.company ?? lane.mesh_company ?? undefined,
331
+ env: options.env,
332
+ });
333
+ }
334
+ catch (error) {
335
+ return failedResolution(error, onError);
336
+ }
337
+ if (!flagEnabled) {
338
+ return { flagEnabled: false, enabled: false, windowHours: KR_FRESHNESS_DEFAULT_HOURS, kpis: [], stalledKeys: [] };
339
+ }
340
+ try {
341
+ const target = resolveManagerStatusTarget(root, lane);
342
+ if (!target.manager || target.statusPath === undefined) {
343
+ return {
344
+ flagEnabled: true,
345
+ enabled: true,
346
+ target,
347
+ windowHours: KR_FRESHNESS_DEFAULT_HOURS,
348
+ kpis: [],
349
+ stalledKeys: [],
350
+ };
351
+ }
352
+ const windowHours = loadLanesConfig(root).kr_freshness_hours;
353
+ const statusKpis = readKrFreshnessStatus(target.statusPath);
354
+ const prior = readKrFreshnessLedger(root, lane.lane_id);
355
+ const ledger = updateKrFreshnessLedger(lane.lane_id, prior, statusKpis, options.now ?? Date.now());
356
+ writeKrFreshnessLedger(root, ledger);
357
+ const effectiveNow = options.now ?? Date.now();
358
+ return {
359
+ flagEnabled: true,
360
+ enabled: true,
361
+ target,
362
+ statusPath: target.statusPath,
363
+ windowHours,
364
+ kpis: viewsFromLedger(ledger, effectiveNow, windowHours),
365
+ stalledKeys: Object.entries(ledger.kpis)
366
+ .filter(([, kpi]) => kpi.removed_at === undefined && krFreshnessVerdict(kpi, effectiveNow, windowHours) === "stalled")
367
+ .map(([key]) => key)
368
+ .sort(),
369
+ };
370
+ }
371
+ catch (error) {
372
+ return failedResolution(error, onError);
373
+ }
374
+ }
375
+ /** Read a previously written ledger for `lanes show`; this never reads status. */
376
+ export async function resolveKrFreshnessForShow(root, lane, options = {}) {
377
+ const onError = options.onError ?? ((error) => reportFailure(lane, error));
378
+ let enabled;
379
+ try {
380
+ enabled = await (options.readFlag ?? readLanesRegistryFlag)(KR_FRESHNESS_FLAG, {
381
+ company: lane.company ?? lane.mesh_company ?? undefined,
382
+ env: options.env,
383
+ });
384
+ }
385
+ catch (error) {
386
+ onError(error);
387
+ return null;
388
+ }
389
+ if (!enabled)
390
+ return null;
391
+ try {
392
+ const windowHours = loadLanesConfig(root).kr_freshness_hours;
393
+ const ledger = readKrFreshnessLedger(root, lane.lane_id);
394
+ return { enabled: true, kpis: viewsFromLedger(ledger, options.now ?? Date.now(), windowHours) };
395
+ }
396
+ catch (error) {
397
+ onError(error);
398
+ return null;
399
+ }
400
+ }
401
+ /** Operator report path: the ledger remains readable while runtime enforcement is off. */
402
+ export function readKrFreshnessReport(root, laneId, now) {
403
+ const ledger = readKrFreshnessLedger(root, laneId);
404
+ const windowHours = loadLanesConfig(root).kr_freshness_hours;
405
+ return {
406
+ ledger_path: krFreshnessLedgerPath(root, laneId),
407
+ available: ledger !== null,
408
+ kr_freshness_hours: windowHours,
409
+ kpis: viewsFromLedger(ledger, now, windowHours),
410
+ };
411
+ }
412
+ //# sourceMappingURL=kr-freshness.js.map
@@ -0,0 +1,9 @@
1
+ import type { LaneCompanyRef, LaneRecord } from "./types.js";
2
+ export type LaneCompanySource = "lane" | "project" | "unresolved";
3
+ export interface LaneCompanyResolution {
4
+ company: LaneCompanyRef | null;
5
+ source: LaneCompanySource;
6
+ }
7
+ /** Resolve a lane's company without inferring ownership from its worker or caller. */
8
+ export declare function resolveLaneCompany(hqRoot: string, lane: Pick<LaneRecord, "lane_id" | "company" | "project_id">): LaneCompanyResolution;
9
+ //# sourceMappingURL=lane-company.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { resolveProjectQuestionCompany } from "./questions.js";
2
+ import { LanesError } from "./errors.js";
3
+ /** Resolve a lane's company without inferring ownership from its worker or caller. */
4
+ export function resolveLaneCompany(hqRoot, lane) {
5
+ const slug = typeof lane.company?.slug === "string" ? lane.company.slug.trim() : "";
6
+ const uid = typeof lane.company?.uid === "string" ? lane.company.uid.trim() : "";
7
+ if (slug && uid) {
8
+ return { company: { slug, uid }, source: "lane" };
9
+ }
10
+ let projectCompany;
11
+ try {
12
+ projectCompany = resolveProjectQuestionCompany(hqRoot, lane);
13
+ }
14
+ catch (err) {
15
+ if (err instanceof LanesError && err.error === "question_company_unavailable") {
16
+ return { company: null, source: "unresolved" };
17
+ }
18
+ throw err;
19
+ }
20
+ return projectCompany
21
+ ? { company: projectCompany, source: "project" }
22
+ : { company: null, source: "unresolved" };
23
+ }
24
+ //# sourceMappingURL=lane-company.js.map
@@ -22,6 +22,7 @@ import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, envelopeRepairMode, isPidRecor
22
22
  import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, updateLane, updateQuestion, } from "./store.js";
23
23
  import { applyThrottleDecision, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, throttleStatePath, writeThrottleState, } from "./throttle.js";
24
24
  import { readLanesRegistryFlag } from "./registry-flag.js";
25
+ import { resolveLaneCompany } from "./lane-company.js";
25
26
  import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
26
27
  import { providerSupportsSessionResume } from "./delivery.js";
27
28
  import { applyDefaultQuotaHold, readQuotaSignal, recordAccountQuota } from "./quota.js";
@@ -851,8 +852,11 @@ export async function runPassBody(hqRoot, deps) {
851
852
  if (doneKeepAliveVerdict(lane, envelope, hasQueuedWork) !== "eligible")
852
853
  continue;
853
854
  }
854
- const key = keepAliveCompanyKey(lane.company);
855
- keepAliveFlagCompanies.set(key, lane.company);
855
+ const company = resolveLaneCompany(hqRoot, lane).company;
856
+ if (company === null)
857
+ continue;
858
+ const key = keepAliveCompanyKey(company);
859
+ keepAliveFlagCompanies.set(key, company);
856
860
  }
857
861
  const keepAliveFlagByCompany = new Map();
858
862
  const supersessionLineageAwareByCompany = new Map();
@@ -1598,6 +1602,15 @@ function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCom
1598
1602
  const nowMs = now.getTime();
1599
1603
  const nowIso = now.toISOString();
1600
1604
  const config = loadLanesConfig(hqRoot);
1605
+ const resolvedCompanyByLaneVersion = new Map();
1606
+ const companyForLane = (candidate) => {
1607
+ const key = `${candidate.lane_id}\u0000${candidate.version}`;
1608
+ if (resolvedCompanyByLaneVersion.has(key))
1609
+ return resolvedCompanyByLaneVersion.get(key) ?? null;
1610
+ const company = resolveLaneCompany(hqRoot, candidate).company;
1611
+ resolvedCompanyByLaneVersion.set(key, company);
1612
+ return company;
1613
+ };
1601
1614
  const keepAliveEnabled = [...flagByCompany.values()].some((enabled) => enabled);
1602
1615
  let retiredLaneIds = null;
1603
1616
  if (keepAliveEnabled) {
@@ -1615,7 +1628,7 @@ function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCom
1615
1628
  originalLane.state === "cancelled" ||
1616
1629
  originalLane.state === "failed") {
1617
1630
  if (originalLane.keep_alive === true &&
1618
- flagByCompany.get(keepAliveCompanyKey(originalLane.company)) === true)
1631
+ flagByCompany.get(keepAliveCompanyKey(companyForLane(originalLane))) === true)
1619
1632
  recordKeepAliveSkip(hqRoot, originalLane, "terminal_state", summary, deps);
1620
1633
  continue;
1621
1634
  }
@@ -1641,8 +1654,28 @@ function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCom
1641
1654
  continue;
1642
1655
  }
1643
1656
  }
1644
- if (flagByCompany.get(keepAliveCompanyKey(lane.company)) !== true)
1657
+ const company = companyForLane(lane);
1658
+ if (company === null) {
1659
+ if (lane.keep_alive === true) {
1660
+ summary.skipped.push({
1661
+ kind: "lane",
1662
+ id: lane.lane_id,
1663
+ why: "keep_alive_company_unresolved",
1664
+ });
1665
+ }
1645
1666
  continue;
1667
+ }
1668
+ const companyKey = keepAliveCompanyKey(company);
1669
+ if (flagByCompany.get(companyKey) !== true) {
1670
+ if (lane.keep_alive === true) {
1671
+ summary.skipped.push({
1672
+ kind: "lane",
1673
+ id: lane.lane_id,
1674
+ why: "keep_alive_flag_off",
1675
+ });
1676
+ }
1677
+ continue;
1678
+ }
1646
1679
  // Persist a stable timestamp for legacy interrupted rows before a later
1647
1680
  // keep-alive status write can advance their general updated_at clock. The
1648
1681
  // watcher classification is the interruption time; updated_at is only an
@@ -1760,7 +1793,7 @@ function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCom
1760
1793
  recordKeepAliveSkip(hqRoot, lane, "retired_list_invalid", summary, deps);
1761
1794
  continue;
1762
1795
  }
1763
- const exclusion = keepAliveExclusion(lane, lanes, retiredLaneIds, supersessionLineageAwareByCompany.get(keepAliveCompanyKey(lane.company)) === true);
1796
+ const exclusion = keepAliveExclusion(lane, lanes, retiredLaneIds, supersessionLineageAwareByCompany.get(companyKey) === true, companyForLane);
1764
1797
  if (exclusion !== undefined) {
1765
1798
  recordKeepAliveSkip(hqRoot, lane, exclusion, summary, deps);
1766
1799
  continue;
@@ -1851,7 +1884,25 @@ function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCom
1851
1884
  continue;
1852
1885
  }
1853
1886
  const currentLanes = loadLaneRecords(hqRoot, summary);
1854
- const currentExclusion = keepAliveExclusion(current, currentLanes, retiredLaneIds, supersessionLineageAwareByCompany.get(keepAliveCompanyKey(current.company)) === true);
1887
+ const currentCompany = companyForLane(current);
1888
+ if (currentCompany === null) {
1889
+ summary.skipped.push({
1890
+ kind: "lane",
1891
+ id: current.lane_id,
1892
+ why: "keep_alive_company_unresolved",
1893
+ });
1894
+ continue;
1895
+ }
1896
+ const currentCompanyKey = keepAliveCompanyKey(currentCompany);
1897
+ if (flagByCompany.get(currentCompanyKey) !== true) {
1898
+ summary.skipped.push({
1899
+ kind: "lane",
1900
+ id: current.lane_id,
1901
+ why: "keep_alive_flag_off",
1902
+ });
1903
+ continue;
1904
+ }
1905
+ const currentExclusion = keepAliveExclusion(current, currentLanes, retiredLaneIds, supersessionLineageAwareByCompany.get(currentCompanyKey) === true, companyForLane);
1855
1906
  if (currentExclusion !== undefined) {
1856
1907
  recordKeepAliveSkip(hqRoot, current, currentExclusion, summary, deps);
1857
1908
  continue;
@@ -370,7 +370,22 @@ export interface BlockerFreshnessHistoryEntry {
370
370
  resume_skipped_reason?: "open_question" | "blocked" | "miss_limit" | "no_provider_session";
371
371
  delivery_failed?: true;
372
372
  }
373
- export type LaneGateHistoryEntry = ManagerStatusGateHistoryEntry | BlockerFreshnessHistoryEntry;
373
+ export interface KrFreshnessState {
374
+ /** Stable fingerprint of the last stalled KPI set, which is resumed once. */
375
+ stalled_fingerprint?: string;
376
+ }
377
+ export interface KrFreshnessHistoryEntry {
378
+ event: "kr_stalled";
379
+ turn_id: string;
380
+ path: string;
381
+ at: string;
382
+ names: string[];
383
+ count: number;
384
+ fingerprint: string;
385
+ resume_queued?: boolean;
386
+ resume_skipped_reason?: "open_question" | "blocked" | "miss_limit" | "no_provider_session";
387
+ }
388
+ export type LaneGateHistoryEntry = ManagerStatusGateHistoryEntry | BlockerFreshnessHistoryEntry | KrFreshnessHistoryEntry;
374
389
  export interface LaneStory {
375
390
  project_id: string;
376
391
  story_id: string;
@@ -516,6 +531,8 @@ export interface LaneRecord {
516
531
  status_gate_history?: LaneGateHistoryEntry[];
517
532
  /** Last blocker-freshness state used to detect consecutive stale turns. */
518
533
  blocker_freshness_state?: BlockerFreshnessState;
534
+ /** Last stalled KR set already offered one automatic resume. */
535
+ kr_freshness_state?: KrFreshnessState;
519
536
  /** Last automatic compaction of a resumed session (compaction.ts). */
520
537
  last_compaction?: LaneCompactionRecord;
521
538
  /**
@@ -857,6 +874,8 @@ export interface LanesConfig {
857
874
  session_question_timeout_secs: number;
858
875
  /** Age in hours after which an open manager blocker must be re-verified. */
859
876
  blocker_freshness_hours: number;
877
+ /** Age in hours after which a manager KPI is considered stalled. */
878
+ kr_freshness_hours: number;
860
879
  }
861
880
  export declare function parseMajorSchema(schemaVersion: string, expectedPrefix: string, expectedMajor: number): void;
862
881
  export declare function parseLaneEnvelope(raw: unknown): LaneEnvelope;
@@ -228,7 +228,7 @@ export const LANE_FIELD_WRITERS = {
228
228
  provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
229
229
  version: ["creator"], created_at: ["creator"], updated_at: ["creator"], attribution_history: ["command"],
230
230
  last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn", "command"], effort_source: ["creator", "spawn", "command"], fast_mode: ["spawn"],
231
- last_message_at: ["command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher"], blocker_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
231
+ last_message_at: ["command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
232
232
  overdue: ["reconcile"], overdue_at: ["reconcile"], last_host_report_at: ["watcher", "spawn", "reconcile"], stalled: ["watcher"], stalled_at: ["watcher"], stalled_reason: ["watcher"],
233
233
  admitted_reason: ["command", "reconcile"], terminal_reason: ["command", "watcher", "spawn", "reconcile"], stop_history: ["command"], interrupted_at: ["watcher", "reconcile"], terminal_details: ["command", "spawn", "watcher", "reconcile"],
234
234
  quota_reset_at: ["watcher", "reconcile"], model_quota_marker: ["watcher", "reconcile"], outpost_command_id: ["command"],
@@ -233,6 +233,8 @@ export interface WatcherLoopDeps {
233
233
  readManagerStatusGateFlag?: typeof readLanesRegistryFlag;
234
234
  /** Test seam; production reads the company-scoped hq-flags row. */
235
235
  readBlockerFreshnessFlag?: typeof readLanesRegistryFlag;
236
+ /** Test seam; production reads the company-scoped hq-flags row. */
237
+ readKrFreshnessFlag?: typeof readLanesRegistryFlag;
236
238
  }
237
239
  /**
238
240
  * Wrap one mesh call. A failed emit is logged as `mesh_event_failed` with