chainlesschain 0.132.0 → 0.143.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.
Files changed (106) hide show
  1. package/package.json +1 -1
  2. package/src/commands/a2a.js +62 -0
  3. package/src/commands/activitypub.js +61 -0
  4. package/src/commands/agent.js +117 -0
  5. package/src/commands/bi.js +61 -0
  6. package/src/commands/bm25.js +78 -0
  7. package/src/commands/browse.js +64 -0
  8. package/src/commands/ccron.js +78 -0
  9. package/src/commands/compliance.js +40 -0
  10. package/src/commands/compt.js +78 -0
  11. package/src/commands/consol.js +231 -0
  12. package/src/commands/cowork.js +42 -0
  13. package/src/commands/crosschain.js +62 -0
  14. package/src/commands/dao.js +62 -0
  15. package/src/commands/dlp.js +61 -0
  16. package/src/commands/economy.js +56 -0
  17. package/src/commands/evolution.js +56 -0
  18. package/src/commands/evomap.js +61 -0
  19. package/src/commands/fflag.js +178 -0
  20. package/src/commands/git.js +45 -0
  21. package/src/commands/hmemory.js +56 -0
  22. package/src/commands/inference.js +42 -0
  23. package/src/commands/itbudget.js +45 -0
  24. package/src/commands/lowcode.js +44 -0
  25. package/src/commands/matrix.js +62 -0
  26. package/src/commands/mcpscaf.js +41 -0
  27. package/src/commands/meminj.js +41 -0
  28. package/src/commands/nostr.js +62 -0
  29. package/src/commands/orchgov.js +45 -0
  30. package/src/commands/pdfp.js +78 -0
  31. package/src/commands/perf.js +39 -0
  32. package/src/commands/perm.js +45 -0
  33. package/src/commands/pipeline.js +57 -1
  34. package/src/commands/planmode.js +45 -0
  35. package/src/commands/promcomp.js +82 -0
  36. package/src/commands/recommend.js +42 -0
  37. package/src/commands/seshhook.js +41 -0
  38. package/src/commands/seshsearch.js +41 -0
  39. package/src/commands/seshtail.js +41 -0
  40. package/src/commands/seshu.js +41 -0
  41. package/src/commands/sganal.js +78 -0
  42. package/src/commands/siem.js +40 -0
  43. package/src/commands/slotfill.js +41 -0
  44. package/src/commands/social.js +34 -0
  45. package/src/commands/svccont.js +45 -0
  46. package/src/commands/tms.js +45 -0
  47. package/src/commands/topiccls.js +45 -0
  48. package/src/commands/uprof.js +45 -0
  49. package/src/commands/vcheck.js +78 -0
  50. package/src/commands/webfetch.js +41 -0
  51. package/src/commands/zkp.js +62 -0
  52. package/src/harness/prompt-compressor.js +331 -0
  53. package/src/index.js +61 -1
  54. package/src/lib/a2a-protocol.js +105 -0
  55. package/src/lib/activitypub-bridge.js +105 -0
  56. package/src/lib/agent-economy.js +105 -0
  57. package/src/lib/app-builder.js +105 -0
  58. package/src/lib/autonomous-agent.js +105 -0
  59. package/src/lib/bi-engine.js +105 -0
  60. package/src/lib/bm25-search.js +81 -0
  61. package/src/lib/browser-automation.js +105 -0
  62. package/src/lib/compliance-framework-reporter.js +105 -0
  63. package/src/lib/compression-telemetry.js +81 -0
  64. package/src/lib/content-recommender.js +105 -0
  65. package/src/lib/cowork-cron.js +81 -0
  66. package/src/lib/cowork-task-runner.js +105 -0
  67. package/src/lib/cross-chain.js +105 -0
  68. package/src/lib/dao-governance.js +105 -0
  69. package/src/lib/dlp-engine.js +105 -0
  70. package/src/lib/evolution-system.js +105 -0
  71. package/src/lib/evomap-manager.js +105 -0
  72. package/src/lib/execution-backend.js +105 -0
  73. package/src/lib/feature-flags.js +85 -0
  74. package/src/lib/git-integration.js +105 -0
  75. package/src/lib/hierarchical-memory.js +105 -0
  76. package/src/lib/inference-network.js +105 -0
  77. package/src/lib/iteration-budget.js +105 -0
  78. package/src/lib/matrix-bridge.js +105 -0
  79. package/src/lib/mcp-scaffold.js +81 -0
  80. package/src/lib/memory-injection.js +81 -0
  81. package/src/lib/nostr-bridge.js +105 -0
  82. package/src/lib/orchestrator.js +105 -0
  83. package/src/lib/pdf-parser.js +81 -0
  84. package/src/lib/perf-tuning.js +105 -0
  85. package/src/lib/permission-engine.js +81 -0
  86. package/src/lib/pipeline-orchestrator.js +105 -0
  87. package/src/lib/plan-mode.js +81 -0
  88. package/src/lib/prompt-compressor.js +1 -10
  89. package/src/lib/service-container.js +81 -0
  90. package/src/lib/session-consolidator.js +105 -0
  91. package/src/lib/session-hooks.js +81 -0
  92. package/src/lib/session-search.js +81 -0
  93. package/src/lib/session-tail.js +81 -0
  94. package/src/lib/session-usage.js +83 -0
  95. package/src/lib/siem-exporter.js +105 -0
  96. package/src/lib/slot-filler.js +81 -0
  97. package/src/lib/social-graph-analytics.js +81 -0
  98. package/src/lib/social-graph.js +81 -0
  99. package/src/lib/sub-agent-registry.js +110 -0
  100. package/src/lib/task-model-selector.js +81 -0
  101. package/src/lib/todo-manager.js +105 -0
  102. package/src/lib/topic-classifier.js +105 -0
  103. package/src/lib/user-profile.js +81 -0
  104. package/src/lib/version-checker.js +81 -0
  105. package/src/lib/web-fetch.js +81 -0
  106. package/src/lib/zkp-engine.js +105 -0
@@ -218,3 +218,113 @@ export class SubAgentRegistry {
218
218
  };
219
219
  }
220
220
  }
221
+
222
+
223
+ // ===== V2 Surface: Sub-Agent Registry governance overlay (CLI v0.133.0) =====
224
+ export const SUBAGENT_PROFILE_MATURITY_V2 = Object.freeze({
225
+ PENDING: "pending", ACTIVE: "active", PAUSED: "paused", RETIRED: "retired",
226
+ });
227
+ export const SUBAGENT_TASK_LIFECYCLE_V2 = Object.freeze({
228
+ QUEUED: "queued", RUNNING: "running", COMPLETED: "completed", FAILED: "failed", CANCELLED: "cancelled",
229
+ });
230
+
231
+ const _saProfileTrans = new Map([
232
+ [SUBAGENT_PROFILE_MATURITY_V2.PENDING, new Set([SUBAGENT_PROFILE_MATURITY_V2.ACTIVE, SUBAGENT_PROFILE_MATURITY_V2.RETIRED])],
233
+ [SUBAGENT_PROFILE_MATURITY_V2.ACTIVE, new Set([SUBAGENT_PROFILE_MATURITY_V2.PAUSED, SUBAGENT_PROFILE_MATURITY_V2.RETIRED])],
234
+ [SUBAGENT_PROFILE_MATURITY_V2.PAUSED, new Set([SUBAGENT_PROFILE_MATURITY_V2.ACTIVE, SUBAGENT_PROFILE_MATURITY_V2.RETIRED])],
235
+ [SUBAGENT_PROFILE_MATURITY_V2.RETIRED, new Set()],
236
+ ]);
237
+ const _saProfileTerminal = new Set([SUBAGENT_PROFILE_MATURITY_V2.RETIRED]);
238
+ const _saTaskTrans = new Map([
239
+ [SUBAGENT_TASK_LIFECYCLE_V2.QUEUED, new Set([SUBAGENT_TASK_LIFECYCLE_V2.RUNNING, SUBAGENT_TASK_LIFECYCLE_V2.CANCELLED])],
240
+ [SUBAGENT_TASK_LIFECYCLE_V2.RUNNING, new Set([SUBAGENT_TASK_LIFECYCLE_V2.COMPLETED, SUBAGENT_TASK_LIFECYCLE_V2.FAILED, SUBAGENT_TASK_LIFECYCLE_V2.CANCELLED])],
241
+ [SUBAGENT_TASK_LIFECYCLE_V2.COMPLETED, new Set()],
242
+ [SUBAGENT_TASK_LIFECYCLE_V2.FAILED, new Set()],
243
+ [SUBAGENT_TASK_LIFECYCLE_V2.CANCELLED, new Set()],
244
+ ]);
245
+
246
+ const _saProfiles = new Map();
247
+ const _saTasks = new Map();
248
+ let _saMaxActivePerOwner = 12;
249
+ let _saMaxPendingPerProfile = 24;
250
+ let _saProfileIdleMs = 2 * 60 * 60 * 1000;
251
+ let _saTaskStuckMs = 5 * 60 * 1000;
252
+
253
+ function _saPos(n, lbl) { const v = Math.floor(Number(n)); if (!Number.isFinite(v) || v <= 0) throw new Error(`${lbl} must be positive integer`); return v; }
254
+
255
+ export function setMaxActiveSubagentsPerOwnerV2(n) { _saMaxActivePerOwner = _saPos(n, "maxActiveSubagentsPerOwner"); }
256
+ export function getMaxActiveSubagentsPerOwnerV2() { return _saMaxActivePerOwner; }
257
+ export function setMaxPendingTasksPerSubagentV2(n) { _saMaxPendingPerProfile = _saPos(n, "maxPendingTasksPerSubagent"); }
258
+ export function getMaxPendingTasksPerSubagentV2() { return _saMaxPendingPerProfile; }
259
+ export function setSubagentIdleMsV2(n) { _saProfileIdleMs = _saPos(n, "subagentIdleMs"); }
260
+ export function getSubagentIdleMsV2() { return _saProfileIdleMs; }
261
+ export function setSubagentTaskStuckMsV2(n) { _saTaskStuckMs = _saPos(n, "subagentTaskStuckMs"); }
262
+ export function getSubagentTaskStuckMsV2() { return _saTaskStuckMs; }
263
+
264
+ export function _resetStateSubAgentRegistryV2() {
265
+ _saProfiles.clear(); _saTasks.clear();
266
+ _saMaxActivePerOwner = 12; _saMaxPendingPerProfile = 24;
267
+ _saProfileIdleMs = 2 * 60 * 60 * 1000; _saTaskStuckMs = 5 * 60 * 1000;
268
+ }
269
+
270
+ export function registerSubagentProfileV2({ id, owner, role, metadata } = {}) {
271
+ if (!id || typeof id !== "string") throw new Error("id is required");
272
+ if (!owner || typeof owner !== "string") throw new Error("owner is required");
273
+ if (_saProfiles.has(id)) throw new Error(`subagent profile ${id} already registered`);
274
+ const now = Date.now();
275
+ const p = { id, owner, role: role || "generic", status: SUBAGENT_PROFILE_MATURITY_V2.PENDING,
276
+ createdAt: now, updatedAt: now, activatedAt: null, retiredAt: null, lastTouchedAt: now,
277
+ metadata: { ...(metadata || {}) } };
278
+ _saProfiles.set(id, p);
279
+ return { ...p, metadata: { ...p.metadata } };
280
+ }
281
+
282
+ function _saCheckP(from, to) { const allowed = _saProfileTrans.get(from); if (!allowed || !allowed.has(to)) throw new Error(`invalid subagent profile transition ${from} → ${to}`); }
283
+ function _saCountActiveByOwner(owner) { let n = 0; for (const p of _saProfiles.values()) if (p.owner === owner && p.status === SUBAGENT_PROFILE_MATURITY_V2.ACTIVE) n++; return n; }
284
+
285
+ export function activateSubagentProfileV2(id) {
286
+ const p = _saProfiles.get(id); if (!p) throw new Error(`subagent profile ${id} not found`);
287
+ _saCheckP(p.status, SUBAGENT_PROFILE_MATURITY_V2.ACTIVE);
288
+ const recovery = p.status === SUBAGENT_PROFILE_MATURITY_V2.PAUSED;
289
+ if (!recovery) { const a = _saCountActiveByOwner(p.owner); if (a >= _saMaxActivePerOwner) throw new Error(`max active subagents per owner (${_saMaxActivePerOwner}) reached for ${p.owner}`); }
290
+ const now = Date.now(); p.status = SUBAGENT_PROFILE_MATURITY_V2.ACTIVE; p.updatedAt = now; p.lastTouchedAt = now;
291
+ if (!p.activatedAt) p.activatedAt = now;
292
+ return { ...p, metadata: { ...p.metadata } };
293
+ }
294
+ export function pauseSubagentProfileV2(id) { const p = _saProfiles.get(id); if (!p) throw new Error(`subagent profile ${id} not found`); _saCheckP(p.status, SUBAGENT_PROFILE_MATURITY_V2.PAUSED); p.status = SUBAGENT_PROFILE_MATURITY_V2.PAUSED; p.updatedAt = Date.now(); return { ...p, metadata: { ...p.metadata } }; }
295
+ export function retireSubagentProfileV2(id) { const p = _saProfiles.get(id); if (!p) throw new Error(`subagent profile ${id} not found`); _saCheckP(p.status, SUBAGENT_PROFILE_MATURITY_V2.RETIRED); const now = Date.now(); p.status = SUBAGENT_PROFILE_MATURITY_V2.RETIRED; p.updatedAt = now; if (!p.retiredAt) p.retiredAt = now; return { ...p, metadata: { ...p.metadata } }; }
296
+ export function touchSubagentProfileV2(id) { const p = _saProfiles.get(id); if (!p) throw new Error(`subagent profile ${id} not found`); if (_saProfileTerminal.has(p.status)) throw new Error(`cannot touch terminal subagent profile ${id}`); const now = Date.now(); p.lastTouchedAt = now; p.updatedAt = now; return { ...p, metadata: { ...p.metadata } }; }
297
+ export function getSubagentProfileV2(id) { const p = _saProfiles.get(id); if (!p) return null; return { ...p, metadata: { ...p.metadata } }; }
298
+ export function listSubagentProfilesV2() { return [..._saProfiles.values()].map((p) => ({ ...p, metadata: { ...p.metadata } })); }
299
+
300
+ function _saCountPendingByProfile(pid) { let n = 0; for (const t of _saTasks.values()) if (t.profileId === pid && (t.status === SUBAGENT_TASK_LIFECYCLE_V2.QUEUED || t.status === SUBAGENT_TASK_LIFECYCLE_V2.RUNNING)) n++; return n; }
301
+
302
+ export function createSubagentTaskV2({ id, profileId, description, metadata } = {}) {
303
+ if (!id || typeof id !== "string") throw new Error("id is required");
304
+ if (!profileId || typeof profileId !== "string") throw new Error("profileId is required");
305
+ if (_saTasks.has(id)) throw new Error(`subagent task ${id} already exists`);
306
+ if (!_saProfiles.has(profileId)) throw new Error(`subagent profile ${profileId} not found`);
307
+ const pending = _saCountPendingByProfile(profileId);
308
+ if (pending >= _saMaxPendingPerProfile) throw new Error(`max pending tasks per subagent (${_saMaxPendingPerProfile}) reached for ${profileId}`);
309
+ const now = Date.now();
310
+ const t = { id, profileId, description: description || "", status: SUBAGENT_TASK_LIFECYCLE_V2.QUEUED,
311
+ createdAt: now, updatedAt: now, startedAt: null, settledAt: null, metadata: { ...(metadata || {}) } };
312
+ _saTasks.set(id, t);
313
+ return { ...t, metadata: { ...t.metadata } };
314
+ }
315
+ function _saCheckT(from, to) { const allowed = _saTaskTrans.get(from); if (!allowed || !allowed.has(to)) throw new Error(`invalid subagent task transition ${from} → ${to}`); }
316
+ export function startSubagentTaskV2(id) { const t = _saTasks.get(id); if (!t) throw new Error(`subagent task ${id} not found`); _saCheckT(t.status, SUBAGENT_TASK_LIFECYCLE_V2.RUNNING); const now = Date.now(); t.status = SUBAGENT_TASK_LIFECYCLE_V2.RUNNING; t.updatedAt = now; if (!t.startedAt) t.startedAt = now; return { ...t, metadata: { ...t.metadata } }; }
317
+ export function completeSubagentTaskV2(id) { const t = _saTasks.get(id); if (!t) throw new Error(`subagent task ${id} not found`); _saCheckT(t.status, SUBAGENT_TASK_LIFECYCLE_V2.COMPLETED); const now = Date.now(); t.status = SUBAGENT_TASK_LIFECYCLE_V2.COMPLETED; t.updatedAt = now; if (!t.settledAt) t.settledAt = now; return { ...t, metadata: { ...t.metadata } }; }
318
+ export function failSubagentTaskV2(id, reason) { const t = _saTasks.get(id); if (!t) throw new Error(`subagent task ${id} not found`); _saCheckT(t.status, SUBAGENT_TASK_LIFECYCLE_V2.FAILED); const now = Date.now(); t.status = SUBAGENT_TASK_LIFECYCLE_V2.FAILED; t.updatedAt = now; if (!t.settledAt) t.settledAt = now; if (reason) t.metadata.failReason = String(reason); return { ...t, metadata: { ...t.metadata } }; }
319
+ export function cancelSubagentTaskV2(id, reason) { const t = _saTasks.get(id); if (!t) throw new Error(`subagent task ${id} not found`); _saCheckT(t.status, SUBAGENT_TASK_LIFECYCLE_V2.CANCELLED); const now = Date.now(); t.status = SUBAGENT_TASK_LIFECYCLE_V2.CANCELLED; t.updatedAt = now; if (!t.settledAt) t.settledAt = now; if (reason) t.metadata.cancelReason = String(reason); return { ...t, metadata: { ...t.metadata } }; }
320
+ export function getSubagentTaskV2(id) { const t = _saTasks.get(id); if (!t) return null; return { ...t, metadata: { ...t.metadata } }; }
321
+ export function listSubagentTasksV2() { return [..._saTasks.values()].map((t) => ({ ...t, metadata: { ...t.metadata } })); }
322
+
323
+ export function autoPauseIdleSubagentsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const p of _saProfiles.values()) if (p.status === SUBAGENT_PROFILE_MATURITY_V2.ACTIVE && (t - p.lastTouchedAt) >= _saProfileIdleMs) { p.status = SUBAGENT_PROFILE_MATURITY_V2.PAUSED; p.updatedAt = t; flipped.push(p.id); } return { flipped, count: flipped.length }; }
324
+ export function autoFailStuckSubagentTasksV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const k of _saTasks.values()) if (k.status === SUBAGENT_TASK_LIFECYCLE_V2.RUNNING && k.startedAt != null && (t - k.startedAt) >= _saTaskStuckMs) { k.status = SUBAGENT_TASK_LIFECYCLE_V2.FAILED; k.updatedAt = t; if (!k.settledAt) k.settledAt = t; k.metadata.failReason = "auto-fail-stuck"; flipped.push(k.id); } return { flipped, count: flipped.length }; }
325
+
326
+ export function getSubAgentRegistryStatsV2() {
327
+ const profilesByStatus = {}; for (const s of Object.values(SUBAGENT_PROFILE_MATURITY_V2)) profilesByStatus[s] = 0; for (const p of _saProfiles.values()) profilesByStatus[p.status]++;
328
+ const tasksByStatus = {}; for (const s of Object.values(SUBAGENT_TASK_LIFECYCLE_V2)) tasksByStatus[s] = 0; for (const t of _saTasks.values()) tasksByStatus[t.status]++;
329
+ return { totalProfilesV2: _saProfiles.size, totalTasksV2: _saTasks.size, maxActiveSubagentsPerOwner: _saMaxActivePerOwner, maxPendingTasksPerSubagent: _saMaxPendingPerProfile, subagentIdleMs: _saProfileIdleMs, subagentTaskStuckMs: _saTaskStuckMs, profilesByStatus, tasksByStatus };
330
+ }
@@ -242,3 +242,84 @@ export function getTaskName(taskType) {
242
242
  export function getTaskTypes() {
243
243
  return { ...TaskType };
244
244
  }
245
+
246
+ // ===== V2 Surface: Task Model Selector governance overlay (CLI v0.141.0) =====
247
+ export const TMS_PROFILE_MATURITY_V2 = Object.freeze({
248
+ PENDING: "pending", ACTIVE: "active", STALE: "stale", DECOMMISSIONED: "decommissioned",
249
+ });
250
+ export const TMS_SELECTION_LIFECYCLE_V2 = Object.freeze({
251
+ QUEUED: "queued", SCORING: "scoring", COMPLETED: "completed", FAILED: "failed", CANCELLED: "cancelled",
252
+ });
253
+ const _tmsPTrans = new Map([
254
+ [TMS_PROFILE_MATURITY_V2.PENDING, new Set([TMS_PROFILE_MATURITY_V2.ACTIVE, TMS_PROFILE_MATURITY_V2.DECOMMISSIONED])],
255
+ [TMS_PROFILE_MATURITY_V2.ACTIVE, new Set([TMS_PROFILE_MATURITY_V2.STALE, TMS_PROFILE_MATURITY_V2.DECOMMISSIONED])],
256
+ [TMS_PROFILE_MATURITY_V2.STALE, new Set([TMS_PROFILE_MATURITY_V2.ACTIVE, TMS_PROFILE_MATURITY_V2.DECOMMISSIONED])],
257
+ [TMS_PROFILE_MATURITY_V2.DECOMMISSIONED, new Set()],
258
+ ]);
259
+ const _tmsPTerminal = new Set([TMS_PROFILE_MATURITY_V2.DECOMMISSIONED]);
260
+ const _tmsSTrans = new Map([
261
+ [TMS_SELECTION_LIFECYCLE_V2.QUEUED, new Set([TMS_SELECTION_LIFECYCLE_V2.SCORING, TMS_SELECTION_LIFECYCLE_V2.CANCELLED])],
262
+ [TMS_SELECTION_LIFECYCLE_V2.SCORING, new Set([TMS_SELECTION_LIFECYCLE_V2.COMPLETED, TMS_SELECTION_LIFECYCLE_V2.FAILED, TMS_SELECTION_LIFECYCLE_V2.CANCELLED])],
263
+ [TMS_SELECTION_LIFECYCLE_V2.COMPLETED, new Set()],
264
+ [TMS_SELECTION_LIFECYCLE_V2.FAILED, new Set()],
265
+ [TMS_SELECTION_LIFECYCLE_V2.CANCELLED, new Set()],
266
+ ]);
267
+ const _tmsPsV2 = new Map();
268
+ const _tmsSsV2 = new Map();
269
+ let _tmsMaxActivePerOwner = 8, _tmsMaxPendingSelPerProfile = 16, _tmsIdleMs = 14 * 24 * 60 * 60 * 1000, _tmsStuckMs = 2 * 60 * 1000;
270
+ function _tmsPos(n, label) { const v = Math.floor(Number(n)); if (!Number.isFinite(v) || v <= 0) throw new Error(`${label} must be positive integer`); return v; }
271
+ function _tmsCheckP(from, to) { const a = _tmsPTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid tms profile transition ${from} → ${to}`); }
272
+ function _tmsCheckS(from, to) { const a = _tmsSTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid tms selection transition ${from} → ${to}`); }
273
+ export function setMaxActiveTmsProfilesPerOwnerV2(n) { _tmsMaxActivePerOwner = _tmsPos(n, "maxActiveTmsProfilesPerOwner"); }
274
+ export function getMaxActiveTmsProfilesPerOwnerV2() { return _tmsMaxActivePerOwner; }
275
+ export function setMaxPendingTmsSelectionsPerProfileV2(n) { _tmsMaxPendingSelPerProfile = _tmsPos(n, "maxPendingTmsSelectionsPerProfile"); }
276
+ export function getMaxPendingTmsSelectionsPerProfileV2() { return _tmsMaxPendingSelPerProfile; }
277
+ export function setTmsProfileIdleMsV2(n) { _tmsIdleMs = _tmsPos(n, "tmsProfileIdleMs"); }
278
+ export function getTmsProfileIdleMsV2() { return _tmsIdleMs; }
279
+ export function setTmsSelectionStuckMsV2(n) { _tmsStuckMs = _tmsPos(n, "tmsSelectionStuckMs"); }
280
+ export function getTmsSelectionStuckMsV2() { return _tmsStuckMs; }
281
+ export function _resetStateTaskModelSelectorV2() { _tmsPsV2.clear(); _tmsSsV2.clear(); _tmsMaxActivePerOwner = 8; _tmsMaxPendingSelPerProfile = 16; _tmsIdleMs = 14 * 24 * 60 * 60 * 1000; _tmsStuckMs = 2 * 60 * 1000; }
282
+ export function registerTmsProfileV2({ id, owner, strategy, metadata } = {}) {
283
+ if (!id) throw new Error("tms profile id required"); if (!owner) throw new Error("tms profile owner required");
284
+ if (_tmsPsV2.has(id)) throw new Error(`tms profile ${id} already registered`);
285
+ const now = Date.now();
286
+ const p = { id, owner, strategy: strategy || "default", status: TMS_PROFILE_MATURITY_V2.PENDING, createdAt: now, updatedAt: now, activatedAt: null, decommissionedAt: null, lastTouchedAt: now, metadata: { ...(metadata || {}) } };
287
+ _tmsPsV2.set(id, p); return { ...p, metadata: { ...p.metadata } };
288
+ }
289
+ function _tmsCountActive(owner) { let n = 0; for (const p of _tmsPsV2.values()) if (p.owner === owner && p.status === TMS_PROFILE_MATURITY_V2.ACTIVE) n++; return n; }
290
+ export function activateTmsProfileV2(id) {
291
+ const p = _tmsPsV2.get(id); if (!p) throw new Error(`tms profile ${id} not found`);
292
+ _tmsCheckP(p.status, TMS_PROFILE_MATURITY_V2.ACTIVE);
293
+ const recovery = p.status === TMS_PROFILE_MATURITY_V2.STALE;
294
+ if (!recovery && _tmsCountActive(p.owner) >= _tmsMaxActivePerOwner) throw new Error(`max active tms profiles for owner ${p.owner} reached`);
295
+ const now = Date.now(); p.status = TMS_PROFILE_MATURITY_V2.ACTIVE; p.updatedAt = now; p.lastTouchedAt = now; if (!p.activatedAt) p.activatedAt = now;
296
+ return { ...p, metadata: { ...p.metadata } };
297
+ }
298
+ export function staleTmsProfileV2(id) { const p = _tmsPsV2.get(id); if (!p) throw new Error(`tms profile ${id} not found`); _tmsCheckP(p.status, TMS_PROFILE_MATURITY_V2.STALE); p.status = TMS_PROFILE_MATURITY_V2.STALE; p.updatedAt = Date.now(); return { ...p, metadata: { ...p.metadata } }; }
299
+ export function decommissionTmsProfileV2(id) { const p = _tmsPsV2.get(id); if (!p) throw new Error(`tms profile ${id} not found`); _tmsCheckP(p.status, TMS_PROFILE_MATURITY_V2.DECOMMISSIONED); const now = Date.now(); p.status = TMS_PROFILE_MATURITY_V2.DECOMMISSIONED; p.updatedAt = now; if (!p.decommissionedAt) p.decommissionedAt = now; return { ...p, metadata: { ...p.metadata } }; }
300
+ export function touchTmsProfileV2(id) { const p = _tmsPsV2.get(id); if (!p) throw new Error(`tms profile ${id} not found`); if (_tmsPTerminal.has(p.status)) throw new Error(`cannot touch terminal tms profile ${id}`); const now = Date.now(); p.lastTouchedAt = now; p.updatedAt = now; return { ...p, metadata: { ...p.metadata } }; }
301
+ export function getTmsProfileV2(id) { const p = _tmsPsV2.get(id); if (!p) return null; return { ...p, metadata: { ...p.metadata } }; }
302
+ export function listTmsProfilesV2() { return [..._tmsPsV2.values()].map((p) => ({ ...p, metadata: { ...p.metadata } })); }
303
+ function _tmsCountPending(profileId) { let n = 0; for (const s of _tmsSsV2.values()) if (s.profileId === profileId && (s.status === TMS_SELECTION_LIFECYCLE_V2.QUEUED || s.status === TMS_SELECTION_LIFECYCLE_V2.SCORING)) n++; return n; }
304
+ export function createTmsSelectionV2({ id, profileId, task, metadata } = {}) {
305
+ if (!id) throw new Error("tms selection id required"); if (!profileId) throw new Error("tms selection profileId required");
306
+ if (_tmsSsV2.has(id)) throw new Error(`tms selection ${id} already exists`);
307
+ if (!_tmsPsV2.has(profileId)) throw new Error(`tms profile ${profileId} not found`);
308
+ if (_tmsCountPending(profileId) >= _tmsMaxPendingSelPerProfile) throw new Error(`max pending tms selections for profile ${profileId} reached`);
309
+ const now = Date.now();
310
+ const s = { id, profileId, task: task || "", status: TMS_SELECTION_LIFECYCLE_V2.QUEUED, createdAt: now, updatedAt: now, startedAt: null, settledAt: null, metadata: { ...(metadata || {}) } };
311
+ _tmsSsV2.set(id, s); return { ...s, metadata: { ...s.metadata } };
312
+ }
313
+ export function scoreTmsSelectionV2(id) { const s = _tmsSsV2.get(id); if (!s) throw new Error(`tms selection ${id} not found`); _tmsCheckS(s.status, TMS_SELECTION_LIFECYCLE_V2.SCORING); const now = Date.now(); s.status = TMS_SELECTION_LIFECYCLE_V2.SCORING; s.updatedAt = now; if (!s.startedAt) s.startedAt = now; return { ...s, metadata: { ...s.metadata } }; }
314
+ export function completeTmsSelectionV2(id) { const s = _tmsSsV2.get(id); if (!s) throw new Error(`tms selection ${id} not found`); _tmsCheckS(s.status, TMS_SELECTION_LIFECYCLE_V2.COMPLETED); const now = Date.now(); s.status = TMS_SELECTION_LIFECYCLE_V2.COMPLETED; s.updatedAt = now; if (!s.settledAt) s.settledAt = now; return { ...s, metadata: { ...s.metadata } }; }
315
+ export function failTmsSelectionV2(id, reason) { const s = _tmsSsV2.get(id); if (!s) throw new Error(`tms selection ${id} not found`); _tmsCheckS(s.status, TMS_SELECTION_LIFECYCLE_V2.FAILED); const now = Date.now(); s.status = TMS_SELECTION_LIFECYCLE_V2.FAILED; s.updatedAt = now; if (!s.settledAt) s.settledAt = now; if (reason) s.metadata.failReason = String(reason); return { ...s, metadata: { ...s.metadata } }; }
316
+ export function cancelTmsSelectionV2(id, reason) { const s = _tmsSsV2.get(id); if (!s) throw new Error(`tms selection ${id} not found`); _tmsCheckS(s.status, TMS_SELECTION_LIFECYCLE_V2.CANCELLED); const now = Date.now(); s.status = TMS_SELECTION_LIFECYCLE_V2.CANCELLED; s.updatedAt = now; if (!s.settledAt) s.settledAt = now; if (reason) s.metadata.cancelReason = String(reason); return { ...s, metadata: { ...s.metadata } }; }
317
+ export function getTmsSelectionV2(id) { const s = _tmsSsV2.get(id); if (!s) return null; return { ...s, metadata: { ...s.metadata } }; }
318
+ export function listTmsSelectionsV2() { return [..._tmsSsV2.values()].map((s) => ({ ...s, metadata: { ...s.metadata } })); }
319
+ export function autoStaleIdleTmsProfilesV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const p of _tmsPsV2.values()) if (p.status === TMS_PROFILE_MATURITY_V2.ACTIVE && (t - p.lastTouchedAt) >= _tmsIdleMs) { p.status = TMS_PROFILE_MATURITY_V2.STALE; p.updatedAt = t; flipped.push(p.id); } return { flipped, count: flipped.length }; }
320
+ export function autoFailStuckTmsSelectionsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const s of _tmsSsV2.values()) if (s.status === TMS_SELECTION_LIFECYCLE_V2.SCORING && s.startedAt != null && (t - s.startedAt) >= _tmsStuckMs) { s.status = TMS_SELECTION_LIFECYCLE_V2.FAILED; s.updatedAt = t; if (!s.settledAt) s.settledAt = t; s.metadata.failReason = "auto-fail-stuck"; flipped.push(s.id); } return { flipped, count: flipped.length }; }
321
+ export function getTaskModelSelectorGovStatsV2() {
322
+ const profilesByStatus = {}; for (const v of Object.values(TMS_PROFILE_MATURITY_V2)) profilesByStatus[v] = 0; for (const p of _tmsPsV2.values()) profilesByStatus[p.status]++;
323
+ const selectionsByStatus = {}; for (const v of Object.values(TMS_SELECTION_LIFECYCLE_V2)) selectionsByStatus[v] = 0; for (const s of _tmsSsV2.values()) selectionsByStatus[s.status]++;
324
+ return { totalTmsProfilesV2: _tmsPsV2.size, totalTmsSelectionsV2: _tmsSsV2.size, maxActiveTmsProfilesPerOwner: _tmsMaxActivePerOwner, maxPendingTmsSelectionsPerProfile: _tmsMaxPendingSelPerProfile, tmsProfileIdleMs: _tmsIdleMs, tmsSelectionStuckMs: _tmsStuckMs, profilesByStatus, selectionsByStatus };
325
+ }
@@ -106,3 +106,108 @@ export function resetAllStores() {
106
106
  }
107
107
 
108
108
  export const _deps = { _stores };
109
+
110
+
111
+ // ===== V2 Surface: Todo Manager governance overlay (CLI v0.133.0) =====
112
+ export const TODO_LIST_MATURITY_V2 = Object.freeze({
113
+ DRAFT: "draft", ACTIVE: "active", PAUSED: "paused", ARCHIVED: "archived",
114
+ });
115
+ export const TODO_ITEM_LIFECYCLE_V2 = Object.freeze({
116
+ PENDING: "pending", IN_PROGRESS: "in_progress", COMPLETED: "completed", FAILED: "failed", CANCELLED: "cancelled",
117
+ });
118
+
119
+ const _tlListTrans = new Map([
120
+ [TODO_LIST_MATURITY_V2.DRAFT, new Set([TODO_LIST_MATURITY_V2.ACTIVE, TODO_LIST_MATURITY_V2.ARCHIVED])],
121
+ [TODO_LIST_MATURITY_V2.ACTIVE, new Set([TODO_LIST_MATURITY_V2.PAUSED, TODO_LIST_MATURITY_V2.ARCHIVED])],
122
+ [TODO_LIST_MATURITY_V2.PAUSED, new Set([TODO_LIST_MATURITY_V2.ACTIVE, TODO_LIST_MATURITY_V2.ARCHIVED])],
123
+ [TODO_LIST_MATURITY_V2.ARCHIVED, new Set()],
124
+ ]);
125
+ const _tlListTerminal = new Set([TODO_LIST_MATURITY_V2.ARCHIVED]);
126
+ const _tlItemTrans = new Map([
127
+ [TODO_ITEM_LIFECYCLE_V2.PENDING, new Set([TODO_ITEM_LIFECYCLE_V2.IN_PROGRESS, TODO_ITEM_LIFECYCLE_V2.CANCELLED])],
128
+ [TODO_ITEM_LIFECYCLE_V2.IN_PROGRESS, new Set([TODO_ITEM_LIFECYCLE_V2.COMPLETED, TODO_ITEM_LIFECYCLE_V2.FAILED, TODO_ITEM_LIFECYCLE_V2.CANCELLED])],
129
+ [TODO_ITEM_LIFECYCLE_V2.COMPLETED, new Set()],
130
+ [TODO_ITEM_LIFECYCLE_V2.FAILED, new Set()],
131
+ [TODO_ITEM_LIFECYCLE_V2.CANCELLED, new Set()],
132
+ ]);
133
+
134
+ const _tlLists = new Map();
135
+ const _tlItems = new Map();
136
+ let _tlMaxActivePerOwner = 10;
137
+ let _tlMaxPendingPerList = 40;
138
+ let _tlListIdleMs = 7 * 24 * 60 * 60 * 1000;
139
+ let _tlItemStuckMs = 24 * 60 * 60 * 1000;
140
+
141
+ function _tlPos(n, lbl) { const v = Math.floor(Number(n)); if (!Number.isFinite(v) || v <= 0) throw new Error(`${lbl} must be positive integer`); return v; }
142
+
143
+ export function setMaxActiveTodoListsPerOwnerV2(n) { _tlMaxActivePerOwner = _tlPos(n, "maxActiveTodoListsPerOwner"); }
144
+ export function getMaxActiveTodoListsPerOwnerV2() { return _tlMaxActivePerOwner; }
145
+ export function setMaxPendingItemsPerTodoListV2(n) { _tlMaxPendingPerList = _tlPos(n, "maxPendingItemsPerTodoList"); }
146
+ export function getMaxPendingItemsPerTodoListV2() { return _tlMaxPendingPerList; }
147
+ export function setTodoListIdleMsV2(n) { _tlListIdleMs = _tlPos(n, "todoListIdleMs"); }
148
+ export function getTodoListIdleMsV2() { return _tlListIdleMs; }
149
+ export function setTodoItemStuckMsV2(n) { _tlItemStuckMs = _tlPos(n, "todoItemStuckMs"); }
150
+ export function getTodoItemStuckMsV2() { return _tlItemStuckMs; }
151
+
152
+ export function _resetStateTodoManagerV2() {
153
+ _tlLists.clear(); _tlItems.clear();
154
+ _tlMaxActivePerOwner = 10; _tlMaxPendingPerList = 40;
155
+ _tlListIdleMs = 7 * 24 * 60 * 60 * 1000; _tlItemStuckMs = 24 * 60 * 60 * 1000;
156
+ }
157
+
158
+ export function registerTodoListV2({ id, owner, title, metadata } = {}) {
159
+ if (!id || typeof id !== "string") throw new Error("id is required");
160
+ if (!owner || typeof owner !== "string") throw new Error("owner is required");
161
+ if (_tlLists.has(id)) throw new Error(`todo list ${id} already registered`);
162
+ const now = Date.now();
163
+ const l = { id, owner, title: title || id, status: TODO_LIST_MATURITY_V2.DRAFT, createdAt: now, updatedAt: now, activatedAt: null, archivedAt: null, lastTouchedAt: now, metadata: { ...(metadata || {}) } };
164
+ _tlLists.set(id, l);
165
+ return { ...l, metadata: { ...l.metadata } };
166
+ }
167
+ function _tlCheckL(from, to) { const allowed = _tlListTrans.get(from); if (!allowed || !allowed.has(to)) throw new Error(`invalid todo list transition ${from} → ${to}`); }
168
+ function _tlCountActive(owner) { let n = 0; for (const l of _tlLists.values()) if (l.owner === owner && l.status === TODO_LIST_MATURITY_V2.ACTIVE) n++; return n; }
169
+
170
+ export function activateTodoListV2(id) {
171
+ const l = _tlLists.get(id); if (!l) throw new Error(`todo list ${id} not found`);
172
+ _tlCheckL(l.status, TODO_LIST_MATURITY_V2.ACTIVE);
173
+ const recovery = l.status === TODO_LIST_MATURITY_V2.PAUSED;
174
+ if (!recovery) { const a = _tlCountActive(l.owner); if (a >= _tlMaxActivePerOwner) throw new Error(`max active todo lists per owner (${_tlMaxActivePerOwner}) reached for ${l.owner}`); }
175
+ const now = Date.now(); l.status = TODO_LIST_MATURITY_V2.ACTIVE; l.updatedAt = now; l.lastTouchedAt = now; if (!l.activatedAt) l.activatedAt = now;
176
+ return { ...l, metadata: { ...l.metadata } };
177
+ }
178
+ export function pauseTodoListV2(id) { const l = _tlLists.get(id); if (!l) throw new Error(`todo list ${id} not found`); _tlCheckL(l.status, TODO_LIST_MATURITY_V2.PAUSED); l.status = TODO_LIST_MATURITY_V2.PAUSED; l.updatedAt = Date.now(); return { ...l, metadata: { ...l.metadata } }; }
179
+ export function archiveTodoListV2(id) { const l = _tlLists.get(id); if (!l) throw new Error(`todo list ${id} not found`); _tlCheckL(l.status, TODO_LIST_MATURITY_V2.ARCHIVED); const now = Date.now(); l.status = TODO_LIST_MATURITY_V2.ARCHIVED; l.updatedAt = now; if (!l.archivedAt) l.archivedAt = now; return { ...l, metadata: { ...l.metadata } }; }
180
+ export function touchTodoListV2(id) { const l = _tlLists.get(id); if (!l) throw new Error(`todo list ${id} not found`); if (_tlListTerminal.has(l.status)) throw new Error(`cannot touch terminal todo list ${id}`); const now = Date.now(); l.lastTouchedAt = now; l.updatedAt = now; return { ...l, metadata: { ...l.metadata } }; }
181
+ export function getTodoListV2(id) { const l = _tlLists.get(id); if (!l) return null; return { ...l, metadata: { ...l.metadata } }; }
182
+ export function listTodoListsV2() { return [..._tlLists.values()].map((l) => ({ ...l, metadata: { ...l.metadata } })); }
183
+
184
+ function _tlCountPending(lid) { let n = 0; for (const it of _tlItems.values()) if (it.listId === lid && (it.status === TODO_ITEM_LIFECYCLE_V2.PENDING || it.status === TODO_ITEM_LIFECYCLE_V2.IN_PROGRESS)) n++; return n; }
185
+
186
+ export function createTodoItemV2({ id, listId, description, metadata } = {}) {
187
+ if (!id || typeof id !== "string") throw new Error("id is required");
188
+ if (!listId || typeof listId !== "string") throw new Error("listId is required");
189
+ if (_tlItems.has(id)) throw new Error(`todo item ${id} already exists`);
190
+ if (!_tlLists.has(listId)) throw new Error(`todo list ${listId} not found`);
191
+ const pending = _tlCountPending(listId);
192
+ if (pending >= _tlMaxPendingPerList) throw new Error(`max pending items per todo list (${_tlMaxPendingPerList}) reached for ${listId}`);
193
+ const now = Date.now();
194
+ const it = { id, listId, description: description || "", status: TODO_ITEM_LIFECYCLE_V2.PENDING, createdAt: now, updatedAt: now, startedAt: null, settledAt: null, metadata: { ...(metadata || {}) } };
195
+ _tlItems.set(id, it);
196
+ return { ...it, metadata: { ...it.metadata } };
197
+ }
198
+ function _tlCheckI(from, to) { const allowed = _tlItemTrans.get(from); if (!allowed || !allowed.has(to)) throw new Error(`invalid todo item transition ${from} → ${to}`); }
199
+ export function startTodoItemV2(id) { const it = _tlItems.get(id); if (!it) throw new Error(`todo item ${id} not found`); _tlCheckI(it.status, TODO_ITEM_LIFECYCLE_V2.IN_PROGRESS); const now = Date.now(); it.status = TODO_ITEM_LIFECYCLE_V2.IN_PROGRESS; it.updatedAt = now; if (!it.startedAt) it.startedAt = now; return { ...it, metadata: { ...it.metadata } }; }
200
+ export function completeTodoItemV2(id) { const it = _tlItems.get(id); if (!it) throw new Error(`todo item ${id} not found`); _tlCheckI(it.status, TODO_ITEM_LIFECYCLE_V2.COMPLETED); const now = Date.now(); it.status = TODO_ITEM_LIFECYCLE_V2.COMPLETED; it.updatedAt = now; if (!it.settledAt) it.settledAt = now; return { ...it, metadata: { ...it.metadata } }; }
201
+ export function failTodoItemV2(id, reason) { const it = _tlItems.get(id); if (!it) throw new Error(`todo item ${id} not found`); _tlCheckI(it.status, TODO_ITEM_LIFECYCLE_V2.FAILED); const now = Date.now(); it.status = TODO_ITEM_LIFECYCLE_V2.FAILED; it.updatedAt = now; if (!it.settledAt) it.settledAt = now; if (reason) it.metadata.failReason = String(reason); return { ...it, metadata: { ...it.metadata } }; }
202
+ export function cancelTodoItemV2(id, reason) { const it = _tlItems.get(id); if (!it) throw new Error(`todo item ${id} not found`); _tlCheckI(it.status, TODO_ITEM_LIFECYCLE_V2.CANCELLED); const now = Date.now(); it.status = TODO_ITEM_LIFECYCLE_V2.CANCELLED; it.updatedAt = now; if (!it.settledAt) it.settledAt = now; if (reason) it.metadata.cancelReason = String(reason); return { ...it, metadata: { ...it.metadata } }; }
203
+ export function getTodoItemV2(id) { const it = _tlItems.get(id); if (!it) return null; return { ...it, metadata: { ...it.metadata } }; }
204
+ export function listTodoItemsV2() { return [..._tlItems.values()].map((it) => ({ ...it, metadata: { ...it.metadata } })); }
205
+
206
+ export function autoPauseIdleTodoListsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const l of _tlLists.values()) if (l.status === TODO_LIST_MATURITY_V2.ACTIVE && (t - l.lastTouchedAt) >= _tlListIdleMs) { l.status = TODO_LIST_MATURITY_V2.PAUSED; l.updatedAt = t; flipped.push(l.id); } return { flipped, count: flipped.length }; }
207
+ export function autoFailStuckTodoItemsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const it of _tlItems.values()) if (it.status === TODO_ITEM_LIFECYCLE_V2.IN_PROGRESS && it.startedAt != null && (t - it.startedAt) >= _tlItemStuckMs) { it.status = TODO_ITEM_LIFECYCLE_V2.FAILED; it.updatedAt = t; if (!it.settledAt) it.settledAt = t; it.metadata.failReason = "auto-fail-stuck"; flipped.push(it.id); } return { flipped, count: flipped.length }; }
208
+
209
+ export function getTodoManagerStatsV2() {
210
+ const listsByStatus = {}; for (const s of Object.values(TODO_LIST_MATURITY_V2)) listsByStatus[s] = 0; for (const l of _tlLists.values()) listsByStatus[l.status]++;
211
+ const itemsByStatus = {}; for (const s of Object.values(TODO_ITEM_LIFECYCLE_V2)) itemsByStatus[s] = 0; for (const it of _tlItems.values()) itemsByStatus[it.status]++;
212
+ return { totalListsV2: _tlLists.size, totalItemsV2: _tlItems.size, maxActiveTodoListsPerOwner: _tlMaxActivePerOwner, maxPendingItemsPerTodoList: _tlMaxPendingPerList, todoListIdleMs: _tlListIdleMs, todoItemStuckMs: _tlItemStuckMs, listsByStatus, itemsByStatus };
213
+ }
@@ -398,3 +398,108 @@ export function classifyTopic(text, opts = {}) {
398
398
  export function _resetState() {
399
399
  _customLexicons.clear();
400
400
  }
401
+
402
+
403
+ // ===== V2 Surface: Topic Classifier governance overlay (CLI v0.140.0) =====
404
+ export const TOPIC_CLS_PROFILE_MATURITY_V2 = Object.freeze({
405
+ PENDING: "pending", ACTIVE: "active", STALE: "stale", ARCHIVED: "archived",
406
+ });
407
+ export const TOPIC_CLS_JOB_LIFECYCLE_V2 = Object.freeze({
408
+ QUEUED: "queued", RUNNING: "running", COMPLETED: "completed", FAILED: "failed", CANCELLED: "cancelled",
409
+ });
410
+
411
+ const _tcpTrans = new Map([
412
+ [TOPIC_CLS_PROFILE_MATURITY_V2.PENDING, new Set([TOPIC_CLS_PROFILE_MATURITY_V2.ACTIVE, TOPIC_CLS_PROFILE_MATURITY_V2.ARCHIVED])],
413
+ [TOPIC_CLS_PROFILE_MATURITY_V2.ACTIVE, new Set([TOPIC_CLS_PROFILE_MATURITY_V2.STALE, TOPIC_CLS_PROFILE_MATURITY_V2.ARCHIVED])],
414
+ [TOPIC_CLS_PROFILE_MATURITY_V2.STALE, new Set([TOPIC_CLS_PROFILE_MATURITY_V2.ACTIVE, TOPIC_CLS_PROFILE_MATURITY_V2.ARCHIVED])],
415
+ [TOPIC_CLS_PROFILE_MATURITY_V2.ARCHIVED, new Set()],
416
+ ]);
417
+ const _tcpTerminal = new Set([TOPIC_CLS_PROFILE_MATURITY_V2.ARCHIVED]);
418
+ const _tcjTrans = new Map([
419
+ [TOPIC_CLS_JOB_LIFECYCLE_V2.QUEUED, new Set([TOPIC_CLS_JOB_LIFECYCLE_V2.RUNNING, TOPIC_CLS_JOB_LIFECYCLE_V2.CANCELLED])],
420
+ [TOPIC_CLS_JOB_LIFECYCLE_V2.RUNNING, new Set([TOPIC_CLS_JOB_LIFECYCLE_V2.COMPLETED, TOPIC_CLS_JOB_LIFECYCLE_V2.FAILED, TOPIC_CLS_JOB_LIFECYCLE_V2.CANCELLED])],
421
+ [TOPIC_CLS_JOB_LIFECYCLE_V2.COMPLETED, new Set()],
422
+ [TOPIC_CLS_JOB_LIFECYCLE_V2.FAILED, new Set()],
423
+ [TOPIC_CLS_JOB_LIFECYCLE_V2.CANCELLED, new Set()],
424
+ ]);
425
+
426
+ const _tcpsV2 = new Map();
427
+ const _tcjsV2 = new Map();
428
+ let _tcpMaxActivePerOwner = 8;
429
+ let _tcpMaxPendingJobsPerProfile = 20;
430
+ let _tcpIdleMs = 14 * 24 * 60 * 60 * 1000;
431
+ let _tcjStuckMs = 5 * 60 * 1000;
432
+
433
+ function _tcpPos(n, lbl) { const v = Math.floor(Number(n)); if (!Number.isFinite(v) || v <= 0) throw new Error(`${lbl} must be positive integer`); return v; }
434
+
435
+ export function setMaxActiveTopicClsProfilesPerOwnerV2(n) { _tcpMaxActivePerOwner = _tcpPos(n, "maxActiveTopicClsProfilesPerOwner"); }
436
+ export function getMaxActiveTopicClsProfilesPerOwnerV2() { return _tcpMaxActivePerOwner; }
437
+ export function setMaxPendingTopicClsJobsPerProfileV2(n) { _tcpMaxPendingJobsPerProfile = _tcpPos(n, "maxPendingTopicClsJobsPerProfile"); }
438
+ export function getMaxPendingTopicClsJobsPerProfileV2() { return _tcpMaxPendingJobsPerProfile; }
439
+ export function setTopicClsProfileIdleMsV2(n) { _tcpIdleMs = _tcpPos(n, "topicClsProfileIdleMs"); }
440
+ export function getTopicClsProfileIdleMsV2() { return _tcpIdleMs; }
441
+ export function setTopicClsJobStuckMsV2(n) { _tcjStuckMs = _tcpPos(n, "topicClsJobStuckMs"); }
442
+ export function getTopicClsJobStuckMsV2() { return _tcjStuckMs; }
443
+
444
+ export function _resetStateTopicClsV2() {
445
+ _tcpsV2.clear(); _tcjsV2.clear();
446
+ _tcpMaxActivePerOwner = 8; _tcpMaxPendingJobsPerProfile = 20;
447
+ _tcpIdleMs = 14 * 24 * 60 * 60 * 1000; _tcjStuckMs = 5 * 60 * 1000;
448
+ }
449
+
450
+ export function registerTopicClsProfileV2({ id, owner, model, metadata } = {}) {
451
+ if (!id || typeof id !== "string") throw new Error("id is required");
452
+ if (!owner || typeof owner !== "string") throw new Error("owner is required");
453
+ if (_tcpsV2.has(id)) throw new Error(`topic cls profile ${id} already registered`);
454
+ const now = Date.now();
455
+ const p = { id, owner, model: model || "default", status: TOPIC_CLS_PROFILE_MATURITY_V2.PENDING, createdAt: now, updatedAt: now, activatedAt: null, archivedAt: null, lastTouchedAt: now, metadata: { ...(metadata || {}) } };
456
+ _tcpsV2.set(id, p);
457
+ return { ...p, metadata: { ...p.metadata } };
458
+ }
459
+ function _tcpCheckP(from, to) { const a = _tcpTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid topic cls profile transition ${from} → ${to}`); }
460
+ function _tcpCountActive(owner) { let n = 0; for (const p of _tcpsV2.values()) if (p.owner === owner && p.status === TOPIC_CLS_PROFILE_MATURITY_V2.ACTIVE) n++; return n; }
461
+
462
+ export function activateTopicClsProfileV2(id) {
463
+ const p = _tcpsV2.get(id); if (!p) throw new Error(`topic cls profile ${id} not found`);
464
+ _tcpCheckP(p.status, TOPIC_CLS_PROFILE_MATURITY_V2.ACTIVE);
465
+ const recovery = p.status === TOPIC_CLS_PROFILE_MATURITY_V2.STALE;
466
+ if (!recovery) { const c = _tcpCountActive(p.owner); if (c >= _tcpMaxActivePerOwner) throw new Error(`max active topic cls profiles per owner (${_tcpMaxActivePerOwner}) reached for ${p.owner}`); }
467
+ const now = Date.now(); p.status = TOPIC_CLS_PROFILE_MATURITY_V2.ACTIVE; p.updatedAt = now; p.lastTouchedAt = now; if (!p.activatedAt) p.activatedAt = now;
468
+ return { ...p, metadata: { ...p.metadata } };
469
+ }
470
+ export function staleTopicClsProfileV2(id) { const p = _tcpsV2.get(id); if (!p) throw new Error(`topic cls profile ${id} not found`); _tcpCheckP(p.status, TOPIC_CLS_PROFILE_MATURITY_V2.STALE); p.status = TOPIC_CLS_PROFILE_MATURITY_V2.STALE; p.updatedAt = Date.now(); return { ...p, metadata: { ...p.metadata } }; }
471
+ export function archiveTopicClsProfileV2(id) { const p = _tcpsV2.get(id); if (!p) throw new Error(`topic cls profile ${id} not found`); _tcpCheckP(p.status, TOPIC_CLS_PROFILE_MATURITY_V2.ARCHIVED); const now = Date.now(); p.status = TOPIC_CLS_PROFILE_MATURITY_V2.ARCHIVED; p.updatedAt = now; if (!p.archivedAt) p.archivedAt = now; return { ...p, metadata: { ...p.metadata } }; }
472
+ export function touchTopicClsProfileV2(id) { const p = _tcpsV2.get(id); if (!p) throw new Error(`topic cls profile ${id} not found`); if (_tcpTerminal.has(p.status)) throw new Error(`cannot touch terminal topic cls profile ${id}`); const now = Date.now(); p.lastTouchedAt = now; p.updatedAt = now; return { ...p, metadata: { ...p.metadata } }; }
473
+ export function getTopicClsProfileV2(id) { const p = _tcpsV2.get(id); if (!p) return null; return { ...p, metadata: { ...p.metadata } }; }
474
+ export function listTopicClsProfilesV2() { return [..._tcpsV2.values()].map((p) => ({ ...p, metadata: { ...p.metadata } })); }
475
+
476
+ function _tcjCountPending(profileId) { let n = 0; for (const j of _tcjsV2.values()) if (j.profileId === profileId && (j.status === TOPIC_CLS_JOB_LIFECYCLE_V2.QUEUED || j.status === TOPIC_CLS_JOB_LIFECYCLE_V2.RUNNING)) n++; return n; }
477
+
478
+ export function createTopicClsJobV2({ id, profileId, text, metadata } = {}) {
479
+ if (!id || typeof id !== "string") throw new Error("id is required");
480
+ if (!profileId || typeof profileId !== "string") throw new Error("profileId is required");
481
+ if (_tcjsV2.has(id)) throw new Error(`topic cls job ${id} already exists`);
482
+ if (!_tcpsV2.has(profileId)) throw new Error(`topic cls profile ${profileId} not found`);
483
+ const pending = _tcjCountPending(profileId);
484
+ if (pending >= _tcpMaxPendingJobsPerProfile) throw new Error(`max pending topic cls jobs per profile (${_tcpMaxPendingJobsPerProfile}) reached for ${profileId}`);
485
+ const now = Date.now();
486
+ const j = { id, profileId, text: text || "", status: TOPIC_CLS_JOB_LIFECYCLE_V2.QUEUED, createdAt: now, updatedAt: now, startedAt: null, settledAt: null, metadata: { ...(metadata || {}) } };
487
+ _tcjsV2.set(id, j);
488
+ return { ...j, metadata: { ...j.metadata } };
489
+ }
490
+ function _tcjCheckJ(from, to) { const a = _tcjTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid topic cls job transition ${from} → ${to}`); }
491
+ export function startTopicClsJobV2(id) { const j = _tcjsV2.get(id); if (!j) throw new Error(`topic cls job ${id} not found`); _tcjCheckJ(j.status, TOPIC_CLS_JOB_LIFECYCLE_V2.RUNNING); const now = Date.now(); j.status = TOPIC_CLS_JOB_LIFECYCLE_V2.RUNNING; j.updatedAt = now; if (!j.startedAt) j.startedAt = now; return { ...j, metadata: { ...j.metadata } }; }
492
+ export function completeTopicClsJobV2(id) { const j = _tcjsV2.get(id); if (!j) throw new Error(`topic cls job ${id} not found`); _tcjCheckJ(j.status, TOPIC_CLS_JOB_LIFECYCLE_V2.COMPLETED); const now = Date.now(); j.status = TOPIC_CLS_JOB_LIFECYCLE_V2.COMPLETED; j.updatedAt = now; if (!j.settledAt) j.settledAt = now; return { ...j, metadata: { ...j.metadata } }; }
493
+ export function failTopicClsJobV2(id, reason) { const j = _tcjsV2.get(id); if (!j) throw new Error(`topic cls job ${id} not found`); _tcjCheckJ(j.status, TOPIC_CLS_JOB_LIFECYCLE_V2.FAILED); const now = Date.now(); j.status = TOPIC_CLS_JOB_LIFECYCLE_V2.FAILED; j.updatedAt = now; if (!j.settledAt) j.settledAt = now; if (reason) j.metadata.failReason = String(reason); return { ...j, metadata: { ...j.metadata } }; }
494
+ export function cancelTopicClsJobV2(id, reason) { const j = _tcjsV2.get(id); if (!j) throw new Error(`topic cls job ${id} not found`); _tcjCheckJ(j.status, TOPIC_CLS_JOB_LIFECYCLE_V2.CANCELLED); const now = Date.now(); j.status = TOPIC_CLS_JOB_LIFECYCLE_V2.CANCELLED; j.updatedAt = now; if (!j.settledAt) j.settledAt = now; if (reason) j.metadata.cancelReason = String(reason); return { ...j, metadata: { ...j.metadata } }; }
495
+ export function getTopicClsJobV2(id) { const j = _tcjsV2.get(id); if (!j) return null; return { ...j, metadata: { ...j.metadata } }; }
496
+ export function listTopicClsJobsV2() { return [..._tcjsV2.values()].map((j) => ({ ...j, metadata: { ...j.metadata } })); }
497
+
498
+ export function autoStaleIdleTopicClsProfilesV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const p of _tcpsV2.values()) if (p.status === TOPIC_CLS_PROFILE_MATURITY_V2.ACTIVE && (t - p.lastTouchedAt) >= _tcpIdleMs) { p.status = TOPIC_CLS_PROFILE_MATURITY_V2.STALE; p.updatedAt = t; flipped.push(p.id); } return { flipped, count: flipped.length }; }
499
+ export function autoFailStuckTopicClsJobsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const j of _tcjsV2.values()) if (j.status === TOPIC_CLS_JOB_LIFECYCLE_V2.RUNNING && j.startedAt != null && (t - j.startedAt) >= _tcjStuckMs) { j.status = TOPIC_CLS_JOB_LIFECYCLE_V2.FAILED; j.updatedAt = t; if (!j.settledAt) j.settledAt = t; j.metadata.failReason = "auto-fail-stuck"; flipped.push(j.id); } return { flipped, count: flipped.length }; }
500
+
501
+ export function getTopicClassifierGovStatsV2() {
502
+ const profilesByStatus = {}; for (const s of Object.values(TOPIC_CLS_PROFILE_MATURITY_V2)) profilesByStatus[s] = 0; for (const p of _tcpsV2.values()) profilesByStatus[p.status]++;
503
+ const jobsByStatus = {}; for (const s of Object.values(TOPIC_CLS_JOB_LIFECYCLE_V2)) jobsByStatus[s] = 0; for (const j of _tcjsV2.values()) jobsByStatus[j.status]++;
504
+ return { totalTopicClsProfilesV2: _tcpsV2.size, totalTopicClsJobsV2: _tcjsV2.size, maxActiveTopicClsProfilesPerOwner: _tcpMaxActivePerOwner, maxPendingTopicClsJobsPerProfile: _tcpMaxPendingJobsPerProfile, topicClsProfileIdleMs: _tcpIdleMs, topicClsJobStuckMs: _tcjStuckMs, profilesByStatus, jobsByStatus };
505
+ }
@@ -170,3 +170,84 @@ export async function consolidateUserProfile(llmFn) {
170
170
  // ─── Exports ────────────────────────────────────────────────────────────────
171
171
 
172
172
  export const MAX_USER_PROFILE_LENGTH = MAX_PROFILE_LENGTH;
173
+
174
+ // ===== V2 Surface: User Profile governance overlay (CLI v0.141.0) =====
175
+ export const USER_PROFILE_MATURITY_V2 = Object.freeze({
176
+ PENDING: "pending", ACTIVE: "active", DORMANT: "dormant", ARCHIVED: "archived",
177
+ });
178
+ export const USER_PREF_LIFECYCLE_V2 = Object.freeze({
179
+ PROPOSED: "proposed", APPLIED: "applied", REJECTED: "rejected", SUPERSEDED: "superseded", CANCELLED: "cancelled",
180
+ });
181
+ const _upTrans = new Map([
182
+ [USER_PROFILE_MATURITY_V2.PENDING, new Set([USER_PROFILE_MATURITY_V2.ACTIVE, USER_PROFILE_MATURITY_V2.ARCHIVED])],
183
+ [USER_PROFILE_MATURITY_V2.ACTIVE, new Set([USER_PROFILE_MATURITY_V2.DORMANT, USER_PROFILE_MATURITY_V2.ARCHIVED])],
184
+ [USER_PROFILE_MATURITY_V2.DORMANT, new Set([USER_PROFILE_MATURITY_V2.ACTIVE, USER_PROFILE_MATURITY_V2.ARCHIVED])],
185
+ [USER_PROFILE_MATURITY_V2.ARCHIVED, new Set()],
186
+ ]);
187
+ const _upTerminal = new Set([USER_PROFILE_MATURITY_V2.ARCHIVED]);
188
+ const _uprefTrans = new Map([
189
+ [USER_PREF_LIFECYCLE_V2.PROPOSED, new Set([USER_PREF_LIFECYCLE_V2.APPLIED, USER_PREF_LIFECYCLE_V2.REJECTED, USER_PREF_LIFECYCLE_V2.CANCELLED])],
190
+ [USER_PREF_LIFECYCLE_V2.APPLIED, new Set([USER_PREF_LIFECYCLE_V2.SUPERSEDED])],
191
+ [USER_PREF_LIFECYCLE_V2.REJECTED, new Set()],
192
+ [USER_PREF_LIFECYCLE_V2.SUPERSEDED, new Set()],
193
+ [USER_PREF_LIFECYCLE_V2.CANCELLED, new Set()],
194
+ ]);
195
+ const _upsV2 = new Map();
196
+ const _uprefsV2 = new Map();
197
+ let _upMaxActivePerOwner = 5, _upMaxPendingPrefsPerProfile = 20, _upIdleMs = 90 * 24 * 60 * 60 * 1000, _uprefStuckMs = 7 * 24 * 60 * 60 * 1000;
198
+ function _upPos(n, label) { const v = Math.floor(Number(n)); if (!Number.isFinite(v) || v <= 0) throw new Error(`${label} must be positive integer`); return v; }
199
+ function _upCheckP(from, to) { const a = _upTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid user profile transition ${from} → ${to}`); }
200
+ function _uprefCheck(from, to) { const a = _uprefTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid user pref transition ${from} → ${to}`); }
201
+ export function setMaxActiveUserProfilesPerOwnerV2(n) { _upMaxActivePerOwner = _upPos(n, "maxActiveUserProfilesPerOwner"); }
202
+ export function getMaxActiveUserProfilesPerOwnerV2() { return _upMaxActivePerOwner; }
203
+ export function setMaxPendingUserPrefsPerProfileV2(n) { _upMaxPendingPrefsPerProfile = _upPos(n, "maxPendingUserPrefsPerProfile"); }
204
+ export function getMaxPendingUserPrefsPerProfileV2() { return _upMaxPendingPrefsPerProfile; }
205
+ export function setUserProfileIdleMsV2(n) { _upIdleMs = _upPos(n, "userProfileIdleMs"); }
206
+ export function getUserProfileIdleMsV2() { return _upIdleMs; }
207
+ export function setUserPrefStuckMsV2(n) { _uprefStuckMs = _upPos(n, "userPrefStuckMs"); }
208
+ export function getUserPrefStuckMsV2() { return _uprefStuckMs; }
209
+ export function _resetStateUserProfileV2() { _upsV2.clear(); _uprefsV2.clear(); _upMaxActivePerOwner = 5; _upMaxPendingPrefsPerProfile = 20; _upIdleMs = 90 * 24 * 60 * 60 * 1000; _uprefStuckMs = 7 * 24 * 60 * 60 * 1000; }
210
+ export function registerUserProfileV2({ id, owner, handle, metadata } = {}) {
211
+ if (!id) throw new Error("user profile id required"); if (!owner) throw new Error("user profile owner required");
212
+ if (_upsV2.has(id)) throw new Error(`user profile ${id} already registered`);
213
+ const now = Date.now();
214
+ const p = { id, owner, handle: handle || id, status: USER_PROFILE_MATURITY_V2.PENDING, createdAt: now, updatedAt: now, activatedAt: null, archivedAt: null, lastTouchedAt: now, metadata: { ...(metadata || {}) } };
215
+ _upsV2.set(id, p); return { ...p, metadata: { ...p.metadata } };
216
+ }
217
+ function _upCountActive(owner) { let n = 0; for (const p of _upsV2.values()) if (p.owner === owner && p.status === USER_PROFILE_MATURITY_V2.ACTIVE) n++; return n; }
218
+ export function activateUserProfileV2(id) {
219
+ const p = _upsV2.get(id); if (!p) throw new Error(`user profile ${id} not found`);
220
+ _upCheckP(p.status, USER_PROFILE_MATURITY_V2.ACTIVE);
221
+ const recovery = p.status === USER_PROFILE_MATURITY_V2.DORMANT;
222
+ if (!recovery && _upCountActive(p.owner) >= _upMaxActivePerOwner) throw new Error(`max active user profiles for owner ${p.owner} reached`);
223
+ const now = Date.now(); p.status = USER_PROFILE_MATURITY_V2.ACTIVE; p.updatedAt = now; p.lastTouchedAt = now; if (!p.activatedAt) p.activatedAt = now;
224
+ return { ...p, metadata: { ...p.metadata } };
225
+ }
226
+ export function dormantUserProfileV2(id) { const p = _upsV2.get(id); if (!p) throw new Error(`user profile ${id} not found`); _upCheckP(p.status, USER_PROFILE_MATURITY_V2.DORMANT); p.status = USER_PROFILE_MATURITY_V2.DORMANT; p.updatedAt = Date.now(); return { ...p, metadata: { ...p.metadata } }; }
227
+ export function archiveUserProfileV2(id) { const p = _upsV2.get(id); if (!p) throw new Error(`user profile ${id} not found`); _upCheckP(p.status, USER_PROFILE_MATURITY_V2.ARCHIVED); const now = Date.now(); p.status = USER_PROFILE_MATURITY_V2.ARCHIVED; p.updatedAt = now; if (!p.archivedAt) p.archivedAt = now; return { ...p, metadata: { ...p.metadata } }; }
228
+ export function touchUserProfileV2(id) { const p = _upsV2.get(id); if (!p) throw new Error(`user profile ${id} not found`); if (_upTerminal.has(p.status)) throw new Error(`cannot touch terminal user profile ${id}`); const now = Date.now(); p.lastTouchedAt = now; p.updatedAt = now; return { ...p, metadata: { ...p.metadata } }; }
229
+ export function getUserProfileV2(id) { const p = _upsV2.get(id); if (!p) return null; return { ...p, metadata: { ...p.metadata } }; }
230
+ export function listUserProfilesV2() { return [..._upsV2.values()].map((p) => ({ ...p, metadata: { ...p.metadata } })); }
231
+ function _uprefCountPending(profileId) { let n = 0; for (const r of _uprefsV2.values()) if (r.profileId === profileId && r.status === USER_PREF_LIFECYCLE_V2.PROPOSED) n++; return n; }
232
+ export function createUserPrefV2({ id, profileId, key, metadata } = {}) {
233
+ if (!id) throw new Error("user pref id required"); if (!profileId) throw new Error("user pref profileId required");
234
+ if (_uprefsV2.has(id)) throw new Error(`user pref ${id} already exists`);
235
+ if (!_upsV2.has(profileId)) throw new Error(`user profile ${profileId} not found`);
236
+ if (_uprefCountPending(profileId) >= _upMaxPendingPrefsPerProfile) throw new Error(`max pending user prefs for profile ${profileId} reached`);
237
+ const now = Date.now();
238
+ const r = { id, profileId, key: key || "", status: USER_PREF_LIFECYCLE_V2.PROPOSED, createdAt: now, updatedAt: now, startedAt: now, settledAt: null, metadata: { ...(metadata || {}) } };
239
+ _uprefsV2.set(id, r); return { ...r, metadata: { ...r.metadata } };
240
+ }
241
+ export function applyUserPrefV2(id) { const r = _uprefsV2.get(id); if (!r) throw new Error(`user pref ${id} not found`); _uprefCheck(r.status, USER_PREF_LIFECYCLE_V2.APPLIED); const now = Date.now(); r.status = USER_PREF_LIFECYCLE_V2.APPLIED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; return { ...r, metadata: { ...r.metadata } }; }
242
+ export function rejectUserPrefV2(id, reason) { const r = _uprefsV2.get(id); if (!r) throw new Error(`user pref ${id} not found`); _uprefCheck(r.status, USER_PREF_LIFECYCLE_V2.REJECTED); const now = Date.now(); r.status = USER_PREF_LIFECYCLE_V2.REJECTED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; if (reason) r.metadata.rejectReason = String(reason); return { ...r, metadata: { ...r.metadata } }; }
243
+ export function supersedeUserPrefV2(id) { const r = _uprefsV2.get(id); if (!r) throw new Error(`user pref ${id} not found`); _uprefCheck(r.status, USER_PREF_LIFECYCLE_V2.SUPERSEDED); const now = Date.now(); r.status = USER_PREF_LIFECYCLE_V2.SUPERSEDED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; return { ...r, metadata: { ...r.metadata } }; }
244
+ export function cancelUserPrefV2(id, reason) { const r = _uprefsV2.get(id); if (!r) throw new Error(`user pref ${id} not found`); _uprefCheck(r.status, USER_PREF_LIFECYCLE_V2.CANCELLED); const now = Date.now(); r.status = USER_PREF_LIFECYCLE_V2.CANCELLED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; if (reason) r.metadata.cancelReason = String(reason); return { ...r, metadata: { ...r.metadata } }; }
245
+ export function getUserPrefV2(id) { const r = _uprefsV2.get(id); if (!r) return null; return { ...r, metadata: { ...r.metadata } }; }
246
+ export function listUserPrefsV2() { return [..._uprefsV2.values()].map((r) => ({ ...r, metadata: { ...r.metadata } })); }
247
+ export function autoDormantIdleUserProfilesV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const p of _upsV2.values()) if (p.status === USER_PROFILE_MATURITY_V2.ACTIVE && (t - p.lastTouchedAt) >= _upIdleMs) { p.status = USER_PROFILE_MATURITY_V2.DORMANT; p.updatedAt = t; flipped.push(p.id); } return { flipped, count: flipped.length }; }
248
+ export function autoCancelStaleUserPrefsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const r of _uprefsV2.values()) if (r.status === USER_PREF_LIFECYCLE_V2.PROPOSED && (t - r.startedAt) >= _uprefStuckMs) { r.status = USER_PREF_LIFECYCLE_V2.CANCELLED; r.updatedAt = t; if (!r.settledAt) r.settledAt = t; r.metadata.cancelReason = "auto-cancel-stale"; flipped.push(r.id); } return { flipped, count: flipped.length }; }
249
+ export function getUserProfileGovStatsV2() {
250
+ const profilesByStatus = {}; for (const v of Object.values(USER_PROFILE_MATURITY_V2)) profilesByStatus[v] = 0; for (const p of _upsV2.values()) profilesByStatus[p.status]++;
251
+ const prefsByStatus = {}; for (const v of Object.values(USER_PREF_LIFECYCLE_V2)) prefsByStatus[v] = 0; for (const r of _uprefsV2.values()) prefsByStatus[r.status]++;
252
+ return { totalUserProfilesV2: _upsV2.size, totalUserPrefsV2: _uprefsV2.size, maxActiveUserProfilesPerOwner: _upMaxActivePerOwner, maxPendingUserPrefsPerProfile: _upMaxPendingPrefsPerProfile, userProfileIdleMs: _upIdleMs, userPrefStuckMs: _uprefStuckMs, profilesByStatus, prefsByStatus };
253
+ }
@@ -98,3 +98,84 @@ function filterByChannel(releases, channel) {
98
98
  }
99
99
 
100
100
  export { fetchReleases, filterByChannel };
101
+
102
+
103
+ // =====================================================================
104
+ // Version Checker V2 governance overlay
105
+ // =====================================================================
106
+ export const VCHK_PROFILE_MATURITY_V2 = Object.freeze({ PENDING: "pending", ACTIVE: "active", STALE: "stale", ARCHIVED: "archived" });
107
+ export const VCHK_CHECK_LIFECYCLE_V2 = Object.freeze({ QUEUED: "queued", CHECKING: "checking", COMPLETED: "completed", FAILED: "failed", CANCELLED: "cancelled" });
108
+ const _vchkPTrans = new Map([
109
+ [VCHK_PROFILE_MATURITY_V2.PENDING, new Set([VCHK_PROFILE_MATURITY_V2.ACTIVE, VCHK_PROFILE_MATURITY_V2.ARCHIVED])],
110
+ [VCHK_PROFILE_MATURITY_V2.ACTIVE, new Set([VCHK_PROFILE_MATURITY_V2.STALE, VCHK_PROFILE_MATURITY_V2.ARCHIVED])],
111
+ [VCHK_PROFILE_MATURITY_V2.STALE, new Set([VCHK_PROFILE_MATURITY_V2.ACTIVE, VCHK_PROFILE_MATURITY_V2.ARCHIVED])],
112
+ [VCHK_PROFILE_MATURITY_V2.ARCHIVED, new Set()],
113
+ ]);
114
+ const _vchkPTerminal = new Set([VCHK_PROFILE_MATURITY_V2.ARCHIVED]);
115
+ const _vchkCTrans = new Map([
116
+ [VCHK_CHECK_LIFECYCLE_V2.QUEUED, new Set([VCHK_CHECK_LIFECYCLE_V2.CHECKING, VCHK_CHECK_LIFECYCLE_V2.CANCELLED])],
117
+ [VCHK_CHECK_LIFECYCLE_V2.CHECKING, new Set([VCHK_CHECK_LIFECYCLE_V2.COMPLETED, VCHK_CHECK_LIFECYCLE_V2.FAILED, VCHK_CHECK_LIFECYCLE_V2.CANCELLED])],
118
+ [VCHK_CHECK_LIFECYCLE_V2.COMPLETED, new Set()],
119
+ [VCHK_CHECK_LIFECYCLE_V2.FAILED, new Set()],
120
+ [VCHK_CHECK_LIFECYCLE_V2.CANCELLED, new Set()],
121
+ ]);
122
+ const _vchkPsV2 = new Map();
123
+ const _vchkCsV2 = new Map();
124
+ let _vchkMaxActive = 5, _vchkMaxPending = 10, _vchkIdleMs = 30 * 24 * 60 * 60 * 1000, _vchkStuckMs = 30 * 1000;
125
+ function _vchkPos(n, label) { const v = Math.floor(Number(n)); if (!Number.isFinite(v) || v <= 0) throw new Error(`${label} must be positive integer`); return v; }
126
+ function _vchkCheckP(from, to) { const a = _vchkPTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid vchk profile transition ${from} → ${to}`); }
127
+ function _vchkCheckC(from, to) { const a = _vchkCTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid vchk check transition ${from} → ${to}`); }
128
+ function _vchkCountActive(owner) { let c = 0; for (const p of _vchkPsV2.values()) if (p.owner === owner && p.status === VCHK_PROFILE_MATURITY_V2.ACTIVE) c++; return c; }
129
+ function _vchkCountPending(profileId) { let c = 0; for (const x of _vchkCsV2.values()) if (x.profileId === profileId && (x.status === VCHK_CHECK_LIFECYCLE_V2.QUEUED || x.status === VCHK_CHECK_LIFECYCLE_V2.CHECKING)) c++; return c; }
130
+ export function setMaxActiveVchkProfilesPerOwnerV2(n) { _vchkMaxActive = _vchkPos(n, "maxActiveVchkProfilesPerOwner"); }
131
+ export function getMaxActiveVchkProfilesPerOwnerV2() { return _vchkMaxActive; }
132
+ export function setMaxPendingVchkChecksPerProfileV2(n) { _vchkMaxPending = _vchkPos(n, "maxPendingVchkChecksPerProfile"); }
133
+ export function getMaxPendingVchkChecksPerProfileV2() { return _vchkMaxPending; }
134
+ export function setVchkProfileIdleMsV2(n) { _vchkIdleMs = _vchkPos(n, "vchkProfileIdleMs"); }
135
+ export function getVchkProfileIdleMsV2() { return _vchkIdleMs; }
136
+ export function setVchkCheckStuckMsV2(n) { _vchkStuckMs = _vchkPos(n, "vchkCheckStuckMs"); }
137
+ export function getVchkCheckStuckMsV2() { return _vchkStuckMs; }
138
+ export function _resetStateVersionCheckerV2() { _vchkPsV2.clear(); _vchkCsV2.clear(); _vchkMaxActive = 5; _vchkMaxPending = 10; _vchkIdleMs = 30 * 24 * 60 * 60 * 1000; _vchkStuckMs = 30 * 1000; }
139
+ export function registerVchkProfileV2({ id, owner, channel, metadata } = {}) {
140
+ if (!id || !owner) throw new Error("id and owner required");
141
+ if (_vchkPsV2.has(id)) throw new Error(`vchk profile ${id} already exists`);
142
+ const now = Date.now();
143
+ const p = { id, owner, channel: channel || "stable", status: VCHK_PROFILE_MATURITY_V2.PENDING, createdAt: now, updatedAt: now, lastTouchedAt: now, activatedAt: null, archivedAt: null, metadata: { ...(metadata || {}) } };
144
+ _vchkPsV2.set(id, p); return { ...p, metadata: { ...p.metadata } };
145
+ }
146
+ export function activateVchkProfileV2(id) {
147
+ const p = _vchkPsV2.get(id); if (!p) throw new Error(`vchk profile ${id} not found`);
148
+ const isInitial = p.status === VCHK_PROFILE_MATURITY_V2.PENDING;
149
+ _vchkCheckP(p.status, VCHK_PROFILE_MATURITY_V2.ACTIVE);
150
+ if (isInitial && _vchkCountActive(p.owner) >= _vchkMaxActive) throw new Error(`max active vchk profiles for owner ${p.owner} reached`);
151
+ const now = Date.now(); p.status = VCHK_PROFILE_MATURITY_V2.ACTIVE; p.updatedAt = now; p.lastTouchedAt = now;
152
+ if (!p.activatedAt) p.activatedAt = now;
153
+ return { ...p, metadata: { ...p.metadata } };
154
+ }
155
+ export function staleVchkProfileV2(id) { const p = _vchkPsV2.get(id); if (!p) throw new Error(`vchk profile ${id} not found`); _vchkCheckP(p.status, VCHK_PROFILE_MATURITY_V2.STALE); p.status = VCHK_PROFILE_MATURITY_V2.STALE; p.updatedAt = Date.now(); return { ...p, metadata: { ...p.metadata } }; }
156
+ export function archiveVchkProfileV2(id) { const p = _vchkPsV2.get(id); if (!p) throw new Error(`vchk profile ${id} not found`); _vchkCheckP(p.status, VCHK_PROFILE_MATURITY_V2.ARCHIVED); const now = Date.now(); p.status = VCHK_PROFILE_MATURITY_V2.ARCHIVED; p.updatedAt = now; if (!p.archivedAt) p.archivedAt = now; return { ...p, metadata: { ...p.metadata } }; }
157
+ export function touchVchkProfileV2(id) { const p = _vchkPsV2.get(id); if (!p) throw new Error(`vchk profile ${id} not found`); if (_vchkPTerminal.has(p.status)) throw new Error(`cannot touch terminal vchk profile ${id}`); const now = Date.now(); p.lastTouchedAt = now; p.updatedAt = now; return { ...p, metadata: { ...p.metadata } }; }
158
+ export function getVchkProfileV2(id) { const p = _vchkPsV2.get(id); if (!p) return null; return { ...p, metadata: { ...p.metadata } }; }
159
+ export function listVchkProfilesV2() { return [..._vchkPsV2.values()].map((p) => ({ ...p, metadata: { ...p.metadata } })); }
160
+ export function createVchkCheckV2({ id, profileId, currentVersion, metadata } = {}) {
161
+ if (!id || !profileId) throw new Error("id and profileId required");
162
+ if (_vchkCsV2.has(id)) throw new Error(`vchk check ${id} already exists`);
163
+ if (!_vchkPsV2.has(profileId)) throw new Error(`vchk profile ${profileId} not found`);
164
+ if (_vchkCountPending(profileId) >= _vchkMaxPending) throw new Error(`max pending vchk checks for profile ${profileId} reached`);
165
+ const now = Date.now();
166
+ const c = { id, profileId, currentVersion: currentVersion || "", status: VCHK_CHECK_LIFECYCLE_V2.QUEUED, createdAt: now, updatedAt: now, startedAt: null, settledAt: null, metadata: { ...(metadata || {}) } };
167
+ _vchkCsV2.set(id, c); return { ...c, metadata: { ...c.metadata } };
168
+ }
169
+ export function checkingVchkCheckV2(id) { const c = _vchkCsV2.get(id); if (!c) throw new Error(`vchk check ${id} not found`); _vchkCheckC(c.status, VCHK_CHECK_LIFECYCLE_V2.CHECKING); const now = Date.now(); c.status = VCHK_CHECK_LIFECYCLE_V2.CHECKING; c.updatedAt = now; if (!c.startedAt) c.startedAt = now; return { ...c, metadata: { ...c.metadata } }; }
170
+ export function completeVchkCheckV2(id) { const c = _vchkCsV2.get(id); if (!c) throw new Error(`vchk check ${id} not found`); _vchkCheckC(c.status, VCHK_CHECK_LIFECYCLE_V2.COMPLETED); const now = Date.now(); c.status = VCHK_CHECK_LIFECYCLE_V2.COMPLETED; c.updatedAt = now; if (!c.settledAt) c.settledAt = now; return { ...c, metadata: { ...c.metadata } }; }
171
+ export function failVchkCheckV2(id, reason) { const c = _vchkCsV2.get(id); if (!c) throw new Error(`vchk check ${id} not found`); _vchkCheckC(c.status, VCHK_CHECK_LIFECYCLE_V2.FAILED); const now = Date.now(); c.status = VCHK_CHECK_LIFECYCLE_V2.FAILED; c.updatedAt = now; if (!c.settledAt) c.settledAt = now; if (reason) c.metadata.failReason = String(reason); return { ...c, metadata: { ...c.metadata } }; }
172
+ export function cancelVchkCheckV2(id, reason) { const c = _vchkCsV2.get(id); if (!c) throw new Error(`vchk check ${id} not found`); _vchkCheckC(c.status, VCHK_CHECK_LIFECYCLE_V2.CANCELLED); const now = Date.now(); c.status = VCHK_CHECK_LIFECYCLE_V2.CANCELLED; c.updatedAt = now; if (!c.settledAt) c.settledAt = now; if (reason) c.metadata.cancelReason = String(reason); return { ...c, metadata: { ...c.metadata } }; }
173
+ export function getVchkCheckV2(id) { const c = _vchkCsV2.get(id); if (!c) return null; return { ...c, metadata: { ...c.metadata } }; }
174
+ export function listVchkChecksV2() { return [..._vchkCsV2.values()].map((c) => ({ ...c, metadata: { ...c.metadata } })); }
175
+ export function autoStaleIdleVchkProfilesV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const p of _vchkPsV2.values()) if (p.status === VCHK_PROFILE_MATURITY_V2.ACTIVE && (t - p.lastTouchedAt) >= _vchkIdleMs) { p.status = VCHK_PROFILE_MATURITY_V2.STALE; p.updatedAt = t; flipped.push(p.id); } return { flipped, count: flipped.length }; }
176
+ export function autoFailStuckVchkChecksV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const c of _vchkCsV2.values()) if (c.status === VCHK_CHECK_LIFECYCLE_V2.CHECKING && c.startedAt != null && (t - c.startedAt) >= _vchkStuckMs) { c.status = VCHK_CHECK_LIFECYCLE_V2.FAILED; c.updatedAt = t; if (!c.settledAt) c.settledAt = t; c.metadata.failReason = "auto-fail-stuck"; flipped.push(c.id); } return { flipped, count: flipped.length }; }
177
+ export function getVersionCheckerGovStatsV2() {
178
+ const profilesByStatus = {}; for (const v of Object.values(VCHK_PROFILE_MATURITY_V2)) profilesByStatus[v] = 0; for (const p of _vchkPsV2.values()) profilesByStatus[p.status]++;
179
+ const checksByStatus = {}; for (const v of Object.values(VCHK_CHECK_LIFECYCLE_V2)) checksByStatus[v] = 0; for (const c of _vchkCsV2.values()) checksByStatus[c.status]++;
180
+ return { totalVchkProfilesV2: _vchkPsV2.size, totalVchkChecksV2: _vchkCsV2.size, maxActiveVchkProfilesPerOwner: _vchkMaxActive, maxPendingVchkChecksPerProfile: _vchkMaxPending, vchkProfileIdleMs: _vchkIdleMs, vchkCheckStuckMs: _vchkStuckMs, profilesByStatus, checksByStatus };
181
+ }