@markstanden/opencode-test-container 0.0.0-stage → 0.1.1

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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -2
  3. package/container/Containerfile +24 -0
  4. package/container/README.md +29 -0
  5. package/dist/adapters/opencode/events.d.ts +90 -0
  6. package/dist/adapters/opencode/events.js +213 -0
  7. package/dist/adapters/opencode/runtime.d.ts +87 -0
  8. package/dist/adapters/opencode/runtime.js +136 -0
  9. package/dist/adapters/podman/container.d.ts +62 -0
  10. package/dist/adapters/podman/container.js +119 -0
  11. package/dist/adapters/podman/image.d.ts +50 -0
  12. package/dist/adapters/podman/image.js +124 -0
  13. package/dist/adapters/process.d.ts +38 -0
  14. package/dist/adapters/process.js +71 -0
  15. package/dist/campaign/assess.d.ts +45 -0
  16. package/dist/campaign/assess.js +208 -0
  17. package/dist/campaign/close.d.ts +17 -0
  18. package/dist/campaign/close.js +112 -0
  19. package/dist/campaign/evidence-guard.d.ts +24 -0
  20. package/dist/campaign/evidence-guard.js +49 -0
  21. package/dist/campaign/freeze.d.ts +94 -0
  22. package/dist/campaign/freeze.js +297 -0
  23. package/dist/campaign/inventory.d.ts +62 -0
  24. package/dist/campaign/inventory.js +192 -0
  25. package/dist/campaign/judge.d.ts +209 -0
  26. package/dist/campaign/judge.js +1285 -0
  27. package/dist/campaign/ledger.d.ts +64 -0
  28. package/dist/campaign/ledger.js +149 -0
  29. package/dist/campaign/prepare.d.ts +129 -0
  30. package/dist/campaign/prepare.js +483 -0
  31. package/dist/campaign/primary.d.ts +96 -0
  32. package/dist/campaign/primary.js +189 -0
  33. package/dist/campaign/run.d.ts +151 -0
  34. package/dist/campaign/run.js +562 -0
  35. package/dist/campaign/status.d.ts +76 -0
  36. package/dist/campaign/status.js +237 -0
  37. package/dist/cli/otc.d.ts +51 -0
  38. package/dist/cli/otc.js +435 -0
  39. package/dist/contracts/canonical.d.ts +21 -0
  40. package/dist/contracts/canonical.js +44 -0
  41. package/dist/contracts/types.d.ts +261 -0
  42. package/dist/contracts/types.js +9 -0
  43. package/dist/contracts/validate.d.ts +72 -0
  44. package/dist/contracts/validate.js +1021 -0
  45. package/dist/contracts/version.d.ts +44 -0
  46. package/dist/contracts/version.js +147 -0
  47. package/dist/evaluators/docs/analysis/accounting.d.ts +140 -0
  48. package/dist/evaluators/docs/analysis/accounting.js +237 -0
  49. package/dist/evaluators/docs/analysis/blinding.d.ts +40 -0
  50. package/dist/evaluators/docs/analysis/blinding.js +80 -0
  51. package/dist/evaluators/docs/analysis/bounds.d.ts +40 -0
  52. package/dist/evaluators/docs/analysis/bounds.js +31 -0
  53. package/dist/evaluators/docs/analysis/budget.d.ts +137 -0
  54. package/dist/evaluators/docs/analysis/budget.js +305 -0
  55. package/dist/evaluators/docs/analysis/closure.d.ts +175 -0
  56. package/dist/evaluators/docs/analysis/closure.js +251 -0
  57. package/dist/evaluators/docs/analysis/decision.d.ts +59 -0
  58. package/dist/evaluators/docs/analysis/decision.js +161 -0
  59. package/dist/evaluators/docs/analysis/freeze.d.ts +201 -0
  60. package/dist/evaluators/docs/analysis/freeze.js +395 -0
  61. package/dist/evaluators/docs/analysis/guardrails.d.ts +51 -0
  62. package/dist/evaluators/docs/analysis/guardrails.js +194 -0
  63. package/dist/evaluators/docs/analysis/journal.d.ts +150 -0
  64. package/dist/evaluators/docs/analysis/journal.js +475 -0
  65. package/dist/evaluators/docs/analysis/ledger.d.ts +78 -0
  66. package/dist/evaluators/docs/analysis/ledger.js +145 -0
  67. package/dist/evaluators/docs/analysis/measures.d.ts +302 -0
  68. package/dist/evaluators/docs/analysis/measures.js +1069 -0
  69. package/dist/evaluators/docs/analysis/model.d.ts +236 -0
  70. package/dist/evaluators/docs/analysis/model.js +740 -0
  71. package/dist/evaluators/docs/analysis/pairing.d.ts +68 -0
  72. package/dist/evaluators/docs/analysis/pairing.js +95 -0
  73. package/dist/evaluators/docs/analysis/records.d.ts +229 -0
  74. package/dist/evaluators/docs/analysis/records.js +935 -0
  75. package/dist/evaluators/docs/analysis/recovery.d.ts +225 -0
  76. package/dist/evaluators/docs/analysis/recovery.js +507 -0
  77. package/dist/evaluators/docs/analysis/render.d.ts +235 -0
  78. package/dist/evaluators/docs/analysis/render.js +566 -0
  79. package/dist/evaluators/docs/analysis/replay.d.ts +15 -0
  80. package/dist/evaluators/docs/analysis/replay.js +1272 -0
  81. package/dist/evaluators/docs/analysis/screening/inventory.d.ts +136 -0
  82. package/dist/evaluators/docs/analysis/screening/inventory.js +307 -0
  83. package/dist/evaluators/docs/analysis/screening/noteVersion.d.ts +15 -0
  84. package/dist/evaluators/docs/analysis/screening/noteVersion.js +46 -0
  85. package/dist/evaluators/docs/analysis/screening/reconcile.d.ts +38 -0
  86. package/dist/evaluators/docs/analysis/screening/reconcile.js +40 -0
  87. package/dist/evaluators/docs/analysis/screening/rules.d.ts +163 -0
  88. package/dist/evaluators/docs/analysis/screening/rules.js +330 -0
  89. package/dist/evaluators/docs/analysis/screening/scan.d.ts +72 -0
  90. package/dist/evaluators/docs/analysis/screening/scan.js +362 -0
  91. package/dist/evaluators/docs/analysis/selector.d.ts +68 -0
  92. package/dist/evaluators/docs/analysis/selector.js +123 -0
  93. package/dist/evaluators/docs/assess.d.ts +129 -0
  94. package/dist/evaluators/docs/assess.js +433 -0
  95. package/dist/evaluators/docs/campaign.d.ts +133 -0
  96. package/dist/evaluators/docs/campaign.js +222 -0
  97. package/dist/evaluators/docs/index.d.ts +25 -0
  98. package/dist/evaluators/docs/index.js +25 -0
  99. package/dist/evaluators/docs/journal.d.ts +150 -0
  100. package/dist/evaluators/docs/journal.js +475 -0
  101. package/dist/evaluators/docs/scoring.d.ts +367 -0
  102. package/dist/evaluators/docs/scoring.js +662 -0
  103. package/dist/evaluators/skills/assess.d.ts +94 -0
  104. package/dist/evaluators/skills/assess.js +207 -0
  105. package/dist/index.d.ts +46 -0
  106. package/dist/index.js +29 -0
  107. package/dist/legacy/prod014.d.ts +86 -0
  108. package/dist/legacy/prod014.js +209 -0
  109. package/dist/reporting/report.d.ts +79 -0
  110. package/dist/reporting/report.js +242 -0
  111. package/docs/architecture.md +106 -0
  112. package/docs/capability-matrix.md +84 -0
  113. package/docs/offline-rehearsal.md +75 -0
  114. package/docs/parity-record.md +81 -0
  115. package/docs/pr-description.md +103 -0
  116. package/docs/release-0.1.0.md +52 -0
  117. package/docs/releasing.md +82 -0
  118. package/docs/schema.md +97 -0
  119. package/examples/skills/README.md +13 -0
  120. package/examples/skills/campaign.json +73 -0
  121. package/examples/skills/skills/otel-helper/SKILL.md +10 -0
  122. package/examples/skills/suite.json +29 -0
  123. package/package.json +45 -5
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Static-screening orchestration for one recorded trial.
3
+ *
4
+ * Reads the resulting documents, before/after states, the final response, the
5
+ * task prompt and the recorded writes, applies the configured rules and
6
+ * returns findings with hashed, root-relative sources and trial provenance.
7
+ * Every input is a {@link Loaded} record: a rule runs only with sufficient
8
+ * evidence, so an unobserved change can never be published as a mechanical
9
+ * contradiction. The screen is advisory infrastructure: it never modifies
10
+ * judge inputs, eligibility, outcomes, scores or guardrails, and it is
11
+ * generated independently of any judge packet.
12
+ */
13
+ import { docChanges, } from "../../campaign.js";
14
+ import { claimMutationFindings, duplicationFindings, permissionFindings, sourceOf, structureFindings, transientWordingFindings, SCREENING_RULES_VERSION, } from "./rules.js";
15
+ import { scanInventory } from "./inventory.js";
16
+ /** Map one source kind to the evidence role it plays in a trial. */
17
+ const ROLE_OF = {
18
+ "before-document": "before",
19
+ "after-document": "after",
20
+ "resulting-document": "after",
21
+ "final-response": "response",
22
+ "task-prompt": "prompt",
23
+ "recorded-write": "writes",
24
+ };
25
+ /**
26
+ * Rewrite one source's tree-relative reference to its root-relative identity.
27
+ * Identity is positional, never content-derived: two trials may hold identical
28
+ * final answers and still cite distinguishable sources.
29
+ * @param source The source with a tree-relative or label reference.
30
+ * @param paths The trial's root-relative references.
31
+ * @returns The source with a root-relative reference.
32
+ */
33
+ function rooted(source, paths) {
34
+ switch (source.kind) {
35
+ case "before-document":
36
+ return {
37
+ ...source,
38
+ reference: `${paths.beforeRoot}/${source.reference}`,
39
+ };
40
+ case "after-document":
41
+ case "resulting-document":
42
+ return {
43
+ ...source,
44
+ reference: `${paths.afterRoot}/${source.reference}`,
45
+ };
46
+ case "final-response":
47
+ return { ...source, reference: paths.finalAnswer };
48
+ case "task-prompt":
49
+ return { ...source, reference: paths.prompt };
50
+ case "recorded-write":
51
+ return { ...source, reference: paths.writes };
52
+ }
53
+ }
54
+ /**
55
+ * Attach the trial identity, evidence role and root-relative source to one
56
+ * rule-produced finding.
57
+ * @param draft The rule finding.
58
+ * @param input The screen input.
59
+ * @returns The finding with provenance.
60
+ */
61
+ function withProvenance(draft, input) {
62
+ return {
63
+ ...draft,
64
+ trial: input.trial,
65
+ role: ROLE_OF[draft.source.kind],
66
+ source: rooted(draft.source, input.paths),
67
+ };
68
+ }
69
+ /**
70
+ * Convert one inventory trigger into a finding.
71
+ * @param trigger The trigger with its spans.
72
+ * @param source The scanned document's source record.
73
+ * @param scenario The scenario id.
74
+ * @returns The finding.
75
+ */
76
+ function triggerFinding(trigger, source, scenario) {
77
+ const base = {
78
+ ruleVersion: SCREENING_RULES_VERSION,
79
+ applicability: scenario,
80
+ authority: "mechanical",
81
+ source,
82
+ excerpt: trigger.mention.text,
83
+ span: { start: trigger.mention.start, end: trigger.mention.end },
84
+ factId: trigger.factId,
85
+ };
86
+ if (trigger.kind === "obsolete-current") {
87
+ return {
88
+ ...base,
89
+ ruleId: "fact-obsolete-current",
90
+ category: "fact-obsolete-current",
91
+ explanation: "the configured obsolete claim is presented as current; no local negation qualifies it",
92
+ };
93
+ }
94
+ if (trigger.kind === "conflict") {
95
+ return {
96
+ ...base,
97
+ ruleId: "fact-conflict",
98
+ category: "fact-conflict",
99
+ explanation: `the document asserts and locally negates the same fact; opposing assertion: ${JSON.stringify(trigger.other?.assertion ?? "")}`,
100
+ };
101
+ }
102
+ return {
103
+ ...base,
104
+ ruleId: "fact-negation",
105
+ category: "fact-negation",
106
+ explanation: `a local negation cue ${JSON.stringify(trigger.negation?.text ?? "")} applies to this mention; a local cue does not by itself prove semantic loss`,
107
+ };
108
+ }
109
+ /**
110
+ * Convert one unrecognised inventory fact into an advisory finding.
111
+ * @param factId The fact id.
112
+ * @param factLabel The configured label.
113
+ * @param source The scanned document's source record.
114
+ * @param scenario The scenario id.
115
+ * @returns The finding.
116
+ */
117
+ function coverageFinding(factId, factLabel, source, scenario) {
118
+ return {
119
+ ruleId: "fact-coverage-unknown",
120
+ ruleVersion: SCREENING_RULES_VERSION,
121
+ category: "fact-coverage-unknown",
122
+ applicability: scenario,
123
+ authority: "advisory",
124
+ source,
125
+ excerpt: "",
126
+ span: { start: 0, end: 0 },
127
+ factId,
128
+ explanation: `no recognised mention of ${JSON.stringify(factLabel)}; a missing keyword does not prove loss, and an unrecognised paraphrase remains possible`,
129
+ };
130
+ }
131
+ /**
132
+ * Read every available source the screen consults, hashed, rooted and sorted.
133
+ * A missing input contributes no source, so no hash can imply it was read.
134
+ * @param input The screen input.
135
+ * @returns The source records.
136
+ */
137
+ function sourcesOf(input) {
138
+ const sources = [];
139
+ const add = (source) => {
140
+ sources.push(rooted(source, input.paths));
141
+ };
142
+ if (input.finalAnswer.state === "available") {
143
+ add(sourceOf("final-response", "final.md", input.finalAnswer.value));
144
+ }
145
+ if (input.taskPrompt.state === "available") {
146
+ add(sourceOf("task-prompt", "prompt.md", input.taskPrompt.value));
147
+ }
148
+ if (input.writesText.state === "available") {
149
+ add(sourceOf("recorded-write", "writes.txt", input.writesText.value));
150
+ }
151
+ if (input.beforeDocs.state === "available") {
152
+ for (const doc of input.beforeDocs.value) {
153
+ add(sourceOf("before-document", doc.path, doc.contents));
154
+ }
155
+ }
156
+ if (input.afterDocs.state === "available") {
157
+ for (const doc of input.afterDocs.value) {
158
+ add(sourceOf("after-document", doc.path, doc.contents));
159
+ }
160
+ }
161
+ return sources.sort((left, right) => left.reference.localeCompare(right.reference) ||
162
+ left.kind.localeCompare(right.kind));
163
+ }
164
+ /** One recorded change line: verb, path and artefact kind. */
165
+ const WRITE_LINE = /^(?:added|modified|removed|created|deleted): (.+?) \([a-z]+\)$/;
166
+ /**
167
+ * Extract changed paths from a recorded writes report. Only the report's
168
+ * change lines count: summary headers and repetition sections describe the
169
+ * same changes again and must not become phantom paths.
170
+ * @param writesText The report text.
171
+ * @returns The distinct changed paths, in report order.
172
+ */
173
+ function changedPathsOf(writesText) {
174
+ const paths = new Set();
175
+ for (const line of writesText.split("\n")) {
176
+ const match = WRITE_LINE.exec(line.trim());
177
+ if (match?.[1] !== undefined)
178
+ paths.add(match[1]);
179
+ }
180
+ return [...paths];
181
+ }
182
+ /**
183
+ * Transient-wording findings over the answer and every changed document. The
184
+ * answer check needs the answer; the document check needs the after state.
185
+ * @param input The screen input.
186
+ * @param changedDocs The new or modified documents.
187
+ * @returns Advisory findings.
188
+ */
189
+ function wordingFindings(input, changedDocs) {
190
+ const patterns = input.transientPatterns ?? [];
191
+ if (patterns.length === 0)
192
+ return [];
193
+ const counterexamples = input.durableCounterexamples ?? [];
194
+ const findings = [];
195
+ if (input.finalAnswer.state === "available") {
196
+ findings.push(...transientWordingFindings({
197
+ text: input.finalAnswer.value,
198
+ reference: "final.md",
199
+ kind: "final-response",
200
+ scenario: input.trial.scenario,
201
+ patterns,
202
+ counterexamples,
203
+ }));
204
+ }
205
+ for (const doc of changedDocs) {
206
+ findings.push(...transientWordingFindings({
207
+ text: doc.contents,
208
+ reference: doc.path,
209
+ kind: "resulting-document",
210
+ scenario: input.trial.scenario,
211
+ patterns,
212
+ counterexamples,
213
+ }));
214
+ }
215
+ return findings;
216
+ }
217
+ /**
218
+ * Permission findings, when the campaign records a policy and the writes are
219
+ * observable. Without either, out-of-policy mutation is unknown, not clean.
220
+ * @param input The screen input.
221
+ * @returns Mechanical findings for out-of-policy paths.
222
+ */
223
+ function permissionFindingList(input) {
224
+ const policy = input.mutationPolicy;
225
+ if (policy === undefined || policy.allowed.length === 0)
226
+ return [];
227
+ if (input.writesText.state !== "available")
228
+ return [];
229
+ return permissionFindings({
230
+ changedPaths: changedPathsOf(input.writesText.value),
231
+ allowedGlobs: policy.allowed,
232
+ taskPrompt: input.taskPrompt.state === "available"
233
+ ? input.taskPrompt.value
234
+ : null,
235
+ visibleConstraints: policy.visibleConstraints,
236
+ writesText: input.writesText.value,
237
+ reference: "writes.txt",
238
+ scenario: input.trial.scenario,
239
+ });
240
+ }
241
+ /**
242
+ * Inventory findings over every candidate document.
243
+ * @param input The screen input.
244
+ * @param candidates The documents to scan.
245
+ * @returns Trigger and coverage-unknown findings.
246
+ */
247
+ function inventoryFindingList(input, candidates) {
248
+ const inventory = input.inventory;
249
+ if (inventory === undefined)
250
+ return [];
251
+ return candidates.flatMap((doc) => {
252
+ const source = sourceOf("resulting-document", doc.path, doc.contents);
253
+ const scan = scanInventory(doc.contents, inventory);
254
+ const triggerList = scan.triggers.map((trigger) => triggerFinding(trigger, source, input.trial.scenario));
255
+ const coverageList = scan.missing.map((factId) => {
256
+ const fact = inventory.facts.find((entry) => entry.id === factId);
257
+ return coverageFinding(factId, fact?.label ?? factId, source, input.trial.scenario);
258
+ });
259
+ return [...triggerList, ...coverageList];
260
+ });
261
+ }
262
+ /**
263
+ * The documents a resulting-document rule should describe. With the after
264
+ * state observable but the before state absent, change is unknown and every
265
+ * after document is a candidate; with both states, only new or modified ones.
266
+ * @param before The before state, or null when unavailable.
267
+ * @param after The after state, or null when unavailable.
268
+ * @returns The candidate documents, or null when the after state is missing.
269
+ */
270
+ function candidatesOf(before, after) {
271
+ if (after === null)
272
+ return null;
273
+ if (before === null)
274
+ return [...after];
275
+ const beforeByPath = new Map(before.map((doc) => [doc.path, doc.contents]));
276
+ return after.filter((doc) => beforeByPath.get(doc.path) !== doc.contents);
277
+ }
278
+ /**
279
+ * Structural findings, only when the resulting documents are observable.
280
+ * @param input The screen input.
281
+ * @param candidates The candidate documents, or null.
282
+ * @returns Structural findings.
283
+ */
284
+ function structureFor(input, candidates) {
285
+ if (candidates === null)
286
+ return [];
287
+ return candidates.flatMap((doc) => structureFindings(doc, input.trial.scenario));
288
+ }
289
+ /**
290
+ * Claimed-mutation contradictions, only when both states are observable.
291
+ * @param input The screen input.
292
+ * @param before The before state, or null.
293
+ * @param after The after state, or null.
294
+ * @param answer The final answer, or null.
295
+ * @returns Contradiction findings.
296
+ */
297
+ function mutationFor(input, before, after, answer) {
298
+ if (before === null || after === null || answer === null)
299
+ return [];
300
+ return claimMutationFindings({
301
+ answer,
302
+ changes: docChanges([...before], [...after]),
303
+ reference: "final.md",
304
+ scenario: input.trial.scenario,
305
+ });
306
+ }
307
+ /**
308
+ * Duplication findings, only when both states are observable.
309
+ * @param input The screen input.
310
+ * @param before The before state, or null.
311
+ * @param after The after state, or null.
312
+ * @returns Duplication findings.
313
+ */
314
+ function duplicationFor(input, before, after) {
315
+ if (before === null || after === null)
316
+ return [];
317
+ return duplicationFindings([...before], [...after], input.trial.scenario);
318
+ }
319
+ /**
320
+ * Inventory findings, only when the resulting documents are observable.
321
+ * @param input The screen input.
322
+ * @param candidates The candidate documents, or null.
323
+ * @returns Inventory findings.
324
+ */
325
+ function inventoryFor(input, candidates) {
326
+ if (candidates === null)
327
+ return [];
328
+ return inventoryFindingList(input, candidates);
329
+ }
330
+ /**
331
+ * Screen one recorded trial. Each rule sees only the evidence it needs: the
332
+ * after state for resulting-document rules, both states for mutation and
333
+ * duplication, the answer for response wording, and the writes plus a
334
+ * recorded policy for permissions.
335
+ * @param input Everything the screen reads.
336
+ * @returns Findings with provenance, hashed sources and the change state.
337
+ */
338
+ export function screenTrial(input) {
339
+ const before = input.beforeDocs.state === "available" ? input.beforeDocs.value : null;
340
+ const after = input.afterDocs.state === "available" ? input.afterDocs.value : null;
341
+ const answer = input.finalAnswer.state === "available"
342
+ ? input.finalAnswer.value
343
+ : null;
344
+ const changes = before !== null && after !== null
345
+ ? docChanges([...before], [...after])
346
+ : null;
347
+ const candidates = candidatesOf(before, after);
348
+ const drafts = [
349
+ ...structureFor(input, candidates),
350
+ ...mutationFor(input, before, after, answer),
351
+ ...duplicationFor(input, before, after),
352
+ ...wordingFindings(input, candidates ?? []),
353
+ ...permissionFindingList(input),
354
+ ...inventoryFor(input, candidates),
355
+ ];
356
+ const findings = drafts
357
+ .map((draft) => withProvenance(draft, input))
358
+ .sort((left, right) => left.source.reference.localeCompare(right.source.reference) ||
359
+ left.span.start - right.span.start ||
360
+ left.ruleId.localeCompare(right.ruleId));
361
+ return { findings, sources: sourcesOf(input), changes };
362
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * One frozen attempt selection shared by every consumer.
3
+ *
4
+ * The campaign manifest is the single declared source of which judge attempt
5
+ * the trial, judge, resolver, continuation and analyser read. A command-line
6
+ * override may agree with it or be absent; it may never contradict it. That
7
+ * refusal happens before a spawn or a ledger write, so a conflicting flag
8
+ * cannot spend money or mutate evidence.
9
+ */
10
+ /** Where a selection came from. */
11
+ export type SelectionSource = "default" | "manifest" | "override";
12
+ /** One resolved attempt selection. */
13
+ export interface AttemptSelection {
14
+ /** The selected attempt directory name, e.g. `attempt-002`. */
15
+ attemptId: string;
16
+ /** Which declaration produced it. */
17
+ source: SelectionSource;
18
+ }
19
+ /** The declarations a caller supplies. */
20
+ export interface SelectionInput {
21
+ /** The manifest's `assessment.attemptId`, when declared. */
22
+ manifestAttemptId?: string | null | undefined;
23
+ /** A command-line override, when supplied. */
24
+ overrideAttemptId?: string | null | undefined;
25
+ }
26
+ /** The selection outcome. */
27
+ export type SelectionResolution = {
28
+ ok: true;
29
+ selection: AttemptSelection;
30
+ } | {
31
+ ok: false;
32
+ problems: string[];
33
+ };
34
+ /**
35
+ * Whether an attempt id has the accepted shape.
36
+ * @param attemptId The candidate id.
37
+ * @returns True for `attempt-NNN`.
38
+ */
39
+ export declare function isAttemptId(attemptId: string): boolean;
40
+ /** The default attempt id when nothing is declared. */
41
+ export declare const DEFAULT_ATTEMPT_ID = "attempt-001";
42
+ /**
43
+ * Resolve the single attempt selection.
44
+ *
45
+ * Precedence is: a valid manifest declaration wins; an override may agree with
46
+ * it but never contradict it. With no manifest declaration the selection
47
+ * defaults to `attempt-001`, unless the caller supplies an override — the
48
+ * legacy-freeze recovery path, where the frozen record names no attempt and an
49
+ * operator selects one explicitly. An invalid id or a contradiction returns
50
+ * problems and no selection, so callers refuse rather than guess.
51
+ * @param input The manifest declaration and any override.
52
+ * @returns The selection, or every problem found.
53
+ */
54
+ export declare function resolveAttemptSelection(input: SelectionInput): SelectionResolution;
55
+ /**
56
+ * The selection a recovery run may use. Recovery is a separate authorised
57
+ * budget and a separately identified analysis selection; it must never reuse
58
+ * the normal selection's attempt directory, and it must not rewrite the
59
+ * original freeze.
60
+ * @param input The manifest declaration and any recovery override.
61
+ * @param input.manifestAttemptId The frozen manifest selection, or null.
62
+ * @param input.recoveryAttemptId The explicit recovery selection.
63
+ * @returns The selection, or the refusal.
64
+ */
65
+ export declare function resolveRecoverySelection(input: {
66
+ manifestAttemptId?: string | null;
67
+ recoveryAttemptId?: string | null;
68
+ }): SelectionResolution;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * One frozen attempt selection shared by every consumer.
3
+ *
4
+ * The campaign manifest is the single declared source of which judge attempt
5
+ * the trial, judge, resolver, continuation and analyser read. A command-line
6
+ * override may agree with it or be absent; it may never contradict it. That
7
+ * refusal happens before a spawn or a ledger write, so a conflicting flag
8
+ * cannot spend money or mutate evidence.
9
+ */
10
+ /** The attempt id shape every consumer accepts. */
11
+ const ATTEMPT_PATTERN = /^attempt-\d{3}$/;
12
+ /**
13
+ * Whether an attempt id has the accepted shape.
14
+ * @param attemptId The candidate id.
15
+ * @returns True for `attempt-NNN`.
16
+ */
17
+ export function isAttemptId(attemptId) {
18
+ return ATTEMPT_PATTERN.test(attemptId);
19
+ }
20
+ /** The default attempt id when nothing is declared. */
21
+ export const DEFAULT_ATTEMPT_ID = "attempt-001";
22
+ /**
23
+ * Resolve the single attempt selection.
24
+ *
25
+ * Precedence is: a valid manifest declaration wins; an override may agree with
26
+ * it but never contradict it. With no manifest declaration the selection
27
+ * defaults to `attempt-001`, unless the caller supplies an override — the
28
+ * legacy-freeze recovery path, where the frozen record names no attempt and an
29
+ * operator selects one explicitly. An invalid id or a contradiction returns
30
+ * problems and no selection, so callers refuse rather than guess.
31
+ * @param input The manifest declaration and any override.
32
+ * @returns The selection, or every problem found.
33
+ */
34
+ export function resolveAttemptSelection(input) {
35
+ const manifest = input.manifestAttemptId ?? null;
36
+ const override = input.overrideAttemptId ?? null;
37
+ const problems = selectionProblems(manifest, override);
38
+ if (problems.length > 0)
39
+ return { ok: false, problems };
40
+ if (manifest !== null) {
41
+ return {
42
+ ok: true,
43
+ selection: { attemptId: manifest, source: "manifest" },
44
+ };
45
+ }
46
+ if (override !== null) {
47
+ return {
48
+ ok: true,
49
+ selection: { attemptId: override, source: "override" },
50
+ };
51
+ }
52
+ return {
53
+ ok: true,
54
+ selection: { attemptId: DEFAULT_ATTEMPT_ID, source: "default" },
55
+ };
56
+ }
57
+ /**
58
+ * Validate the declared ids and their agreement.
59
+ * @param manifest The manifest attempt id, or null.
60
+ * @param override The override attempt id, or null.
61
+ * @returns Problems; empty means the selection is unambiguous.
62
+ */
63
+ function selectionProblems(manifest, override) {
64
+ const problems = [];
65
+ if (manifest !== null && !isAttemptId(manifest)) {
66
+ problems.push(`manifest attemptId ${JSON.stringify(manifest)} must match attempt-NNN`);
67
+ }
68
+ if (override !== null && !isAttemptId(override)) {
69
+ problems.push(`override attemptId ${JSON.stringify(override)} must match attempt-NNN`);
70
+ }
71
+ if (manifest !== null && override !== null && manifest !== override) {
72
+ problems.push(`conflicting attempt selections: manifest ${manifest} and override ${override}`);
73
+ }
74
+ return problems;
75
+ }
76
+ /**
77
+ * The selection a recovery run may use. Recovery is a separate authorised
78
+ * budget and a separately identified analysis selection; it must never reuse
79
+ * the normal selection's attempt directory, and it must not rewrite the
80
+ * original freeze.
81
+ * @param input The manifest declaration and any recovery override.
82
+ * @param input.manifestAttemptId The frozen manifest selection, or null.
83
+ * @param input.recoveryAttemptId The explicit recovery selection.
84
+ * @returns The selection, or the refusal.
85
+ */
86
+ export function resolveRecoverySelection(input) {
87
+ const manifest = input.manifestAttemptId ?? null;
88
+ const recovery = input.recoveryAttemptId ?? null;
89
+ if (recovery === null) {
90
+ return {
91
+ ok: false,
92
+ problems: ["recovery requires an explicit attempt id"],
93
+ };
94
+ }
95
+ if (!isAttemptId(recovery)) {
96
+ return {
97
+ ok: false,
98
+ problems: [
99
+ `recovery attemptId ${JSON.stringify(recovery)} must match attempt-NNN`,
100
+ ],
101
+ };
102
+ }
103
+ if (manifest !== null && ordinal(recovery) <= ordinal(manifest)) {
104
+ return {
105
+ ok: false,
106
+ problems: [
107
+ `recovery selection ${recovery} must be a later attempt than the frozen manifest selection ${manifest}`,
108
+ ],
109
+ };
110
+ }
111
+ return {
112
+ ok: true,
113
+ selection: { attemptId: recovery, source: "override" },
114
+ };
115
+ }
116
+ /**
117
+ * The numeric ordinal of an attempt id.
118
+ * @param attemptId An id of the accepted shape.
119
+ * @returns The parsed number.
120
+ */
121
+ function ordinal(attemptId) {
122
+ return Number(attemptId.slice("attempt-".length));
123
+ }
@@ -0,0 +1,129 @@
1
+ import type { SuiteCase } from "../../contracts/types.ts";
2
+ import { type Usage } from "../../adapters/opencode/events.ts";
3
+ import { type JournalVerification } from "./journal.ts";
4
+ import { type PrimaryCheckRecord } from "../../campaign/primary.ts";
5
+ /** The one docs case shape. */
6
+ type DocsCase = Extract<SuiteCase, {
7
+ kind: "docs-task";
8
+ }>;
9
+ /** The evidence states the deterministic docs evaluator can establish. */
10
+ export type DocsValidity = "valid" | "invalid" | "unknown" | "unrun";
11
+ /** The deterministic docs criterion verdict. */
12
+ export type DocsOutcome = "pass" | "fail" | "unknown";
13
+ /** The docs operation a journal record proves. */
14
+ export type DocsVerb = "search" | "add" | "read" | "check" | "help" | "other";
15
+ /** Exact counts, with attempted and verified writes kept distinct. */
16
+ export interface DocsCounts {
17
+ /** The credited calls value. */
18
+ creditedCalls: number;
19
+ /** The credited writes value. */
20
+ creditedWrites: number;
21
+ /** The attempted writes value. */
22
+ attemptedWrites: number;
23
+ /** The rejected lines value. */
24
+ rejectedLines: number;
25
+ /** The search calls value. */
26
+ searchCalls: number;
27
+ }
28
+ /** The deterministic assessment of one docs-recording trial. */
29
+ export interface DocsAssessment {
30
+ /** The schema version value. */
31
+ schemaVersion: 1;
32
+ /** The kind value. */
33
+ kind: "docs-recording";
34
+ /** The campaign id value. */
35
+ campaignId: string;
36
+ /** The slot id value. */
37
+ slotId: string;
38
+ /** The case id value. */
39
+ caseId: string;
40
+ /** The arm id value. */
41
+ armId: string;
42
+ /** The repetition id value. */
43
+ repetitionId: string;
44
+ /** The attempt id value. */
45
+ attemptId: string;
46
+ /** The expected value. */
47
+ expected: DocsCase["expectedDocs"]["kind"];
48
+ /** The validity value. */
49
+ validity: DocsValidity;
50
+ /** The outcome value. */
51
+ outcome: DocsOutcome;
52
+ /** The counts value. */
53
+ counts: DocsCounts;
54
+ /** The journal value. */
55
+ journal: {
56
+ state: JournalVerification["state"];
57
+ credited: number;
58
+ rejected: number;
59
+ problems: string[];
60
+ };
61
+ /** The target value. */
62
+ target: string | null;
63
+ /** The independent primary-check result, or null when none was declared. */
64
+ primary: {
65
+ id: string;
66
+ status: PrimaryCheckRecord["status"];
67
+ exit: number | null;
68
+ } | null;
69
+ /** The reasons value. */
70
+ reasons: string[];
71
+ /** The usage value. */
72
+ usage: Usage | null;
73
+ /** The answered value. */
74
+ answered: boolean;
75
+ /** The assessed at value. */
76
+ assessedAt: string;
77
+ }
78
+ /** Determine which docs verb one host input names. */
79
+ export declare function docsVerbOf(input: unknown): DocsVerb;
80
+ /** Whether a credited record proves a completed write and where. */
81
+ export declare function creditedWriteOf(record: Record<string, unknown>): {
82
+ path: string | null;
83
+ mode: string | null;
84
+ } | null;
85
+ /**
86
+ * Assess one docs-recording trial from the signed journal and the uploaded
87
+ * workspace. The journal is the only route that credits a served operation;
88
+ * an attempted but refused write retains zero recording credit.
89
+ * @param options Trial identity, case definition, evidence and workspace.
90
+ * @returns The assessment.
91
+ * @param options.campaignId The campaignId value.
92
+ * @param options.slotId The slotId value.
93
+ * @param options.caseId The caseId value.
94
+ * @param options.armId The armId value.
95
+ * @param options.repetitionId The repetitionId value.
96
+ * @param options.testCase The testCase value.
97
+ * @param options.trialDir The trialDir value.
98
+ * @param options.workspace The workspace value.
99
+ * @param options.prepared The prepared value.
100
+ * @param options.attemptId The attemptId value.
101
+ * @param options.assessedAt The assessedAt value.
102
+ */
103
+ export declare function assessDocsTrial(options: {
104
+ campaignId: string;
105
+ slotId: string;
106
+ caseId: string;
107
+ armId: string;
108
+ repetitionId: string;
109
+ testCase: DocsCase;
110
+ trialDir: string;
111
+ workspace: string;
112
+ prepared: ReadonlyMap<string, string>;
113
+ attemptId: string;
114
+ assessedAt: string;
115
+ }): DocsAssessment;
116
+ /** The compact journal summary a report carries. */
117
+ export declare function journalSummary(verification: JournalVerification): DocsAssessment["journal"];
118
+ /**
119
+ * The judge-visible docs operations for one trial: credited journal records
120
+ * only. An unattributable journal yields no lines and an explicit problem.
121
+ * @param trialDir The trial evidence directory.
122
+ * @returns The rendered operations and the verification state.
123
+ */
124
+ export declare function creditedOperationLines(trialDir: string): {
125
+ state: JournalVerification["state"];
126
+ lines: string[];
127
+ problems: string[];
128
+ };
129
+ export {};