@open-agent-toolkit/cli 0.3.10 → 0.3.11
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/README.md +2 -1
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/meta.json +13 -0
- package/assets/docs/cli-utilities/tool-packs.md +8 -2
- package/assets/docs/cli-utilities/workflow-gates.md +13 -0
- package/assets/docs/contributing/code.md +8 -2
- package/assets/docs/contributing/documentation.md +7 -1
- package/assets/docs/contributing/meta.json +15 -0
- package/assets/docs/docs-tooling/add-docs-to-a-repo.md +14 -5
- package/assets/docs/docs-tooling/commands.md +26 -8
- package/assets/docs/docs-tooling/meta.json +4 -0
- package/assets/docs/docs-tooling/workflows.md +2 -2
- package/assets/docs/guide/meta.json +4 -0
- package/assets/docs/meta.json +14 -0
- package/assets/docs/provider-sync/instruction-sync.md +8 -0
- package/assets/docs/provider-sync/meta.json +11 -0
- package/assets/docs/provider-sync/scope-and-surface.md +3 -4
- package/assets/docs/reference/cli-reference.md +5 -2
- package/assets/docs/reference/docs-index-contract.md +22 -6
- package/assets/docs/reference/file-locations.md +7 -2
- package/assets/docs/reference/index.md +1 -1
- package/assets/docs/reference/meta.json +10 -0
- package/assets/docs/reference/troubleshooting.md +41 -6
- package/assets/docs/workflows/ideas/meta.json +4 -0
- package/assets/docs/workflows/meta.json +4 -0
- package/assets/docs/workflows/projects/cursor-cloud.md +8 -3
- package/assets/docs/workflows/projects/evidence-layers.md +6 -8
- package/assets/docs/workflows/projects/implementation-execution.md +30 -4
- package/assets/docs/workflows/projects/lifecycle.md +7 -1
- package/assets/docs/workflows/projects/meta.json +25 -0
- package/assets/docs/workflows/projects/orchestration-model.md +5 -6
- package/assets/docs/workflows/skills/meta.json +10 -0
- package/assets/docs/workflows/skills/recon.md +28 -3
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-cursor-cloud-projects/SKILL.md +30 -16
- package/assets/skills/oat-dispatch-subagents/SKILL.md +4 -12
- package/assets/skills/oat-dispatch-subagents/references/managed-claude-example.json +1 -1
- package/assets/skills/oat-dispatch-subagents/references/provider-codex.md +51 -0
- package/assets/skills/oat-dispatch-subagents/references/record-schema.md +8 -7
- package/assets/skills/oat-docs-analyze/SKILL.md +28 -3
- package/assets/skills/oat-docs-apply/SKILL.md +3 -3
- package/assets/skills/oat-docs-authoring/SKILL.md +1 -1
- package/assets/skills/oat-docs-authoring/references/oat-fumadocs-contract.md +5 -2
- package/assets/skills/oat-docs-bootstrap/SKILL.md +4 -3
- package/assets/skills/oat-docs-bootstrap/assets/AGENTS.md.template +3 -3
- package/assets/skills/oat-pjm-decision/SKILL.md +3 -2
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +4 -4
- package/assets/skills/oat-project-complete/SKILL.md +47 -2
- package/assets/skills/oat-project-design/SKILL.md +10 -6
- package/assets/skills/oat-project-discover/SKILL.md +3 -3
- package/assets/skills/oat-project-dispatch-subagents/SKILL.md +3 -10
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +4 -4
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +135 -3
- package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +7 -9
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +4 -4
- package/assets/skills/oat-project-implement/references/plan-and-resume.md +1 -1
- package/assets/skills/oat-project-import-plan/SKILL.md +3 -3
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +4 -4
- package/assets/skills/oat-project-new/SKILL.md +3 -3
- package/assets/skills/oat-project-next/SKILL.md +50 -7
- package/assets/skills/oat-project-plan/SKILL.md +4 -4
- package/assets/skills/oat-project-plan-writing/SKILL.md +2 -2
- package/assets/skills/oat-project-pr-final/SKILL.md +11 -1
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +4 -4
- package/assets/skills/oat-project-promote-spec-driven/SKILL.md +2 -2
- package/assets/skills/oat-project-quick-start/SKILL.md +12 -8
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +4 -4
- package/assets/skills/oat-project-retro/SKILL.md +6 -3
- package/assets/skills/oat-project-review-provide/SKILL.md +2 -2
- package/assets/skills/oat-project-review-provide-remote/SKILL.md +2 -2
- package/assets/skills/oat-project-spec/SKILL.md +3 -3
- package/assets/skills/oat-project-summary/SKILL.md +22 -3
- package/assets/skills/oat-repo-improve/SKILL.md +2 -2
- package/assets/skills/recon/SKILL.md +24 -3
- package/assets/skills/recon/references/packet-contract.md +36 -6
- package/assets/skills/recon/references/profiles.md +3 -0
- package/assets/skills/recon/references/worker-contract.md +16 -3
- package/assets/skills/recon/scripts/create-review-brief.mjs +17 -86
- package/assets/skills/recon/scripts/lib/contracts.mjs +158 -41
- package/assets/skills/recon/scripts/lib/review-binding.mjs +123 -0
- package/assets/skills/recon/scripts/lib/routing.mjs +5 -2
- package/assets/skills/recon/scripts/reconcile-ledger.mjs +18 -7
- package/assets/skills/recon/scripts/render-packet.mjs +93 -1
- package/assets/skills/recon/scripts/validate-packet.mjs +119 -88
- package/assets/templates/state.md +1 -0
- package/dist/commands/backlog/new.d.ts +2 -2
- package/dist/commands/backlog/new.d.ts.map +1 -1
- package/dist/commands/backlog/new.js +2 -2
- package/dist/commands/decision/new.d.ts +2 -2
- package/dist/commands/decision/new.d.ts.map +1 -1
- package/dist/commands/decision/new.js +2 -2
- package/dist/commands/docs/nav/contents.d.ts +9 -0
- package/dist/commands/docs/nav/contents.d.ts.map +1 -1
- package/dist/commands/docs/nav/contents.js +7 -2
- package/dist/commands/docs/nav/fumadocs.d.ts +51 -0
- package/dist/commands/docs/nav/fumadocs.d.ts.map +1 -0
- package/dist/commands/docs/nav/fumadocs.js +301 -0
- package/dist/commands/docs/nav/sync.d.ts +31 -1
- package/dist/commands/docs/nav/sync.d.ts.map +1 -1
- package/dist/commands/docs/nav/sync.js +163 -6
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/instructions/instructions.utils.d.ts +19 -0
- package/dist/commands/instructions/instructions.utils.d.ts.map +1 -1
- package/dist/commands/instructions/instructions.utils.js +89 -17
- package/dist/commands/instructions/sync/sync.d.ts.map +1 -1
- package/dist/commands/instructions/sync/sync.js +49 -2
- package/dist/commands/pjm/init.d.ts.map +1 -1
- package/dist/commands/pjm/init.js +2 -2
- package/dist/commands/project/closeout-check/closeout-invariant.d.ts +97 -0
- package/dist/commands/project/closeout-check/closeout-invariant.d.ts.map +1 -0
- package/dist/commands/project/closeout-check/closeout-invariant.js +301 -0
- package/dist/commands/project/closeout-check/index.d.ts +38 -0
- package/dist/commands/project/closeout-check/index.d.ts.map +1 -0
- package/dist/commands/project/closeout-check/index.js +117 -0
- package/dist/commands/project/complete-state/index.d.ts +4 -0
- package/dist/commands/project/complete-state/index.d.ts.map +1 -1
- package/dist/commands/project/complete-state/index.js +11 -13
- package/dist/commands/project/dispatch/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch/index.js +6 -30
- package/dist/commands/project/dispatch/record.d.ts +9 -18
- package/dist/commands/project/dispatch/record.d.ts.map +1 -1
- package/dist/commands/project/dispatch/record.js +15 -212
- package/dist/commands/project/index.d.ts.map +1 -1
- package/dist/commands/project/index.js +2 -0
- package/dist/commands/project/new/scaffold.d.ts +0 -1
- package/dist/commands/project/new/scaffold.d.ts.map +1 -1
- package/dist/commands/project/new/scaffold.js +19 -20
- package/dist/commands/project/promote/promote.d.ts +2 -2
- package/dist/commands/project/promote/promote.d.ts.map +1 -1
- package/dist/commands/project/promote/promote.js +13 -11
- package/dist/commands/shared/frontmatter.d.ts +21 -0
- package/dist/commands/shared/frontmatter.d.ts.map +1 -1
- package/dist/commands/shared/frontmatter.js +70 -4
- package/dist/commands/shared/template-source.d.ts +29 -0
- package/dist/commands/shared/template-source.d.ts.map +1 -0
- package/dist/commands/{pjm → shared}/template-source.js +24 -7
- package/dist/commands/state/generate.js +2 -2
- package/dist/commands/template/index.d.ts +10 -0
- package/dist/commands/template/index.d.ts.map +1 -0
- package/dist/commands/template/index.js +87 -0
- package/dist/commands/template/resolve.d.ts +31 -0
- package/dist/commands/template/resolve.d.ts.map +1 -0
- package/dist/commands/template/resolve.js +86 -0
- package/dist/fs/io.d.ts +0 -41
- package/dist/fs/io.d.ts.map +1 -1
- package/dist/fs/io.js +2 -414
- package/dist/providers/identity/absolute-paths.d.ts +4 -13
- package/dist/providers/identity/absolute-paths.d.ts.map +1 -1
- package/dist/providers/identity/absolute-paths.js +4 -17
- package/dist/providers/identity/generic-dispatch-record.d.ts +1 -1
- package/dist/providers/identity/generic-dispatch-record.js +2 -2
- package/dist/providers/identity/oat-dispatch-record.d.ts +4 -357
- package/dist/providers/identity/oat-dispatch-record.d.ts.map +1 -1
- package/dist/providers/identity/oat-dispatch-record.js +4 -236
- package/dist/release/public-package-contract.d.ts.map +1 -1
- package/dist/release/public-package-contract.js +12 -4
- package/dist/validation/skills.d.ts.map +1 -1
- package/dist/validation/skills.js +32 -6
- package/package.json +2 -2
- package/dist/commands/pjm/template-source.d.ts +0 -14
- package/dist/commands/pjm/template-source.d.ts.map +0 -1
|
@@ -6,6 +6,13 @@ import { dirname, relative, resolve } from 'node:path';
|
|
|
6
6
|
import { canonicalJson, hashFile } from './lib/canonical-json.mjs';
|
|
7
7
|
import { isDirectExecution } from './lib/cli-entry.mjs';
|
|
8
8
|
import { issue, isObject, validateArtifactShape } from './lib/contracts.mjs';
|
|
9
|
+
import {
|
|
10
|
+
projectEvidenceLink,
|
|
11
|
+
projectReviewQuestions,
|
|
12
|
+
projectReviewScope,
|
|
13
|
+
projectReviewSources,
|
|
14
|
+
reviewBriefExcludedInputs,
|
|
15
|
+
} from './lib/review-binding.mjs';
|
|
9
16
|
import { assertSafeOutputPath } from './lib/safe-path.mjs';
|
|
10
17
|
|
|
11
18
|
const commonKeys = [
|
|
@@ -38,53 +45,11 @@ const forbiddenKeys = new Set([
|
|
|
38
45
|
'dossierPath',
|
|
39
46
|
]);
|
|
40
47
|
|
|
41
|
-
const reviewSourceFields = {
|
|
42
|
-
repository: ['root', 'revision', 'dirty', 'contentHashes'],
|
|
43
|
-
file: ['path', 'contentHash'],
|
|
44
|
-
url: ['url', 'capturePath', 'captureDigest', 'validatorState'],
|
|
45
|
-
'command-output': [
|
|
46
|
-
'argv',
|
|
47
|
-
'cwd',
|
|
48
|
-
'exitStatus',
|
|
49
|
-
'outputPath',
|
|
50
|
-
'outputDigest',
|
|
51
|
-
'environmentNames',
|
|
52
|
-
],
|
|
53
|
-
'connected-resource': [
|
|
54
|
-
'system',
|
|
55
|
-
'resourceId',
|
|
56
|
-
'resourceVersion',
|
|
57
|
-
'retrievalToken',
|
|
58
|
-
'capturePath',
|
|
59
|
-
'captureDigest',
|
|
60
|
-
],
|
|
61
|
-
};
|
|
62
|
-
const commonReviewSourceFields = [
|
|
63
|
-
'id',
|
|
64
|
-
'kind',
|
|
65
|
-
'available',
|
|
66
|
-
'authority',
|
|
67
|
-
'observedAt',
|
|
68
|
-
'validationState',
|
|
69
|
-
];
|
|
70
|
-
|
|
71
48
|
function selectedClaims(ledger, claimIds) {
|
|
72
49
|
const selected = claimIds ? new Set(claimIds) : null;
|
|
73
50
|
return ledger.claims.filter((claim) => !selected || selected.has(claim.id));
|
|
74
51
|
}
|
|
75
52
|
|
|
76
|
-
function projectReviewSource(source) {
|
|
77
|
-
const kindFields = reviewSourceFields[source.kind];
|
|
78
|
-
if (!kindFields) {
|
|
79
|
-
throw new Error(`Cannot project unknown source kind ${source.kind}`);
|
|
80
|
-
}
|
|
81
|
-
return Object.fromEntries(
|
|
82
|
-
[...commonReviewSourceFields, ...kindFields]
|
|
83
|
-
.filter((key) => Object.hasOwn(source, key))
|
|
84
|
-
.map((key) => [key, structuredClone(source[key])]),
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
53
|
function verificationBrief(input) {
|
|
89
54
|
const claims = selectedClaims(input.ledger, input.claimIds);
|
|
90
55
|
const evidenceById = new Map(
|
|
@@ -95,19 +60,14 @@ function verificationBrief(input) {
|
|
|
95
60
|
id: claim.id,
|
|
96
61
|
statement: claim.statement,
|
|
97
62
|
evidence: claim.evidence.map((link) => {
|
|
98
|
-
const evidence =
|
|
63
|
+
const evidence = projectEvidenceLink(link, evidenceById);
|
|
99
64
|
if (!evidence) {
|
|
100
65
|
throw new Error(
|
|
101
66
|
`Claim ${claim.id} references missing evidence ${link.evidenceId}`,
|
|
102
67
|
);
|
|
103
68
|
}
|
|
104
69
|
sourceIds.add(evidence.sourceId);
|
|
105
|
-
return
|
|
106
|
-
id: evidence.id,
|
|
107
|
-
sourceId: evidence.sourceId,
|
|
108
|
-
displayExcerpt: evidence.displayExcerpt,
|
|
109
|
-
locator: structuredClone(evidence.locator),
|
|
110
|
-
};
|
|
70
|
+
return evidence;
|
|
111
71
|
}),
|
|
112
72
|
}));
|
|
113
73
|
const declaredSourceIds = new Set(
|
|
@@ -125,17 +85,9 @@ function verificationBrief(input) {
|
|
|
125
85
|
runId: input.manifest.run.id,
|
|
126
86
|
mode: 'verify',
|
|
127
87
|
createdAt: input.createdAt,
|
|
128
|
-
excludedInputs: [
|
|
129
|
-
'worker_intermediates',
|
|
130
|
-
'prior_reasoning',
|
|
131
|
-
'consumer_summary',
|
|
132
|
-
'artifact_lineage',
|
|
133
|
-
'earlier_reviews',
|
|
134
|
-
],
|
|
88
|
+
excludedInputs: [...reviewBriefExcludedInputs.verify],
|
|
135
89
|
claims: projectedClaims,
|
|
136
|
-
sources: input.manifest
|
|
137
|
-
.filter((source) => sourceIds.has(source.id))
|
|
138
|
-
.map(projectReviewSource),
|
|
90
|
+
sources: projectReviewSources(input.manifest, sourceIds),
|
|
139
91
|
};
|
|
140
92
|
}
|
|
141
93
|
|
|
@@ -147,19 +99,9 @@ function adversarialBrief(input) {
|
|
|
147
99
|
runId: input.manifest.run.id,
|
|
148
100
|
mode: 'adversary',
|
|
149
101
|
createdAt: input.createdAt,
|
|
150
|
-
excludedInputs: [
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
'consumer_summary',
|
|
154
|
-
'artifact_lineage',
|
|
155
|
-
'earlier_reviews',
|
|
156
|
-
'verification_conclusions',
|
|
157
|
-
],
|
|
158
|
-
scope: {
|
|
159
|
-
included: structuredClone(input.manifest.request.includedScope ?? []),
|
|
160
|
-
excluded: structuredClone(input.manifest.request.excludedScope ?? []),
|
|
161
|
-
},
|
|
162
|
-
questions: structuredClone(input.manifest.request.questions ?? []),
|
|
102
|
+
excludedInputs: [...reviewBriefExcludedInputs.adversary],
|
|
103
|
+
scope: projectReviewScope(input.manifest),
|
|
104
|
+
questions: projectReviewQuestions(input.manifest),
|
|
163
105
|
provisionalStatements: selectedClaims(input.ledger, input.claimIds).map(
|
|
164
106
|
(claim) => ({ id: claim.id, statement: claim.statement }),
|
|
165
107
|
),
|
|
@@ -174,20 +116,9 @@ function coverageBrief(input) {
|
|
|
174
116
|
runId: input.manifest.run.id,
|
|
175
117
|
mode: 'coverage',
|
|
176
118
|
createdAt: input.createdAt,
|
|
177
|
-
excludedInputs: [
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
'consumer_summary',
|
|
181
|
-
'artifact_lineage',
|
|
182
|
-
'earlier_reviews',
|
|
183
|
-
'verification_conclusions',
|
|
184
|
-
'adversarial_conclusions',
|
|
185
|
-
],
|
|
186
|
-
scope: {
|
|
187
|
-
included: structuredClone(input.manifest.request.includedScope ?? []),
|
|
188
|
-
excluded: structuredClone(input.manifest.request.excludedScope ?? []),
|
|
189
|
-
},
|
|
190
|
-
questions: structuredClone(input.manifest.request.questions ?? []),
|
|
119
|
+
excludedInputs: [...reviewBriefExcludedInputs.coverage],
|
|
120
|
+
scope: projectReviewScope(input.manifest),
|
|
121
|
+
questions: projectReviewQuestions(input.manifest),
|
|
191
122
|
claims: selectedClaims(input.ledger, input.claimIds).map((claim) => ({
|
|
192
123
|
id: claim.id,
|
|
193
124
|
statement: claim.statement,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commonReviewSourceFields,
|
|
3
|
+
reviewSourceKindFields,
|
|
4
|
+
} from './review-binding.mjs';
|
|
5
|
+
|
|
1
6
|
export const SCHEMA_VERSION = 1;
|
|
2
7
|
export const MANIFEST_SCHEMA_VERSION = 2;
|
|
3
8
|
|
|
@@ -1807,36 +1812,6 @@ function validateDossier(value, errors) {
|
|
|
1807
1812
|
}
|
|
1808
1813
|
}
|
|
1809
1814
|
|
|
1810
|
-
const reviewSourceKindFields = {
|
|
1811
|
-
repository: ['root', 'revision', 'dirty', 'contentHashes'],
|
|
1812
|
-
file: ['path', 'contentHash'],
|
|
1813
|
-
url: ['url', 'capturePath', 'captureDigest', 'validatorState'],
|
|
1814
|
-
'command-output': [
|
|
1815
|
-
'argv',
|
|
1816
|
-
'cwd',
|
|
1817
|
-
'exitStatus',
|
|
1818
|
-
'outputPath',
|
|
1819
|
-
'outputDigest',
|
|
1820
|
-
'environmentNames',
|
|
1821
|
-
],
|
|
1822
|
-
'connected-resource': [
|
|
1823
|
-
'system',
|
|
1824
|
-
'resourceId',
|
|
1825
|
-
'resourceVersion',
|
|
1826
|
-
'retrievalToken',
|
|
1827
|
-
'capturePath',
|
|
1828
|
-
'captureDigest',
|
|
1829
|
-
],
|
|
1830
|
-
};
|
|
1831
|
-
const commonReviewSourceFields = [
|
|
1832
|
-
'id',
|
|
1833
|
-
'kind',
|
|
1834
|
-
'available',
|
|
1835
|
-
'authority',
|
|
1836
|
-
'observedAt',
|
|
1837
|
-
'validationState',
|
|
1838
|
-
];
|
|
1839
|
-
|
|
1840
1815
|
function validateReviewBriefSource(source, index, errors) {
|
|
1841
1816
|
const path = `$.sources[${index}]`;
|
|
1842
1817
|
if (!isObject(source)) {
|
|
@@ -2073,10 +2048,44 @@ function validateReviewBriefSource(source, index, errors) {
|
|
|
2073
2048
|
}
|
|
2074
2049
|
}
|
|
2075
2050
|
|
|
2051
|
+
const reviewBriefIdPattern = /^[a-z0-9]+(?:[-_.][a-z0-9]+)*$/;
|
|
2052
|
+
const reviewBriefTimestampPattern =
|
|
2053
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
|
|
2054
|
+
|
|
2076
2055
|
function validateReviewBriefArtifact(value, errors) {
|
|
2077
2056
|
for (const key of ['id', 'runId', 'mode', 'createdAt']) {
|
|
2078
2057
|
requiredString(value, key, errors);
|
|
2079
2058
|
}
|
|
2059
|
+
// A brief's identifier and timestamp are shown to a blind reviewer, so
|
|
2060
|
+
// both are structurally fixed: a short lowercase slug and a UTC ISO-8601
|
|
2061
|
+
// instant. Neither can carry free text.
|
|
2062
|
+
if (
|
|
2063
|
+
typeof value.id === 'string' &&
|
|
2064
|
+
(value.id.length > 64 || !reviewBriefIdPattern.test(value.id))
|
|
2065
|
+
) {
|
|
2066
|
+
errors.push(
|
|
2067
|
+
issue(
|
|
2068
|
+
'INVALID_REVIEW_BRIEF',
|
|
2069
|
+
'Brief id must be a lowercase slug of at most 64 characters',
|
|
2070
|
+
'$.id',
|
|
2071
|
+
),
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
if (
|
|
2075
|
+
typeof value.createdAt === 'string' &&
|
|
2076
|
+
!(
|
|
2077
|
+
reviewBriefTimestampPattern.test(value.createdAt) &&
|
|
2078
|
+
Number.isFinite(Date.parse(value.createdAt))
|
|
2079
|
+
)
|
|
2080
|
+
) {
|
|
2081
|
+
errors.push(
|
|
2082
|
+
issue(
|
|
2083
|
+
'INVALID_REVIEW_BRIEF',
|
|
2084
|
+
'Brief createdAt must be a UTC ISO-8601 timestamp',
|
|
2085
|
+
'$.createdAt',
|
|
2086
|
+
),
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2080
2089
|
requiredArray(value, 'excludedInputs', errors);
|
|
2081
2090
|
if (value.mode === 'verify') {
|
|
2082
2091
|
requiredArray(value, 'claims', errors);
|
|
@@ -2271,6 +2280,124 @@ function validateReviewBriefArtifact(value, errors) {
|
|
|
2271
2280
|
}
|
|
2272
2281
|
}
|
|
2273
2282
|
|
|
2283
|
+
// The affirming disposition each review kind gives a claim it supports. The
|
|
2284
|
+
// first three are the required reviews for `verified`. The reconciler, the
|
|
2285
|
+
// validator, and the renderer all read this one table.
|
|
2286
|
+
export const affirmingDispositionByReviewKind = Object.freeze({
|
|
2287
|
+
semantic: 'affirmed',
|
|
2288
|
+
adversarial: 'unchallenged',
|
|
2289
|
+
coverage: 'covered',
|
|
2290
|
+
'redundant-verification': 'affirmed',
|
|
2291
|
+
'contradiction-resolution': 'resolved',
|
|
2292
|
+
});
|
|
2293
|
+
|
|
2294
|
+
export const requiredReviewKinds = Object.freeze([
|
|
2295
|
+
'semantic',
|
|
2296
|
+
'adversarial',
|
|
2297
|
+
'coverage',
|
|
2298
|
+
]);
|
|
2299
|
+
|
|
2300
|
+
// `unresolvedIssues` entries are a closed union:
|
|
2301
|
+
// - a string (legacy), read as a global issue;
|
|
2302
|
+
// - `{ text, claimIds }`, scoped to a non-empty list of unique claims the
|
|
2303
|
+
// review covers; or
|
|
2304
|
+
// - `{ text, scope: 'global' }`.
|
|
2305
|
+
// Returns `{ scope: 'global' }`, `{ scope: 'claims', claimIds }`, or
|
|
2306
|
+
// `{ error }`. `coveredClaimIds`, when given, also rejects a scoped claim the
|
|
2307
|
+
// review does not cover. An error is rejected at artifact acceptance and is
|
|
2308
|
+
// never read as "no issue": `unresolvedIssuesBlockClaim` treats it as global.
|
|
2309
|
+
export function classifyUnresolvedIssue(entry, coveredClaimIds = null) {
|
|
2310
|
+
if (typeof entry === 'string') return { scope: 'global' };
|
|
2311
|
+
if (!isObject(entry)) {
|
|
2312
|
+
return {
|
|
2313
|
+
error: 'unresolvedIssues members must be strings or scoped issue objects',
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
if (typeof entry.text !== 'string' || entry.text.trim().length === 0) {
|
|
2317
|
+
return { error: 'Scoped unresolved issues require non-empty text' };
|
|
2318
|
+
}
|
|
2319
|
+
const hasClaimIds = Object.hasOwn(entry, 'claimIds');
|
|
2320
|
+
const hasScope = Object.hasOwn(entry, 'scope');
|
|
2321
|
+
if (hasClaimIds && hasScope) {
|
|
2322
|
+
return {
|
|
2323
|
+
error: 'An unresolved issue names claimIds or a global scope, not both',
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
if (!hasClaimIds && !hasScope) {
|
|
2327
|
+
return {
|
|
2328
|
+
error: 'An unresolved issue object requires claimIds or scope: global',
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
const unknown = Object.keys(entry).filter(
|
|
2332
|
+
(key) => !['text', hasClaimIds ? 'claimIds' : 'scope'].includes(key),
|
|
2333
|
+
);
|
|
2334
|
+
if (unknown.length > 0) {
|
|
2335
|
+
return {
|
|
2336
|
+
error: `Unresolved issue has unknown fields: ${unknown.join(', ')}`,
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
if (hasScope) {
|
|
2340
|
+
return entry.scope === 'global'
|
|
2341
|
+
? { scope: 'global' }
|
|
2342
|
+
: { error: 'An unresolved issue scope must be global' };
|
|
2343
|
+
}
|
|
2344
|
+
if (!Array.isArray(entry.claimIds) || entry.claimIds.length === 0) {
|
|
2345
|
+
return {
|
|
2346
|
+
error: 'Scoped unresolved issues require a non-empty claimIds array',
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
if (!entry.claimIds.every((id) => typeof id === 'string' && id.length > 0)) {
|
|
2350
|
+
return {
|
|
2351
|
+
error: 'Scoped unresolved issue claim IDs must be non-empty strings',
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
if (new Set(entry.claimIds).size !== entry.claimIds.length) {
|
|
2355
|
+
return { error: 'Scoped unresolved issue claim IDs must be unique' };
|
|
2356
|
+
}
|
|
2357
|
+
const uncovered = coveredClaimIds
|
|
2358
|
+
? entry.claimIds.filter((id) => !coveredClaimIds.has(id))
|
|
2359
|
+
: [];
|
|
2360
|
+
if (uncovered.length > 0) {
|
|
2361
|
+
return {
|
|
2362
|
+
error: `Scoped unresolved issue names claims the review does not cover: ${uncovered.join(', ')}`,
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
return { scope: 'claims', claimIds: [...entry.claimIds] };
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// True when any of the review's unresolved issues applies to `claimId`. Call
|
|
2369
|
+
// it only for a claim the review covers: a global issue applies to every
|
|
2370
|
+
// covered claim, a scoped issue only to the claims it names.
|
|
2371
|
+
export function unresolvedIssuesBlockClaim(review, claimId) {
|
|
2372
|
+
return (
|
|
2373
|
+
Array.isArray(review?.unresolvedIssues) ? review.unresolvedIssues : []
|
|
2374
|
+
).some((entry) => {
|
|
2375
|
+
const classification = classifyUnresolvedIssue(entry);
|
|
2376
|
+
if (classification.scope !== 'claims') return true;
|
|
2377
|
+
return classification.claimIds.includes(claimId);
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
function validateUnresolvedIssues(value, errors) {
|
|
2382
|
+
const coveredClaimIds = new Set(
|
|
2383
|
+
(Array.isArray(value.dispositions) ? value.dispositions : [])
|
|
2384
|
+
.map((disposition) => disposition?.claimId)
|
|
2385
|
+
.filter((id) => typeof id === 'string'),
|
|
2386
|
+
);
|
|
2387
|
+
for (const [index, entry] of value.unresolvedIssues.entries()) {
|
|
2388
|
+
const { error } = classifyUnresolvedIssue(entry, coveredClaimIds);
|
|
2389
|
+
if (error) {
|
|
2390
|
+
errors.push(
|
|
2391
|
+
issue(
|
|
2392
|
+
'INVALID_UNRESOLVED_ISSUE',
|
|
2393
|
+
error,
|
|
2394
|
+
`$.unresolvedIssues[${index}]`,
|
|
2395
|
+
),
|
|
2396
|
+
);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2274
2401
|
function validateReviewResult(value, errors) {
|
|
2275
2402
|
for (const key of ['id', 'runId', 'reviewKind', 'reviewerLane', 'status']) {
|
|
2276
2403
|
requiredString(value, key, errors);
|
|
@@ -2286,17 +2413,7 @@ function validateReviewResult(value, errors) {
|
|
|
2286
2413
|
requiredArray(value, key, errors);
|
|
2287
2414
|
}
|
|
2288
2415
|
if (Array.isArray(value.unresolvedIssues)) {
|
|
2289
|
-
|
|
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
|
-
}
|
|
2416
|
+
validateUnresolvedIssues(value, errors);
|
|
2300
2417
|
}
|
|
2301
2418
|
if (
|
|
2302
2419
|
![
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// One owner for review-brief projection: the descriptor allowlists, source,
|
|
2
|
+
// evidence, scope, and question projections, and each mode's fixed exclusion
|
|
3
|
+
// list. The brief generator and the artifact-shape contract read these, and
|
|
4
|
+
// the packet validator checks a brief by rebuilding it through the generator,
|
|
5
|
+
// so there is no second projection to drift. Briefs stay blind: only the
|
|
6
|
+
// fields below are projected, never full manifest entries or provenance.
|
|
7
|
+
|
|
8
|
+
export const reviewSourceKindFields = Object.freeze({
|
|
9
|
+
repository: Object.freeze(['root', 'revision', 'dirty', 'contentHashes']),
|
|
10
|
+
file: Object.freeze(['path', 'contentHash']),
|
|
11
|
+
url: Object.freeze(['url', 'capturePath', 'captureDigest', 'validatorState']),
|
|
12
|
+
'command-output': Object.freeze([
|
|
13
|
+
'argv',
|
|
14
|
+
'cwd',
|
|
15
|
+
'exitStatus',
|
|
16
|
+
'outputPath',
|
|
17
|
+
'outputDigest',
|
|
18
|
+
'environmentNames',
|
|
19
|
+
]),
|
|
20
|
+
'connected-resource': Object.freeze([
|
|
21
|
+
'system',
|
|
22
|
+
'resourceId',
|
|
23
|
+
'resourceVersion',
|
|
24
|
+
'retrievalToken',
|
|
25
|
+
'capturePath',
|
|
26
|
+
'captureDigest',
|
|
27
|
+
]),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const commonReviewSourceFields = Object.freeze([
|
|
31
|
+
'id',
|
|
32
|
+
'kind',
|
|
33
|
+
'available',
|
|
34
|
+
'authority',
|
|
35
|
+
'observedAt',
|
|
36
|
+
'validationState',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
// The fixed exclusion declaration each brief mode carries. It is structural,
|
|
40
|
+
// not caller-supplied, so a brief cannot use it as a free-text channel.
|
|
41
|
+
export const reviewBriefExcludedInputs = Object.freeze({
|
|
42
|
+
verify: Object.freeze([
|
|
43
|
+
'worker_intermediates',
|
|
44
|
+
'prior_reasoning',
|
|
45
|
+
'consumer_summary',
|
|
46
|
+
'artifact_lineage',
|
|
47
|
+
'earlier_reviews',
|
|
48
|
+
]),
|
|
49
|
+
adversary: Object.freeze([
|
|
50
|
+
'worker_intermediates',
|
|
51
|
+
'prior_reasoning',
|
|
52
|
+
'consumer_summary',
|
|
53
|
+
'artifact_lineage',
|
|
54
|
+
'earlier_reviews',
|
|
55
|
+
'verification_conclusions',
|
|
56
|
+
]),
|
|
57
|
+
coverage: Object.freeze([
|
|
58
|
+
'worker_intermediates',
|
|
59
|
+
'prior_reasoning',
|
|
60
|
+
'consumer_summary',
|
|
61
|
+
'artifact_lineage',
|
|
62
|
+
'earlier_reviews',
|
|
63
|
+
'verification_conclusions',
|
|
64
|
+
'adversarial_conclusions',
|
|
65
|
+
]),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Adversarial and coverage briefs carry the approved request's scope and
|
|
69
|
+
// questions, projected here for both the generator and the validator.
|
|
70
|
+
export function projectReviewScope(manifest) {
|
|
71
|
+
return {
|
|
72
|
+
included: structuredClone(manifest?.request?.includedScope ?? []),
|
|
73
|
+
excluded: structuredClone(manifest?.request?.excludedScope ?? []),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function projectReviewQuestions(manifest) {
|
|
78
|
+
return structuredClone(manifest?.request?.questions ?? []);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const adversaryReviewKinds = new Set([
|
|
82
|
+
'adversarial',
|
|
83
|
+
'contradiction-resolution',
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
export function projectReviewSource(source) {
|
|
87
|
+
const kindFields = reviewSourceKindFields[source?.kind];
|
|
88
|
+
if (!kindFields) {
|
|
89
|
+
throw new Error(`Cannot project unknown source kind ${source?.kind}`);
|
|
90
|
+
}
|
|
91
|
+
return Object.fromEntries(
|
|
92
|
+
[...commonReviewSourceFields, ...kindFields]
|
|
93
|
+
.filter((key) => Object.hasOwn(source, key))
|
|
94
|
+
.map((key) => [key, structuredClone(source[key])]),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Projects the manifest sources named by `sourceIds`, in manifest order.
|
|
99
|
+
export function projectReviewSources(manifest, sourceIds) {
|
|
100
|
+
return (manifest?.sources ?? [])
|
|
101
|
+
.filter((source) => sourceIds.has(source?.id))
|
|
102
|
+
.map(projectReviewSource);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function projectEvidenceLink(link, evidenceById) {
|
|
106
|
+
const evidence = evidenceById.get(link?.evidenceId);
|
|
107
|
+
if (!evidence) return null;
|
|
108
|
+
return {
|
|
109
|
+
id: evidence.id,
|
|
110
|
+
sourceId: evidence.sourceId,
|
|
111
|
+
displayExcerpt: evidence.displayExcerpt,
|
|
112
|
+
locator: structuredClone(evidence.locator),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The claim-bearing entries of a brief: adversarial briefs project
|
|
117
|
+
// `provisionalStatements`; verification and coverage briefs project `claims`.
|
|
118
|
+
export function reviewBriefEntries(brief, reviewKind) {
|
|
119
|
+
const entries = adversaryReviewKinds.has(reviewKind)
|
|
120
|
+
? brief?.provisionalStatements
|
|
121
|
+
: brief?.claims;
|
|
122
|
+
return Array.isArray(entries) ? entries : [];
|
|
123
|
+
}
|
|
@@ -428,7 +428,10 @@ export function createRoutingPreview(manifest) {
|
|
|
428
428
|
maxConcurrency: execution.maxConcurrency,
|
|
429
429
|
deadlineSeconds: execution.deadlineSeconds,
|
|
430
430
|
retryLimit: execution.retryLimit,
|
|
431
|
-
|
|
431
|
+
// retryLimit counts pre-acceptance admission retries per lane, and the
|
|
432
|
+
// controller makes at most one, so a lane has at most two attempts.
|
|
433
|
+
worstCaseLaneAttempts:
|
|
434
|
+
laneCount * (Math.min(execution.retryLimit, 1) + 1),
|
|
432
435
|
},
|
|
433
436
|
});
|
|
434
437
|
}
|
|
@@ -512,7 +515,7 @@ export function renderRoutingPreview(preview, format = 'markdown') {
|
|
|
512
515
|
`- Conditions: ${encodeMarkdownValue(preview.limits.conditionCount)}`,
|
|
513
516
|
`- Concurrency: ${encodeMarkdownValue(preview.limits.maxConcurrency)}`,
|
|
514
517
|
`- Deadline seconds: ${encodeMarkdownValue(preview.limits.deadlineSeconds)}`,
|
|
515
|
-
`-
|
|
518
|
+
`- Pre-acceptance admission retries per lane (at most one used): ${encodeMarkdownValue(preview.limits.retryLimit)}`,
|
|
516
519
|
`- Lane attempts: ${encodeMarkdownValue(preview.limits.worstCaseLaneAttempts)}`,
|
|
517
520
|
);
|
|
518
521
|
if (preview.profileCaps) {
|
|
@@ -12,7 +12,12 @@ import { dirname, resolve } from 'node:path';
|
|
|
12
12
|
|
|
13
13
|
import { canonicalJson, hashFile } from './lib/canonical-json.mjs';
|
|
14
14
|
import { isDirectExecution } from './lib/cli-entry.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
affirmingDispositionByReviewKind,
|
|
17
|
+
requiredReviewKinds,
|
|
18
|
+
unresolvedIssuesBlockClaim,
|
|
19
|
+
validateArtifactShape,
|
|
20
|
+
} from './lib/contracts.mjs';
|
|
16
21
|
import {
|
|
17
22
|
assertCanonicalRoot,
|
|
18
23
|
assertSafeExistingPath,
|
|
@@ -20,11 +25,12 @@ import {
|
|
|
20
25
|
assertUnchangedRoot,
|
|
21
26
|
} from './lib/safe-path.mjs';
|
|
22
27
|
|
|
23
|
-
const requiredDispositions = new Map(
|
|
24
|
-
[
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
])
|
|
28
|
+
const requiredDispositions = new Map(
|
|
29
|
+
requiredReviewKinds.map((kind) => [
|
|
30
|
+
kind,
|
|
31
|
+
affirmingDispositionByReviewKind[kind],
|
|
32
|
+
]),
|
|
33
|
+
);
|
|
28
34
|
|
|
29
35
|
const permittedDispositions = new Map([
|
|
30
36
|
['semantic', new Set(['affirmed', 'rejected', 'uncertain'])],
|
|
@@ -233,6 +239,11 @@ export function reconcileLedger({
|
|
|
233
239
|
(item) => item.claimId === claim.id && item.disposition === 'uncertain',
|
|
234
240
|
),
|
|
235
241
|
);
|
|
242
|
+
// A covering review's global issue, or an issue scoped to this claim,
|
|
243
|
+
// keeps the claim below verified. Publication applies the same rule.
|
|
244
|
+
const issueBlocked = supporting.some((review) =>
|
|
245
|
+
unresolvedIssuesBlockClaim(review, claim.id),
|
|
246
|
+
);
|
|
236
247
|
const incomplete = [...requiredDispositions.keys()].some(
|
|
237
248
|
(kind) =>
|
|
238
249
|
!reviewResults.some(
|
|
@@ -291,7 +302,7 @@ export function reconcileLedger({
|
|
|
291
302
|
}
|
|
292
303
|
continue;
|
|
293
304
|
}
|
|
294
|
-
if (uncertain || incomplete) {
|
|
305
|
+
if (uncertain || issueBlocked || incomplete) {
|
|
295
306
|
const from = claim.status;
|
|
296
307
|
const to = 'unresolved';
|
|
297
308
|
if (legalReconciliationTransitions.has(`${from}:${to}`)) {
|