instar 0.28.83 → 0.28.85

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 (114) hide show
  1. package/dashboard/index.html +239 -0
  2. package/dist/cli.js +10 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/intent.d.ts.map +1 -1
  5. package/dist/commands/intent.js +29 -2
  6. package/dist/commands/intent.js.map +1 -1
  7. package/dist/commands/memoryBackfillEvidence.d.ts +69 -0
  8. package/dist/commands/memoryBackfillEvidence.d.ts.map +1 -0
  9. package/dist/commands/memoryBackfillEvidence.js +220 -0
  10. package/dist/commands/memoryBackfillEvidence.js.map +1 -0
  11. package/dist/commands/server.d.ts.map +1 -1
  12. package/dist/commands/server.js +86 -1
  13. package/dist/commands/server.js.map +1 -1
  14. package/dist/core/DecisionJournal.d.ts +66 -3
  15. package/dist/core/DecisionJournal.d.ts.map +1 -1
  16. package/dist/core/DecisionJournal.js +116 -3
  17. package/dist/core/DecisionJournal.js.map +1 -1
  18. package/dist/core/DispatchExecutor.d.ts +74 -1
  19. package/dist/core/DispatchExecutor.d.ts.map +1 -1
  20. package/dist/core/DispatchExecutor.js +120 -1
  21. package/dist/core/DispatchExecutor.js.map +1 -1
  22. package/dist/core/EvolutionManager.d.ts +133 -0
  23. package/dist/core/EvolutionManager.d.ts.map +1 -1
  24. package/dist/core/EvolutionManager.js +494 -0
  25. package/dist/core/EvolutionManager.js.map +1 -1
  26. package/dist/core/InitiativeTracker.d.ts +82 -15
  27. package/dist/core/InitiativeTracker.d.ts.map +1 -1
  28. package/dist/core/InitiativeTracker.js +502 -31
  29. package/dist/core/InitiativeTracker.js.map +1 -1
  30. package/dist/core/LearnSkillBridge.d.ts +119 -0
  31. package/dist/core/LearnSkillBridge.d.ts.map +1 -0
  32. package/dist/core/LearnSkillBridge.js +160 -0
  33. package/dist/core/LearnSkillBridge.js.map +1 -0
  34. package/dist/core/SharedStateLedger.d.ts.map +1 -1
  35. package/dist/core/SharedStateLedger.js +4 -0
  36. package/dist/core/SharedStateLedger.js.map +1 -1
  37. package/dist/core/types.d.ts +124 -1
  38. package/dist/core/types.d.ts.map +1 -1
  39. package/dist/core/types.js +8 -0
  40. package/dist/core/types.js.map +1 -1
  41. package/dist/memory/EvidenceRenderer.d.ts +64 -0
  42. package/dist/memory/EvidenceRenderer.d.ts.map +1 -0
  43. package/dist/memory/EvidenceRenderer.js +119 -0
  44. package/dist/memory/EvidenceRenderer.js.map +1 -0
  45. package/dist/memory/SemanticMemory.d.ts +61 -1
  46. package/dist/memory/SemanticMemory.d.ts.map +1 -1
  47. package/dist/memory/SemanticMemory.js +381 -11
  48. package/dist/memory/SemanticMemory.js.map +1 -1
  49. package/dist/server/AgentServer.d.ts +4 -0
  50. package/dist/server/AgentServer.d.ts.map +1 -1
  51. package/dist/server/AgentServer.js +2 -0
  52. package/dist/server/AgentServer.js.map +1 -1
  53. package/dist/server/routes.d.ts +8 -0
  54. package/dist/server/routes.d.ts.map +1 -1
  55. package/dist/server/routes.js +500 -14
  56. package/dist/server/routes.js.map +1 -1
  57. package/dist/tasks/DivergenceChecker.d.ts +89 -0
  58. package/dist/tasks/DivergenceChecker.d.ts.map +1 -0
  59. package/dist/tasks/DivergenceChecker.js +255 -0
  60. package/dist/tasks/DivergenceChecker.js.map +1 -0
  61. package/dist/tasks/LruCache.d.ts +33 -0
  62. package/dist/tasks/LruCache.d.ts.map +1 -0
  63. package/dist/tasks/LruCache.js +88 -0
  64. package/dist/tasks/LruCache.js.map +1 -0
  65. package/dist/tasks/RateLimiter.d.ts +61 -0
  66. package/dist/tasks/RateLimiter.d.ts.map +1 -0
  67. package/dist/tasks/RateLimiter.js +108 -0
  68. package/dist/tasks/RateLimiter.js.map +1 -0
  69. package/dist/tasks/TaskFlowDueWaker.d.ts +29 -0
  70. package/dist/tasks/TaskFlowDueWaker.d.ts.map +1 -0
  71. package/dist/tasks/TaskFlowDueWaker.js +73 -0
  72. package/dist/tasks/TaskFlowDueWaker.js.map +1 -0
  73. package/dist/tasks/TaskFlowMaintenanceSweeper.d.ts +40 -0
  74. package/dist/tasks/TaskFlowMaintenanceSweeper.d.ts.map +1 -0
  75. package/dist/tasks/TaskFlowMaintenanceSweeper.js +131 -0
  76. package/dist/tasks/TaskFlowMaintenanceSweeper.js.map +1 -0
  77. package/dist/tasks/TaskFlowRegistry.d.ts +180 -0
  78. package/dist/tasks/TaskFlowRegistry.d.ts.map +1 -0
  79. package/dist/tasks/TaskFlowRegistry.js +791 -0
  80. package/dist/tasks/TaskFlowRegistry.js.map +1 -0
  81. package/dist/tasks/ThreadlineFlowBridge.d.ts +54 -0
  82. package/dist/tasks/ThreadlineFlowBridge.d.ts.map +1 -0
  83. package/dist/tasks/ThreadlineFlowBridge.js +129 -0
  84. package/dist/tasks/ThreadlineFlowBridge.js.map +1 -0
  85. package/dist/tasks/task-flow-registry.store.sqlite.d.ts +77 -0
  86. package/dist/tasks/task-flow-registry.store.sqlite.d.ts.map +1 -0
  87. package/dist/tasks/task-flow-registry.store.sqlite.js +350 -0
  88. package/dist/tasks/task-flow-registry.store.sqlite.js.map +1 -0
  89. package/dist/tasks/task-flow-types.d.ts +229 -0
  90. package/dist/tasks/task-flow-types.d.ts.map +1 -0
  91. package/dist/tasks/task-flow-types.js +128 -0
  92. package/dist/tasks/task-flow-types.js.map +1 -0
  93. package/dist/threadline/ThreadlineMCPServer.d.ts +32 -0
  94. package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
  95. package/dist/threadline/ThreadlineMCPServer.js +82 -5
  96. package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
  97. package/dist/threadline/mcp-stdio-entry.js +56 -0
  98. package/dist/threadline/mcp-stdio-entry.js.map +1 -1
  99. package/package.json +1 -1
  100. package/src/data/builtin-manifest.json +78 -78
  101. package/upgrades/0.28.84.md +89 -0
  102. package/upgrades/0.28.85.md +64 -0
  103. package/upgrades/side-effects/0.28.83.md +27 -0
  104. package/upgrades/side-effects/taskflow-phase1.md +130 -0
  105. package/upgrades/side-effects/taskflow-phase2.md +110 -0
  106. package/upgrades/side-effects/taskflow-phase3a.md +138 -0
  107. package/upgrades/side-effects/taskflow-phase4.md +377 -0
  108. package/upgrades/side-effects/taskflow-phase5.md +234 -0
  109. package/upgrades/side-effects/threadline-discover-relay-and-trust.md +122 -0
  110. package/upgrades/side-effects/wikiclaim-evidence-phase1.md +138 -0
  111. package/upgrades/side-effects/wikiclaim-evidence-phase2.md +148 -0
  112. package/upgrades/side-effects/wikiclaim-evidence-phase3.md +125 -0
  113. package/upgrades/side-effects/wikiclaim-evidence-phase4.md +129 -0
  114. package/upgrades/side-effects/wikiclaim-evidence-phase5.md +285 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * DivergenceChecker — TaskFlow Phase 3a state-coherence monitor.
3
+ *
4
+ * Runs every 15 minutes during the Phase 3a dual-write window. Compares
5
+ * EvolutionManager's authoritative JSON state (proposals) against the
6
+ * TaskFlow registry's flow records on the (ownerKey, status, currentStep,
7
+ * waitJson.kind) tuple. Emits a `taskflow_divergence_count` metric on every
8
+ * run and a SharedStateLedger note kind `taskflow-divergence` per mismatch.
9
+ *
10
+ * Signal-vs-authority compliance: this is a SIGNAL emitter, never a gate.
11
+ * - It does not block any user-facing action.
12
+ * - It does not block any agent-internal action.
13
+ * - On divergence > 0, it asks EvolutionManager to halt shadow-writes via
14
+ * `setShadowWritesHalted(true)`. EvolutionManager continues JSON writes
15
+ * uninterrupted; only the secondary TaskFlow shadow-write is paused
16
+ * until divergence clears. This is a self-applied brake, not an
17
+ * authority decision over user-facing flow.
18
+ *
19
+ * Phase 3b cutover requires `divergenceCount == 0` for ≥7 consecutive days
20
+ * AND ledger contains zero `taskflow-divergence` notes in that window. The
21
+ * cutover gate is enforced in the Phase 3b PR, not here.
22
+ *
23
+ * Spec: docs/specs/OPENCLAW-IMPORT-TASKFLOW-SPEC.md § Phase 3a.
24
+ */
25
+ import type { TaskFlowRegistry } from './TaskFlowRegistry.js';
26
+ import type { EvolutionManager } from '../core/EvolutionManager.js';
27
+ import type { SharedStateLedger } from '../core/SharedStateLedger.js';
28
+ import type { TaskFlowStatus } from './task-flow-types.js';
29
+ import type { EvolutionStatus } from '../core/types.js';
30
+ /** Default cron interval — 15 minutes per spec § Phase 3a. */
31
+ export declare const DIVERGENCE_CHECK_INTERVAL_MS: number;
32
+ export interface DivergenceMismatch {
33
+ kind: 'json-only' | 'taskflow-only' | 'status-mismatch' | 'step-mismatch' | 'wait-kind-mismatch';
34
+ ownerKey: string;
35
+ proposalId?: string;
36
+ flowId?: string;
37
+ proposalStatus?: EvolutionStatus;
38
+ flowStatus?: TaskFlowStatus;
39
+ expectedStep?: string;
40
+ actualStep?: string;
41
+ expectedWaitKind?: string | null;
42
+ actualWaitKind?: string | null;
43
+ }
44
+ export interface DivergenceReport {
45
+ scannedJsonProposals: number;
46
+ scannedTaskFlowRecords: number;
47
+ divergenceCount: number;
48
+ mismatches: DivergenceMismatch[];
49
+ checkedAt: string;
50
+ }
51
+ export interface DivergenceCheckerOptions {
52
+ registry: TaskFlowRegistry;
53
+ evolutionManager: EvolutionManager;
54
+ ledger?: SharedStateLedger;
55
+ intervalMs?: number;
56
+ now?: () => number;
57
+ }
58
+ export declare class DivergenceChecker {
59
+ private timer;
60
+ private readonly registry;
61
+ private readonly evolutionManager;
62
+ private readonly ledger?;
63
+ private readonly intervalMs;
64
+ private readonly now;
65
+ /** Most recent divergence count; readable by /health probes. */
66
+ divergenceCount: number;
67
+ /** Wall-clock of the most recent runOnce(). null until first run. */
68
+ lastCheckAt: string | null;
69
+ /** The most recent report, kept for /health and test assertions. */
70
+ lastReport: DivergenceReport | null;
71
+ constructor(opts: DivergenceCheckerOptions);
72
+ start(): void;
73
+ stop(): void;
74
+ /**
75
+ * Single-pass divergence scan. Idempotent; safe to call as often as desired.
76
+ *
77
+ * Returns the report. Side effects:
78
+ * 1. Updates `divergenceCount` / `lastCheckAt` / `lastReport`.
79
+ * 2. Emits a SharedStateLedger note per mismatch (kind: 'note',
80
+ * subject: 'taskflow-divergence').
81
+ * 3. Calls `evolutionManager.setShadowWritesHalted(...)`:
82
+ * divergenceCount > 0 → halted=true
83
+ * divergenceCount == 0 → halted=false (resume shadow-writes)
84
+ */
85
+ runOnce(): Promise<DivergenceReport>;
86
+ private emitMetric;
87
+ private emitLedgerNote;
88
+ }
89
+ //# sourceMappingURL=DivergenceChecker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DivergenceChecker.d.ts","sourceRoot":"","sources":["../../src/tasks/DivergenceChecker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,KAAK,EAAqB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE3E,8DAA8D;AAC9D,eAAO,MAAM,4BAA4B,QAAiB,CAAC;AAI3D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EACA,WAAW,GACX,eAAe,GACf,iBAAiB,GACjB,eAAe,GACf,oBAAoB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAsCD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,gEAAgE;IAChE,eAAe,SAAK;IACpB,qEAAqE;IACrE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAClC,oEAAoE;IACpE,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAQ;gBAE/B,IAAI,EAAE,wBAAwB;IAQ1C,KAAK,IAAI,IAAI;IAUb,IAAI,IAAI,IAAI;IAOZ;;;;;;;;;;OAUG;IACG,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;IA0H1C,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,cAAc;CA4BvB"}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * DivergenceChecker — TaskFlow Phase 3a state-coherence monitor.
3
+ *
4
+ * Runs every 15 minutes during the Phase 3a dual-write window. Compares
5
+ * EvolutionManager's authoritative JSON state (proposals) against the
6
+ * TaskFlow registry's flow records on the (ownerKey, status, currentStep,
7
+ * waitJson.kind) tuple. Emits a `taskflow_divergence_count` metric on every
8
+ * run and a SharedStateLedger note kind `taskflow-divergence` per mismatch.
9
+ *
10
+ * Signal-vs-authority compliance: this is a SIGNAL emitter, never a gate.
11
+ * - It does not block any user-facing action.
12
+ * - It does not block any agent-internal action.
13
+ * - On divergence > 0, it asks EvolutionManager to halt shadow-writes via
14
+ * `setShadowWritesHalted(true)`. EvolutionManager continues JSON writes
15
+ * uninterrupted; only the secondary TaskFlow shadow-write is paused
16
+ * until divergence clears. This is a self-applied brake, not an
17
+ * authority decision over user-facing flow.
18
+ *
19
+ * Phase 3b cutover requires `divergenceCount == 0` for ≥7 consecutive days
20
+ * AND ledger contains zero `taskflow-divergence` notes in that window. The
21
+ * cutover gate is enforced in the Phase 3b PR, not here.
22
+ *
23
+ * Spec: docs/specs/OPENCLAW-IMPORT-TASKFLOW-SPEC.md § Phase 3a.
24
+ */
25
+ /** Default cron interval — 15 minutes per spec § Phase 3a. */
26
+ export const DIVERGENCE_CHECK_INTERVAL_MS = 15 * 60 * 1000;
27
+ const CONTROLLER_ID = 'EvolutionManager';
28
+ /**
29
+ * Status mapping — what TaskFlow status corresponds to a given proposal status?
30
+ * Single source of truth shared with EvolutionManager's dual-write logic.
31
+ *
32
+ * `null` means "ignore this proposal during divergence checks" — used for
33
+ * pre-Phase-3a JSON-only records that have not yet been backfilled.
34
+ */
35
+ function expectedFlowStatusFor(p) {
36
+ switch (p) {
37
+ case 'proposed':
38
+ return 'queued';
39
+ case 'approved':
40
+ case 'in_progress':
41
+ return 'running';
42
+ case 'implemented':
43
+ return 'succeeded';
44
+ case 'rejected':
45
+ return 'failed';
46
+ case 'deferred':
47
+ return 'cancelled';
48
+ default:
49
+ return null;
50
+ }
51
+ }
52
+ function expectedStepFor(p) {
53
+ switch (p) {
54
+ case 'approved':
55
+ return 'approved';
56
+ case 'in_progress':
57
+ return 'in_progress';
58
+ default:
59
+ return null;
60
+ }
61
+ }
62
+ export class DivergenceChecker {
63
+ timer = null;
64
+ registry;
65
+ evolutionManager;
66
+ ledger;
67
+ intervalMs;
68
+ now;
69
+ /** Most recent divergence count; readable by /health probes. */
70
+ divergenceCount = 0;
71
+ /** Wall-clock of the most recent runOnce(). null until first run. */
72
+ lastCheckAt = null;
73
+ /** The most recent report, kept for /health and test assertions. */
74
+ lastReport = null;
75
+ constructor(opts) {
76
+ this.registry = opts.registry;
77
+ this.evolutionManager = opts.evolutionManager;
78
+ this.ledger = opts.ledger;
79
+ this.intervalMs = opts.intervalMs ?? DIVERGENCE_CHECK_INTERVAL_MS;
80
+ this.now = opts.now ?? (() => Date.now());
81
+ }
82
+ start() {
83
+ if (this.timer)
84
+ return;
85
+ this.timer = setInterval(() => {
86
+ this.runOnce().catch(() => {
87
+ /* swallow — divergence checking is best-effort */
88
+ });
89
+ }, this.intervalMs);
90
+ if (this.timer.unref)
91
+ this.timer.unref();
92
+ }
93
+ stop() {
94
+ if (this.timer) {
95
+ clearInterval(this.timer);
96
+ this.timer = null;
97
+ }
98
+ }
99
+ /**
100
+ * Single-pass divergence scan. Idempotent; safe to call as often as desired.
101
+ *
102
+ * Returns the report. Side effects:
103
+ * 1. Updates `divergenceCount` / `lastCheckAt` / `lastReport`.
104
+ * 2. Emits a SharedStateLedger note per mismatch (kind: 'note',
105
+ * subject: 'taskflow-divergence').
106
+ * 3. Calls `evolutionManager.setShadowWritesHalted(...)`:
107
+ * divergenceCount > 0 → halted=true
108
+ * divergenceCount == 0 → halted=false (resume shadow-writes)
109
+ */
110
+ async runOnce() {
111
+ const checkedAt = new Date(this.now()).toISOString();
112
+ // Read JSON-side proposals via the public API.
113
+ const proposals = this.evolutionManager.listProposals();
114
+ const flowRecs = this.registry.findByControllerId(CONTROLLER_ID);
115
+ const mismatches = [];
116
+ const flowByOwnerKey = new Map();
117
+ for (const f of flowRecs)
118
+ flowByOwnerKey.set(f.ownerKey, f);
119
+ const jsonByOwnerKey = new Map();
120
+ for (const p of proposals) {
121
+ jsonByOwnerKey.set(`evolution:cluster:${p.id}`, p);
122
+ }
123
+ // Forward scan: JSON → TaskFlow.
124
+ for (const p of proposals) {
125
+ const ownerKey = `evolution:cluster:${p.id}`;
126
+ const flow = flowByOwnerKey.get(ownerKey);
127
+ if (!flow) {
128
+ mismatches.push({
129
+ kind: 'json-only',
130
+ ownerKey,
131
+ proposalId: p.id,
132
+ proposalStatus: p.status,
133
+ });
134
+ continue;
135
+ }
136
+ const expectedStatus = expectedFlowStatusFor(p.status);
137
+ if (expectedStatus !== null && flow.status !== expectedStatus) {
138
+ mismatches.push({
139
+ kind: 'status-mismatch',
140
+ ownerKey,
141
+ proposalId: p.id,
142
+ flowId: flow.flowId,
143
+ proposalStatus: p.status,
144
+ flowStatus: flow.status,
145
+ });
146
+ }
147
+ const expectedStep = expectedStepFor(p.status);
148
+ if (expectedStep !== null && flow.currentStep !== expectedStep) {
149
+ mismatches.push({
150
+ kind: 'step-mismatch',
151
+ ownerKey,
152
+ proposalId: p.id,
153
+ flowId: flow.flowId,
154
+ expectedStep,
155
+ actualStep: flow.currentStep,
156
+ });
157
+ }
158
+ // Wait-kind: Phase 3a does not yet use TaskFlow waits for proposals,
159
+ // so any waitJson on the flow is itself a divergence signal.
160
+ if (flow.waitJson) {
161
+ mismatches.push({
162
+ kind: 'wait-kind-mismatch',
163
+ ownerKey,
164
+ proposalId: p.id,
165
+ flowId: flow.flowId,
166
+ expectedWaitKind: null,
167
+ actualWaitKind: flow.waitJson.kind,
168
+ });
169
+ }
170
+ }
171
+ // Reverse scan: TaskFlow → JSON.
172
+ for (const f of flowRecs) {
173
+ if (!jsonByOwnerKey.has(f.ownerKey)) {
174
+ mismatches.push({
175
+ kind: 'taskflow-only',
176
+ ownerKey: f.ownerKey,
177
+ flowId: f.flowId,
178
+ flowStatus: f.status,
179
+ });
180
+ }
181
+ }
182
+ const report = {
183
+ scannedJsonProposals: proposals.length,
184
+ scannedTaskFlowRecords: flowRecs.length,
185
+ divergenceCount: mismatches.length,
186
+ mismatches,
187
+ checkedAt,
188
+ };
189
+ this.divergenceCount = report.divergenceCount;
190
+ this.lastCheckAt = checkedAt;
191
+ this.lastReport = report;
192
+ // Emit metric counter — surfaced via /health and ledger-stats.
193
+ this.emitMetric('taskflow_divergence_count', report.divergenceCount, {
194
+ controllerId: CONTROLLER_ID,
195
+ checkedAt,
196
+ });
197
+ // Emit one ledger note per mismatch (capped at 50 per pass to bound spam).
198
+ const noteLimit = 50;
199
+ for (const m of report.mismatches.slice(0, noteLimit)) {
200
+ this.emitLedgerNote(m, checkedAt);
201
+ }
202
+ // Signal-consumed brake — flip EvolutionManager's shadow-write switch.
203
+ // Tag the halt with our identity so a manual halt by an operator survives
204
+ // until that operator clears it. Auto-resume only clears halts we set.
205
+ if (report.divergenceCount > 0) {
206
+ this.evolutionManager.setShadowWritesHalted(true, `taskflow-divergence: ${report.divergenceCount} mismatch(es) at ${checkedAt}`, 'divergence-checker');
207
+ }
208
+ else {
209
+ const cur = this.evolutionManager.isShadowWritesHalted();
210
+ if (cur.halted && cur.source === 'divergence-checker') {
211
+ this.evolutionManager.setShadowWritesHalted(false);
212
+ }
213
+ }
214
+ return report;
215
+ }
216
+ // ── private helpers ──────────────────────────────────────────────
217
+ emitMetric(name, value, labels) {
218
+ // Lightweight surface — instar's metrics are emitted via console.log
219
+ // tagged lines that the dashboard scrapes; structured JSON keeps the
220
+ // line greppable. If a metric backend is wired later, this is the seam.
221
+ try {
222
+ console.log(`[metric] ${name}=${value} ${JSON.stringify(labels)}`);
223
+ }
224
+ catch {
225
+ /* swallow */
226
+ }
227
+ }
228
+ emitLedgerNote(m, checkedAt) {
229
+ if (!this.ledger)
230
+ return;
231
+ void Promise.resolve()
232
+ .then(() => this.ledger.append({
233
+ kind: 'note',
234
+ provenance: 'subsystem-asserted',
235
+ emittedBy: {
236
+ subsystem: 'taskflow-divergence',
237
+ instance: 'DivergenceChecker',
238
+ },
239
+ counterparty: { type: 'self', name: 'EvolutionManager', trustTier: 'trusted' },
240
+ subject: 'taskflow-divergence',
241
+ summary: `${m.kind}: ownerKey=${m.ownerKey} ` +
242
+ (m.proposalId ? `proposalId=${m.proposalId} ` : '') +
243
+ (m.flowId ? `flowId=${m.flowId} ` : '') +
244
+ (m.proposalStatus ? `proposalStatus=${m.proposalStatus} ` : '') +
245
+ (m.flowStatus ? `flowStatus=${m.flowStatus} ` : '') +
246
+ (m.expectedStep !== undefined ? `expectedStep=${m.expectedStep ?? 'none'} actualStep=${m.actualStep ?? 'none'} ` : '') +
247
+ (m.expectedWaitKind !== undefined ? `expectedWaitKind=${m.expectedWaitKind ?? 'none'} actualWaitKind=${m.actualWaitKind ?? 'none'}` : ''),
248
+ dedupKey: `taskflow-divergence:${m.kind}:${m.ownerKey}:${checkedAt}`,
249
+ }))
250
+ .catch(() => {
251
+ /* swallow — best-effort */
252
+ });
253
+ }
254
+ }
255
+ //# sourceMappingURL=DivergenceChecker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DivergenceChecker.js","sourceRoot":"","sources":["../../src/tasks/DivergenceChecker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * LruCache — bounded LRU for TaskFlowRegistry.
3
+ *
4
+ * Spec: docs/specs/OPENCLAW-IMPORT-TASKFLOW-SPEC.md § Phase 5 line 652
5
+ * ("LRU cache eviction tuning").
6
+ *
7
+ * Implementation: leans on JavaScript Map's insertion-order iteration. On `get`
8
+ * we delete-then-set to refresh recency. On `set` overflow we evict the oldest
9
+ * key (Map iterator first). Eviction count is exposed for metric emission.
10
+ */
11
+ export interface LruCacheOptions<V> {
12
+ maxEntries: number;
13
+ /** Optional eviction observer for metric emission. */
14
+ onEvict?: (key: string, value: V) => void;
15
+ }
16
+ export declare class LruCache<V> {
17
+ private maxEntries;
18
+ private readonly map;
19
+ private readonly onEvict?;
20
+ private evictions;
21
+ constructor(opts: LruCacheOptions<V>);
22
+ get(key: string): V | undefined;
23
+ set(key: string, value: V): void;
24
+ delete(key: string): boolean;
25
+ has(key: string): boolean;
26
+ get size(): number;
27
+ get evictionCount(): number;
28
+ /** Test-only: iterate keys in LRU order (oldest first). */
29
+ keysInOrder(): string[];
30
+ /** Resize cap; immediately evict overflow if shrinking. */
31
+ setMaxEntries(n: number): void;
32
+ }
33
+ //# sourceMappingURL=LruCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LruCache.d.ts","sourceRoot":"","sources":["../../src/tasks/LruCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;CAC3C;AAED,qBAAa,QAAQ,CAAC,CAAC;IACrB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAwB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkC;IAC3D,OAAO,CAAC,SAAS,CAAK;gBAEV,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC;IAKpC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAS/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAqBhC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,2DAA2D;IAC3D,WAAW,IAAI,MAAM,EAAE;IAIvB,2DAA2D;IAC3D,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;CAa/B"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * LruCache — bounded LRU for TaskFlowRegistry.
3
+ *
4
+ * Spec: docs/specs/OPENCLAW-IMPORT-TASKFLOW-SPEC.md § Phase 5 line 652
5
+ * ("LRU cache eviction tuning").
6
+ *
7
+ * Implementation: leans on JavaScript Map's insertion-order iteration. On `get`
8
+ * we delete-then-set to refresh recency. On `set` overflow we evict the oldest
9
+ * key (Map iterator first). Eviction count is exposed for metric emission.
10
+ */
11
+ export class LruCache {
12
+ maxEntries;
13
+ map = new Map();
14
+ onEvict;
15
+ evictions = 0;
16
+ constructor(opts) {
17
+ this.maxEntries = Math.max(0, opts.maxEntries | 0);
18
+ this.onEvict = opts.onEvict;
19
+ }
20
+ get(key) {
21
+ const v = this.map.get(key);
22
+ if (v === undefined)
23
+ return undefined;
24
+ // refresh recency: re-insert at tail.
25
+ this.map.delete(key);
26
+ this.map.set(key, v);
27
+ return v;
28
+ }
29
+ set(key, value) {
30
+ if (this.maxEntries <= 0) {
31
+ // disabled — no caching.
32
+ return;
33
+ }
34
+ if (this.map.has(key)) {
35
+ this.map.delete(key);
36
+ }
37
+ this.map.set(key, value);
38
+ while (this.map.size > this.maxEntries) {
39
+ const firstKey = this.map.keys().next().value;
40
+ if (!firstKey)
41
+ break;
42
+ const v = this.map.get(firstKey);
43
+ this.map.delete(firstKey);
44
+ this.evictions++;
45
+ if (this.onEvict) {
46
+ try {
47
+ this.onEvict(firstKey, v);
48
+ }
49
+ catch { /* swallow */ }
50
+ }
51
+ }
52
+ }
53
+ delete(key) {
54
+ return this.map.delete(key);
55
+ }
56
+ has(key) {
57
+ return this.map.has(key);
58
+ }
59
+ get size() {
60
+ return this.map.size;
61
+ }
62
+ get evictionCount() {
63
+ return this.evictions;
64
+ }
65
+ /** Test-only: iterate keys in LRU order (oldest first). */
66
+ keysInOrder() {
67
+ return Array.from(this.map.keys());
68
+ }
69
+ /** Resize cap; immediately evict overflow if shrinking. */
70
+ setMaxEntries(n) {
71
+ this.maxEntries = Math.max(0, n | 0);
72
+ while (this.map.size > this.maxEntries) {
73
+ const firstKey = this.map.keys().next().value;
74
+ if (!firstKey)
75
+ break;
76
+ const v = this.map.get(firstKey);
77
+ this.map.delete(firstKey);
78
+ this.evictions++;
79
+ if (this.onEvict) {
80
+ try {
81
+ this.onEvict(firstKey, v);
82
+ }
83
+ catch { /* swallow */ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ //# sourceMappingURL=LruCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LruCache.js","sourceRoot":"","sources":["../../src/tasks/LruCache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * RateLimiter — sliding-window rate limiter for TaskFlow Phase 5.
3
+ *
4
+ * Spec: docs/specs/OPENCLAW-IMPORT-TASKFLOW-SPEC.md § Threat Model lines 679, 685.
5
+ *
6
+ * Design notes:
7
+ * - Per-key sliding window with monotonic clock injection (defaults to Date.now).
8
+ * - Buckets are pruned lazily on each `tryAcquire` to avoid an unref'd timer
9
+ * purely for cleanup. A bucket whose window is fully in the past is dropped
10
+ * on its next visit, and an O(N) sweep prunes stale entries every
11
+ * PRUNE_EVERY_N acquisitions to bound memory under fan-out scenarios.
12
+ * - `Infinity` limit means "disabled" (always allow, no bookkeeping).
13
+ * - Returns retryAfterMs as the time until the oldest in-window timestamp ages
14
+ * out. This is the spec-required retry hint surfaced to HTTP 429.
15
+ *
16
+ * Threat-model coverage:
17
+ * - Sliding window (not fixed window) — bursts at second boundaries cannot
18
+ * double the effective rate. Clock skew on a single-writer server is bounded
19
+ * by `now()` monotonicity; the limiter does not rely on wall-clock.
20
+ * - Per-key isolation prevents one noisy controller from starving others.
21
+ */
22
+ export interface RateLimiterOptions {
23
+ /** Window length in milliseconds. */
24
+ windowMs: number;
25
+ /** Max events permitted within a single window. Use Infinity to disable. */
26
+ limit: number;
27
+ /** Injected clock for tests; defaults to Date.now. */
28
+ now?: () => number;
29
+ }
30
+ export interface AcquireResult {
31
+ ok: boolean;
32
+ /** ms until the oldest in-window timestamp ages out (only set when !ok). */
33
+ retryAfterMs?: number;
34
+ /** current count in window after this call (for tests/metrics). */
35
+ currentCount?: number;
36
+ }
37
+ export declare class RateLimiter {
38
+ private readonly windowMs;
39
+ private readonly limit;
40
+ private readonly now;
41
+ private readonly buckets;
42
+ private acquireCount;
43
+ constructor(opts: RateLimiterOptions);
44
+ /**
45
+ * Attempt to acquire one slot for `key`. Returns ok=false with retryAfterMs
46
+ * when the per-key limit is exceeded.
47
+ *
48
+ * Note: this method is reentrancy-safe within a single-threaded event loop
49
+ * (Node.js) but is NOT designed for shared-memory concurrent callers. The
50
+ * TaskFlow server is a single-writer process by design.
51
+ */
52
+ tryAcquire(key: string): AcquireResult;
53
+ /** Forget a key entirely. Used when an entity is deleted (e.g. flow goes terminal). */
54
+ forget(key: string): void;
55
+ /** Test/debug — current bucket size for key. */
56
+ countFor(key: string): number;
57
+ /** Total number of keys currently tracked. */
58
+ size(): number;
59
+ private maybePrune;
60
+ }
61
+ //# sourceMappingURL=RateLimiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../src/tasks/RateLimiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,YAAY,CAAK;gBAEb,IAAI,EAAE,kBAAkB;IAMpC;;;;;;;OAOG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa;IA+BtC,uFAAuF;IACvF,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,gDAAgD;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAU7B,8CAA8C;IAC9C,IAAI,IAAI,MAAM;IAId,OAAO,CAAC,UAAU;CAUnB"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * RateLimiter — sliding-window rate limiter for TaskFlow Phase 5.
3
+ *
4
+ * Spec: docs/specs/OPENCLAW-IMPORT-TASKFLOW-SPEC.md § Threat Model lines 679, 685.
5
+ *
6
+ * Design notes:
7
+ * - Per-key sliding window with monotonic clock injection (defaults to Date.now).
8
+ * - Buckets are pruned lazily on each `tryAcquire` to avoid an unref'd timer
9
+ * purely for cleanup. A bucket whose window is fully in the past is dropped
10
+ * on its next visit, and an O(N) sweep prunes stale entries every
11
+ * PRUNE_EVERY_N acquisitions to bound memory under fan-out scenarios.
12
+ * - `Infinity` limit means "disabled" (always allow, no bookkeeping).
13
+ * - Returns retryAfterMs as the time until the oldest in-window timestamp ages
14
+ * out. This is the spec-required retry hint surfaced to HTTP 429.
15
+ *
16
+ * Threat-model coverage:
17
+ * - Sliding window (not fixed window) — bursts at second boundaries cannot
18
+ * double the effective rate. Clock skew on a single-writer server is bounded
19
+ * by `now()` monotonicity; the limiter does not rely on wall-clock.
20
+ * - Per-key isolation prevents one noisy controller from starving others.
21
+ */
22
+ const PRUNE_EVERY_N = 256;
23
+ const MAX_BUCKETS = 50_000;
24
+ export class RateLimiter {
25
+ windowMs;
26
+ limit;
27
+ now;
28
+ buckets = new Map();
29
+ acquireCount = 0;
30
+ constructor(opts) {
31
+ this.windowMs = opts.windowMs;
32
+ this.limit = opts.limit;
33
+ this.now = opts.now ?? (() => Date.now());
34
+ }
35
+ /**
36
+ * Attempt to acquire one slot for `key`. Returns ok=false with retryAfterMs
37
+ * when the per-key limit is exceeded.
38
+ *
39
+ * Note: this method is reentrancy-safe within a single-threaded event loop
40
+ * (Node.js) but is NOT designed for shared-memory concurrent callers. The
41
+ * TaskFlow server is a single-writer process by design.
42
+ */
43
+ tryAcquire(key) {
44
+ if (!isFinite(this.limit) || this.limit <= 0) {
45
+ // limit=0 also degenerate; treat as disabled for safety. The route layer
46
+ // is the place to reject if a sane minimum is required.
47
+ if (this.limit === 0) {
48
+ return { ok: false, retryAfterMs: this.windowMs, currentCount: 0 };
49
+ }
50
+ return { ok: true, currentCount: 0 };
51
+ }
52
+ const ts = this.now();
53
+ const windowStart = ts - this.windowMs;
54
+ let bucket = this.buckets.get(key);
55
+ if (!bucket) {
56
+ bucket = [];
57
+ this.buckets.set(key, bucket);
58
+ }
59
+ // Drop in-place from the head while expired. Sliding window.
60
+ while (bucket.length > 0 && bucket[0] <= windowStart) {
61
+ bucket.shift();
62
+ }
63
+ if (bucket.length >= this.limit) {
64
+ const oldest = bucket[0];
65
+ const retryAfterMs = Math.max(0, oldest + this.windowMs - ts);
66
+ this.maybePrune(ts);
67
+ return { ok: false, retryAfterMs, currentCount: bucket.length };
68
+ }
69
+ bucket.push(ts);
70
+ this.maybePrune(ts);
71
+ return { ok: true, currentCount: bucket.length };
72
+ }
73
+ /** Forget a key entirely. Used when an entity is deleted (e.g. flow goes terminal). */
74
+ forget(key) {
75
+ this.buckets.delete(key);
76
+ }
77
+ /** Test/debug — current bucket size for key. */
78
+ countFor(key) {
79
+ const b = this.buckets.get(key);
80
+ if (!b)
81
+ return 0;
82
+ const ts = this.now();
83
+ const windowStart = ts - this.windowMs;
84
+ let n = 0;
85
+ for (const x of b)
86
+ if (x > windowStart)
87
+ n++;
88
+ return n;
89
+ }
90
+ /** Total number of keys currently tracked. */
91
+ size() {
92
+ return this.buckets.size;
93
+ }
94
+ maybePrune(ts) {
95
+ this.acquireCount++;
96
+ if (this.acquireCount < PRUNE_EVERY_N && this.buckets.size < MAX_BUCKETS)
97
+ return;
98
+ this.acquireCount = 0;
99
+ const windowStart = ts - this.windowMs;
100
+ for (const [k, b] of this.buckets) {
101
+ while (b.length > 0 && b[0] <= windowStart)
102
+ b.shift();
103
+ if (b.length === 0)
104
+ this.buckets.delete(k);
105
+ }
106
+ }
107
+ }
108
+ //# sourceMappingURL=RateLimiter.js.map