opencode-swarm 7.146.2 → 7.146.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/council-prompts.d.ts +3 -3
- package/dist/cli/{config-doctor-vstrprja.js → config-doctor-yd866pd5.js} +2 -2
- package/dist/cli/{core-3wzpwe5z.js → core-nc9a9g5m.js} +1 -1
- package/dist/cli/{curation-policy-x19e8bqc.js → curation-policy-9vv7v5rf.js} +6 -6
- package/dist/cli/{curator-cvy1s7wd.js → curator-acp80hpc.js} +27 -27
- package/dist/cli/{curator-llm-factory-6bra0bfd.js → curator-llm-factory-ewffa8r1.js} +27 -27
- package/dist/cli/{evidence-summary-service-yhap2xph.js → evidence-summary-service-8gw74j8g.js} +11 -11
- package/dist/cli/{gate-evidence-vxbyq5sb.js → gate-evidence-jj3zt2x1.js} +6 -6
- package/dist/cli/{guardrail-explain-h8cnwa02.js → guardrail-explain-50cp8k3r.js} +28 -28
- package/dist/cli/{guardrail-log-syb7tz2w.js → guardrail-log-jazq10gt.js} +3 -3
- package/dist/cli/{hive-promoter-3tex870c.js → hive-promoter-qhrngsvb.js} +27 -27
- package/dist/cli/{index-z5ppkn8y.js → index-0a4s2803.js} +6 -6
- package/dist/cli/{index-qbpxrw95.js → index-1kq3x5q6.js} +2 -2
- package/dist/cli/{index-tj861zhd.js → index-262kyrk3.js} +4 -4
- package/dist/cli/{index-c9jr4j2q.js → index-3377n9qp.js} +1 -1
- package/dist/cli/{index-p0m03xa6.js → index-3gagma8p.js} +4 -4
- package/dist/cli/{index-5e5a1y2r.js → index-3p4wgm0d.js} +1 -1
- package/dist/cli/{index-e1hjd15c.js → index-463msy1e.js} +1 -1
- package/dist/cli/{index-4vwn66wk.js → index-4zevsvaa.js} +3 -3
- package/dist/cli/{index-bghwrr59.js → index-6q35swn2.js} +1 -1
- package/dist/cli/{index-z9ed68jt.js → index-7ph5pz77.js} +17 -4
- package/dist/cli/{index-42nq97rb.js → index-923phxsp.js} +6 -6
- package/dist/cli/{index-cr90rcg3.js → index-a7cnyp5e.js} +7 -7
- package/dist/cli/{index-cx67qa9g.js → index-bxk21f2w.js} +1 -1
- package/dist/cli/{index-97psh79d.js → index-bxs4wkjf.js} +1 -1
- package/dist/cli/{index-y3hmyvt6.js → index-bydybrke.js} +2 -2
- package/dist/cli/{index-n4k0pt5f.js → index-cgcs823z.js} +4 -4
- package/dist/cli/{index-jv9xs6mb.js → index-eybamsqg.js} +2 -2
- package/dist/cli/{index-01ybcsa5.js → index-fypq68yn.js} +2 -2
- package/dist/cli/{index-yxhcy5cm.js → index-gayh8tmh.js} +55 -1
- package/dist/cli/{index-a1febb2k.js → index-gwt7ceh7.js} +2 -2
- package/dist/cli/{index-w0cj7zyd.js → index-j2ckvbx7.js} +1 -1
- package/dist/cli/{index-rfph0nhf.js → index-n96nt4gb.js} +1235 -589
- package/dist/cli/{index-zkaz3fhw.js → index-pc4423w8.js} +2 -2
- package/dist/cli/{index-vmfaafte.js → index-pgsvn005.js} +5 -5
- package/dist/cli/{index-qyvjp0bq.js → index-q81s0m8k.js} +66 -37
- package/dist/cli/{index-w5r84rd1.js → index-qjgg9qzs.js} +1 -1
- package/dist/cli/{index-r46q6996.js → index-qqawmxe7.js} +29 -29
- package/dist/cli/{index-vj9bba5k.js → index-v2eevdbj.js} +4 -4
- package/dist/cli/{index-ycgm9pzg.js → index-xz9w7sa9.js} +3 -2
- package/dist/cli/{index-f3x2gsby.js → index-ypwwchs0.js} +1 -1
- package/dist/cli/{index-46cypnz3.js → index-yr1aabxa.js} +4 -4
- package/dist/cli/index.js +27 -27
- package/dist/cli/{knowledge-escalator-mn4sx4qd.js → knowledge-escalator-7zpg79dx.js} +11 -11
- package/dist/cli/{knowledge-events-x975ngy4.js → knowledge-events-8abttcnt.js} +9 -9
- package/dist/cli/{knowledge-link-8pr02pza.js → knowledge-link-mgg482z6.js} +5 -5
- package/dist/cli/{knowledge-store-33p762ey.js → knowledge-store-a5mzf986.js} +6 -6
- package/dist/cli/{knowledge-validator-q5jc0scs.js → knowledge-validator-fn2myc73.js} +8 -8
- package/dist/cli/{pending-delegations-kkrczqv7.js → pending-delegations-591vme3d.js} +3 -3
- package/dist/cli/{pr-subscriptions-2xn238xt.js → pr-subscriptions-js5v2xr6.js} +3 -3
- package/dist/cli/{runner-e7z9cf5y.js → runner-rcnpm510.js} +6 -6
- package/dist/cli/{scan-cursor-p7t5mb4x.js → scan-cursor-4jv70zwb.js} +7 -7
- package/dist/cli/{schema-t8bmshhj.js → schema-jhz9hme9.js} +1 -1
- package/dist/cli/{scope-persistence-2kyrgsca.js → scope-persistence-ykswenxj.js} +7 -7
- package/dist/cli/{skill-generator-fwvmcv8m.js → skill-generator-k5pxb3w2.js} +13 -13
- package/dist/cli/{telemetry-t109dhv7.js → telemetry-xzy0qeap.js} +1 -1
- package/dist/cli/{worktree-collision-ownership-2jtg2my1.js → worktree-collision-ownership-vt0jzmdt.js} +3 -3
- package/dist/cli/{worktree-isolation-87j70zg1.js → worktree-isolation-443q0pah.js} +27 -27
- package/dist/commands/close.d.ts +1 -0
- package/dist/commands/context-map-stats.d.ts +5 -0
- package/dist/config/schema.d.ts +16 -0
- package/dist/context-map/telemetry.d.ts +168 -26
- package/dist/council/council-evidence-writer.d.ts +2 -1
- package/dist/council/council-freshness.d.ts +49 -0
- package/dist/council/council-review-identity.d.ts +133 -0
- package/dist/council/council-round-state.d.ts +27 -3
- package/dist/council/disagreement-detector.d.ts +12 -2
- package/dist/council/general-council-types.d.ts +32 -0
- package/dist/council/types.d.ts +43 -7
- package/dist/index.js +160 -138
- package/dist/observability/catalog.d.ts +7 -5
- package/dist/telemetry.d.ts +22 -1
- package/dist/tools/phase-complete/gates/types.d.ts +7 -0
- package/dist/tools/write-final-council-evidence.d.ts +1 -7
- package/package.json +1 -1
|
@@ -2,8 +2,32 @@
|
|
|
2
2
|
* Telemetry recording and persistence for the Context Capsule feature.
|
|
3
3
|
*
|
|
4
4
|
* Records capsule telemetry data (token estimates, cache hit/miss counts,
|
|
5
|
-
* stale summary counts) per delegation.
|
|
6
|
-
*
|
|
5
|
+
* stale summary counts) per delegation.
|
|
6
|
+
*
|
|
7
|
+
* ISSUE #2037 (Observability PR 09/23): this store was previously an unbounded
|
|
8
|
+
* append-only JSONL (`.swarm/context-telemetry.jsonl`) full-read on every
|
|
9
|
+
* summary. It is now a BOUNDED single-file store:
|
|
10
|
+
*
|
|
11
|
+
* Line 1: `ctx-telemetry-manifest` header carrying a size-bounded FOLDED
|
|
12
|
+
* aggregate (records compacted away) + health counters.
|
|
13
|
+
* Line 2+: raw `TelemetryEntry` JSONL — the RECENT retained window, bounded
|
|
14
|
+
* to CONTEXT_TELEMETRY_LIMITS.activeMaxBytes / activeMaxEntries.
|
|
15
|
+
*
|
|
16
|
+
* Lifetime totals = folded aggregate (header) + retained window (file). Because
|
|
17
|
+
* every structural mutation (first write, compaction, cutover migration,
|
|
18
|
+
* finalize) is a synchronous, atomic single-file rewrite (write tmp + atomic
|
|
19
|
+
* rename), a reader sees either the old complete file or the new complete file
|
|
20
|
+
* — there is no partial-apply state, so double-count and loss cannot arise from
|
|
21
|
+
* a crash mid-mutation.
|
|
22
|
+
*
|
|
23
|
+
* Concurrency: in production the ONLY writer is the plugin's
|
|
24
|
+
* `context-capsule-inject` hook, and every mutating function here is
|
|
25
|
+
* synchronous, so within a single plugin process there is no interleaving at
|
|
26
|
+
* all. A second plugin instance on the same project root is guarded by an
|
|
27
|
+
* exclusive `.swarm/context-telemetry.lock` (`wx` create, stale-broken)
|
|
28
|
+
* acquired for the compaction/cutover read→rewrite window. Cross-process
|
|
29
|
+
* CONCURRENT WRITERS to this store are documented as unsupported (it is a
|
|
30
|
+
* single-writer store). All disk failures are fail-open.
|
|
7
31
|
*
|
|
8
32
|
* All functions are synchronous for simplicity and reliability. The module
|
|
9
33
|
* uses the `_internals` DI seam pattern so tests can override filesystem
|
|
@@ -15,9 +39,32 @@
|
|
|
15
39
|
*
|
|
16
40
|
* No `bun:` imports — this module is Node-ESM-loadable (Invariant 2).
|
|
17
41
|
*
|
|
18
|
-
* See issue #1104, FR-007.
|
|
42
|
+
* See issue #1104, FR-007, and issue #2037.
|
|
19
43
|
*/
|
|
20
44
|
import * as fs from 'node:fs';
|
|
45
|
+
export interface ContextTelemetryLimits {
|
|
46
|
+
/** Hard ceiling on the retained raw window (bytes). */
|
|
47
|
+
activeMaxBytes: number;
|
|
48
|
+
/** Hard ceiling on the retained raw window (entries). */
|
|
49
|
+
activeMaxEntries: number;
|
|
50
|
+
/** Raw retention age: records older than this are pruned from the raw
|
|
51
|
+
* window (the folded aggregate is lifetime and unaffected by pruning). */
|
|
52
|
+
ageMaxMs: number;
|
|
53
|
+
/** Bounded work per legacy-migration / compaction pass (bytes folded). */
|
|
54
|
+
compactMaxBytes: number;
|
|
55
|
+
/** Hard documented read bound: getTelemetrySummary never reads more than
|
|
56
|
+
* this, even when legacy history is arbitrarily large. Must satisfy
|
|
57
|
+
* readMaxBytes >= activeMaxBytes + headerMaxBytes + 1 KiB slack. */
|
|
58
|
+
readMaxBytes: number;
|
|
59
|
+
/** Writes between throttled maintenance checks (mirrors core telemetry's
|
|
60
|
+
* ROTATION_CHECK_INTERVAL). */
|
|
61
|
+
checkInterval: number;
|
|
62
|
+
/** Disk-pressure/failure warning cooldown. */
|
|
63
|
+
warnCooldownMs: number;
|
|
64
|
+
/** Upper bound for a serialized manifest header (single line). */
|
|
65
|
+
headerMaxBytes: number;
|
|
66
|
+
}
|
|
67
|
+
export declare const CONTEXT_TELEMETRY_LIMITS: ContextTelemetryLimits;
|
|
21
68
|
/**
|
|
22
69
|
* A single telemetry record for one capsule delegation.
|
|
23
70
|
* Written as one JSON line to `.swarm/context-telemetry.jsonl`.
|
|
@@ -46,12 +93,23 @@ export interface TelemetryEntry {
|
|
|
46
93
|
/** Whether capsule generation succeeded */
|
|
47
94
|
success: boolean;
|
|
48
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Coverage disclosure for a computed summary (issue #2037). `'partial-unmigrated'`
|
|
98
|
+
* is produced whenever the on-disk store exceeds the read bound (a pre-cutover
|
|
99
|
+
* header-less file, or a header'd store mid-migration with an unmigrated legacy
|
|
100
|
+
* tail) — in both cases the totals are INCOMPLETE. `'truncated'` is reserved for
|
|
101
|
+
* the physical "read exceeded the bound" state but is never surfaced to callers:
|
|
102
|
+
* a truncated header'd read is re-labeled `'partial-unmigrated'`.
|
|
103
|
+
*/
|
|
104
|
+
export type TelemetryCoverage = 'complete' | 'partial-unmigrated' | 'truncated' | 'empty';
|
|
49
105
|
/**
|
|
50
106
|
* Aggregate statistics computed from all telemetry entries.
|
|
51
|
-
* Returned by {@link getTelemetrySummary}.
|
|
107
|
+
* Returned by {@link getTelemetrySummary}. The eight leading fields preserve the
|
|
108
|
+
* pre-issue-#2037 public surface (semantically compatible); the trailing fields
|
|
109
|
+
* are additive issue-#2037 disclosure (partial coverage / drops / retention).
|
|
52
110
|
*/
|
|
53
111
|
export interface TelemetrySummary {
|
|
54
|
-
/** Total number of capsule delegations recorded */
|
|
112
|
+
/** Total number of capsule delegations recorded (lifetime) */
|
|
55
113
|
total_delegations: number;
|
|
56
114
|
/** Sum of all cache hits across entries */
|
|
57
115
|
total_cache_hits: number;
|
|
@@ -67,53 +125,137 @@ export interface TelemetrySummary {
|
|
|
67
125
|
total_skipped_reads: number;
|
|
68
126
|
/** Percentage of successful capsule generations (0–100) */
|
|
69
127
|
success_rate: number;
|
|
128
|
+
/** Coverage disclosure (issue #2037). 'complete' = full lifetime history
|
|
129
|
+
* accounted; 'partial-unmigrated' = the on-disk store exceeds the read bound
|
|
130
|
+
* (legacy history not yet fully migrated) so only the accounted part is
|
|
131
|
+
* reflected — never presented as a complete-looking number. */
|
|
132
|
+
coverage: TelemetryCoverage;
|
|
133
|
+
/** Newest - oldest timestamp across all accounted records (ms, or null when
|
|
134
|
+
* there is no accounted history). */
|
|
135
|
+
tracked_period_ms: number | null;
|
|
136
|
+
/** Records currently retained in the on-disk raw window. */
|
|
137
|
+
retained_entries: number;
|
|
138
|
+
/** Records folded into the durable aggregate. */
|
|
139
|
+
folded_entries: number;
|
|
140
|
+
/** Corrupt/partial lines encountered (maintenance + current window). */
|
|
141
|
+
corrupt_entries: number;
|
|
142
|
+
/** Records folded into the aggregate due to age retention — included in
|
|
143
|
+
* lifetime totals (not an additive counter on top of them), just not
|
|
144
|
+
* retained in the raw window. */
|
|
145
|
+
dropped_entries: number;
|
|
146
|
+
/** Approximate on-disk bytes of the store. */
|
|
147
|
+
on_disk_bytes: number;
|
|
70
148
|
}
|
|
71
149
|
/**
|
|
72
150
|
* Test-only dependency-injection seam. Production code calls through this
|
|
73
151
|
* object so tests can replace the underlying implementations without
|
|
74
152
|
* `mock.module` (which leaks across files in Bun's shared test-runner process).
|
|
75
153
|
* Mutating this local object is file-scoped and trivially restorable
|
|
76
|
-
* via `afterEach`.
|
|
154
|
+
* via `afterEach`. `limits` is also overridable so tests can exercise small
|
|
155
|
+
* budgets.
|
|
77
156
|
*/
|
|
78
157
|
export declare const _internals: {
|
|
79
158
|
readonly appendFileSync: typeof fs.appendFileSync;
|
|
80
159
|
readonly readFileSync: typeof fs.readFileSync;
|
|
81
160
|
readonly existsSync: typeof fs.existsSync;
|
|
82
161
|
readonly mkdirSync: typeof fs.mkdirSync;
|
|
162
|
+
readonly statSync: fs.StatSyncFn;
|
|
163
|
+
readonly renameSync: typeof fs.renameSync;
|
|
164
|
+
readonly writeFileSync: typeof fs.writeFileSync;
|
|
165
|
+
readonly unlinkSync: typeof fs.unlinkSync;
|
|
166
|
+
readonly openSync: typeof fs.openSync;
|
|
167
|
+
readonly closeSync: typeof fs.closeSync;
|
|
168
|
+
readonly readSync: typeof fs.readSync;
|
|
169
|
+
readonly limits: ContextTelemetryLimits;
|
|
170
|
+
readonly emitHealth: typeof emitContextTelemetryHealth;
|
|
171
|
+
readonly withStoreLock: typeof withStoreLock;
|
|
83
172
|
};
|
|
173
|
+
/** Size-bounded folded aggregate persisted in the manifest header. FOLDED-ONLY:
|
|
174
|
+
* records compacted/cut away from the raw window. Retained records are NOT in
|
|
175
|
+
* here. Lifetime total = folded + retained. */
|
|
176
|
+
export interface FoldedAggregate {
|
|
177
|
+
delegations: number;
|
|
178
|
+
successCount: number;
|
|
179
|
+
cacheHits: number;
|
|
180
|
+
cacheMisses: number;
|
|
181
|
+
staleEntries: number;
|
|
182
|
+
tokenSum: number;
|
|
183
|
+
recommendedReads: number;
|
|
184
|
+
skippedReads: number;
|
|
185
|
+
corrupt: number;
|
|
186
|
+
dropped: number;
|
|
187
|
+
oldestTimestamp: string | null;
|
|
188
|
+
newestTimestamp: string | null;
|
|
189
|
+
}
|
|
190
|
+
/** The manifest header — line 1 of `.swarm/context-telemetry.jsonl`. */
|
|
191
|
+
export interface TelemetryManifest {
|
|
192
|
+
v: 2;
|
|
193
|
+
type: 'ctx-telemetry-manifest';
|
|
194
|
+
schemaVersion: number;
|
|
195
|
+
folded: FoldedAggregate;
|
|
196
|
+
updatedAt: string;
|
|
197
|
+
}
|
|
198
|
+
declare function withStoreLock<T>(directory: string, fn: () => T): T | null;
|
|
84
199
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* Returns `true` on success, `false` on any error. Never throws.
|
|
91
|
-
*
|
|
92
|
-
* @param entry - The telemetry record to append
|
|
93
|
-
* @param directory - Project root directory (must contain `.swarm/`)
|
|
200
|
+
* Test seam (AGENTS.md invariant 7): resets the module-scoped maintenance
|
|
201
|
+
* counters so an unswept run in Bun's shared test-runner process cannot shift
|
|
202
|
+
* a later test's first maintenance pass. Restore is one call, always in
|
|
203
|
+
* `afterEach`.
|
|
94
204
|
*/
|
|
205
|
+
export declare function _resetMaintenanceCounters(): void;
|
|
95
206
|
export declare function recordTelemetry(entry: TelemetryEntry, directory: string): boolean;
|
|
96
207
|
/**
|
|
97
|
-
* Read
|
|
208
|
+
* Read the telemetry entries currently retained in `.swarm/context-telemetry.jsonl`.
|
|
98
209
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
210
|
+
* ISSUE #2037: this now returns ONLY the bounded recent window (the retained
|
|
211
|
+
* records after the manifest header) — NOT the entire lifetime history. Full
|
|
212
|
+
* lifetime history is represented by {@link getTelemetrySummary}, which folds
|
|
213
|
+
* the durable aggregate + retained window. This bounded read is the intended
|
|
214
|
+
* contract change: no whole-history file read on any command/runtime path.
|
|
215
|
+
*
|
|
216
|
+
* Malformed/partial lines are skipped. Returns an empty array if the file does
|
|
217
|
+
* not exist or cannot be parsed. Never throws.
|
|
102
218
|
*
|
|
103
219
|
* @param directory - Project root directory
|
|
104
|
-
* @returns Array of parsed telemetry entries
|
|
220
|
+
* @returns Array of parsed telemetry entries in the retained window
|
|
105
221
|
*/
|
|
106
222
|
export declare function readTelemetry(directory: string): TelemetryEntry[];
|
|
107
223
|
/**
|
|
108
|
-
* Compute aggregate statistics from all telemetry entries.
|
|
109
|
-
*
|
|
110
|
-
* Reads all entries via {@link readTelemetry}, then computes totals
|
|
111
|
-
* and averages across the dataset.
|
|
224
|
+
* Compute aggregate statistics from all telemetry entries (lifetime).
|
|
112
225
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
226
|
+
* Lifetime totals = folded aggregate (durable header) + current retained
|
|
227
|
+
* window. This is a PURE READ: it never writes. Reads are always bounded to
|
|
228
|
+
* {@link ContextTelemetryLimits.readMaxBytes}; if a legacy (pre-issue-#2037)
|
|
229
|
+
* header-less file is larger than the read bound, only part of it is folded
|
|
230
|
+
* in-memory and {@link TelemetrySummary.coverage} is disclosed as
|
|
231
|
+
* 'partial-unmigrated' rather than silently presenting a complete-looking
|
|
232
|
+
* number. Returns a zeroed summary if no entries exist or on error. Never
|
|
233
|
+
* throws.
|
|
115
234
|
*
|
|
116
235
|
* @param directory - Project root directory
|
|
117
236
|
* @returns Aggregate telemetry summary
|
|
118
237
|
*/
|
|
119
238
|
export declare function getTelemetrySummary(directory: string): TelemetrySummary;
|
|
239
|
+
/**
|
|
240
|
+
* Fold the entire remaining retained window into the aggregate and atomically
|
|
241
|
+
* publish a defined, validated cut. Called by `/swarm close` before archiving
|
|
242
|
+
* (issue #2037: "close archives a defined, validated cut and leaves active
|
|
243
|
+
* state usable"). Also migrates any header-less legacy file. Fail-open: never
|
|
244
|
+
* throws to the close pipeline.
|
|
245
|
+
*
|
|
246
|
+
* @param directory - Project root directory
|
|
247
|
+
*/
|
|
248
|
+
export declare function finalizeContextTelemetry(directory: string): void;
|
|
249
|
+
declare function emitContextTelemetryHealth(_directory: string, payload: {
|
|
250
|
+
trigger: 'compaction' | 'close';
|
|
251
|
+
accepted: number;
|
|
252
|
+
compacted: number;
|
|
253
|
+
retained: number;
|
|
254
|
+
dropped: number;
|
|
255
|
+
corrupt: number;
|
|
256
|
+
oldest: string | null;
|
|
257
|
+
newest: string | null;
|
|
258
|
+
bytes: number;
|
|
259
|
+
limitBytes: number;
|
|
260
|
+
}): void;
|
|
261
|
+
export {};
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* any filesystem op.
|
|
16
16
|
*/
|
|
17
17
|
import { withTaskEvidenceLock } from '../evidence/task-file.js';
|
|
18
|
+
import type { CouncilReviewIdentity } from './council-review-identity';
|
|
18
19
|
import type { CouncilSynthesis } from './types';
|
|
19
20
|
/**
|
|
20
21
|
* Dependency-injection seam for testing. Tests can temporarily replace
|
|
@@ -24,5 +25,5 @@ import type { CouncilSynthesis } from './types';
|
|
|
24
25
|
export declare const _internals: {
|
|
25
26
|
withTaskEvidenceLock: typeof withTaskEvidenceLock;
|
|
26
27
|
};
|
|
27
|
-
export declare function writeCouncilEvidence(workingDir: string, synthesis: CouncilSynthesis, attemptId?: string, expectedGeneration?: number): Promise<void>;
|
|
28
|
+
export declare function writeCouncilEvidence(workingDir: string, synthesis: CouncilSynthesis, attemptId?: string, expectedGeneration?: number, identity?: CouncilReviewIdentity): Promise<void>;
|
|
28
29
|
export declare function hasCouncilEvidenceAttempt(workingDir: string, taskId: string, attemptId: string, roundNumber: number): boolean;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized council evidence freshness evaluation (issue #2102 contract D).
|
|
3
|
+
*
|
|
4
|
+
* ONE evaluator + ONE bounded config field (`council.freshnessMaxAgeHours`,
|
|
5
|
+
* default 24 hours to preserve prior behavior) govern the phase council,
|
|
6
|
+
* architecture supervisor, and final council gates. Callers pass a single
|
|
7
|
+
* captured `nowMs` (one clock per aggregate phase preflight) so gates cannot
|
|
8
|
+
* disagree across an age boundary mid-run.
|
|
9
|
+
*
|
|
10
|
+
* Wall-clock age is never the sole identity check: generation binding is the
|
|
11
|
+
* identity-digest comparison in the gates. Freshness only decides whether
|
|
12
|
+
* otherwise-correctly-bound evidence is recent enough, not in the future,
|
|
13
|
+
* and not older than a later required input (e.g. the phase retrospective).
|
|
14
|
+
*/
|
|
15
|
+
import type { CouncilConfig } from './types';
|
|
16
|
+
export declare const DEFAULT_FRESHNESS_MAX_AGE_MS: number;
|
|
17
|
+
export type CouncilFreshnessFailureReason = 'invalid_timestamp' | 'future_timestamp' | 'stale_evidence' | 'predates_required_input' | 'invalid_required_input';
|
|
18
|
+
export interface CouncilFreshnessOutcome {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
reason?: CouncilFreshnessFailureReason;
|
|
21
|
+
/** Human-facing explanation (gate message body). */
|
|
22
|
+
message: string;
|
|
23
|
+
/** Concrete recovery action. */
|
|
24
|
+
recovery: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveCouncilFreshnessMaxAgeMs(config?: CouncilConfig): number;
|
|
27
|
+
/**
|
|
28
|
+
* Evaluate one evidence timestamp against the shared policy.
|
|
29
|
+
*
|
|
30
|
+
* - `timestampMs: null` → invalid_timestamp (missing/unparseable).
|
|
31
|
+
* - `timestampMs > nowMs` → future_timestamp (fail closed).
|
|
32
|
+
* - age > maxAgeMs → stale_evidence.
|
|
33
|
+
* - `mustPostdateMs` set (e.g. latest retrospective timestamp) and
|
|
34
|
+
* `timestampMs < mustPostdateMs` → predates_required_input.
|
|
35
|
+
*/
|
|
36
|
+
export declare function evaluateCouncilFreshness(input: {
|
|
37
|
+
nowMs: number;
|
|
38
|
+
timestampMs: number | null;
|
|
39
|
+
maxAgeMs: number;
|
|
40
|
+
mustPostdateMs?: number | null;
|
|
41
|
+
}): CouncilFreshnessOutcome;
|
|
42
|
+
export declare function parseTimestampMs(value: unknown): number | null;
|
|
43
|
+
/**
|
|
44
|
+
* Latest retrospective-relevant timestamp from an already-loaded retro bundle
|
|
45
|
+
* (created_at/updated_at + this phase's retrospective entries). Shared by the
|
|
46
|
+
* phase-council and final-council gates so both prefer the bundle captured by
|
|
47
|
+
* the aggregate preflight over re-reading the disk (PRR-021(g)).
|
|
48
|
+
*/
|
|
49
|
+
export declare function latestRetroTimestampMsFromBundle(bundle: unknown, phase: number): number | null;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical council review identity (issue #2102 contract A).
|
|
3
|
+
*
|
|
4
|
+
* ONE shared implementation used by the task/phase/final council producers,
|
|
5
|
+
* the #2085 authoritative round store (as the scope/generation key), the
|
|
6
|
+
* evidence writers, and the completion gates. Writers and gates compute the
|
|
7
|
+
* identity from the same plan + config through this module, so the digests
|
|
8
|
+
* match byte-for-byte by construction.
|
|
9
|
+
*
|
|
10
|
+
* Identity components:
|
|
11
|
+
* - `planId` / `planIdentityHash` — collision-resistant raw plan identity
|
|
12
|
+
* (swarm/title), delegated to `plan/utils`.
|
|
13
|
+
* - `reviewHash` — a purpose-built, status-stable hash of every
|
|
14
|
+
* review-relevant plan field. Pure execution progress (statuses, the
|
|
15
|
+
* current-phase pointer, transient blocked reasons, timestamps) is
|
|
16
|
+
* excluded, so normal progress never invalidates a completed review.
|
|
17
|
+
* - `policyDigest` — canonical digest of the council policy that shaped the
|
|
18
|
+
* review (quorum/veto/concerns/maxRounds/freshness). Any policy change
|
|
19
|
+
* opens a new generation and invalidates prior evidence.
|
|
20
|
+
* - `identityDigest` — sha256 over the canonical identity object; this is
|
|
21
|
+
* the round-store scope/generation key and the evidence binding.
|
|
22
|
+
*
|
|
23
|
+
* `computePlanHash` (ledger integrity) and `computePlanStructureHash`
|
|
24
|
+
* (plan-critic execution gate) are intentionally NOT reused or modified:
|
|
25
|
+
* the first is status-sensitive by design, and the second still hashes
|
|
26
|
+
* progress-like fields (`blocked_reason`, `current_phase`) while dropping
|
|
27
|
+
* the review-relevant `fr_refs`. See `src/plan/ledger.ts` doc comments.
|
|
28
|
+
*/
|
|
29
|
+
import type { RuntimePlan } from '../config/plan-schema';
|
|
30
|
+
import type { CouncilAgent, CouncilConfig } from './types';
|
|
31
|
+
/** Schema/cutover version of the council review identity itself. */
|
|
32
|
+
export declare const COUNCIL_REVIEW_IDENTITY_VERSION: 2;
|
|
33
|
+
export type CouncilLevel = 'task' | 'phase' | 'final';
|
|
34
|
+
export type CouncilReviewScope = {
|
|
35
|
+
kind: 'task';
|
|
36
|
+
taskId: string;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'phase';
|
|
39
|
+
phaseNumber: number;
|
|
40
|
+
} | {
|
|
41
|
+
kind: 'final';
|
|
42
|
+
final: true;
|
|
43
|
+
};
|
|
44
|
+
/** Normalized final-council completion policy (issue #2102 contract C). */
|
|
45
|
+
export type FinalCompletionPolicy = {
|
|
46
|
+
mode: 'all_required';
|
|
47
|
+
} | {
|
|
48
|
+
mode: 'quorum';
|
|
49
|
+
minimumMembers: number;
|
|
50
|
+
};
|
|
51
|
+
export interface CouncilReviewIdentity {
|
|
52
|
+
version: number;
|
|
53
|
+
level: CouncilLevel;
|
|
54
|
+
scope: CouncilReviewScope;
|
|
55
|
+
planId: string | null;
|
|
56
|
+
planIdentityHash: string | null;
|
|
57
|
+
reviewHash: string | null;
|
|
58
|
+
policyDigest: string;
|
|
59
|
+
identityDigest: string;
|
|
60
|
+
}
|
|
61
|
+
export declare function isIdentityDigest(value: unknown): value is string;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a submitted council member name to its canonical role (issue
|
|
64
|
+
* #2102 contract C). Accepts the exact canonical role (`critic`) and
|
|
65
|
+
* multi-swarm prefixed names (`local_critic`) via suffix resolution.
|
|
66
|
+
* Returns null for unknown names — they never count toward quorum.
|
|
67
|
+
*
|
|
68
|
+
* Swarm-membership itself cannot be verified for architect-submitted JSON
|
|
69
|
+
* verdicts (there is no per-agent authentication at the tool boundary), so
|
|
70
|
+
* the enforceable cross-swarm defense is canonical-role deduplication: two
|
|
71
|
+
* prefixed names that resolve to the same canonical role (`local_critic` +
|
|
72
|
+
* `mega_critic`) count as ONE member. Writers, gates, and tests must all
|
|
73
|
+
* use this shared resolver so the role policy never drifts.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveCouncilMemberRole(agentName: string): CouncilAgent | null;
|
|
76
|
+
/**
|
|
77
|
+
* Status-stable hash of every review-relevant plan field.
|
|
78
|
+
*
|
|
79
|
+
* INCLUDED (the council judges these): schema_version, title, swarm,
|
|
80
|
+
* execution_profile (execution requirements), migration_status (durable plan
|
|
81
|
+
* property), phase id/name/type/required_agents, task id/phase/size/
|
|
82
|
+
* description/depends/acceptance/files_touched/evidence_path/fr_refs
|
|
83
|
+
* (FR/spec references, #1687).
|
|
84
|
+
*
|
|
85
|
+
* EXCLUDED (pure execution progress — the issue's explicit list):
|
|
86
|
+
* phase.status, task.status, current_phase (progress pointer), blocked_reason
|
|
87
|
+
* (transient), specMtime/specHash (timestamps + spec content, tracked
|
|
88
|
+
* separately by the ledger's spec_updated path per ledger.ts's established
|
|
89
|
+
* rationale).
|
|
90
|
+
*/
|
|
91
|
+
export declare function computeCouncilReviewHash(plan: RuntimePlan): string;
|
|
92
|
+
/**
|
|
93
|
+
* Canonical per-level council policy digest.
|
|
94
|
+
*
|
|
95
|
+
* Every field that shapes what the council required at this level is hashed,
|
|
96
|
+
* so changing the mode, a minimum, the role/quorum knobs, veto/concerns
|
|
97
|
+
* behavior, max rounds, or the freshness window opens a new generation.
|
|
98
|
+
* Inert fields (`parallelTimeoutMs`, `escalateOnMaxRounds`) deliberately do
|
|
99
|
+
* NOT contribute — changing them must not invalidate evidence.
|
|
100
|
+
*/
|
|
101
|
+
export declare function computeCouncilPolicyDigest(level: CouncilLevel, config?: CouncilConfig): string;
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the final-council completion policy from config.
|
|
104
|
+
*
|
|
105
|
+
* Missing/absent config, a missing `finalCompletionPolicy`, or an invalid
|
|
106
|
+
* quorum declaration all fail closed to the strict legacy `all_required`
|
|
107
|
+
* requirement (all five canonical roles, zero absentees). Explicit
|
|
108
|
+
* `quorum` requires a bounded `minimumMembers` of 3..5; only distinct
|
|
109
|
+
* members of the canonical five-role set ever count toward it.
|
|
110
|
+
*/
|
|
111
|
+
export declare function resolveFinalCompletionPolicy(config?: CouncilConfig): FinalCompletionPolicy;
|
|
112
|
+
/**
|
|
113
|
+
* Compute the canonical council review identity. `plan` may be null (no plan
|
|
114
|
+
* on disk): the identity then carries null plan fields, and any consumer
|
|
115
|
+
* holding a plan will fail closed against it — evidence without plan binding
|
|
116
|
+
* can never satisfy a gate that has a plan.
|
|
117
|
+
*/
|
|
118
|
+
export declare function computeCouncilReviewIdentity(input: {
|
|
119
|
+
level: CouncilLevel;
|
|
120
|
+
scope: CouncilReviewScope;
|
|
121
|
+
plan: RuntimePlan | null;
|
|
122
|
+
config?: CouncilConfig;
|
|
123
|
+
}): CouncilReviewIdentity;
|
|
124
|
+
/**
|
|
125
|
+
* The evidence-entry projection of an identity. Writers embed these fields in
|
|
126
|
+
* council evidence; gates recompute the identity and compare byte-for-byte.
|
|
127
|
+
*/
|
|
128
|
+
export declare function councilIdentityEvidenceFields(identity: CouncilReviewIdentity): {
|
|
129
|
+
identity_version: number;
|
|
130
|
+
review_hash: string | null;
|
|
131
|
+
policy_digest: string;
|
|
132
|
+
identity_digest: string;
|
|
133
|
+
};
|
|
@@ -1,29 +1,45 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { withEvidenceLock } from '../evidence/lock.js';
|
|
3
3
|
import { atomicWriteFile } from '../evidence/task-file.js';
|
|
4
|
+
/**
|
|
5
|
+
* Council round scope, keyed by the canonical council review identity
|
|
6
|
+
* (issue #2102 contract B). `identityDigest` binds every round to the exact
|
|
7
|
+
* review-relevant plan content and council policy it was convened under:
|
|
8
|
+
* a status-only progress change keeps the identity (and therefore the
|
|
9
|
+
* accepted round), while any review-relevant plan or policy change opens a
|
|
10
|
+
* fresh authoritative generation under a new token. Legacy v1 files (whose
|
|
11
|
+
* tokens predate identity binding) are never read — they remain on disk,
|
|
12
|
+
* auditable, and are never rewritten as if they carried identity proof.
|
|
13
|
+
*/
|
|
4
14
|
export type CouncilRoundScope = {
|
|
5
15
|
kind: 'task';
|
|
6
16
|
taskId: string;
|
|
17
|
+
identityDigest: string;
|
|
7
18
|
} | {
|
|
8
19
|
kind: 'phase';
|
|
9
20
|
phaseNumber: number;
|
|
21
|
+
identityDigest: string;
|
|
10
22
|
} | {
|
|
11
23
|
kind: 'final';
|
|
12
|
-
|
|
24
|
+
identityDigest: string;
|
|
13
25
|
};
|
|
14
26
|
export type CouncilRoundTransition = 'stay' | 'advance' | 'close';
|
|
15
27
|
type AuditScope = {
|
|
16
28
|
kind: 'task';
|
|
17
29
|
scopeHash: string;
|
|
30
|
+
identityDigest: string;
|
|
18
31
|
} | {
|
|
19
32
|
kind: 'phase';
|
|
20
33
|
phaseNumber: number;
|
|
34
|
+
identityDigest: string;
|
|
21
35
|
} | {
|
|
22
36
|
kind: 'final';
|
|
23
37
|
scopeHash: string;
|
|
38
|
+
identityDigest: string;
|
|
24
39
|
};
|
|
25
40
|
interface StateSnapshot {
|
|
26
|
-
version:
|
|
41
|
+
version: 2;
|
|
42
|
+
identityDigest: string;
|
|
27
43
|
currentRound: number;
|
|
28
44
|
status: 'open' | 'closed';
|
|
29
45
|
maxRoundsExhausted: boolean;
|
|
@@ -31,7 +47,7 @@ interface StateSnapshot {
|
|
|
31
47
|
lastDigest?: string;
|
|
32
48
|
}
|
|
33
49
|
interface AttemptRecord {
|
|
34
|
-
version:
|
|
50
|
+
version: 2;
|
|
35
51
|
event: 'received' | 'finalized' | 'recovered';
|
|
36
52
|
attemptId: string;
|
|
37
53
|
timestamp: string;
|
|
@@ -76,6 +92,14 @@ export interface CouncilAttemptInput {
|
|
|
76
92
|
request: unknown;
|
|
77
93
|
verdictCount: number;
|
|
78
94
|
members: string[];
|
|
95
|
+
/**
|
|
96
|
+
* True when the user explicitly configured `council.escalateOnMaxRounds`.
|
|
97
|
+
* Only this boolean reaches the durable max-rounds exhaustion event —
|
|
98
|
+
* the configured handler/webhook string itself is never persisted or
|
|
99
|
+
* logged (URL/query redaction, issue #2102 contract F). No outbound
|
|
100
|
+
* execution ever happens.
|
|
101
|
+
*/
|
|
102
|
+
escalationConfigured?: boolean;
|
|
79
103
|
probePendingEvidence?: (attemptId: string, round: number, evidenceRef?: string) => Promise<boolean>;
|
|
80
104
|
evaluate: (authoritativeRound: number) => Promise<CouncilAttemptEvaluation>;
|
|
81
105
|
}
|
|
@@ -5,9 +5,19 @@
|
|
|
5
5
|
* responses, returns the set of factual disagreements that should be routed
|
|
6
6
|
* back to disputing members for Round 2 reconciliation.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Three-pass detection:
|
|
9
9
|
* Pass 1 — Explicit linguistic markers ("I disagree with", "unlike", etc.)
|
|
10
|
-
* Pass 2 —
|
|
10
|
+
* Pass 2 — Structured claims (issue #2102 contract G): contrary typed
|
|
11
|
+
* stances on the same subject, detectable without any marker
|
|
12
|
+
* phrase. Claims are optional and bounded; when absent, invalid,
|
|
13
|
+
* or malformed this pass contributes nothing and the fallback
|
|
14
|
+
* passes below still run.
|
|
15
|
+
* Pass 3 — Claim divergence heuristic (mutually exclusive recommendations)
|
|
16
|
+
*
|
|
17
|
+
* Marker-based disagreements are always kept: the combined list orders the
|
|
18
|
+
* explicit-marker pass FIRST, so structured data can never push a marker
|
|
19
|
+
* detection out of the MAX_DISAGREEMENTS cap, and dedupe merges positions
|
|
20
|
+
* instead of removing them.
|
|
11
21
|
*
|
|
12
22
|
* NSED design note (arXiv:2601.16863): only the disagreement delta is fed
|
|
13
23
|
* forward to Round 2, not full Round 1 context — mirrors the "semantic forget
|
|
@@ -18,6 +18,31 @@ export interface WebSearchResult {
|
|
|
18
18
|
snippet: string;
|
|
19
19
|
query: string;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Bounded structured claim on a council question (issue #2102 contract G).
|
|
23
|
+
* Claims are OPTIONAL: smaller models may omit them entirely and the
|
|
24
|
+
* disagreement detector falls back to its phrase/heuristic passes. A claim
|
|
25
|
+
* by itself is never proof of consensus or correctness — claims only make
|
|
26
|
+
* positions comparable so a phrase-free contrary stance is detectable.
|
|
27
|
+
*/
|
|
28
|
+
export interface GeneralCouncilClaim {
|
|
29
|
+
/** Bounded subject/topic the claim is about (≤120 chars). */
|
|
30
|
+
subject: string;
|
|
31
|
+
/** Bounded statement of the member's position (≤600 chars). */
|
|
32
|
+
statement: string;
|
|
33
|
+
/** Typed stance; 'oppose'/'alternative' mark contrary positions. */
|
|
34
|
+
stance: 'support' | 'oppose' | 'neutral' | 'concern' | 'alternative';
|
|
35
|
+
/** Self-reported confidence, same validated range as the response. */
|
|
36
|
+
confidence: number;
|
|
37
|
+
/**
|
|
38
|
+
* Optional bounded evidence/source references (≤4 items, each ≤240 chars).
|
|
39
|
+
* The bound is enforced at the Zod tool boundary (`MemberClaimsSchema` in
|
|
40
|
+
* src/tools/convene-general-council.ts) and independently re-checked by
|
|
41
|
+
* `isWellFormedClaim` in the disagreement detector — the TypeScript type
|
|
42
|
+
* alone cannot express it.
|
|
43
|
+
*/
|
|
44
|
+
evidence?: string[];
|
|
45
|
+
}
|
|
21
46
|
export interface GeneralCouncilMemberConfig {
|
|
22
47
|
memberId: string;
|
|
23
48
|
model: string;
|
|
@@ -35,6 +60,13 @@ export interface GeneralCouncilMemberResponse {
|
|
|
35
60
|
confidence: number;
|
|
36
61
|
areasOfUncertainty: string[];
|
|
37
62
|
durationMs: number;
|
|
63
|
+
/**
|
|
64
|
+
* Optional bounded structured claims (issue #2102 contract G). Absent,
|
|
65
|
+
* malformed, or oversized claims degrade to the phrase/Jaccard fallback —
|
|
66
|
+
* they never reject the submission and never suppress explicit
|
|
67
|
+
* disagreement evidence.
|
|
68
|
+
*/
|
|
69
|
+
claims?: GeneralCouncilClaim[];
|
|
38
70
|
}
|
|
39
71
|
export interface GeneralCouncilDisagreementPosition {
|
|
40
72
|
memberId: string;
|