@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.
- package/assets/agents/recon-worker.md +12 -12
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/workflows/skills/recon.md +53 -27
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/explainer-kit/SKILL.md +1 -1
- package/assets/skills/explainer-kit/scripts/bundle.mjs +26 -2
- package/assets/skills/explainer-kit/scripts/record.mjs +26 -3
- package/assets/skills/explainer-kit/scripts/verify.mjs +26 -3
- package/assets/skills/oat-explainer-kit/SKILL.md +1 -1
- package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +25 -5
- package/assets/skills/oat-project-complete/SKILL.md +1 -1
- package/assets/skills/oat-project-complete/scripts/consume-persisted-recap-intent.mjs +26 -6
- package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +26 -6
- package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +7 -0
- package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +26 -5
- package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +26 -5
- package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +26 -6
- package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +26 -6
- package/assets/skills/oat-project-complete/scripts/resolve-synced-archive-entry.mjs +26 -5
- package/assets/skills/oat-project-complete/scripts/validate-durable-archive-receipt.mjs +7 -0
- package/assets/skills/oat-project-complete/scripts/validate-nonarchive-lifecycle-receipt.mjs +126 -95
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/scripts/capture-dirty-tree.mjs +3 -0
- package/assets/skills/recon/SKILL.md +37 -19
- package/assets/skills/recon/references/packet-contract.md +33 -30
- package/assets/skills/recon/references/profiles.md +18 -15
- package/assets/skills/recon/references/worker-contract.md +87 -7
- package/assets/skills/recon/scripts/create-review-brief.mjs +2 -5
- package/assets/skills/recon/scripts/lib/cli-entry.mjs +21 -0
- package/assets/skills/recon/scripts/lib/contracts.mjs +144 -38
- package/assets/skills/recon/scripts/lib/routing.mjs +2 -5
- package/assets/skills/recon/scripts/lib/validated-run.mjs +1 -1
- package/assets/skills/recon/scripts/prepare-routing.mjs +2 -2
- package/assets/skills/recon/scripts/reconcile-ledger.mjs +269 -2
- package/assets/skills/recon/scripts/render-packet.mjs +2 -5
- package/assets/skills/recon/scripts/validate-artifact.mjs +2 -5
- package/assets/skills/recon/scripts/validate-packet.mjs +73 -23
- package/package.json +2 -2
|
@@ -28,19 +28,20 @@ claims are sufficient.
|
|
|
28
28
|
Use for load-bearing evidence that needs independent semantic challenge.
|
|
29
29
|
|
|
30
30
|
- Required: quick topology plus selectively blind semantic verification,
|
|
31
|
-
adversarial review, coverage review, and reconciliation.
|
|
32
|
-
-
|
|
31
|
+
adversarial review, coverage review, and deterministic controller reconciliation.
|
|
32
|
+
- Counted evidence lanes: `gather`, `semantic-verification`, `adversarial`,
|
|
33
33
|
`coverage`, and any predeclared `contradiction-resolution`; hard cap 10
|
|
34
|
-
across those
|
|
35
|
-
`
|
|
36
|
-
maximum is
|
|
34
|
+
across those modes. `gather` may partition work across lanes. Every typed
|
|
35
|
+
review wave, plus required `map` and `compile`, is fixed at exactly one lane,
|
|
36
|
+
so the total maximum is 12 worker lanes.
|
|
37
|
+
One deterministic controller reconciliation follows.
|
|
37
38
|
- Maximum concurrency: 6.
|
|
38
39
|
- Assurance ceiling: `verified` only after exact locator validation, affirmative
|
|
39
40
|
independent semantic review, no unresolved material challenge, and adequate
|
|
40
41
|
coverage.
|
|
41
42
|
- Conditional work: at most one predeclared `contradiction-resolution`
|
|
42
43
|
evidence wave for a material challenge, followed when triggered or not by
|
|
43
|
-
the same one mandatory
|
|
44
|
+
the same one mandatory controller reconciliation.
|
|
44
45
|
|
|
45
46
|
## thorough
|
|
46
47
|
|
|
@@ -51,17 +52,19 @@ reopening and verification.
|
|
|
51
52
|
redundant verification for load-bearing claims. Redundant gathering finishes
|
|
52
53
|
before compilation, and every completed primary or redundant gather lane is
|
|
53
54
|
represented in the compiled ledger inputs before review briefs are created.
|
|
54
|
-
-
|
|
55
|
+
- Counted evidence lanes: the standard counted modes plus `redundant-gather`
|
|
55
56
|
and `redundant-verification`; hard cap 20 across those counted modes.
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
`gather` and `redundant-gather` may partition work across lanes. Every typed
|
|
58
|
+
review wave, plus required `map` and `compile`, is fixed at exactly one lane,
|
|
59
|
+
so the total maximum is 22 worker lanes.
|
|
60
|
+
One deterministic controller reconciliation follows.
|
|
58
61
|
- Maximum concurrency: 8.
|
|
59
62
|
- Assurance ceiling: `verified` only for claims affirmed by the required
|
|
60
63
|
independent passes and left without unresolved material challenge.
|
|
61
|
-
- Conditional work: optionally predeclare
|
|
62
|
-
`contradiction-resolution` evidence
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
- Conditional work: optionally predeclare one condition-bound
|
|
65
|
+
`contradiction-resolution` evidence wave within the manifest's hard cap. It
|
|
66
|
+
runs only when its approved predicate triggers; both branches feed exactly
|
|
67
|
+
one mandatory controller reconciliation.
|
|
65
68
|
|
|
66
69
|
## Planning Rules
|
|
67
70
|
|
|
@@ -69,8 +72,8 @@ reopening and verification.
|
|
|
69
72
|
2. Classify required and conditional waves before target preparation.
|
|
70
73
|
3. Classify the actual bounded assignment. Citation reopening, explicit
|
|
71
74
|
inventory comparison, counterexample search, and mechanical dossier
|
|
72
|
-
compilation can remain economical
|
|
73
|
-
|
|
75
|
+
compilation can remain economical. Reconciliation is deterministic
|
|
76
|
+
controller work; new semantic judgment is a caller-owned unresolved gap.
|
|
74
77
|
4. Resolve and explain each wave's exact provider-native target and unsupported
|
|
75
78
|
controls without cross-harness normalization.
|
|
76
79
|
5. Reduce lanes when scope is small; never invent work to fill a profile cap.
|
|
@@ -21,7 +21,7 @@ Reject an incomplete or contradictory assignment before reading sources. Never
|
|
|
21
21
|
request credentials, mutate an investigated source, broaden scope, or choose an
|
|
22
22
|
alternate write path.
|
|
23
23
|
|
|
24
|
-
The manifest's
|
|
24
|
+
The manifest's nine wave modes map to the worker's closed six-mode vocabulary:
|
|
25
25
|
|
|
26
26
|
| Manifest wave mode | Worker assignment mode |
|
|
27
27
|
| -------------------------- | ---------------------- |
|
|
@@ -31,7 +31,6 @@ The manifest's ten wave modes map to the worker's closed seven-mode vocabulary:
|
|
|
31
31
|
| `semantic-verification` | `verify` |
|
|
32
32
|
| `adversarial` | `adversary` |
|
|
33
33
|
| `coverage` | `coverage` |
|
|
34
|
-
| `reconciliation` | `reconcile` |
|
|
35
34
|
| `redundant-gather` | `gather` |
|
|
36
35
|
| `redundant-verification` | `verify` |
|
|
37
36
|
| `contradiction-resolution` | `adversary` |
|
|
@@ -47,17 +46,16 @@ wave target; the worker neither selects nor upgrades it.
|
|
|
47
46
|
- `gather`: inspect assigned sources and emit source-grounded findings with
|
|
48
47
|
typed locators, minimal display excerpts, uncertainty, and contradictions.
|
|
49
48
|
- `compile`: consume only designated dossiers and create a provisional claim
|
|
50
|
-
ledger candidate. Deduplicate without inventing evidence.
|
|
49
|
+
ledger candidate. Deduplicate without inventing evidence. Display excerpts
|
|
50
|
+
must be exact contiguous source substrings, except for the explicit
|
|
51
|
+
`redacted-exact` representation; never paraphrase an excerpt.
|
|
51
52
|
- `verify`: consume an immutable verification brief, reopen only its declared
|
|
52
53
|
sources, test locators and claim semantics, and report claim dispositions.
|
|
53
54
|
- `adversary`: consume scope, questions, and provisional statements only; seek
|
|
54
55
|
counterevidence, unsupported inference, and missing alternatives.
|
|
55
56
|
- `coverage`: compare declared scope and questions with ledger coverage without
|
|
56
57
|
reading gatherer reasoning.
|
|
57
|
-
|
|
58
|
-
ledger candidate. Preserve prior revisions and never invent evidence.
|
|
59
|
-
|
|
60
|
-
No other mode is valid.
|
|
58
|
+
No other mode is valid.
|
|
61
59
|
|
|
62
60
|
## Output Contract
|
|
63
61
|
|
|
@@ -83,11 +81,93 @@ Persist minimal excerpts only. Detect and redact secret spans before writing;
|
|
|
83
81
|
never persist the secret or its sensitive-span digest. Finish by returning the
|
|
84
82
|
artifact path and compact outcome only.
|
|
85
83
|
|
|
84
|
+
Before returning, run the supplied deterministic validator against the sole
|
|
85
|
+
unpromoted `writePath`. Correct an invalid candidate within the same accepted
|
|
86
|
+
task and validate again. A terminal invalid candidate is `PASS_FAILED`; it does
|
|
87
|
+
not authorize controller retry or replacement.
|
|
88
|
+
|
|
89
|
+
Closed review-result examples (all omitted arrays are still required as shown):
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"kind": "recon.review-result",
|
|
94
|
+
"schemaVersion": 1,
|
|
95
|
+
"id": "review-semantic",
|
|
96
|
+
"runId": "run-1",
|
|
97
|
+
"reviewKind": "semantic",
|
|
98
|
+
"reviewerLane": "lane-semantic",
|
|
99
|
+
"status": "complete",
|
|
100
|
+
"brief": {
|
|
101
|
+
"path": "reviews/briefs/verify.json",
|
|
102
|
+
"digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
103
|
+
},
|
|
104
|
+
"permittedInputs": [],
|
|
105
|
+
"excludedInputs": [],
|
|
106
|
+
"dispositions": [],
|
|
107
|
+
"newEvidence": [],
|
|
108
|
+
"evidenceAssociations": [],
|
|
109
|
+
"coverageFindings": [],
|
|
110
|
+
"unresolvedIssues": []
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"kind": "recon.review-result",
|
|
117
|
+
"schemaVersion": 1,
|
|
118
|
+
"id": "review-adversarial",
|
|
119
|
+
"runId": "run-1",
|
|
120
|
+
"reviewKind": "adversarial",
|
|
121
|
+
"reviewerLane": "lane-adversarial",
|
|
122
|
+
"status": "complete",
|
|
123
|
+
"brief": {
|
|
124
|
+
"path": "reviews/briefs/adversary.json",
|
|
125
|
+
"digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
126
|
+
},
|
|
127
|
+
"permittedInputs": [],
|
|
128
|
+
"excludedInputs": [],
|
|
129
|
+
"dispositions": [],
|
|
130
|
+
"newEvidence": [],
|
|
131
|
+
"evidenceAssociations": [],
|
|
132
|
+
"coverageFindings": [],
|
|
133
|
+
"unresolvedIssues": []
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"kind": "recon.review-result",
|
|
140
|
+
"schemaVersion": 1,
|
|
141
|
+
"id": "review-coverage",
|
|
142
|
+
"runId": "run-1",
|
|
143
|
+
"reviewKind": "coverage",
|
|
144
|
+
"reviewerLane": "lane-coverage",
|
|
145
|
+
"status": "complete",
|
|
146
|
+
"brief": {
|
|
147
|
+
"path": "reviews/briefs/coverage.json",
|
|
148
|
+
"digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
149
|
+
},
|
|
150
|
+
"permittedInputs": [],
|
|
151
|
+
"excludedInputs": [],
|
|
152
|
+
"dispositions": [],
|
|
153
|
+
"newEvidence": [],
|
|
154
|
+
"evidenceAssociations": [],
|
|
155
|
+
"coverageFindings": [],
|
|
156
|
+
"unresolvedIssues": []
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
86
160
|
The controller validates the candidate against `packet-contract.md` with the
|
|
87
161
|
bundled deterministic artifact validator. A validation failure quarantines the
|
|
88
162
|
candidate; it never authorizes the worker to rewrite a shared artifact, retry,
|
|
89
163
|
or launch a replacement.
|
|
90
164
|
|
|
165
|
+
For an accepted launch, the approved-path artifact is authoritative over the
|
|
166
|
+
return transport. If its identity, schema, bytes, and digest validate, a later
|
|
167
|
+
stream-close diagnostic is non-material and the lane is complete. Missing or
|
|
168
|
+
invalid output remains terminal `PASS_FAILED`; neither outcome authorizes a
|
|
169
|
+
replacement launch.
|
|
170
|
+
|
|
91
171
|
## Invariants
|
|
92
172
|
|
|
93
173
|
- Read only the assignment's allowed inputs using its source-read authority.
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, relative, resolve } from 'node:path';
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
6
5
|
|
|
7
6
|
import { canonicalJson, hashFile } from './lib/canonical-json.mjs';
|
|
7
|
+
import { isDirectExecution } from './lib/cli-entry.mjs';
|
|
8
8
|
import { issue, isObject, validateArtifactShape } from './lib/contracts.mjs';
|
|
9
9
|
import { assertSafeOutputPath } from './lib/safe-path.mjs';
|
|
10
10
|
|
|
@@ -441,10 +441,7 @@ async function main(argv) {
|
|
|
441
441
|
process.stdout.write(`${JSON.stringify(reference, null, 2)}\n`);
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
if (
|
|
445
|
-
process.argv[1] &&
|
|
446
|
-
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
447
|
-
) {
|
|
444
|
+
if (isDirectExecution(import.meta.url)) {
|
|
448
445
|
main(process.argv.slice(2)).catch((error) => {
|
|
449
446
|
process.stderr.write(`${error instanceof Error ? error.message : error}\n`);
|
|
450
447
|
process.exitCode = 1;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Compare canonical paths on both sides so direct execution works through a
|
|
7
|
+
* symlinked install root, including with `--preserve-symlinks-main`. A path
|
|
8
|
+
* that cannot be canonicalized is not the loaded entry module, so resolution
|
|
9
|
+
* failure returns false without masking a direct run.
|
|
10
|
+
*/
|
|
11
|
+
export function isDirectExecution(moduleUrl, entryPath = process.argv[1]) {
|
|
12
|
+
if (!entryPath) return false;
|
|
13
|
+
try {
|
|
14
|
+
return (
|
|
15
|
+
realpathSync(fileURLToPath(moduleUrl)) ===
|
|
16
|
+
realpathSync(resolve(entryPath))
|
|
17
|
+
);
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -33,7 +33,6 @@ export const workerModes = [
|
|
|
33
33
|
'verify',
|
|
34
34
|
'adversary',
|
|
35
35
|
'coverage',
|
|
36
|
-
'reconcile',
|
|
37
36
|
];
|
|
38
37
|
export const evidenceRelations = [
|
|
39
38
|
'supports',
|
|
@@ -49,7 +48,6 @@ export const waveModes = [
|
|
|
49
48
|
'semantic-verification',
|
|
50
49
|
'adversarial',
|
|
51
50
|
'coverage',
|
|
52
|
-
'reconciliation',
|
|
53
51
|
'redundant-gather',
|
|
54
52
|
'redundant-verification',
|
|
55
53
|
'contradiction-resolution',
|
|
@@ -67,6 +65,18 @@ export const conditionPredicates = [
|
|
|
67
65
|
'unresolved-material-challenge',
|
|
68
66
|
];
|
|
69
67
|
|
|
68
|
+
const reconciliationReviewPaths = Object.freeze({
|
|
69
|
+
'semantic-verification': 'reviews/semantic.json',
|
|
70
|
+
adversarial: 'reviews/adversarial.json',
|
|
71
|
+
coverage: 'reviews/coverage.json',
|
|
72
|
+
'redundant-verification': 'reviews/redundant-verification.json',
|
|
73
|
+
'contradiction-resolution': 'reviews/contradiction-resolution.json',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const singletonReviewWaveModes = new Set(
|
|
77
|
+
Object.keys(reconciliationReviewPaths),
|
|
78
|
+
);
|
|
79
|
+
|
|
70
80
|
function profilePolicy(
|
|
71
81
|
orderedSingletonWaveModes,
|
|
72
82
|
allowedWaveModes,
|
|
@@ -100,7 +110,6 @@ export const profileRoutingPolicy = Object.freeze({
|
|
|
100
110
|
'semantic-verification',
|
|
101
111
|
'adversarial',
|
|
102
112
|
'coverage',
|
|
103
|
-
'reconciliation',
|
|
104
113
|
],
|
|
105
114
|
[
|
|
106
115
|
'map',
|
|
@@ -109,7 +118,6 @@ export const profileRoutingPolicy = Object.freeze({
|
|
|
109
118
|
'semantic-verification',
|
|
110
119
|
'adversarial',
|
|
111
120
|
'coverage',
|
|
112
|
-
'reconciliation',
|
|
113
121
|
'contradiction-resolution',
|
|
114
122
|
],
|
|
115
123
|
[
|
|
@@ -131,7 +139,6 @@ export const profileRoutingPolicy = Object.freeze({
|
|
|
131
139
|
'adversarial',
|
|
132
140
|
'coverage',
|
|
133
141
|
'redundant-verification',
|
|
134
|
-
'reconciliation',
|
|
135
142
|
],
|
|
136
143
|
[
|
|
137
144
|
'map',
|
|
@@ -142,7 +149,6 @@ export const profileRoutingPolicy = Object.freeze({
|
|
|
142
149
|
'coverage',
|
|
143
150
|
'redundant-gather',
|
|
144
151
|
'redundant-verification',
|
|
145
|
-
'reconciliation',
|
|
146
152
|
'contradiction-resolution',
|
|
147
153
|
],
|
|
148
154
|
[
|
|
@@ -154,7 +160,7 @@ export const profileRoutingPolicy = Object.freeze({
|
|
|
154
160
|
'redundant-verification',
|
|
155
161
|
'contradiction-resolution',
|
|
156
162
|
],
|
|
157
|
-
{ lanes: 20, concurrency: 8, conditions:
|
|
163
|
+
{ lanes: 20, concurrency: 8, conditions: 1 },
|
|
158
164
|
),
|
|
159
165
|
});
|
|
160
166
|
|
|
@@ -359,6 +365,7 @@ const executionV2Keys = new Set([
|
|
|
359
365
|
'retryLimit',
|
|
360
366
|
'waves',
|
|
361
367
|
'conditions',
|
|
368
|
+
'reconciliation',
|
|
362
369
|
'approval',
|
|
363
370
|
]);
|
|
364
371
|
|
|
@@ -410,6 +417,57 @@ function validateExecutionV2(value, errors, path) {
|
|
|
410
417
|
requiredObject(value, 'approval', errors, path);
|
|
411
418
|
validateApprovalEvidence(value.approval, errors, `${path}.approval`);
|
|
412
419
|
|
|
420
|
+
if (Object.hasOwn(value, 'reconciliation')) {
|
|
421
|
+
const reconciliation = value.reconciliation;
|
|
422
|
+
requiredObject(value, 'reconciliation', errors, path);
|
|
423
|
+
if (isObject(reconciliation)) {
|
|
424
|
+
closedObject(
|
|
425
|
+
reconciliation,
|
|
426
|
+
new Set([
|
|
427
|
+
'producer',
|
|
428
|
+
'inputLedger',
|
|
429
|
+
'outputLedger',
|
|
430
|
+
'outputReview',
|
|
431
|
+
'requiredReviews',
|
|
432
|
+
'conditionalReviews',
|
|
433
|
+
]),
|
|
434
|
+
errors,
|
|
435
|
+
`${path}.reconciliation`,
|
|
436
|
+
);
|
|
437
|
+
for (const key of [
|
|
438
|
+
'producer',
|
|
439
|
+
'inputLedger',
|
|
440
|
+
'outputLedger',
|
|
441
|
+
'outputReview',
|
|
442
|
+
]) {
|
|
443
|
+
requiredString(reconciliation, key, errors, `${path}.reconciliation`);
|
|
444
|
+
}
|
|
445
|
+
if (reconciliation.producer !== 'controller:reconcile-ledger-v1') {
|
|
446
|
+
errors.push(
|
|
447
|
+
issue(
|
|
448
|
+
'INVALID_RECONCILIATION_PRODUCER',
|
|
449
|
+
'Controller reconciliation must use controller:reconcile-ledger-v1',
|
|
450
|
+
`${path}.reconciliation.producer`,
|
|
451
|
+
),
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
for (const key of ['requiredReviews', 'conditionalReviews']) {
|
|
455
|
+
requiredArray(reconciliation, key, errors, `${path}.reconciliation`);
|
|
456
|
+
for (const [index, item] of (reconciliation[key] ?? []).entries()) {
|
|
457
|
+
if (typeof item !== 'string' || item.length === 0) {
|
|
458
|
+
errors.push(
|
|
459
|
+
issue(
|
|
460
|
+
'INVALID_RECONCILIATION_PATH',
|
|
461
|
+
`${key} must contain non-empty paths`,
|
|
462
|
+
`${path}.reconciliation.${key}[${index}]`,
|
|
463
|
+
),
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
413
471
|
const waves = Array.isArray(value.waves) ? value.waves : [];
|
|
414
472
|
const waveIds = new Set();
|
|
415
473
|
const laneIds = new Set();
|
|
@@ -624,7 +682,8 @@ export function validateV2ProfileTopology(
|
|
|
624
682
|
for (const [waveIndex, wave] of waves.entries()) {
|
|
625
683
|
if (
|
|
626
684
|
policy.allowedWaveModes.includes(wave?.mode) &&
|
|
627
|
-
!policy.countedLaneModes.includes(wave.mode)
|
|
685
|
+
(!policy.countedLaneModes.includes(wave.mode) ||
|
|
686
|
+
singletonReviewWaveModes.has(wave.mode)) &&
|
|
628
687
|
Array.isArray(wave?.lanes) &&
|
|
629
688
|
wave.lanes.length !== 1
|
|
630
689
|
) {
|
|
@@ -673,25 +732,77 @@ export function validateV2ProfileTopology(
|
|
|
673
732
|
),
|
|
674
733
|
);
|
|
675
734
|
}
|
|
676
|
-
const reconciliationIndexes = waves
|
|
677
|
-
.map((wave, index) => (wave?.mode === 'reconciliation' ? index : -1))
|
|
678
|
-
.filter((index) => index !== -1);
|
|
679
735
|
const requiresReconciliation = ['standard', 'thorough'].includes(
|
|
680
736
|
requestedProfile,
|
|
681
737
|
);
|
|
682
|
-
if (
|
|
683
|
-
reconciliationIndexes.length > 1 ||
|
|
684
|
-
(requiresReconciliation && reconciliationIndexes.length !== 1) ||
|
|
685
|
-
reconciliationIndexes.some((index) => index !== waves.length - 1) ||
|
|
686
|
-
reconciliationIndexes.some((index) => waves[index]?.conditional === true)
|
|
687
|
-
) {
|
|
738
|
+
if (requiresReconciliation !== isObject(execution.reconciliation)) {
|
|
688
739
|
errors.push(
|
|
689
740
|
issue(
|
|
690
741
|
'INVALID_TERMINAL_TOPOLOGY',
|
|
691
|
-
'Standard and thorough routing require exactly one
|
|
692
|
-
`${path}.
|
|
742
|
+
'Standard and thorough routing require exactly one controller reconciliation declaration',
|
|
743
|
+
`${path}.reconciliation`,
|
|
693
744
|
),
|
|
694
745
|
);
|
|
746
|
+
} else if (requiresReconciliation) {
|
|
747
|
+
const reconciliation = execution.reconciliation;
|
|
748
|
+
const expected = {
|
|
749
|
+
producer: 'controller:reconcile-ledger-v1',
|
|
750
|
+
inputLedger: 'raw/drafts/claims-v1.json',
|
|
751
|
+
outputLedger: 'raw/drafts/claims-v2.json',
|
|
752
|
+
outputReview: 'reviews/reconciliation.json',
|
|
753
|
+
requiredReviews: [
|
|
754
|
+
reconciliationReviewPaths['semantic-verification'],
|
|
755
|
+
reconciliationReviewPaths.adversarial,
|
|
756
|
+
reconciliationReviewPaths.coverage,
|
|
757
|
+
],
|
|
758
|
+
conditionalReviews: [
|
|
759
|
+
reconciliationReviewPaths['redundant-verification'],
|
|
760
|
+
reconciliationReviewPaths['contradiction-resolution'],
|
|
761
|
+
],
|
|
762
|
+
};
|
|
763
|
+
const matchesExpected =
|
|
764
|
+
['producer', 'inputLedger', 'outputLedger', 'outputReview'].every(
|
|
765
|
+
(key) => reconciliation[key] === expected[key],
|
|
766
|
+
) &&
|
|
767
|
+
['requiredReviews', 'conditionalReviews'].every(
|
|
768
|
+
(key) =>
|
|
769
|
+
Array.isArray(reconciliation[key]) &&
|
|
770
|
+
reconciliation[key].length === expected[key].length &&
|
|
771
|
+
reconciliation[key].every(
|
|
772
|
+
(value, index) => value === expected[key][index],
|
|
773
|
+
),
|
|
774
|
+
);
|
|
775
|
+
if (!matchesExpected) {
|
|
776
|
+
errors.push(
|
|
777
|
+
issue(
|
|
778
|
+
'INVALID_RECONCILIATION_PATH',
|
|
779
|
+
'Controller reconciliation paths and review sets must exactly match the closed manifest contract',
|
|
780
|
+
`${path}.reconciliation`,
|
|
781
|
+
),
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
for (const [mode, reviewPath] of Object.entries(
|
|
785
|
+
reconciliationReviewPaths,
|
|
786
|
+
)) {
|
|
787
|
+
const matchingWaves = waves.filter((wave) => wave?.mode === mode);
|
|
788
|
+
if (matchingWaves.length !== 1 || matchingWaves[0].lanes?.length !== 1) {
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
const writeRoot = matchingWaves[0].lanes[0].writeRoot;
|
|
792
|
+
if (
|
|
793
|
+
typeof writeRoot === 'string' &&
|
|
794
|
+
reviewPath !== writeRoot &&
|
|
795
|
+
!reviewPath.startsWith(`${writeRoot}/`)
|
|
796
|
+
) {
|
|
797
|
+
errors.push(
|
|
798
|
+
issue(
|
|
799
|
+
'INVALID_RECONCILIATION_PATH',
|
|
800
|
+
`${mode} output ${reviewPath} must be inside its approved write root`,
|
|
801
|
+
`${path}.waves`,
|
|
802
|
+
),
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
695
806
|
}
|
|
696
807
|
|
|
697
808
|
const waveIndexes = new Map(
|
|
@@ -708,7 +819,6 @@ export function validateV2ProfileTopology(
|
|
|
708
819
|
'predicate',
|
|
709
820
|
'maxActivations',
|
|
710
821
|
]);
|
|
711
|
-
const terminalIndex = reconciliationIndexes[0] ?? -1;
|
|
712
822
|
for (const [index, condition] of conditions.entries()) {
|
|
713
823
|
const conditionPath = `${path}.conditions[${index}]`;
|
|
714
824
|
if (
|
|
@@ -852,24 +962,10 @@ export function validateV2ProfileTopology(
|
|
|
852
962
|
);
|
|
853
963
|
}
|
|
854
964
|
}
|
|
855
|
-
if (
|
|
856
|
-
terminalIndex !== -1 &&
|
|
857
|
-
destinationIndex !== undefined &&
|
|
858
|
-
destinationIndex >= terminalIndex
|
|
859
|
-
) {
|
|
860
|
-
errors.push(
|
|
861
|
-
issue(
|
|
862
|
-
'INVALID_TERMINAL_TOPOLOGY',
|
|
863
|
-
'Conditional evidence waves must complete before the one terminal reconciliation',
|
|
864
|
-
`${conditionPath}.destinationWaveId`,
|
|
865
|
-
),
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
965
|
}
|
|
869
966
|
for (const wave of waves) {
|
|
870
967
|
if (
|
|
871
968
|
wave?.conditional === true &&
|
|
872
|
-
wave.mode !== 'reconciliation' &&
|
|
873
969
|
!policy.orderedSingletonWaveModes.includes(wave.mode) &&
|
|
874
970
|
!conditionDestinations.has(wave.waveId)
|
|
875
971
|
) {
|
|
@@ -927,10 +1023,7 @@ export function validateV2ProfileTopology(
|
|
|
927
1023
|
);
|
|
928
1024
|
}
|
|
929
1025
|
const conditionalRequired = stageIndexes.find(
|
|
930
|
-
({
|
|
931
|
-
mode !== 'reconciliation' &&
|
|
932
|
-
indexes.length === 1 &&
|
|
933
|
-
waves[indexes[0]]?.conditional,
|
|
1026
|
+
({ indexes }) => indexes.length === 1 && waves[indexes[0]]?.conditional,
|
|
934
1027
|
);
|
|
935
1028
|
if (conditionalRequired) {
|
|
936
1029
|
errors.push(
|
|
@@ -2192,6 +2285,19 @@ function validateReviewResult(value, errors) {
|
|
|
2192
2285
|
]) {
|
|
2193
2286
|
requiredArray(value, key, errors);
|
|
2194
2287
|
}
|
|
2288
|
+
if (Array.isArray(value.unresolvedIssues)) {
|
|
2289
|
+
for (const [index, unresolvedIssue] of value.unresolvedIssues.entries()) {
|
|
2290
|
+
if (typeof unresolvedIssue !== 'string') {
|
|
2291
|
+
errors.push(
|
|
2292
|
+
issue(
|
|
2293
|
+
'INVALID_UNRESOLVED_ISSUE',
|
|
2294
|
+
'unresolvedIssues members must be strings',
|
|
2295
|
+
`$.unresolvedIssues[${index}]`,
|
|
2296
|
+
),
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2195
2301
|
if (
|
|
2196
2302
|
![
|
|
2197
2303
|
'semantic',
|
|
@@ -52,11 +52,6 @@ const economicalDefaults = Object.freeze({
|
|
|
52
52
|
'Compare evidence with an explicit question or source inventory.',
|
|
53
53
|
taskClass: 'mechanical-recon',
|
|
54
54
|
}),
|
|
55
|
-
reconciliation: Object.freeze({
|
|
56
|
-
assignment:
|
|
57
|
-
'Assemble agreement and disagreement without deciding implications.',
|
|
58
|
-
taskClass: 'mechanical-recon',
|
|
59
|
-
}),
|
|
60
55
|
'contradiction-resolution': Object.freeze({
|
|
61
56
|
assignment: 'Seek discriminating evidence for a named contradiction.',
|
|
62
57
|
taskClass: 'mechanical-recon',
|
|
@@ -172,6 +167,7 @@ function assertProposalExecution(manifest) {
|
|
|
172
167
|
'retryLimit',
|
|
173
168
|
'waves',
|
|
174
169
|
'conditions',
|
|
170
|
+
'reconciliation',
|
|
175
171
|
'approval',
|
|
176
172
|
]),
|
|
177
173
|
'INVALID_ROUTING_PROPOSAL',
|
|
@@ -377,6 +373,7 @@ export function normalizeManifestRouting(manifest) {
|
|
|
377
373
|
target: resolveEffectiveWaveTarget(execution, wave),
|
|
378
374
|
})),
|
|
379
375
|
conditions: clone(execution.conditions),
|
|
376
|
+
reconciliation: clone(execution.reconciliation ?? null),
|
|
380
377
|
approval: clone(execution.approval),
|
|
381
378
|
};
|
|
382
379
|
return deepFreeze(routing);
|
|
@@ -58,7 +58,7 @@ export function createValidatedRun({
|
|
|
58
58
|
passes: normalizePasses(passes),
|
|
59
59
|
achievedProfile,
|
|
60
60
|
assuranceReviewIds: [...assuranceReviewIds].sort(),
|
|
61
|
-
|
|
61
|
+
controllerReconciliation: reconciliationContext?.reconciliation
|
|
62
62
|
? clone(reconciliationContext.reconciliation)
|
|
63
63
|
: null,
|
|
64
64
|
priorLedger: reconciliationContext?.priorLedger
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
4
|
|
|
5
|
+
import { isDirectExecution } from './lib/cli-entry.mjs';
|
|
6
6
|
import {
|
|
7
7
|
checkApprovedWaveTarget,
|
|
8
8
|
createRoutingPreview,
|
|
@@ -79,7 +79,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
if (import.meta.url
|
|
82
|
+
if (isDirectExecution(import.meta.url)) {
|
|
83
83
|
main().catch((error) => {
|
|
84
84
|
const code = error instanceof RoutingContractError ? error.code : 'ERROR';
|
|
85
85
|
process.stderr.write(`${code}: ${error.message}\n`);
|