opencode-swarm 7.130.2 → 7.131.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.
- package/dist/cli/{config-doctor-489wej17.js → config-doctor-rjvb0hhn.js} +2 -2
- package/dist/cli/{curation-policy-aaz0hap6.js → curation-policy-f5bybzmh.js} +2 -2
- package/dist/cli/{curator-s2ynewsr.js → curator-kjzqa5m7.js} +13 -13
- package/dist/cli/{curator-llm-factory-mxjcctn8.js → curator-llm-factory-ezq4fakt.js} +13 -13
- package/dist/cli/{evidence-summary-service-hf7qq777.js → evidence-summary-service-jfagmvrh.js} +1 -1
- package/dist/cli/{guardrail-explain-s85kv81r.js → guardrail-explain-n93qh8k6.js} +14 -14
- package/dist/cli/{guardrail-log-2cejc33f.js → guardrail-log-h4tmcjwx.js} +3 -3
- package/dist/cli/{hive-promoter-qdgxajht.js → hive-promoter-mammxe5p.js} +13 -13
- package/dist/cli/{index-jvyn76xe.js → index-01nyrk95.js} +3 -3
- package/dist/cli/{index-kzvwb2se.js → index-03zyn94g.js} +110 -85
- package/dist/cli/{index-xa29259t.js → index-1atz85c8.js} +1 -1
- package/dist/cli/{index-6fqj8fcp.js → index-6vg0qczz.js} +2 -2
- package/dist/cli/{index-zft4hp35.js → index-cnwh164k.js} +1 -1
- package/dist/cli/{index-hjar4crm.js → index-cq22pnd2.js} +1 -1
- package/dist/cli/{index-etdgtk3m.js → index-d2tgc580.js} +2688 -1240
- package/dist/cli/{index-jxha73tm.js → index-fepakwhp.js} +1 -1
- package/dist/cli/{index-5xxgeksy.js → index-hynbzh7s.js} +46 -2
- package/dist/cli/{index-p6qgjkbt.js → index-kbfhyxtv.js} +9 -1
- package/dist/cli/{index-3rnkyb60.js → index-m6zfm7cm.js} +5 -5
- package/dist/cli/{index-s8h2cedv.js → index-pft1qxqh.js} +1 -1
- package/dist/cli/{index-ts9wh1ba.js → index-q68sprcy.js} +60 -8
- package/dist/cli/{index-agte9w1e.js → index-szkswbqf.js} +2 -2
- package/dist/cli/{index-nr5pr54q.js → index-tw33v84d.js} +1 -1
- package/dist/cli/{index-jvwkxz8q.js → index-xhd797bz.js} +15 -15
- package/dist/cli/{index-wy3q73j2.js → index-xv2ydncg.js} +18 -14
- package/dist/cli/index.js +13 -13
- package/dist/cli/{knowledge-escalator-1ypz0266.js → knowledge-escalator-cf1kkmfe.js} +3 -3
- package/dist/cli/{knowledge-events-yvcczmja.js → knowledge-events-p8t5ev44.js} +1 -1
- package/dist/cli/{knowledge-store-h8bp9eje.js → knowledge-store-vpdmcwem.js} +3 -1
- package/dist/cli/{knowledge-validator-sz9v1fdq.js → knowledge-validator-w1t0sw5f.js} +4 -4
- package/dist/cli/{scan-cursor-k9zyt374.js → scan-cursor-z4c51902.js} +2 -2
- package/dist/cli/{schema-fkpn2r1s.js → schema-yqzzfavj.js} +5 -1
- package/dist/cli/{skill-generator-a6a9jxfd.js → skill-generator-aaabj00n.js} +5 -5
- package/dist/commands/consolidate.d.ts +14 -0
- package/dist/commands/curate.d.ts +2 -0
- package/dist/commands/promote.d.ts +25 -0
- package/dist/commands/registry.d.ts +2 -2
- package/dist/config/schema.d.ts +101 -0
- package/dist/consensus/contracts.d.ts +479 -0
- package/dist/consensus/corpus.d.ts +261 -0
- package/dist/consensus/miner.d.ts +200 -0
- package/dist/consensus/public-api.d.ts +42 -0
- package/dist/consensus/store.d.ts +119 -0
- package/dist/evidence/immutable-store.d.ts +86 -0
- package/dist/evidence/manager.d.ts +31 -1
- package/dist/hooks/actionability-predicate.d.ts +36 -0
- package/dist/hooks/curator.d.ts +3 -0
- package/dist/hooks/hive-policy.d.ts +11 -0
- package/dist/hooks/hive-promoter.d.ts +27 -1
- package/dist/hooks/knowledge-dedup-sweep.d.ts +145 -0
- package/dist/hooks/knowledge-injector.d.ts +4 -0
- package/dist/hooks/knowledge-store.d.ts +26 -0
- package/dist/hooks/knowledge-types.d.ts +6 -0
- package/dist/hooks/knowledge-validator.d.ts +10 -14
- package/dist/hooks/micro-reflector.d.ts +106 -2
- package/dist/hooks/realtime-learning-nudge.d.ts +19 -0
- package/dist/index.js +426 -418
- package/dist/knowledge/entry-merge.d.ts +112 -0
- package/dist/knowledge/family-migration.d.ts +6 -2
- package/dist/learning/admission.d.ts +154 -0
- package/dist/learning/candidate-queue.d.ts +178 -0
- package/dist/learning/fingerprint.d.ts +56 -0
- package/dist/learning/prm-pattern-support.d.ts +124 -0
- package/dist/learning/provenance.d.ts +86 -0
- package/dist/prm/index.d.ts +46 -1
- package/dist/services/recommendation-ledger.d.ts +266 -0
- package/dist/services/skill-improver.d.ts +4 -0
- package/dist/services/status-service.d.ts +13 -0
- package/dist/services/trajectory-cluster.d.ts +65 -2
- package/dist/tools/consensus-mine.d.ts +172 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/knowledge-add.d.ts +34 -0
- package/dist/tools/manifest.d.ts +1 -0
- package/dist/tools/tool-metadata.d.ts +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only corpus assembly for the consensus miner (issue #1821, Workstream C).
|
|
3
|
+
*
|
|
4
|
+
* This module owns exactly one job: turn every already-existing evidence store
|
|
5
|
+
* in `.swarm/` into a single flat stream of `CorpusObservation` records. It
|
|
6
|
+
* introduces **no new store**. Every reader below is an existing exported
|
|
7
|
+
* function; the two enumerators added here are `listTrajectorySessions` (a
|
|
8
|
+
* `readdir` over `.swarm/trajectories`) and `listEvaluationRunIds` (a `readdir`
|
|
9
|
+
* over `.swarm/evolution/runs`), because both underlying stores read one
|
|
10
|
+
* artifact at a time and never needed a listing of their own. An enumerator is
|
|
11
|
+
* not a store: it writes nothing, caches nothing, and owns no schema. One
|
|
12
|
+
* source composes rather than enumerates: `.swarm/skills/rejected-edits.jsonl`
|
|
13
|
+
* has no bulk reader, so `defaultReaders` pairs that module's already-exported
|
|
14
|
+
* `rejectedEditsPath` with the shared JSONL `readKnowledge`.
|
|
15
|
+
*
|
|
16
|
+
* Hard rules this module upholds (AGENTS.md invariants 4 and 8):
|
|
17
|
+
* - Every reader receives an injected `directory`. There is no `process.cwd()`
|
|
18
|
+
* fallback anywhere in this file, and none may be added: a consensus report
|
|
19
|
+
* mined against the wrong root would silently attribute one project's
|
|
20
|
+
* evidence to another.
|
|
21
|
+
* - Nothing here writes to disk: no artifact, no lock, no cache file, no marker.
|
|
22
|
+
* That is not a property of *reading* — it has to be chosen. `loadEvidence`
|
|
23
|
+
* performs a lazy in-place upgrade of a legacy flat retrospective by default
|
|
24
|
+
* (rewriting the bundle under an `evidence-loader` lock and creating a lock
|
|
25
|
+
* sentinel under `.swarm/locks/`), so `defaultReaders` binds it with
|
|
26
|
+
* `{ migrate: false }`. A mining run must never mutate the evidence it is
|
|
27
|
+
* merely counting. Note that `migrate: false` skips only the PERSISTENCE: the
|
|
28
|
+
* returned bundle is still the wrapped, normalized view — including the
|
|
29
|
+
* `task_complexity` remap, which happens in `wrapFlatRetrospective` before the
|
|
30
|
+
* write branch — so the corpus reads normalized values while the file on disk
|
|
31
|
+
* keeps its legacy ones.
|
|
32
|
+
* Three upstream readers do populate PROCESS-LOCAL caches, which is worth
|
|
33
|
+
* stating rather than hiding behind the word "read-only": `readTrajectory`
|
|
34
|
+
* fills the PRM in-memory trajectory cache (`src/prm/trajectory-store.ts`),
|
|
35
|
+
* while `readKnowledge` and `loadEvidence` fill the two SEPARATE maps in
|
|
36
|
+
* `src/utils/swarm-artifact-cache.ts` — the parsed-artifact cache and the text
|
|
37
|
+
* cache, each independently bounded at `MAX_CACHE_ENTRIES` = 128 entries. All
|
|
38
|
+
* are bounded and FIFO-evicting, so mining a large `.swarm/` tree CAN evict
|
|
39
|
+
* another subsystem's cached entries and change what that subsystem sees next.
|
|
40
|
+
* The PRM one goes further: it is a plain `Map` with no revalidation, and
|
|
41
|
+
* `readTrajectory` REPLACES the entry for the session it read, re-bounded by
|
|
42
|
+
* its own `maxLines` default rather than by whatever bound the live writer was
|
|
43
|
+
* using — a session whose list exceeds 1000 entries is cut to the newest
|
|
44
|
+
* `floor(maxLines / 2)` = 500 ENTRIES. That 500 is unrelated to
|
|
45
|
+
* `MAX_TRACKED_TRAJECTORY_SESSIONS` = 500, which bounds how many SESSIONS the
|
|
46
|
+
* cache holds at once; the two constants merely happen to share a value.
|
|
47
|
+
* None of these caches is on disk: nothing about them writes a file, and none
|
|
48
|
+
* survives the process. (The readers themselves obviously do read files — the
|
|
49
|
+
* claim above is that nothing here WRITES, not that nothing here touches the
|
|
50
|
+
* filesystem. The artifact cache in particular `stat`s the file on every
|
|
51
|
+
* lookup to check freshness, and on a miss invokes the caller's own read
|
|
52
|
+
* function and `stat`s again before storing.)
|
|
53
|
+
* - Every free-text fragment that survives into a signal, a statement, or an
|
|
54
|
+
* evidence reference passes through `redactSecrets` and a hard length bound
|
|
55
|
+
* before it is retained. Prompts and reasoning traces are never read into an
|
|
56
|
+
* observation at all — only outcomes, verdicts, categories, and the bounded
|
|
57
|
+
* excerpts below.
|
|
58
|
+
* - The whole corpus is capped at `maxEvidenceItems` observations. The cap is
|
|
59
|
+
* applied against a deterministic source order and a deterministic per-source
|
|
60
|
+
* sort, so the same `.swarm/` tree always yields the same truncated corpus.
|
|
61
|
+
* Within a source the cut is *balanced between failing and succeeding
|
|
62
|
+
* observations* rather than lexicographic, because a lexicographic cut
|
|
63
|
+
* systematically drops whichever class sorts late and would let truncation
|
|
64
|
+
* erase counterexamples while confidence rose. That removes the systematic
|
|
65
|
+
* bias; it does not make truncation lossless. The balance is struck PER
|
|
66
|
+
* SOURCE, so one signal can still lose every counterexample it had, and once
|
|
67
|
+
* the budget is spent every later source is dropped WHOLE. `report.truncation`
|
|
68
|
+
* exists so a reader can tell a partial view from a complete one — see
|
|
69
|
+
* `docs/consensus-mining.md`.
|
|
70
|
+
*/
|
|
71
|
+
import type { EvaluationRunV1 } from '../evaluation/contracts.js';
|
|
72
|
+
import { readGateGroundTruth } from '../evaluation/gate-ground-truth.js';
|
|
73
|
+
import { type GateAuditReadSummary } from '../evaluation/store.js';
|
|
74
|
+
import { type LoadEvidenceResult } from '../evidence/manager.js';
|
|
75
|
+
import type { RetrievalOutcome } from '../hooks/knowledge-types.js';
|
|
76
|
+
import { readSkillUsageEntries } from '../hooks/skill-usage-log.js';
|
|
77
|
+
import type { TrajectoryEntry } from '../prm/types.js';
|
|
78
|
+
import type { ConsensusCorpusHash, ConsensusSourceKind } from './contracts.js';
|
|
79
|
+
/**
|
|
80
|
+
* One normalized evidence record.
|
|
81
|
+
*
|
|
82
|
+
* `runId` is the *support unit*: the miner counts distinct `runId` values, so
|
|
83
|
+
* whatever a source uses as its independent-trial identity belongs here. For
|
|
84
|
+
* evaluation and gate-audit evidence that is the literal run id; for per-task
|
|
85
|
+
* evidence it is the task's own identity, and for session trajectories the
|
|
86
|
+
* session id. Each is namespaced so two sources can never collide.
|
|
87
|
+
*/
|
|
88
|
+
export interface CorpusObservation {
|
|
89
|
+
runId: string;
|
|
90
|
+
taskId?: string;
|
|
91
|
+
taskCategory?: string;
|
|
92
|
+
agentRole?: string;
|
|
93
|
+
modelId?: string;
|
|
94
|
+
seed?: string;
|
|
95
|
+
success: boolean;
|
|
96
|
+
signals: string[];
|
|
97
|
+
evidenceRef: string;
|
|
98
|
+
}
|
|
99
|
+
export interface ConsensusCorpus {
|
|
100
|
+
observations: CorpusObservation[];
|
|
101
|
+
hashes: ConsensusCorpusHash[];
|
|
102
|
+
/** True when `maxEvidenceItems` truncated the stream. */
|
|
103
|
+
truncated: boolean;
|
|
104
|
+
/** Sources that threw while being read. Never fatal — the corpus degrades. */
|
|
105
|
+
unreadableSources: ConsensusSourceKind[];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Injectable readers. Dependency injection rather than `mock.module` (AGENTS.md
|
|
109
|
+
* invariant 7): the corpus pulls from eight subsystems, and mocking those module
|
|
110
|
+
* paths would leak across Bun's shared test-runner process.
|
|
111
|
+
*/
|
|
112
|
+
export interface CorpusReaders {
|
|
113
|
+
listEvaluationRunIds: (directory: string) => Promise<string[]>;
|
|
114
|
+
readEvaluationRun: (directory: string, runId: string) => Promise<EvaluationRunV1 | undefined>;
|
|
115
|
+
listGateAuditResults: (directory: string) => Promise<GateAuditReadSummary>;
|
|
116
|
+
readGateGroundTruth: typeof readGateGroundTruth;
|
|
117
|
+
listEvidenceTaskIds: (directory: string) => Promise<string[]>;
|
|
118
|
+
readTaskTrajectory: (directory: string, taskId: string) => Promise<TrajectoryEntry[]>;
|
|
119
|
+
listTrajectorySessions: (directory: string) => Promise<string[]>;
|
|
120
|
+
readTrajectory: (sessionId: string, directory: string) => Promise<TrajectoryEntry[]>;
|
|
121
|
+
readSkillUsageEntries: typeof readSkillUsageEntries;
|
|
122
|
+
readKnowledgeEntries: (directory: string) => Promise<KnowledgeLike[]>;
|
|
123
|
+
loadEvidence: (directory: string, taskId: string) => Promise<LoadEvidenceResult>;
|
|
124
|
+
readRejectedLessons: (directory: string) => Promise<RejectedLessonLike[]>;
|
|
125
|
+
readRejectedSkillEdits: (directory: string) => Promise<RejectedSkillEditLike[]>;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Structural view of a knowledge entry. Deliberately minimal: the consensus
|
|
129
|
+
* miner reads four fields, and depending on the full `KnowledgeEntry` union
|
|
130
|
+
* would couple this module to a schema another lane actively edits.
|
|
131
|
+
*/
|
|
132
|
+
export interface KnowledgeLike {
|
|
133
|
+
id?: unknown;
|
|
134
|
+
lesson?: unknown;
|
|
135
|
+
category?: unknown;
|
|
136
|
+
retrieval_outcomes?: RetrievalOutcome;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Structural view of a `RejectedLesson` (`src/hooks/knowledge-types.ts`). Same
|
|
140
|
+
* reason as `KnowledgeLike`: the corpus reads four fields and must not couple
|
|
141
|
+
* itself to a schema another lane edits.
|
|
142
|
+
*/
|
|
143
|
+
export interface RejectedLessonLike {
|
|
144
|
+
id?: unknown;
|
|
145
|
+
lesson?: unknown;
|
|
146
|
+
rejection_reason?: unknown;
|
|
147
|
+
rejection_layer?: unknown;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Structural view of a `RejectedSkillEditRecord`
|
|
151
|
+
* (`src/services/skill-evaluator.ts`). `candidatePreview` is deliberately NOT
|
|
152
|
+
* read: it is up to 800 bytes of the rejected skill BODY, which is closer to a
|
|
153
|
+
* prompt than to an outcome, and the corpus reads outcomes only.
|
|
154
|
+
*/
|
|
155
|
+
export interface RejectedSkillEditLike {
|
|
156
|
+
slug?: unknown;
|
|
157
|
+
operation?: unknown;
|
|
158
|
+
reason?: unknown;
|
|
159
|
+
candidateHash?: unknown;
|
|
160
|
+
}
|
|
161
|
+
export interface LoadCorpusOptions {
|
|
162
|
+
/** Hard cap on retained observations. Required — there is no default. */
|
|
163
|
+
maxEvidenceItems: number;
|
|
164
|
+
/** Hard cap on any single retained free-text fragment. */
|
|
165
|
+
maxExcerptChars: number;
|
|
166
|
+
/**
|
|
167
|
+
* Caller-supplied retention predicate, applied per source AFTER that source's
|
|
168
|
+
* hash and observation count are recorded and BEFORE the `maxEvidenceItems`
|
|
169
|
+
* budget is spent.
|
|
170
|
+
*
|
|
171
|
+
* That position is the whole point. The consensus miner's request filters used
|
|
172
|
+
* to run only on the already-truncated stream, so narrowing a request to the
|
|
173
|
+
* one task category that mattered removed observations from a corpus the cap
|
|
174
|
+
* had already shaped — 50 observations in, 0 out. Applying the predicate here
|
|
175
|
+
* spends the budget on observations that can survive the request instead, so
|
|
176
|
+
* narrowing genuinely widens what is available to it.
|
|
177
|
+
*
|
|
178
|
+
* Applied after the per-source hash so `corpusHashes` keeps meaning "what this
|
|
179
|
+
* source contained", independent of any one request; `truncation.observations`
|
|
180
|
+
* on the report is what declares how many were actually tallied.
|
|
181
|
+
*
|
|
182
|
+
* Omitted \u21D2 every observation is retained, which is the pre-existing behaviour.
|
|
183
|
+
*/
|
|
184
|
+
filter?: (observation: CorpusObservation) => boolean;
|
|
185
|
+
/** Reader overrides for tests. Unspecified readers use the real store. */
|
|
186
|
+
readers?: Partial<CorpusReaders>;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Total, locale-INDEPENDENT string order.
|
|
190
|
+
*
|
|
191
|
+
* `String.prototype.localeCompare` without an explicit locale is ICU- and
|
|
192
|
+
* environment-sensitive: it orders `a-b, a:b, ab, aB` differently from code-unit
|
|
193
|
+
* order, and the collation can differ between hosts. That is fine for display
|
|
194
|
+
* and fatal here — this ordering decides which observations survive truncation
|
|
195
|
+
* and the order of the attribute array, both of which are hashed into
|
|
196
|
+
* `integrityHash`. A report whose id depends on the host's collation is not
|
|
197
|
+
* reproducible. Code-unit comparison is the same everywhere.
|
|
198
|
+
*/
|
|
199
|
+
export declare function compareRefs(left: string, right: string): number;
|
|
200
|
+
/**
|
|
201
|
+
* Redact, collapse, and bound a free-text fragment before it is retained.
|
|
202
|
+
*
|
|
203
|
+
* Order matters: redaction runs BEFORE truncation, so a secret that straddles
|
|
204
|
+
* the length bound is still replaced rather than half-copied into the report.
|
|
205
|
+
* Newlines and control characters collapse to single spaces because signals are
|
|
206
|
+
* compared for equality — a fragment that differs only by line wrapping must
|
|
207
|
+
* not split one consensus attribute into two.
|
|
208
|
+
*
|
|
209
|
+
* Format characters (`\p{Cf}`) are collapsed alongside control characters — to a
|
|
210
|
+
* SPACE, like everything else here, not deleted — and that is a correctness rule
|
|
211
|
+
* rather than tidiness. U+202E (RIGHT-TO-LEFT OVERRIDE), U+2066–U+2069 (the
|
|
212
|
+
* isolates) and U+200E/U+200F reorder how the text RENDERS without changing the
|
|
213
|
+
* bytes stored, so a persisted signal, statement, evidence ref, or `llmSummary`
|
|
214
|
+
* could read as something other than the bytes the report actually holds.
|
|
215
|
+
* U+200B–U+200D and U+FEFF are the same class of invisible, and they also hide
|
|
216
|
+
* INSIDE a token: `[REDACTED\u200B:x]` is not the placeholder it renders as, and
|
|
217
|
+
* `sk\u200B-…` is not the secret shape `redactSecrets` matches.
|
|
218
|
+
*
|
|
219
|
+
* Replacing rather than deleting is a real choice, in both directions. What it
|
|
220
|
+
* BUYS: deletion would let `[REDACTED\u200B:x]` close up into a well-formed
|
|
221
|
+
* `[REDACTED:x]` after the forged-marker check in `extractRestatement` has
|
|
222
|
+
* already run on the raw text — manufacturing exactly the forged redaction
|
|
223
|
+
* marker that check exists to reject. A space breaks the token instead, and
|
|
224
|
+
* `MARKUP_RE` then rejects it on the bare `[`. What it does NOT buy: `a\u200Db`
|
|
225
|
+
* becomes `a b`, not `ab`, so a fragment differing from another only by a
|
|
226
|
+
* zero-width joiner still differs afterwards. Equality is canonicalized (every
|
|
227
|
+
* such fragment collapses the same way), not made to agree with the joiner-free
|
|
228
|
+
* spelling.
|
|
229
|
+
*/
|
|
230
|
+
export declare function sanitizeExcerpt(value: string, maxChars: number): string;
|
|
231
|
+
/**
|
|
232
|
+
* READ-ONLY enumerator over `.swarm/trajectories`.
|
|
233
|
+
*
|
|
234
|
+
* `src/prm/trajectory-store.ts` reads exactly one session at a time and has no
|
|
235
|
+
* bulk enumerator, so the consensus corpus needs this to discover which sessions
|
|
236
|
+
* exist. It lives here rather than in the PRM store because it is the consensus
|
|
237
|
+
* miner's need, not the PRM subsystem's, and because adding it there would put a
|
|
238
|
+
* second writer-adjacent surface in a module another lane owns. It performs a
|
|
239
|
+
* single `readdir`, filters to `<sessionId>.jsonl` names that match the shared
|
|
240
|
+
* identifier shape, sorts, bounds, and returns. Missing directory \u21D2 `[]`.
|
|
241
|
+
*/
|
|
242
|
+
export declare function listTrajectorySessions(directory: string): Promise<string[]>;
|
|
243
|
+
/**
|
|
244
|
+
* READ-ONLY enumerator over `.swarm/evolution/runs`.
|
|
245
|
+
*
|
|
246
|
+
* `src/evaluation/store.ts` exposes `readEvaluationRun(directory, runId)` but no
|
|
247
|
+
* listing (its own callers always know the run id). Mirrors the directory
|
|
248
|
+
* discipline of `listGateAuditResults`: validated names only, sorted, bounded.
|
|
249
|
+
*/
|
|
250
|
+
export declare function listEvaluationRunIds(directory: string): Promise<string[]>;
|
|
251
|
+
/**
|
|
252
|
+
* Load and normalize every corpus source.
|
|
253
|
+
*
|
|
254
|
+
* Failure of any single source is non-fatal and recorded in `unreadableSources`:
|
|
255
|
+
* a corrupt trajectory file must not make the whole mining run unavailable, and
|
|
256
|
+
* a silently-empty corpus would be worse than a declared partial one. The
|
|
257
|
+
* returned observations are stable-sorted, reduced by the optional
|
|
258
|
+
* `options.filter`, and then truncated to `maxEvidenceItems` — filter first, so
|
|
259
|
+
* the budget is spent on observations the caller can actually use.
|
|
260
|
+
*/
|
|
261
|
+
export declare function loadConsensusCorpus(directory: string, options: LoadCorpusOptions): Promise<ConsensusCorpus>;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consensus miner (issue #1821, Workstream C).
|
|
3
|
+
*
|
|
4
|
+
* Reads the read-only corpus, counts agreement deterministically, gates the
|
|
5
|
+
* result, and emits a report. It is a **proposals-only** boundary: it activates
|
|
6
|
+
* no skill, writes no knowledge, and mutates none of the evidence it reads.
|
|
7
|
+
* `mineConsensus` itself writes nothing at all; the subsystem's only disk
|
|
8
|
+
* mutations live in `./store.ts`, which persists a report and — under
|
|
9
|
+
* `pruneConsensusReports` — deletes the subsystem's own older ones.
|
|
10
|
+
*
|
|
11
|
+
* Ordering is a correctness property, not a style choice. The pipeline is:
|
|
12
|
+
*
|
|
13
|
+
* 1. deterministic filtering
|
|
14
|
+
* 2. deterministic co-occurrence + distinct-run support counting
|
|
15
|
+
* 3. deterministic gates (support, successful runs, task diversity)
|
|
16
|
+
* 4. deterministic retention of negative evidence
|
|
17
|
+
* 5. deterministic proposal + fingerprint construction
|
|
18
|
+
* 6. ONLY THEN, optional LLM restatement, into a SEPARATE field
|
|
19
|
+
*
|
|
20
|
+
* Step 6 last is what keeps a model from influencing whether something
|
|
21
|
+
* qualifies. A model may only rephrase a conclusion the arithmetic already
|
|
22
|
+
* reached. If no dispatcher is available, or the call times out, or
|
|
23
|
+
* summarization is disabled, or the response fails the restatement guard, the
|
|
24
|
+
* attribute simply has no `llmSummary` — graceful degradation, never a hard
|
|
25
|
+
* dependency.
|
|
26
|
+
*
|
|
27
|
+
* Two properties are easy to lose here and are enforced explicitly:
|
|
28
|
+
*
|
|
29
|
+
* - **Reproducibility.** Ordering alone does NOT make the report reproducible.
|
|
30
|
+
* Two fields had to be moved out of hashed content, and both were reachable in
|
|
31
|
+
* ordinary use. `llm_summarization_enabled` defaults to `true`, so a model's
|
|
32
|
+
* wording in `statement` gave a different `integrityHash` — and a different
|
|
33
|
+
* `reportId` — on every run over an identical corpus; the restatement now goes
|
|
34
|
+
* into `ConsensusAttributeV1.llmSummary`, which the hash excludes. And
|
|
35
|
+
* `provenance.writeOrigin.sessionId` comes from `ctx.sessionID`, so two
|
|
36
|
+
* sessions mining the same corpus forked the artifact too; the hash now
|
|
37
|
+
* excludes the whole `writeOrigin`. `statement` is always the deterministic
|
|
38
|
+
* rendering, and who ran the mine cannot change what the mine found.
|
|
39
|
+
* - **Model prose reaches disk only through a bounded whitelist.**
|
|
40
|
+
* `SUMMARIZATION_SYSTEM` is a request, not a filter, and `sanitizeExcerpt`
|
|
41
|
+
* only redacts secrets, collapses control and format characters, and
|
|
42
|
+
* truncates. `extractRestatement` is the actual guard, and what it enforces is
|
|
43
|
+
* precisely this: one `FINDING:` line per dispatch survives (the first),
|
|
44
|
+
* everything else in the response is discarded, and the captured text is
|
|
45
|
+
* admitted only if it carries no forged `[REDACTED:…]` marker, no bracket or
|
|
46
|
+
* angle-bracket markup, no listed reasoning marker, and — once decimal points
|
|
47
|
+
* and at most one lower-case-continued `e.g.`/`i.e.`/`etc.` are MASKED — no
|
|
48
|
+
* sentence terminator other than a single trailing run, and fits
|
|
49
|
+
* `MAX_CONSENSUS_STATEMENT_CHARS` without truncation (issue #1821 AC18). Note
|
|
50
|
+
* the masking clause: the persisted text can hold several literal `.`
|
|
51
|
+
* characters (`… on 0.8 of the runs, e.g. the refactor pair.` is admitted and
|
|
52
|
+
* contains four: the decimal, the two in `e.g.`, and the trailing one. Three
|
|
53
|
+
* of them are masked, leaving the single trailing run the rule allows). The
|
|
54
|
+
* bound is on UNMASKED terminators, not on periods.
|
|
55
|
+
*
|
|
56
|
+
* The limitation belongs in the same breath, because the absolute version was
|
|
57
|
+
* claimed here twice and is false: **a single grammatical sentence chained
|
|
58
|
+
* with semicolons, colons, dashes, tabs, or the one permitted abbreviation can
|
|
59
|
+
* still read as a multi-step narration, and this guard does not stop that.**
|
|
60
|
+
* It bounds how much model text, in what shape, can reach an attribute — one
|
|
61
|
+
* sentence-shaped fragment of at most `MAX_CONSENSUS_STATEMENT_CHARS`, in a
|
|
62
|
+
* field excluded from the integrity hash that never displaces `statement`. It
|
|
63
|
+
* does not classify meaning.
|
|
64
|
+
*/
|
|
65
|
+
import type { ConsensusConfig } from '../config/schema.js';
|
|
66
|
+
import type { EvaluationModelDispatcher } from '../evaluation/model-dispatcher.js';
|
|
67
|
+
import type { ConsensusMineRequest, ConsensusReportV1 } from './contracts.js';
|
|
68
|
+
import type { ConsensusCorpus, CorpusObservation } from './corpus.js';
|
|
69
|
+
/**
|
|
70
|
+
* Minimum distinct task identities before an attribute may become a proposal.
|
|
71
|
+
*
|
|
72
|
+
* Two is not a tuning knob. One anecdote — one task, however many times it was
|
|
73
|
+
* observed — cannot distinguish "this is how the system behaves" from "this is
|
|
74
|
+
* how that one task behaves". Below this an attribute is still emitted, as an
|
|
75
|
+
* investigation note with `proposedTarget: 'none'`, because suppressing it
|
|
76
|
+
* entirely would hide the very evidence a human needs to decide whether to go
|
|
77
|
+
* looking for a second task.
|
|
78
|
+
*/
|
|
79
|
+
export declare const MIN_TASK_DIVERSITY_FOR_PROPOSAL = 2;
|
|
80
|
+
/**
|
|
81
|
+
* Minimum distinct RUNS before an attribute may become a proposal.
|
|
82
|
+
*
|
|
83
|
+
* A second, independent gate from `MIN_TASK_DIVERSITY_FOR_PROPOSAL`, and it is
|
|
84
|
+
* NOT implied by the request's `minSupport`: `min_support: 1` is an accepted
|
|
85
|
+
* argument, so without this an attribute supported by a single run could clear
|
|
86
|
+
* every threshold the caller asked for and still be a one-run recommendation.
|
|
87
|
+
* Exported so `consensus_mine` can print the gate it actually applies rather
|
|
88
|
+
* than restating it — the printed `thresholds` block used to omit this one, and
|
|
89
|
+
* an attribute that cleared every printed number could still be forced to
|
|
90
|
+
* `proposedTarget: 'none'` with nothing in the output explaining why.
|
|
91
|
+
*/
|
|
92
|
+
export declare const MIN_SUPPORT_FOR_PROPOSAL = 2;
|
|
93
|
+
/**
|
|
94
|
+
* Hard cap on LLM restatement dispatches per report.
|
|
95
|
+
*
|
|
96
|
+
* Exported so `consensus_mine` can state the real ceiling in the description the
|
|
97
|
+
* model reads. Each unit is one `session.create` + one `session.prompt`
|
|
98
|
+
* (`src/evaluation/model-dispatcher.ts`), so this is 20 sessions and 20 prompts
|
|
99
|
+
* in the worst case, not 20 cheap local calls.
|
|
100
|
+
*/
|
|
101
|
+
export declare const MAX_LLM_SUMMARIES = 20;
|
|
102
|
+
export interface MineConsensusDeps {
|
|
103
|
+
/** Effective consensus configuration. Required — the miner reads no config. */
|
|
104
|
+
config: ConsensusConfig;
|
|
105
|
+
/** Corpus loader override. Defaults to the real read-only loader. */
|
|
106
|
+
loadCorpus?: (directory: string, options: {
|
|
107
|
+
maxEvidenceItems: number;
|
|
108
|
+
maxExcerptChars: number;
|
|
109
|
+
filter?: (observation: CorpusObservation) => boolean;
|
|
110
|
+
}) => Promise<ConsensusCorpus>;
|
|
111
|
+
/**
|
|
112
|
+
* Optional LLM dispatcher. Absent ⇒ deterministic statements are kept.
|
|
113
|
+
* Injected rather than constructed so the miner never reaches for a runtime
|
|
114
|
+
* client and stays unit-testable without one.
|
|
115
|
+
*/
|
|
116
|
+
dispatcher?: EvaluationModelDispatcher;
|
|
117
|
+
/** Model id for summarization dispatches. */
|
|
118
|
+
summarizationModelId?: string;
|
|
119
|
+
/** Prefixed-swarm selector forwarded to the dispatcher. */
|
|
120
|
+
preferredSwarm?: string;
|
|
121
|
+
/** Parent session for dispatched child sessions. */
|
|
122
|
+
sessionId?: string;
|
|
123
|
+
/** Agent role recorded in proposal provenance. */
|
|
124
|
+
agentRole?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Fingerprints already proposed by earlier reports. A proposal whose
|
|
127
|
+
* fingerprint appears here is suppressed so a standing recommendation is not
|
|
128
|
+
* re-proposed on every mining run.
|
|
129
|
+
*/
|
|
130
|
+
priorFingerprints?: Iterable<string>;
|
|
131
|
+
/** Clock seam. Defaults to `Date.now`-backed ISO output. */
|
|
132
|
+
now?: () => Date;
|
|
133
|
+
}
|
|
134
|
+
export interface MineConsensusResult {
|
|
135
|
+
report: ConsensusReportV1;
|
|
136
|
+
/** True when `maxEvidenceItems` truncated the corpus. */
|
|
137
|
+
truncated: boolean;
|
|
138
|
+
/** Corpus sources that could not be read. */
|
|
139
|
+
unreadableSources: string[];
|
|
140
|
+
/** Attributes emitted as investigation notes rather than proposals. */
|
|
141
|
+
investigationNoteCount: number;
|
|
142
|
+
/** Proposals suppressed because a prior report already carries them. */
|
|
143
|
+
dedupedProposalCount: number;
|
|
144
|
+
/** How many statements the LLM actually restated. */
|
|
145
|
+
summarizedCount: number;
|
|
146
|
+
/** Why summarization did not run, when it did not. */
|
|
147
|
+
summarizationSkippedReason?: 'disabled_by_config' | 'no_dispatcher' | 'no_attributes';
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* A report body, with the three hash-excluded fields optional so both the
|
|
151
|
+
* pre-id construction path and the post-read verification path can call the
|
|
152
|
+
* same function.
|
|
153
|
+
*/
|
|
154
|
+
export type ConsensusReportIntegrityInput = Omit<ConsensusReportV1, 'integrityHash' | 'reportId' | 'generatedAt'> & Partial<Pick<ConsensusReportV1, 'integrityHash' | 'reportId' | 'generatedAt'>>;
|
|
155
|
+
/**
|
|
156
|
+
* Recompute a report's integrity hash from its own content.
|
|
157
|
+
*
|
|
158
|
+
* Deliberately subtractive rather than additive: it hashes everything except
|
|
159
|
+
* the named exclusions, so a field added to `ConsensusReportV1` later is covered
|
|
160
|
+
* automatically. An additive allow-list would silently leave new fields
|
|
161
|
+
* unhashed — the failure mode where a report's content changes but its
|
|
162
|
+
* integrity hash does not.
|
|
163
|
+
*
|
|
164
|
+
* Exclusions fall into two classes, both failing the same test — "would an
|
|
165
|
+
* identical corpus produce an identical value?":
|
|
166
|
+
*
|
|
167
|
+
* 1. **Who and when, as opposed to what.** The report's own `generatedAt` (plus
|
|
168
|
+
* `reportId`, which is *derived* from this hash and would otherwise be
|
|
169
|
+
* circular), and every proposal's ENTIRE `provenance.writeOrigin` — the
|
|
170
|
+
* `producedAt` clock and the `sessionId` / `agentRole` that identify whoever
|
|
171
|
+
* physically ran the mine — which, since `ProposedSkillChangeProvenance`
|
|
172
|
+
* dropped the unreachable `agentId`, is the whole of that object. All are
|
|
173
|
+
* real provenance and stay in the artifact; none of them is content.
|
|
174
|
+
*
|
|
175
|
+
* Excluding the identity fields is not cosmetic. `sessionId` comes from
|
|
176
|
+
* `ctx.sessionID`, so with only `producedAt` excluded, two sessions mining a
|
|
177
|
+
* byte-identical corpus produced different `integrityHash` values and
|
|
178
|
+
* therefore different `reportId`s — the same user-visible symptom as hashing
|
|
179
|
+
* the model's wording, just from a different field. Who ran the mine cannot
|
|
180
|
+
* be allowed to change what the mine found.
|
|
181
|
+
* 2. **Non-reproducible model prose** — every attribute's `llmSummary`. This one
|
|
182
|
+
* is not a nicety: `llm_summarization_enabled` defaults to `true`, so hashing
|
|
183
|
+
* a model's wording made "same inputs ⇒ identical hash" FALSE in the default
|
|
184
|
+
* configuration. Two mining runs over a byte-identical corpus produced
|
|
185
|
+
* different `integrityHash` values and therefore different `reportId`s,
|
|
186
|
+
* which defeats content addressing, defeats `isEquivalent` in the store, and
|
|
187
|
+
* turns every re-mine into a new artifact. The deterministic `statement` that
|
|
188
|
+
* the summary paraphrases IS hashed, so nothing about the finding escapes
|
|
189
|
+
* coverage — only the paraphrase does.
|
|
190
|
+
*/
|
|
191
|
+
export declare function computeConsensusIntegrityHash(report: ConsensusReportIntegrityInput): string;
|
|
192
|
+
/** Deterministic report id, derived from the integrity hash. */
|
|
193
|
+
export declare function deriveReportId(integrityHash: string): string;
|
|
194
|
+
/**
|
|
195
|
+
* Mine consensus attributes and proposals from `directory`'s `.swarm/` evidence.
|
|
196
|
+
*
|
|
197
|
+
* MUTATES NOTHING. The returned report is a value; persisting it is the
|
|
198
|
+
* caller's separate, explicit `writeConsensusReport` call.
|
|
199
|
+
*/
|
|
200
|
+
export declare function mineConsensus(directory: string, request: ConsensusMineRequest, deps: MineConsensusDeps): Promise<MineConsensusResult>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable package-level boundary for the consensus miner (issue #1821).
|
|
3
|
+
*
|
|
4
|
+
* This module exports exactly one thing — `mineAndStoreConsensusV1` — because
|
|
5
|
+
* exactly one caller exists: `src/tools/consensus-mine.ts`. It previously also
|
|
6
|
+
* exported a frozen callable `consensusV1` namespace mirroring
|
|
7
|
+
* `src/evaluation/public-api.ts`, together with a `src/consensus/index.ts`
|
|
8
|
+
* barrel. Both were dead RUNTIME VALUES — a public API surface with no importer
|
|
9
|
+
* anywhere in `src/`, `tests/`, or `scripts/` — and the evaluation precedent did
|
|
10
|
+
* not apply: that barrel IS consumed (`src/index.ts` re-exports `evaluationV1`),
|
|
11
|
+
* whereas nothing ever wired consensus into the plugin entry. Both were removed
|
|
12
|
+
* rather than left standing (see the "never ship unwired code" directive in
|
|
13
|
+
* `CLAUDE.md`). If a future consumer needs a versioned namespace, reintroduce it
|
|
14
|
+
* together with that consumer.
|
|
15
|
+
*
|
|
16
|
+
* The two type aliases below have no by-name importer either, and are kept for a
|
|
17
|
+
* different and declared reason: they are the parameter and return shapes of
|
|
18
|
+
* `mineAndStoreConsensusV1` itself, so they are already handed out by its
|
|
19
|
+
* signature. See the export carve-out note at the bottom of `./contracts.ts`.
|
|
20
|
+
*/
|
|
21
|
+
import type { ConsensusMineRequest, ConsensusReportV1 } from './contracts.js';
|
|
22
|
+
import { type MineConsensusDeps } from './miner.js';
|
|
23
|
+
export type MineAndStoreConsensusOptions = {
|
|
24
|
+
directory: string;
|
|
25
|
+
request: ConsensusMineRequest;
|
|
26
|
+
deps: MineConsensusDeps;
|
|
27
|
+
};
|
|
28
|
+
export type MineAndStoreConsensusResult = {
|
|
29
|
+
report: ConsensusReportV1;
|
|
30
|
+
truncated: boolean;
|
|
31
|
+
unreadableSources: string[];
|
|
32
|
+
investigationNoteCount: number;
|
|
33
|
+
dedupedProposalCount: number;
|
|
34
|
+
summarizedCount: number;
|
|
35
|
+
summarizationSkippedReason?: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Mine, then persist. The two phases stay separable on purpose — `mineConsensus`
|
|
39
|
+
* mutates nothing and is safe to call speculatively; only this boundary writes,
|
|
40
|
+
* and it writes exactly one artifact: the report itself.
|
|
41
|
+
*/
|
|
42
|
+
export declare function mineAndStoreConsensusV1(options: MineAndStoreConsensusOptions): Promise<MineAndStoreConsensusResult>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Immutable persistence for consensus reports (issue #1821, Workstream C).
|
|
3
|
+
*
|
|
4
|
+
* Reports live under `.swarm/evolution/consensus/<reportId>.json`, alongside the
|
|
5
|
+
* evaluation substrate's runs and decisions. The write goes through the shared
|
|
6
|
+
* `writeImmutableArtifact` pipeline in `src/evidence/immutable-store.ts` — the
|
|
7
|
+
* same one the evaluation store binds — so the lock/read/compare/atomic-rename
|
|
8
|
+
* sequence exists exactly once in the codebase. This module supplies only the
|
|
9
|
+
* three parameters that pipeline deliberately leaves to its callers: the lock
|
|
10
|
+
* actor, the canonical serializer, and the conflict-error factory.
|
|
11
|
+
*
|
|
12
|
+
* Idempotence has three wrinkles a report has and a run does not, all reachable
|
|
13
|
+
* under the DEFAULT configuration. Two mining runs over an identical corpus
|
|
14
|
+
* produce reports that differ in `generatedAt` (a wall clock), in each
|
|
15
|
+
* proposal's `provenance.writeOrigin` (the `producedAt` clock plus the
|
|
16
|
+
* `sessionId` / `agentRole` of whoever ran the mine), and — because
|
|
17
|
+
* `llm_summarization_enabled` defaults to `true` — in each attribute's
|
|
18
|
+
* `llmSummary`, which is model prose and is not reproducible. All three are
|
|
19
|
+
* excluded from `integrityHash`, so an `isEquivalent` escape hatch comparing
|
|
20
|
+
* recomputed hashes treats such reports as the same artifact, exactly how
|
|
21
|
+
* `savePromotionDecision` handles `decidedAt`. A report that differs in any
|
|
22
|
+
* *content* field still conflicts.
|
|
23
|
+
*/
|
|
24
|
+
import type { ConsensusReportV1 } from './contracts.js';
|
|
25
|
+
export declare class ConsensusConflictError extends Error {
|
|
26
|
+
constructor(message: string);
|
|
27
|
+
}
|
|
28
|
+
export declare class ConsensusIntegrityError extends Error {
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Persist a report exactly once.
|
|
33
|
+
*
|
|
34
|
+
* The integrity hash is **verified before persist**, not merely recorded: a
|
|
35
|
+
* report whose declared hash does not match its own content is rejected rather
|
|
36
|
+
* than written, so a corrupted or hand-edited report can never enter the store
|
|
37
|
+
* and later be trusted on read.
|
|
38
|
+
*/
|
|
39
|
+
export declare function writeConsensusReport(directory: string, input: ConsensusReportV1): Promise<ConsensusReportV1>;
|
|
40
|
+
/**
|
|
41
|
+
* Read one report. Returns `undefined` when absent.
|
|
42
|
+
*
|
|
43
|
+
* Re-verifies the integrity hash on the way out: a report that was tampered with
|
|
44
|
+
* on disk after being written must not be handed to a caller as authoritative.
|
|
45
|
+
*/
|
|
46
|
+
export declare function readConsensusReport(directory: string, reportId: string): Promise<ConsensusReportV1 | undefined>;
|
|
47
|
+
export type ConsensusListSummary = {
|
|
48
|
+
reports: ConsensusReportV1[];
|
|
49
|
+
/** Report ids present on disk that failed to parse or verify. */
|
|
50
|
+
corruptReportIds: string[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Enumerate every stored report, newest first.
|
|
54
|
+
*
|
|
55
|
+
* Corrupt entries are reported rather than thrown so one bad file cannot make
|
|
56
|
+
* the whole history unreadable — the same posture `listGateAuditResults` takes.
|
|
57
|
+
* Sorted by `generatedAt` descending, ties broken by id, so the ordering is
|
|
58
|
+
* total and reproducible.
|
|
59
|
+
*/
|
|
60
|
+
export declare function listConsensusReports(directory: string): Promise<ConsensusListSummary>;
|
|
61
|
+
export type ConsensusPruneResult = {
|
|
62
|
+
/** Report ids deleted, oldest-first. */
|
|
63
|
+
deleted: string[];
|
|
64
|
+
/** Report ids retained. */
|
|
65
|
+
retained: string[];
|
|
66
|
+
/** Ids that failed to delete, with the reason. Never fatal. */
|
|
67
|
+
failed: Array<{
|
|
68
|
+
reportId: string;
|
|
69
|
+
error: string;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Report ids on disk that failed to parse or verify.
|
|
73
|
+
*
|
|
74
|
+
* These are neither deleted nor counted in `retained`, so without this field
|
|
75
|
+
* `deleted + retained` silently under-counts what is actually stored and a
|
|
76
|
+
* caller printing those two numbers describes a store it cannot see all of.
|
|
77
|
+
* Empty when pruning is disabled, because that mode enumerates nothing.
|
|
78
|
+
*/
|
|
79
|
+
corrupt: string[];
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Count the report files present, without parsing or verifying any of them.
|
|
83
|
+
*
|
|
84
|
+
* This is the cheap listing primitive `/swarm status` needs: `readdir` plus a
|
|
85
|
+
* name filter, no JSON parse and no hash recomputation, so surfacing the store
|
|
86
|
+
* in an interactive command costs one syscall rather than a full re-verification
|
|
87
|
+
* of every stored report. It therefore counts CORRUPT reports too — it counts
|
|
88
|
+
* files whose name is a well-formed report id, which is exactly the claim the
|
|
89
|
+
* status line makes. Use `listConsensusReports` when you need content.
|
|
90
|
+
*/
|
|
91
|
+
export declare function countConsensusReportFiles(directory: string): Promise<number>;
|
|
92
|
+
/**
|
|
93
|
+
* Enforce `consensus.report_retention`.
|
|
94
|
+
*
|
|
95
|
+
* Deliberate posture, mirroring the evaluation substrate's retention rules:
|
|
96
|
+
* - `retain === 0` **disables** pruning rather than deleting everything. The
|
|
97
|
+
* schema's `min(0)` bound reads as "the constraint is off", the same way
|
|
98
|
+
* `default_min_successful_runs: 0` turns that gate off. Interpreting it as
|
|
99
|
+
* "keep zero reports" would delete the report the caller just wrote.
|
|
100
|
+
* - Corrupt reports are **never** deleted. An unparseable artifact is data-
|
|
101
|
+
* quality evidence; silently discarding it destroys the only trace of the bug
|
|
102
|
+
* that produced it. They are reported as `corrupt` rather than dropped
|
|
103
|
+
* silently: they are excluded from `retained` as well as from `deleted`, so a
|
|
104
|
+
* caller that printed only those two numbers would describe a store smaller
|
|
105
|
+
* than the one on disk.
|
|
106
|
+
* - Newest-first by `generatedAt`, ties broken by id, so pruning is a total
|
|
107
|
+
* order and two runs over the same store delete the same files.
|
|
108
|
+
* - Only files directly under `.swarm/evolution/consensus/` with a validated
|
|
109
|
+
* `<id>.json` name are candidates; nothing recurses (AGENTS.md invariant 4).
|
|
110
|
+
*/
|
|
111
|
+
export declare function pruneConsensusReports(directory: string, retain: number): Promise<ConsensusPruneResult>;
|
|
112
|
+
/**
|
|
113
|
+
* Every proposal fingerprint already present in the store.
|
|
114
|
+
*
|
|
115
|
+
* This is what the miner dedupes against so a standing recommendation is not
|
|
116
|
+
* re-proposed on every run. Corrupt reports are skipped: an unreadable prior
|
|
117
|
+
* report is a reason to re-propose, not to crash.
|
|
118
|
+
*/
|
|
119
|
+
export declare function listConsensusProposalFingerprints(directory: string): Promise<Set<string>>;
|