@open-agent-toolkit/cli 0.2.76 → 0.2.79

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 (38) hide show
  1. package/assets/agents/recon-worker.md +12 -12
  2. package/assets/bundle-metadata.json +1 -1
  3. package/assets/docs/workflows/skills/recon.md +53 -27
  4. package/assets/public-package-versions.json +4 -4
  5. package/assets/skills/explainer-kit/SKILL.md +1 -1
  6. package/assets/skills/explainer-kit/scripts/bundle.mjs +26 -2
  7. package/assets/skills/explainer-kit/scripts/record.mjs +26 -3
  8. package/assets/skills/explainer-kit/scripts/verify.mjs +26 -3
  9. package/assets/skills/oat-explainer-kit/SKILL.md +1 -1
  10. package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +25 -5
  11. package/assets/skills/oat-project-complete/SKILL.md +1 -1
  12. package/assets/skills/oat-project-complete/scripts/consume-persisted-recap-intent.mjs +26 -6
  13. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +26 -6
  14. package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +7 -0
  15. package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +26 -5
  16. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +26 -5
  17. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +26 -6
  18. package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +26 -6
  19. package/assets/skills/oat-project-complete/scripts/resolve-synced-archive-entry.mjs +26 -5
  20. package/assets/skills/oat-project-complete/scripts/validate-durable-archive-receipt.mjs +7 -0
  21. package/assets/skills/oat-project-complete/scripts/validate-nonarchive-lifecycle-receipt.mjs +126 -95
  22. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  23. package/assets/skills/oat-project-implement/scripts/capture-dirty-tree.mjs +3 -0
  24. package/assets/skills/recon/SKILL.md +37 -19
  25. package/assets/skills/recon/references/packet-contract.md +33 -30
  26. package/assets/skills/recon/references/profiles.md +18 -15
  27. package/assets/skills/recon/references/worker-contract.md +87 -7
  28. package/assets/skills/recon/scripts/create-review-brief.mjs +2 -5
  29. package/assets/skills/recon/scripts/lib/cli-entry.mjs +21 -0
  30. package/assets/skills/recon/scripts/lib/contracts.mjs +144 -38
  31. package/assets/skills/recon/scripts/lib/routing.mjs +2 -5
  32. package/assets/skills/recon/scripts/lib/validated-run.mjs +1 -1
  33. package/assets/skills/recon/scripts/prepare-routing.mjs +2 -2
  34. package/assets/skills/recon/scripts/reconcile-ledger.mjs +269 -2
  35. package/assets/skills/recon/scripts/render-packet.mjs +2 -5
  36. package/assets/skills/recon/scripts/validate-artifact.mjs +2 -5
  37. package/assets/skills/recon/scripts/validate-packet.mjs +73 -23
  38. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
1
  import { execFile } from 'node:child_process';
2
- import { basename } from 'node:path';
3
- import { pathToFileURL } from 'node:url';
2
+ import { realpathSync } from 'node:fs';
3
+ import { basename, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
  import { promisify } from 'node:util';
5
6
 
6
7
  const execFileAsync = promisify(execFile);
@@ -692,10 +693,29 @@ async function main(argv) {
692
693
  return recoverCompletionReceipts(options);
693
694
  }
694
695
 
695
- if (
696
- process.argv[1] &&
697
- import.meta.url === pathToFileURL(process.argv[1]).href
698
- ) {
696
+ /**
697
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
698
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
699
+ * that exits 0 whenever the skill is reached through a symlinked install root,
700
+ * and canonicalizing only one side has the same effect under
701
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
702
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
703
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
704
+ * `false`; it never masks a direct run.
705
+ */
706
+ function isDirectInvocation(invokedPath) {
707
+ if (!invokedPath) return false;
708
+ try {
709
+ return (
710
+ realpathSync(fileURLToPath(import.meta.url)) ===
711
+ realpathSync(resolve(invokedPath))
712
+ );
713
+ } catch {
714
+ return false;
715
+ }
716
+ }
717
+
718
+ if (isDirectInvocation(process.argv[1])) {
699
719
  main(process.argv.slice(2))
700
720
  .then((result) => {
701
721
  process.stdout.write(`${JSON.stringify(result)}\n`);
@@ -1,6 +1,7 @@
1
+ import { realpathSync } from 'node:fs';
1
2
  import { lstat, readdir } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { pathToFileURL } from 'node:url';
3
+ import { join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
 
5
6
  import {
6
7
  detectCompletionReceiptCandidate,
@@ -116,10 +117,29 @@ async function main(argv) {
116
117
  return resolveCompletionRetry(parseArguments(argv));
117
118
  }
118
119
 
119
- if (
120
- process.argv[1] &&
121
- import.meta.url === pathToFileURL(process.argv[1]).href
122
- ) {
120
+ /**
121
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
122
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
123
+ * that exits 0 whenever the skill is reached through a symlinked install root,
124
+ * and canonicalizing only one side has the same effect under
125
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
126
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
127
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
128
+ * `false`; it never masks a direct run.
129
+ */
130
+ function isDirectInvocation(invokedPath) {
131
+ if (!invokedPath) return false;
132
+ try {
133
+ return (
134
+ realpathSync(fileURLToPath(import.meta.url)) ===
135
+ realpathSync(resolve(invokedPath))
136
+ );
137
+ } catch {
138
+ return false;
139
+ }
140
+ }
141
+
142
+ if (isDirectInvocation(process.argv[1])) {
123
143
  main(process.argv.slice(2))
124
144
  .then((result) => {
125
145
  process.stdout.write(`${JSON.stringify(result)}\n`);
@@ -1,6 +1,8 @@
1
1
  import { execFile as execFileCallback } from 'node:child_process';
2
+ import { realpathSync } from 'node:fs';
2
3
  import { readFile } from 'node:fs/promises';
3
- import { pathToFileURL } from 'node:url';
4
+ import { resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
4
6
  import { promisify } from 'node:util';
5
7
 
6
8
  const execFile = promisify(execFileCallback);
@@ -149,10 +151,29 @@ async function main(argv) {
149
151
  return resolveSyncedArchiveEntry({ ...options, record });
150
152
  }
151
153
 
152
- if (
153
- process.argv[1] &&
154
- import.meta.url === pathToFileURL(process.argv[1]).href
155
- ) {
154
+ /**
155
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
156
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
157
+ * that exits 0 whenever the skill is reached through a symlinked install root,
158
+ * and canonicalizing only one side has the same effect under
159
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
160
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
161
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
162
+ * `false`; it never masks a direct run.
163
+ */
164
+ function isDirectInvocation(invokedPath) {
165
+ if (!invokedPath) return false;
166
+ try {
167
+ return (
168
+ realpathSync(fileURLToPath(import.meta.url)) ===
169
+ realpathSync(resolve(invokedPath))
170
+ );
171
+ } catch {
172
+ return false;
173
+ }
174
+ }
175
+
176
+ if (isDirectInvocation(process.argv[1])) {
156
177
  main(process.argv.slice(2))
157
178
  .then((result) => process.stdout.write(`${JSON.stringify(result)}\n`))
158
179
  .catch((error) => {
@@ -134,6 +134,10 @@ async function main(argv) {
134
134
  if (options.mode === 'receipt') {
135
135
  let report;
136
136
  try {
137
+ // Stdin is read to EOF with no timeout. Every caller pipes the archive
138
+ // report in (`printf '%s\n' "$ARCHIVE_OUTPUT" | node ...`), so EOF
139
+ // always arrives; a timeout would turn a slow producer into a false
140
+ // failure, and a caller that leaves stdin open is a caller bug.
137
141
  report = JSON.parse(readFileSync(0, 'utf8'));
138
142
  } catch (error) {
139
143
  throw receiptError(
@@ -158,6 +162,9 @@ async function main(argv) {
158
162
  * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`. Either
159
163
  * way the caller reads "exited 0" as "verified" — the fail-open shape this
160
164
  * validator exists to prevent.
165
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
166
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
167
+ * `false`; it never masks a direct run.
161
168
  */
162
169
  function isDirectInvocation(invokedPath) {
163
170
  if (!invokedPath) return false;
@@ -11,6 +11,7 @@ import {
11
11
  resolve,
12
12
  sep,
13
13
  } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
14
15
 
15
16
  function fail(message) {
16
17
  process.stderr.write(`Invalid non-archive lifecycle receipt: ${message}\n`);
@@ -57,104 +58,134 @@ function gitSucceeds(cwd, args) {
57
58
  }
58
59
  }
59
60
 
60
- const [recordArgument, commit, projectName] = process.argv.slice(2);
61
- if (!recordArgument || !/^[0-9a-f]{40}$/.test(commit ?? '') || !projectName) {
62
- fail('expected <record-path> <full-commit-sha> <project-name>.');
63
- }
61
+ function main(argv) {
62
+ const [recordArgument, commit, projectName] = argv;
63
+ if (!recordArgument || !/^[0-9a-f]{40}$/.test(commit ?? '') || !projectName) {
64
+ fail('expected <record-path> <full-commit-sha> <project-name>.');
65
+ }
64
66
 
65
- const unresolvedRecordPath = resolve(recordArgument);
66
- if (!existsSync(unresolvedRecordPath)) {
67
- fail('record path does not exist; restore the synced discovery record.');
68
- }
69
- const recordPath = realpathSync(unresolvedRecordPath);
70
- const repoRoot = realpathSync(
71
- git(dirname(recordPath), ['rev-parse', '--show-toplevel']),
72
- );
73
- const recordRelative = relative(repoRoot, recordPath);
74
- if (
75
- recordRelative === '' ||
76
- recordRelative === '..' ||
77
- recordRelative.startsWith(`..${sep}`) ||
78
- isAbsolute(recordRelative)
79
- ) {
80
- fail('record path is outside the repository root.');
81
- }
82
- const gitRecordPath = recordRelative.split(sep).join('/');
83
- if (basename(gitRecordPath, extname(gitRecordPath)) !== projectName) {
84
- fail('record filename must match the project name.');
85
- }
86
- if (!gitSucceeds(repoRoot, ['merge-base', '--is-ancestor', commit, 'HEAD'])) {
87
- fail('receipt commit must be an ancestor of the current repository HEAD.');
88
- }
89
- const changedPaths = git(repoRoot, [
90
- 'diff-tree',
91
- '--root',
92
- '--no-commit-id',
93
- '--name-only',
94
- '-r',
95
- '--format=',
96
- commit,
97
- ])
98
- .split('\n')
99
- .filter(Boolean);
100
- if (changedPaths.length !== 1 || changedPaths[0] !== gitRecordPath) {
101
- fail('receipt commit must change exactly the synced discovery record path.');
102
- }
103
- const expectedSubject = `chore(oat): complete synced project ${projectName}`;
104
- const subject = git(repoRoot, ['show', '-s', '--format=%s', commit]);
105
- if (subject !== expectedSubject) {
106
- fail(`commit subject must be "${expectedSubject}".`);
107
- }
67
+ const unresolvedRecordPath = resolve(recordArgument);
68
+ if (!existsSync(unresolvedRecordPath)) {
69
+ fail('record path does not exist; restore the synced discovery record.');
70
+ }
71
+ const recordPath = realpathSync(unresolvedRecordPath);
72
+ const repoRoot = realpathSync(
73
+ git(dirname(recordPath), ['rev-parse', '--show-toplevel']),
74
+ );
75
+ const recordRelative = relative(repoRoot, recordPath);
76
+ if (
77
+ recordRelative === '' ||
78
+ recordRelative === '..' ||
79
+ recordRelative.startsWith(`..${sep}`) ||
80
+ isAbsolute(recordRelative)
81
+ ) {
82
+ fail('record path is outside the repository root.');
83
+ }
84
+ const gitRecordPath = recordRelative.split(sep).join('/');
85
+ if (basename(gitRecordPath, extname(gitRecordPath)) !== projectName) {
86
+ fail('record filename must match the project name.');
87
+ }
88
+ if (!gitSucceeds(repoRoot, ['merge-base', '--is-ancestor', commit, 'HEAD'])) {
89
+ fail('receipt commit must be an ancestor of the current repository HEAD.');
90
+ }
91
+ const changedPaths = git(repoRoot, [
92
+ 'diff-tree',
93
+ '--root',
94
+ '--no-commit-id',
95
+ '--name-only',
96
+ '-r',
97
+ '--format=',
98
+ commit,
99
+ ])
100
+ .split('\n')
101
+ .filter(Boolean);
102
+ if (changedPaths.length !== 1 || changedPaths[0] !== gitRecordPath) {
103
+ fail(
104
+ 'receipt commit must change exactly the synced discovery record path.',
105
+ );
106
+ }
107
+ const expectedSubject = `chore(oat): complete synced project ${projectName}`;
108
+ const subject = git(repoRoot, ['show', '-s', '--format=%s', commit]);
109
+ if (subject !== expectedSubject) {
110
+ fail(`commit subject must be "${expectedSubject}".`);
111
+ }
108
112
 
109
- let committedRecordContents;
110
- let record;
111
- try {
112
- committedRecordContents = gitRaw(repoRoot, [
113
- 'show',
114
- `${commit}:${gitRecordPath}`,
113
+ let committedRecordContents;
114
+ let record;
115
+ try {
116
+ committedRecordContents = gitRaw(repoRoot, [
117
+ 'show',
118
+ `${commit}:${gitRecordPath}`,
119
+ ]);
120
+ record = JSON.parse(committedRecordContents);
121
+ } catch (error) {
122
+ fail(
123
+ `committed record is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
124
+ );
125
+ }
126
+ if (readFileSync(recordPath, 'utf8') !== committedRecordContents) {
127
+ fail('working-tree record content must exactly match the receipt commit.');
128
+ }
129
+ const expectedKeys = new Set([
130
+ 'archiveSnapshot',
131
+ 'completedAt',
132
+ 'createdAt',
133
+ 'ref',
134
+ 'remote',
135
+ 'schemaVersion',
136
+ 'scope',
137
+ 'slug',
138
+ 'status',
115
139
  ]);
116
- record = JSON.parse(committedRecordContents);
117
- } catch (error) {
118
- fail(
119
- `committed record is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
120
- );
121
- }
122
- if (readFileSync(recordPath, 'utf8') !== committedRecordContents) {
123
- fail('working-tree record content must exactly match the receipt commit.');
140
+ if (
141
+ !record ||
142
+ typeof record !== 'object' ||
143
+ Array.isArray(record) ||
144
+ Object.keys(record).some((key) => !expectedKeys.has(key)) ||
145
+ record.schemaVersion !== 1 ||
146
+ record.slug !== projectName ||
147
+ record.scope !== 'synced' ||
148
+ record.ref !== `refs/oat/projects/${projectName}` ||
149
+ record.remote !== 'origin' ||
150
+ record.status !== 'complete' ||
151
+ typeof record.createdAt !== 'string' ||
152
+ Number.isNaN(Date.parse(record.createdAt)) ||
153
+ typeof record.completedAt !== 'string' ||
154
+ Number.isNaN(Date.parse(record.completedAt)) ||
155
+ (record.archiveSnapshot !== undefined &&
156
+ (typeof record.archiveSnapshot !== 'string' ||
157
+ record.archiveSnapshot.length === 0))
158
+ ) {
159
+ fail(
160
+ 'committed record must be a complete synced record with the exact project identity and lifecycle content.',
161
+ );
162
+ }
163
+
164
+ process.stdout.write(`${commit}\n`);
124
165
  }
125
- const expectedKeys = new Set([
126
- 'archiveSnapshot',
127
- 'completedAt',
128
- 'createdAt',
129
- 'ref',
130
- 'remote',
131
- 'schemaVersion',
132
- 'scope',
133
- 'slug',
134
- 'status',
135
- ]);
136
- if (
137
- !record ||
138
- typeof record !== 'object' ||
139
- Array.isArray(record) ||
140
- Object.keys(record).some((key) => !expectedKeys.has(key)) ||
141
- record.schemaVersion !== 1 ||
142
- record.slug !== projectName ||
143
- record.scope !== 'synced' ||
144
- record.ref !== `refs/oat/projects/${projectName}` ||
145
- record.remote !== 'origin' ||
146
- record.status !== 'complete' ||
147
- typeof record.createdAt !== 'string' ||
148
- Number.isNaN(Date.parse(record.createdAt)) ||
149
- typeof record.completedAt !== 'string' ||
150
- Number.isNaN(Date.parse(record.completedAt)) ||
151
- (record.archiveSnapshot !== undefined &&
152
- (typeof record.archiveSnapshot !== 'string' ||
153
- record.archiveSnapshot.length === 0))
154
- ) {
155
- fail(
156
- 'committed record must be a complete synced record with the exact project identity and lifecycle content.',
157
- );
166
+
167
+ /**
168
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
169
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
170
+ * that exits 0 whenever the skill is reached through a symlinked install root,
171
+ * and canonicalizing only one side has the same effect under
172
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
173
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
174
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
175
+ * `false`; it never masks a direct run.
176
+ */
177
+ function isDirectInvocation(invokedPath) {
178
+ if (!invokedPath) return false;
179
+ try {
180
+ return (
181
+ realpathSync(fileURLToPath(import.meta.url)) ===
182
+ realpathSync(resolve(invokedPath))
183
+ );
184
+ } catch {
185
+ return false;
186
+ }
158
187
  }
159
188
 
160
- process.stdout.write(`${commit}\n`);
189
+ if (isDirectInvocation(process.argv[1])) {
190
+ main(process.argv.slice(2));
191
+ }
@@ -7,7 +7,7 @@ disable-model-invocation: true
7
7
  user-invocable: true
8
8
  allowed-tools: Read, Write, Bash(git:*), Bash(oat:*), Bash(oat project log:*), Glob, Grep, AskUserQuestion, Task
9
9
  metadata:
10
- version: 2.3.9
10
+ version: 2.3.10
11
11
  ---
12
12
 
13
13
  # Implementation Phase
@@ -1051,6 +1051,9 @@ async function main(argv) {
1051
1051
  * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`. Either
1052
1052
  * way the caller reads "exited 0" as "verified" — the fail-open shape the
1053
1053
  * contracts exist to prevent.
1054
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
1055
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
1056
+ * `false`; it never masks a direct run.
1054
1057
  */
1055
1058
  function isDirectInvocation(invokedPath) {
1056
1059
  if (!invokedPath) return false;
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
  user-invocable: true
7
7
  allowed-tools: Read, Write, Glob, Grep, Bash, AskUserQuestion, Agent, mcp__*
8
8
  metadata:
9
- version: 1.1.2
9
+ version: 1.1.5
10
10
  ---
11
11
 
12
12
  # Recon
@@ -127,8 +127,8 @@ Use the complete mode policy from `references/profiles.md` and
127
127
  `scripts/lib/routing.mjs`. Map manifest wave modes to the worker's closed
128
128
  assignment vocabulary: `redundant-gather` to `gather`;
129
129
  `semantic-verification` and `redundant-verification` to `verify`; `adversarial`
130
- and `contradiction-resolution` to `adversary`; and only `reconciliation` to
131
- `reconcile`. The remaining modes keep their same-named assignment. All lanes
130
+ and `contradiction-resolution` to `adversary`. The remaining worker modes keep
131
+ their same-named assignment; reconciliation is controller-owned. All lanes
132
132
  within one launch are a homogeneous wave. Select each wave independently; a
133
133
  stronger wave never raises unrelated waves' targets or effort.
134
134
 
@@ -164,9 +164,14 @@ task class and class floor. Preserve provider, route, role, model, effort,
164
164
  reasoning mode, and service tier as separate provider-native axes. A null axis
165
165
  means the adapter exposes no independently requested control; never translate
166
166
  effort between harnesses or normalize an opaque selector. Prefer the canonical
167
- `recon-worker` role. If that role is unavailable, plan the generic role with the
168
- complete worker contract as a visible generic role fallback before approval. A
169
- fallback after approval is forbidden.
167
+ `recon-worker` role only when the current live Task catalog exposes it. A
168
+ materialized `.cursor/agents/recon-worker.md` file proves installation, not that
169
+ the current Cursor Task catalog can launch the role. If the role is absent from
170
+ that observed catalog, plan the generic role with the complete worker contract
171
+ as a visible generic role fallback before approval. A fallback after approval
172
+ is forbidden. Every Cursor recon leaf, including that generic fallback, must be
173
+ launched as a background task so a new parent-chat message cannot turn parent
174
+ turn interruption into an apparent worker crash.
170
175
 
171
176
  Prepare a `schemaVersion: 2` draft manifest. Its execution object contains a
172
177
  complete inherited `target`, limits, waves, conditions, and later the approval.
@@ -218,7 +223,8 @@ result. If any approved axis cannot be satisfied, stop with a
218
223
  launch nothing. Never substitute a different axis to make the launch fit.
219
224
 
220
225
  Launch each wave through the dispatch dependency with exactly the approved
221
- axes. Immediately before each launch, check the constructed target through the
226
+ axes. On Cursor, launch both the custom worker role and the pre-approved generic
227
+ fallback in the background. Immediately before each launch, check the constructed target through the
222
228
  same production helper used by preview:
223
229
 
224
230
  ```bash
@@ -235,13 +241,22 @@ approved `waveId` and `laneId`. This structured identity is required for
235
241
  conditional and non-conditional lanes alike; prose is descriptive, not
236
242
  identity.
237
243
 
244
+ Worker completion is decided from the approved-path artifact before interpreting
245
+ a later provider transport error. When the artifact's identity, closed schema,
246
+ bytes, and digest validate, the lane is complete; a subsequent stream-close or
247
+ RPC error is recorded as a non-material diagnostic and never triggers a
248
+ replacement launch. Missing or invalid output remains a terminal `PASS_FAILED`
249
+ outcome with no retry or replacement.
250
+
238
251
  Run the passes in this order:
239
252
 
240
253
  1. `map` and `gather` workers write unique dossiers under `raw/dossiers/`.
241
254
  Thorough runs complete their independent `redundant-gather` wave here too.
242
255
  2. `compile` writes a candidate canonical claim ledger. A thorough candidate
243
256
  directly references a complete dossier from every approved primary and
244
- redundant gather lane before any review brief is created.
257
+ redundant gather lane before any review brief is created. Every display
258
+ excerpt is an exact contiguous substring of its cited source, apart from the
259
+ declared `redacted-exact` representation; paraphrase is invalid evidence.
245
260
  3. Source preflight: run `scripts/validate-artifact.mjs` on the candidate
246
261
  manifest and ledger, then reopen every declared source and evidence locator
247
262
  with the checks in `scripts/validate-packet.mjs`. Resolve source roots to
@@ -251,23 +266,26 @@ Run the passes in this order:
251
266
  manifest and ledger pair validates.
252
267
  4. Profile-required `verify`, `adversary`, and `coverage` workers consume only
253
268
  immutable selectively blind briefs created by
254
- `scripts/create-review-brief.mjs` at unique paths.
269
+ `scripts/create-review-brief.mjs` at unique paths. Each review-producing
270
+ wave has exactly one lane; partition source or question fan-out in the
271
+ applicable gather wave instead.
255
272
  5. Evaluate each predeclared evidence condition only after its completed
256
273
  predecessor artifacts exist. Record exactly one root-authored condition
257
274
  outcome. A triggered `contradiction-resolution` wave runs once as an
258
275
  `adversary` evidence assignment; a not-triggered or unresolved wave supplies
259
276
  no artifact. Accepted failure, cancellation, timeout, or missing output never
260
277
  authorizes replacement work.
261
- 6. Standard and thorough execute exactly one terminal `reconciliation` wave,
262
- mapped to `reconcile`, after required review evidence and any triggered
263
- contradiction investigation. It writes a new candidate ledger without
264
- mutating the prior ledger.
265
-
266
- If `reconciliation-needs-judgment` is foreseeable, select an adequate target
267
- for that one terminal wave before approval. If it appears after approval and
268
- the approved target is inadequate, preserve completed evidence and return an
269
- explicit unresolved, out-of-envelope gap for renewed approval or a new run.
270
- Never mutate the target, launch a second reconciliation, or substitute a
278
+ 6. Standard and thorough run one deterministic controller reconciliation after
279
+ required review evidence and any triggered contradiction investigation. The
280
+ controller invokes `scripts/reconcile-ledger.mjs` once using the manifest's
281
+ `execution.reconciliation` paths and literal producer
282
+ `controller:reconcile-ledger-v1`, validates both candidate outputs, copies
283
+ the exact ledger bytes to a packet-contained temporary file, and atomically
284
+ renames it to `claims.json`. No reconciliation worker is dispatched.
285
+
286
+ If reconciliation requires new semantic judgment, preserve completed evidence
287
+ and return an explicit unresolved caller-owned gap. Never mutate the target or
288
+ substitute a
271
289
  contradiction search for synthesis.
272
290
 
273
291
  Use `references/worker-contract.md` for every assignment. Never allow two
@@ -125,18 +125,21 @@ condition contains `conditionId`, `destinationWaveId`, `afterWaveIds`, one of
125
125
  `insufficient-evidence` or `unresolved-material-challenge`, and
126
126
  `maxActivations: 1`. Each conditional destination is a uniquely identified
127
127
  `contradiction-resolution` evidence wave, has exactly one condition, appears
128
- after every named predecessor and before the one terminal reconciliation, and
128
+ after every named predecessor and before controller reconciliation, and
129
129
  owns unique lane IDs and write roots. Conversely, every wave marked
130
130
  `conditional: true` must be the destination of exactly one activating condition;
131
131
  dead conditional waves are invalid. Quick permits no conditional wave;
132
- standard permits one and thorough two. The profile's 4/10/20 adaptive-lane cap
132
+ standard and thorough each permit one. The profile's 4/10/20 evidence-lane cap
133
133
  counts `gather`, `semantic-verification`, `adversarial`, `coverage`,
134
134
  `redundant-gather`, `redundant-verification`, and
135
135
  `contradiction-resolution` when those modes are permitted by the profile.
136
- Every permitted mode outside that counted set is fixed at exactly one lane:
137
- `map` and `compile` for quick, plus terminal `reconciliation` for standard and
138
- thorough. The resulting total lane maxima are 6/13/23, and concurrency remains
139
- capped at 4/6/8.
136
+ Only `gather` and `redundant-gather` may partition a wave across multiple lanes.
137
+ Every typed review wave and every permitted mode outside the counted set is
138
+ fixed at exactly one lane, including `map` and `compile`. The resulting
139
+ worker-lane maxima are 6/12/22, and concurrency remains capped at 4/6/8.
140
+ Each review lane's approved write root must contain the corresponding fixed
141
+ path in `execution.reconciliation`; a different approved root is invalid before
142
+ launch.
140
143
 
141
144
  Triggered dispositions require exact complete same-run artifacts from every
142
145
  approved predecessor and concrete typed predicate evidence from those same-run
@@ -150,17 +153,14 @@ no achieved pass. Accepted failed, cancelled, timed-out, or missing predecessor
150
153
  work cannot activate replacement work. Required profile passes remain required
151
154
  regardless of conditional annotations.
152
155
 
153
- `reconciliation-needs-judgment` is a controller escalation outcome, never a
154
- condition predicate. Foreseeable judgment changes the one terminal target before
155
- approval. A need discovered later preserves completed work and records an
156
- unresolved out-of-envelope gap until renewed approval or a new run; it never
157
- mutates the approved target or launches a second reconciliation.
156
+ `reconciliation-needs-judgment` is a caller-owned unresolved outcome, never a
157
+ condition predicate or target escalation. The controller preserves completed
158
+ work and records the gap; it never dispatches reconciliation work.
158
159
 
159
- The controller maps the ten manifest wave modes onto the worker contract's
160
- seven assignment modes: redundant gathering uses `gather`; semantic and
160
+ The controller maps the nine manifest wave modes onto the worker contract's
161
+ six assignment modes: redundant gathering uses `gather`; semantic and
161
162
  redundant verification use `verify`; adversarial and contradiction-resolution
162
- use `adversary`; and only terminal reconciliation uses `reconcile`. This mapping
163
- does not change the approved manifest mode used for artifact and pass checks.
163
+ use `adversary`. Reconciliation is not a worker mode or approval target.
164
164
 
165
165
  Approval is `{ type: "explicit-user-approval", approvedAt }`. It is valid only
166
166
  for the exact proposal shown in the same uninterrupted controller flow. Resume,
@@ -183,7 +183,6 @@ complete typed artifacts of the same run:
183
183
  | `semantic-verification` | a `recon.review-result` with kind `semantic` |
184
184
  | `adversarial` | a `recon.review-result` with kind `adversarial` |
185
185
  | `coverage` | a `recon.review-result` with kind `coverage` |
186
- | `reconciliation` | a `recon.review-result` with kind `reconciliation` |
187
186
  | `redundant-gather` | a complete `gather` dossier owned by the approved `redundant-gather` wave |
188
187
  | `redundant-verification` | a `recon.review-result` with kind `redundant-verification` |
189
188
  | `contradiction-resolution` | a `recon.review-result` with kind `contradiction-resolution` |
@@ -191,11 +190,11 @@ complete typed artifacts of the same run:
191
190
  `quick` requires `map` and `gather`; the canonical ledger itself is the
192
191
  compile result, so an approved `compile` lane needs no separate artifact, and
193
192
  locator validation is performed by the validator.
194
- `standard` adds `semantic-verification`, `adversarial`, `coverage`, and
195
- `reconciliation`. `thorough` adds `redundant-gather` and
193
+ `standard` adds `semantic-verification`, `adversarial`, and `coverage`, then
194
+ requires the manifest-bound controller reconciliation. `thorough` adds `redundant-gather` and
196
195
  `redundant-verification`. A predeclared conditional
197
196
  `contradiction-resolution` evidence pass may feed the same mandatory terminal
198
- reconciliation when its predicate triggers; it is not a second terminal pass.
197
+ controller reconciliation when its predicate triggers.
199
198
 
200
199
  Thorough routing completes `redundant-gather` before `compile`. The compiled
201
200
  ledger must directly reference by exact path and digest at least one complete
@@ -346,17 +345,21 @@ branch; source ineligibility never bypasses persistence safety.
346
345
  claim/evidence associations, coverage findings, unresolved issues, and
347
346
  completion status. Every new evidence record has at least one exact
348
347
  association, and an association cannot name evidence absent from that result
349
- or a claim without a disposition in that result. Reconciliation results
350
- replace the brief reference with prior-ledger/revision, additions/removals,
351
- exact transitions, and coverage-disposition bindings. Coverage findings are
352
- closed records bound to affected claims and exact manifest gaps. Accepted
353
- material gaps require a legal downgrade for every affected claim; a resolved
354
- finding instead names exact typed evidence. Non-material coverage gaps
355
- downgrade verified claims and transition provisional claims to unresolved,
356
- while existing supported claims remain supported without verified promotion.
357
- Thorough redundant verification and contradiction resolution are
358
- claim-bearing typed review results, not raw dossiers: they bind immutable
359
- briefs, claim dispositions, and explicit affected-contradiction dispositions.
348
+ or a claim without a disposition in that result.
349
+
350
+ Every `unresolvedIssues` member is a string. Review results are closed objects:
351
+ unknown fields and object-valued issue entries are invalid.
352
+ Reconciliation results replace the brief reference with prior-ledger/revision,
353
+ additions/removals, exact transitions, and coverage-disposition bindings.
354
+ Coverage findings are closed records bound to affected claims and exact
355
+ manifest gaps. Accepted
356
+ material gaps require a legal downgrade for every affected claim; a resolved
357
+ finding instead names exact typed evidence. Non-material coverage gaps
358
+ downgrade verified claims and transition provisional claims to unresolved,
359
+ while existing supported claims remain supported without verified promotion.
360
+ Thorough redundant verification and contradiction resolution are
361
+ claim-bearing typed review results, not raw dossiers: they bind immutable
362
+ briefs, claim dispositions, and explicit affected-contradiction dispositions.
360
363
 
361
364
  Create immutable mode-specific review projections with
362
365
  `scripts/create-review-brief.mjs`. Verification briefs expose only claim