project-tiny-context-harness 0.2.80 → 0.2.82
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 +10 -8
- package/assets/README.md +9 -7
- package/assets/README.zh-CN.md +10 -0
- package/assets/github/harness.yml +1 -1
- package/assets/skills/composite-long-task-workflow/SKILL.md +10 -2
- package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +2 -0
- package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +7 -3
- package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +27 -11
- package/dist/lib/composite-long-task-renderer.js +18 -62
- package/dist/lib/superpowers-task-assertion-normalizers.d.ts +3 -0
- package/dist/lib/superpowers-task-assertion-normalizers.js +70 -0
- package/dist/lib/superpowers-task-assertions.d.ts +20 -0
- package/dist/lib/superpowers-task-assertions.js +243 -0
- package/dist/lib/superpowers-task-compile-diagnostics.d.ts +5 -0
- package/dist/lib/superpowers-task-compile-diagnostics.js +20 -0
- package/dist/lib/superpowers-task-compile-guards.d.ts +2 -0
- package/dist/lib/superpowers-task-compile-guards.js +66 -0
- package/dist/lib/superpowers-task-compile.js +25 -5
- package/dist/lib/superpowers-task-conformance.d.ts +2 -0
- package/dist/lib/superpowers-task-conformance.js +24 -0
- package/dist/lib/superpowers-task-delivery.js +30 -18
- package/dist/lib/superpowers-task-derive.d.ts +1 -0
- package/dist/lib/superpowers-task-derive.js +121 -4
- package/dist/lib/superpowers-task-fields.d.ts +22 -0
- package/dist/lib/superpowers-task-fields.js +276 -0
- package/dist/lib/superpowers-task-gates.js +57 -3
- package/dist/lib/superpowers-task-source-compile.js +128 -92
- package/dist/lib/superpowers-task-source-parser.js +12 -18
- package/dist/lib/superpowers-task-state-schema.d.ts +120 -1
- package/dist/lib/superpowers-task-state.js +33 -8
- package/dist/lib/superpowers-task-status.d.ts +2 -0
- package/dist/lib/superpowers-task-status.js +14 -0
- package/dist/lib/superpowers-task-validator.js +18 -9
- package/package.json +69 -69
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { normalizeProofLayerId } from "./superpowers-task-fields.js";
|
|
2
|
+
import { isRecord } from "./superpowers-task-state-schema.js";
|
|
3
|
+
export function normalizeAssertionResult(value) {
|
|
4
|
+
if (!isRecord(value)) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
return {
|
|
8
|
+
schema_version: String(value.schema_version ?? ""),
|
|
9
|
+
status: String(value.status ?? ""),
|
|
10
|
+
runner: String(value.runner ?? ""),
|
|
11
|
+
exit_code: numberValue(value.exit_code),
|
|
12
|
+
target_ac_ids: stringArray(value.target_ac_ids),
|
|
13
|
+
target_proof_layers: stringArray(value.target_proof_layers).map(normalizeProofLayerId),
|
|
14
|
+
owner_surface: value.owner_surface === undefined ? undefined : String(value.owner_surface),
|
|
15
|
+
route: value.route === undefined ? undefined : String(value.route),
|
|
16
|
+
action: value.action === undefined ? undefined : String(value.action),
|
|
17
|
+
positive_assertions: checkArray(value.positive_assertions),
|
|
18
|
+
negative_assertions: checkArray(value.negative_assertions),
|
|
19
|
+
artifacts: stringArray(value.artifacts)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function normalizeNegativeEvidenceScan(value) {
|
|
23
|
+
if (!isRecord(value)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
schema_version: String(value.schema_version ?? ""),
|
|
28
|
+
status: String(value.status ?? ""),
|
|
29
|
+
target_ac_ids: stringArray(value.target_ac_ids),
|
|
30
|
+
target_proof_layers: stringArray(value.target_proof_layers).map(normalizeProofLayerId),
|
|
31
|
+
invalid_completion_signals_checked: stringArray(value.invalid_completion_signals_checked),
|
|
32
|
+
owner_surface: value.owner_surface === undefined ? undefined : String(value.owner_surface),
|
|
33
|
+
route: value.route === undefined ? undefined : String(value.route),
|
|
34
|
+
forbidden_findings: findingArray(value.forbidden_findings),
|
|
35
|
+
required_findings: checkArray(value.required_findings),
|
|
36
|
+
artifacts: stringArray(value.artifacts)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function checkArray(value) {
|
|
40
|
+
if (!Array.isArray(value)) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
return value.filter(isRecord).map((item) => ({
|
|
44
|
+
id: String(item.id ?? ""),
|
|
45
|
+
status: String(item.status ?? ""),
|
|
46
|
+
actual: item.actual === undefined ? undefined : String(item.actual),
|
|
47
|
+
expected: item.expected === undefined ? undefined : String(item.expected),
|
|
48
|
+
forbidden_text: item.forbidden_text === undefined ? undefined : String(item.forbidden_text)
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
function findingArray(value) {
|
|
52
|
+
if (!Array.isArray(value)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
return value.filter(isRecord).map((item) => ({
|
|
56
|
+
id: String(item.id ?? ""),
|
|
57
|
+
status: String(item.status ?? ""),
|
|
58
|
+
forbidden_text: item.forbidden_text === undefined ? undefined : String(item.forbidden_text),
|
|
59
|
+
actual: item.actual === undefined ? undefined : String(item.actual)
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
function stringArray(value) {
|
|
63
|
+
if (!Array.isArray(value)) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return value.map((item) => String(item)).filter(Boolean);
|
|
67
|
+
}
|
|
68
|
+
function numberValue(value) {
|
|
69
|
+
return typeof value === "number" && Number.isFinite(value) ? value : Number.NaN;
|
|
70
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SuperpowersEvidenceRecord, type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export { normalizeAssertionResult, normalizeNegativeEvidenceScan } from "./superpowers-task-assertion-normalizers.js";
|
|
3
|
+
export type AssertionStatus = "passed" | "failed" | "missing" | "stale" | "not_applicable";
|
|
4
|
+
export interface ProofLayerAssertionEvaluation {
|
|
5
|
+
assertion_status: AssertionStatus;
|
|
6
|
+
blocking_assertion_failures: string[];
|
|
7
|
+
negative_evidence_findings: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare const MACHINE_VERIFIABLE_PROOF_LAYERS: Set<string>;
|
|
10
|
+
export declare const UI_BROWSER_ASSERTION_TYPES: Set<string>;
|
|
11
|
+
export declare function proofLayerName(layerId: string): string;
|
|
12
|
+
export declare function proofLayerAcId(layerId: string): string;
|
|
13
|
+
export declare function isMachineVerifiableLayer(layerId: string): boolean;
|
|
14
|
+
export declare function isUiBrowserLayer(layerId: string): boolean;
|
|
15
|
+
export declare function evaluateAcEvidence(state: SuperpowersTaskState, acId: string): ProofLayerAssertionEvaluation;
|
|
16
|
+
export declare function evaluateProofLayer(state: SuperpowersTaskState, layerId: string): ProofLayerAssertionEvaluation;
|
|
17
|
+
export declare function evaluateProofLayerAssertions(state: SuperpowersTaskState, layerId: string): ProofLayerAssertionEvaluation;
|
|
18
|
+
export declare function assertionFailuresForState(state: SuperpowersTaskState): string[];
|
|
19
|
+
export declare function evaluateAssertionEvidence(evidence: SuperpowersEvidenceRecord, layerId: string): string[];
|
|
20
|
+
export declare function evaluateNegativeEvidence(evidence: SuperpowersEvidenceRecord, layerId: string): string[];
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { MACHINE_VERIFIABLE_LAYER_NAMES, normalizeProofLayerId, normalizeProofLayerName } from "./superpowers-task-fields.js";
|
|
2
|
+
export { normalizeAssertionResult, normalizeNegativeEvidenceScan } from "./superpowers-task-assertion-normalizers.js";
|
|
3
|
+
export const MACHINE_VERIFIABLE_PROOF_LAYERS = new Set(MACHINE_VERIFIABLE_LAYER_NAMES);
|
|
4
|
+
export const UI_BROWSER_ASSERTION_TYPES = new Set(["browser_assertion", "playwright_assertion", "ui_browser_assertion"]);
|
|
5
|
+
const DEFAULT_UI_FORBIDDEN_FINAL_STATES = ["未验证", "不可用", "暂不可用", "页面无明显变化"];
|
|
6
|
+
const GENERIC_INVALID_EVIDENCE_TYPE_PATTERNS = [
|
|
7
|
+
/\bfinal[-_ ]?(?:result[-_ ]?)?card\b/i,
|
|
8
|
+
/\b(?:plan[-_ ]?conformance[-_ ]?)?matrix\b/i,
|
|
9
|
+
/\b(?:final[-_ ]?acceptance[-_ ]?)?verdict\b/i,
|
|
10
|
+
/\bvalidator[-_ ]?pass\b/i,
|
|
11
|
+
/\bauditor[-_ ]?pass\b/i,
|
|
12
|
+
/\bsubagent[-_ ]?summary\b/i,
|
|
13
|
+
/\bagent[-_ ]?summary\b/i,
|
|
14
|
+
/\bprose[-_ ]?summary\b/i,
|
|
15
|
+
/\bfile[-_ ]?exists\b/i,
|
|
16
|
+
/\bartifact[-_ ]?exists\b/i,
|
|
17
|
+
/\btest[-_ ]?name[-_ ]?only\b/i,
|
|
18
|
+
/\bdiagnostic[-_ ]?(?:surface|page)?\b/i,
|
|
19
|
+
/\blocal[-_ ]?audit\b/i,
|
|
20
|
+
/\bcomponent[-_ ]?screenshot\b/i,
|
|
21
|
+
/\bstorybook\b/i,
|
|
22
|
+
/\bdom[-_ ]?snippet\b/i,
|
|
23
|
+
/\bsample[-_ ]?for[-_ ]?full[-_ ]?population\b/i
|
|
24
|
+
];
|
|
25
|
+
export function proofLayerName(layerId) {
|
|
26
|
+
const raw = layerId.includes(".") ? layerId.slice(layerId.lastIndexOf(".") + 1) : layerId;
|
|
27
|
+
return normalizeProofLayerName(raw);
|
|
28
|
+
}
|
|
29
|
+
export function proofLayerAcId(layerId) {
|
|
30
|
+
return layerId.includes(".") ? layerId.slice(0, layerId.lastIndexOf(".")) : "";
|
|
31
|
+
}
|
|
32
|
+
export function isMachineVerifiableLayer(layerId) {
|
|
33
|
+
return MACHINE_VERIFIABLE_PROOF_LAYERS.has(proofLayerName(layerId));
|
|
34
|
+
}
|
|
35
|
+
export function isUiBrowserLayer(layerId) {
|
|
36
|
+
return proofLayerName(layerId) === "ui_browser";
|
|
37
|
+
}
|
|
38
|
+
export function evaluateAcEvidence(state, acId) {
|
|
39
|
+
const ac = state.graph.acceptance_criteria[acId];
|
|
40
|
+
if (!ac) {
|
|
41
|
+
return {
|
|
42
|
+
assertion_status: "missing",
|
|
43
|
+
blocking_assertion_failures: [`AC ${acId} missing from task graph`],
|
|
44
|
+
negative_evidence_findings: []
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const evaluations = ac.required_proof_layers.map((layer) => evaluateProofLayer(state, `${acId}.${layer}`));
|
|
48
|
+
const applicable = evaluations.filter((item) => item.assertion_status !== "not_applicable");
|
|
49
|
+
const blocking_assertion_failures = applicable.flatMap((item) => item.blocking_assertion_failures);
|
|
50
|
+
const negative_evidence_findings = applicable.flatMap((item) => item.negative_evidence_findings);
|
|
51
|
+
const statuses = applicable.map((item) => item.assertion_status);
|
|
52
|
+
const assertion_status = applicable.length === 0
|
|
53
|
+
? "not_applicable"
|
|
54
|
+
: statuses.includes("failed")
|
|
55
|
+
? "failed"
|
|
56
|
+
: statuses.includes("stale")
|
|
57
|
+
? "stale"
|
|
58
|
+
: statuses.includes("missing")
|
|
59
|
+
? "missing"
|
|
60
|
+
: "passed";
|
|
61
|
+
return { assertion_status, blocking_assertion_failures, negative_evidence_findings };
|
|
62
|
+
}
|
|
63
|
+
export function evaluateProofLayer(state, layerId) {
|
|
64
|
+
return evaluateProofLayerAssertions(state, layerId);
|
|
65
|
+
}
|
|
66
|
+
export function evaluateProofLayerAssertions(state, layerId) {
|
|
67
|
+
if (!isMachineVerifiableLayer(layerId)) {
|
|
68
|
+
return { assertion_status: "not_applicable", blocking_assertion_failures: [], negative_evidence_findings: [] };
|
|
69
|
+
}
|
|
70
|
+
const layer = state.graph.proof_layers[layerId];
|
|
71
|
+
const evidenceById = new Map((state.evidence ?? []).map((item) => [item.evidence_id, item]));
|
|
72
|
+
const evidenceRecords = (layer?.evidence_ids ?? []).map((id) => evidenceById.get(id)).filter((item) => Boolean(item));
|
|
73
|
+
if (!layer || evidenceRecords.length === 0) {
|
|
74
|
+
return {
|
|
75
|
+
assertion_status: "missing",
|
|
76
|
+
blocking_assertion_failures: [`proof layer ${layerId} missing assertion result`],
|
|
77
|
+
negative_evidence_findings: []
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const blocking = [];
|
|
81
|
+
const negative = [];
|
|
82
|
+
for (const evidence of evidenceRecords) {
|
|
83
|
+
blocking.push(...evaluateAssertionEvidence(evidence, layerId));
|
|
84
|
+
negative.push(...evaluateNegativeEvidence(evidence, layerId));
|
|
85
|
+
}
|
|
86
|
+
const combined = [...blocking, ...negative];
|
|
87
|
+
if (combined.length === 0) {
|
|
88
|
+
return { assertion_status: "passed", blocking_assertion_failures: [], negative_evidence_findings: [] };
|
|
89
|
+
}
|
|
90
|
+
const text = combined.join("\n");
|
|
91
|
+
const status = /missing assertion result/i.test(text)
|
|
92
|
+
? "missing"
|
|
93
|
+
: /\bstale\b/i.test(text)
|
|
94
|
+
? "stale"
|
|
95
|
+
: "failed";
|
|
96
|
+
return { assertion_status: status, blocking_assertion_failures: blocking, negative_evidence_findings: negative };
|
|
97
|
+
}
|
|
98
|
+
export function assertionFailuresForState(state) {
|
|
99
|
+
return Object.keys(state.graph?.proof_layers ?? {}).flatMap((layerId) => {
|
|
100
|
+
const evaluation = evaluateProofLayerAssertions(state, layerId);
|
|
101
|
+
return [...evaluation.blocking_assertion_failures, ...evaluation.negative_evidence_findings];
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export function evaluateAssertionEvidence(evidence, layerId) {
|
|
105
|
+
const failures = [];
|
|
106
|
+
const acId = proofLayerAcId(layerId);
|
|
107
|
+
const layerName = proofLayerName(layerId);
|
|
108
|
+
const assertion = evidence.assertion_result;
|
|
109
|
+
const label = `proof layer ${layerId} evidence ${evidence.evidence_id}`;
|
|
110
|
+
const invalidEvidence = invalidEvidenceReason(evidence, layerName);
|
|
111
|
+
if (invalidEvidence) {
|
|
112
|
+
failures.push(`${label} invalid evidence forbidden shortcut ${evidence.type || "(missing)"} cannot satisfy ${layerName}: ${invalidEvidence}`);
|
|
113
|
+
}
|
|
114
|
+
if (!assertion) {
|
|
115
|
+
failures.push(`${label} missing assertion result; ${layerName} proof not machine-backed`);
|
|
116
|
+
return failures;
|
|
117
|
+
}
|
|
118
|
+
if (assertion.schema_version !== "assertion-result-v1") {
|
|
119
|
+
failures.push(`${label} assertion_result.schema_version must be assertion-result-v1`);
|
|
120
|
+
}
|
|
121
|
+
if (assertion.status !== "passed") {
|
|
122
|
+
failures.push(`${label} assertion_result.status=${assertion.status}; expected passed`);
|
|
123
|
+
}
|
|
124
|
+
if (assertion.exit_code !== 0) {
|
|
125
|
+
failures.push(`${label} assertion exit_code=${assertion.exit_code}; expected 0`);
|
|
126
|
+
}
|
|
127
|
+
if (evidence.command_exit_code !== undefined && evidence.command_exit_code !== 0) {
|
|
128
|
+
failures.push(`${label} command_exit_code=${evidence.command_exit_code}; expected 0`);
|
|
129
|
+
}
|
|
130
|
+
if (!assertion.target_ac_ids.includes(acId)) {
|
|
131
|
+
failures.push(`${label} assertion target ACs ${assertion.target_ac_ids.join(", ") || "(none)"} do not include ${acId}`);
|
|
132
|
+
}
|
|
133
|
+
const assertionTargetLayers = assertion.target_proof_layers.map(normalizeProofLayerId);
|
|
134
|
+
const normalizedLayerId = normalizeProofLayerId(layerId);
|
|
135
|
+
if (!assertionTargetLayers.includes(normalizedLayerId) && !assertionTargetLayers.includes(layerName)) {
|
|
136
|
+
failures.push(`${label} assertion target_proof_layers ${assertion.target_proof_layers.join(", ") || "(none)"} do not include ${layerId} or ${layerName}`);
|
|
137
|
+
}
|
|
138
|
+
failures.push(...checkAssertions(`${label} positive assertion`, assertion.positive_assertions));
|
|
139
|
+
failures.push(...checkAssertions(`${label} negative assertion`, assertion.negative_assertions));
|
|
140
|
+
if ((assertion.artifacts?.length ?? 0) === 0 && evidence.artifact_paths.length === 0) {
|
|
141
|
+
failures.push(`${label} assertion-backed evidence must include artifacts`);
|
|
142
|
+
}
|
|
143
|
+
if (isUiBrowserLayer(layerId)) {
|
|
144
|
+
failures.push(...validateUiBrowserAssertion(evidence, assertion, layerId));
|
|
145
|
+
}
|
|
146
|
+
return failures;
|
|
147
|
+
}
|
|
148
|
+
function validateUiBrowserAssertion(evidence, assertion, layerId) {
|
|
149
|
+
const failures = [];
|
|
150
|
+
const label = `proof layer ${layerId} evidence ${evidence.evidence_id}`;
|
|
151
|
+
if (!UI_BROWSER_ASSERTION_TYPES.has(evidence.type)) {
|
|
152
|
+
const apiOnly = /\bapi\b/i.test(evidence.type) ? "; API-only cannot satisfy UI Path AC" : "";
|
|
153
|
+
failures.push(`${label} ui_browser evidence type ${evidence.type || "(missing)"} is not allowed${apiOnly}`);
|
|
154
|
+
}
|
|
155
|
+
if (!assertion.owner_surface) {
|
|
156
|
+
failures.push(`${label} missing owner_surface`);
|
|
157
|
+
}
|
|
158
|
+
if (!assertion.route) {
|
|
159
|
+
failures.push(`${label} missing route`);
|
|
160
|
+
}
|
|
161
|
+
if (!assertion.action) {
|
|
162
|
+
failures.push(`${label} missing action-level UI proof`);
|
|
163
|
+
}
|
|
164
|
+
const artifacts = [...(assertion.artifacts ?? []), ...evidence.artifact_paths];
|
|
165
|
+
if (!artifacts.some((artifact) => /\.(png|jpe?g|zip|json)$/i.test(artifact) || /\b(trace|screenshot|report)\b/i.test(artifact))) {
|
|
166
|
+
failures.push(`${label} ui_browser assertion must include screenshot, trace or report artifact`);
|
|
167
|
+
}
|
|
168
|
+
const negativeCoverage = assertion.negative_assertions.map((item) => `${item.id} ${item.forbidden_text ?? ""}`).join("\n");
|
|
169
|
+
for (const forbidden of DEFAULT_UI_FORBIDDEN_FINAL_STATES) {
|
|
170
|
+
if (!negativeCoverage.includes(forbidden)) {
|
|
171
|
+
failures.push(`${label} missing negative assertion coverage for forbidden final state ${forbidden}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (!evidence.negative_evidence_scan) {
|
|
175
|
+
failures.push(`${label} missing negative_evidence_scan for ui_browser layer`);
|
|
176
|
+
}
|
|
177
|
+
return failures;
|
|
178
|
+
}
|
|
179
|
+
function checkAssertions(prefix, checks) {
|
|
180
|
+
return checks
|
|
181
|
+
.filter((check) => check.status !== "passed")
|
|
182
|
+
.map((check) => `${prefix} ${check.id || "(unnamed)"} status=${check.status}; expected passed${check.forbidden_text ? ` forbidden_text=${check.forbidden_text}` : ""}`);
|
|
183
|
+
}
|
|
184
|
+
export function evaluateNegativeEvidence(evidence, layerId) {
|
|
185
|
+
const scan = evidence.negative_evidence_scan;
|
|
186
|
+
if (!scan) {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const acId = proofLayerAcId(layerId);
|
|
190
|
+
const label = `proof layer ${layerId} evidence ${evidence.evidence_id}`;
|
|
191
|
+
const findings = [];
|
|
192
|
+
if (scan.schema_version !== "negative-evidence-scan-v1") {
|
|
193
|
+
findings.push(`${label} negative_evidence_scan.schema_version must be negative-evidence-scan-v1`);
|
|
194
|
+
}
|
|
195
|
+
if (scan.status !== "passed") {
|
|
196
|
+
findings.push(`${label} negative evidence scan status=${scan.status}; expected passed`);
|
|
197
|
+
}
|
|
198
|
+
if (!scan.target_ac_ids.includes(acId)) {
|
|
199
|
+
findings.push(`${label} negative evidence scan target ACs ${scan.target_ac_ids.join(", ") || "(none)"} do not include ${acId}`);
|
|
200
|
+
}
|
|
201
|
+
const targetLayers = (scan.target_proof_layers ?? []).map(normalizeProofLayerId);
|
|
202
|
+
const normalizedLayerId = normalizeProofLayerId(layerId);
|
|
203
|
+
if (targetLayers.length === 0) {
|
|
204
|
+
findings.push(`${label} negative evidence scan target proof layers are missing; expected ${normalizedLayerId}`);
|
|
205
|
+
}
|
|
206
|
+
else if (!targetLayers.includes(normalizedLayerId) && !targetLayers.includes(proofLayerName(layerId))) {
|
|
207
|
+
findings.push(`${label} negative evidence scan target proof layers ${targetLayers.join(", ") || "(none)"} do not include ${normalizedLayerId}`);
|
|
208
|
+
}
|
|
209
|
+
for (const finding of scan.forbidden_findings ?? []) {
|
|
210
|
+
if (finding.status === "found") {
|
|
211
|
+
findings.push(`${label} negative evidence found forbidden text ${finding.forbidden_text ?? finding.id}: ${finding.actual ?? ""}`.trim());
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const required of scan.required_findings ?? []) {
|
|
215
|
+
if (required.status !== "passed") {
|
|
216
|
+
findings.push(`${label} negative evidence required finding ${required.id} status=${required.status}; expected passed`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if ((scan.artifacts ?? []).length === 0) {
|
|
220
|
+
findings.push(`${label} negative evidence scan must include artifacts`);
|
|
221
|
+
}
|
|
222
|
+
return findings;
|
|
223
|
+
}
|
|
224
|
+
function invalidEvidenceReason(evidence, layerName) {
|
|
225
|
+
const evidenceText = [evidence.type, evidence.command, ...evidence.artifact_paths, ...evidence.proves, ...evidence.does_not_prove].join("\n");
|
|
226
|
+
const genericShortcut = GENERIC_INVALID_EVIDENCE_TYPE_PATTERNS.find((pattern) => pattern.test(evidenceText));
|
|
227
|
+
if (genericShortcut) {
|
|
228
|
+
return `matches invalid completion evidence ${genericShortcut}`;
|
|
229
|
+
}
|
|
230
|
+
if ((layerName === "worker_runtime" || layerName === "integration" || layerName === "ui_browser") && /\b(unit|mock|viewmodel)\b/i.test(evidence.type)) {
|
|
231
|
+
return "unit, mock or viewmodel evidence is auxiliary only for runtime, worker, integration and UI layers";
|
|
232
|
+
}
|
|
233
|
+
if (layerName === "ui_browser" && /\b(api|schema)\b/i.test(evidence.type)) {
|
|
234
|
+
return "API-only evidence cannot satisfy UI Path AC";
|
|
235
|
+
}
|
|
236
|
+
if (layerName === "ui_browser" && /^screenshot$/i.test(evidence.type)) {
|
|
237
|
+
return "screenshot-only evidence cannot satisfy UI Path AC";
|
|
238
|
+
}
|
|
239
|
+
if ((layerName === "worker_runtime" || layerName === "data_artifact" || layerName === "security_redaction") && /\b(ui|browser|playwright|screenshot)\b/i.test(evidence.type)) {
|
|
240
|
+
return "UI-only evidence cannot satisfy runtime, data or security proof layers";
|
|
241
|
+
}
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type CompileReportCategory = "blocking_missing_source" | "blocking_missing_plan" | "blocking_missing_checklist" | "blocking_unparseable_object" | "blocking_scope_conflict" | "blocking_missing_assertion_spec" | "blocking_missing_owner_boundary" | "blocking_missing_primary_path" | "blocking_missing_observable_result" | "blocking_missing_invalid_evidence" | "warning_weak_acceptance_wording" | "hygiene_non_canonical_field_order";
|
|
2
|
+
export declare function compileError(message: string, category: CompileReportCategory, file: string, line: number, field: string, whyBlocking: string, requiredFix: string): string;
|
|
3
|
+
export declare function compileReportSuffix(category: CompileReportCategory, file: string, line: number, field: string, whyBlocking: string, requiredFix: string): string;
|
|
4
|
+
export declare function missingCategory(label: string): CompileReportCategory;
|
|
5
|
+
export declare function throwCompileErrors(errors: string[]): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function compileError(message, category, file, line, field, whyBlocking, requiredFix) {
|
|
2
|
+
return `${message}${compileReportSuffix(category, file, line, field, whyBlocking, requiredFix)}`;
|
|
3
|
+
}
|
|
4
|
+
export function compileReportSuffix(category, file, line, field, whyBlocking, requiredFix) {
|
|
5
|
+
return ` [category=${category}; file=${file}; line=${line}; invalid_or_missing_field=${field}; why_blocking=${whyBlocking}; required_fix=${requiredFix}; rerun_compile_enough=true]`;
|
|
6
|
+
}
|
|
7
|
+
export function missingCategory(label) {
|
|
8
|
+
if (/^PI-\d+/i.test(label)) {
|
|
9
|
+
return "blocking_missing_plan";
|
|
10
|
+
}
|
|
11
|
+
if (/^AC-\d+/i.test(label)) {
|
|
12
|
+
return "blocking_missing_checklist";
|
|
13
|
+
}
|
|
14
|
+
return "blocking_missing_source";
|
|
15
|
+
}
|
|
16
|
+
export function throwCompileErrors(errors) {
|
|
17
|
+
if (errors.length > 0) {
|
|
18
|
+
throw new Error(`Superpowers source compile failed:\n- ${errors.join("\n- ")}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { type SuperpowersAcceptanceCriterion, type SuperpowersPlanItem, type SuperpowersProductArchitectureScope } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export declare function validateCompiledSources(product: SuperpowersProductArchitectureScope, planItems: Record<string, SuperpowersPlanItem>, acceptanceCriteria: Record<string, SuperpowersAcceptanceCriterion>): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MACHINE_VERIFIABLE_PROOF_LAYERS } from "./superpowers-task-assertions.js";
|
|
2
|
+
import { compileError, throwCompileErrors } from "./superpowers-task-compile-diagnostics.js";
|
|
3
|
+
const ALLOWED_PROOF_LAYERS = new Set(["code", ...MACHINE_VERIFIABLE_PROOF_LAYERS]);
|
|
4
|
+
export function validateCompiledSources(product, planItems, acceptanceCriteria) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
const planIds = new Set(Object.keys(planItems));
|
|
7
|
+
const acIds = new Set(Object.keys(acceptanceCriteria));
|
|
8
|
+
for (const [planId, item] of Object.entries(planItems)) {
|
|
9
|
+
validatePlanItem(planId, item, acIds, acceptanceCriteria, errors);
|
|
10
|
+
}
|
|
11
|
+
for (const [acId, ac] of Object.entries(acceptanceCriteria)) {
|
|
12
|
+
validateAcceptanceCriterion(product, acId, ac, planIds, planItems, errors);
|
|
13
|
+
}
|
|
14
|
+
throwCompileErrors(errors);
|
|
15
|
+
}
|
|
16
|
+
function validatePlanItem(planId, item, acIds, acceptanceCriteria, errors) {
|
|
17
|
+
for (const acId of item.related_acs) {
|
|
18
|
+
if (!acIds.has(acId)) {
|
|
19
|
+
errors.push(compileError(`${planId} references unknown related_acs ${acId}`, "blocking_unparseable_object", item.source_file, item.source_start_line, "related_acs", "dangling plan-to-AC references make the graph ambiguous", "fix related_acs to existing AC ids"));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (item.delivery_scope !== "out_of_scope_backlog" && item.implementation_paths.length === 0) {
|
|
23
|
+
errors.push(compileError(`${planId} missing implementation_paths`, "blocking_missing_primary_path", item.source_file, item.source_start_line, "implementation_paths", "plan conformance needs an implementation owner path", "add implementation_paths or mark the item out_of_scope_backlog"));
|
|
24
|
+
}
|
|
25
|
+
if (item.owner_surfaces.length > 0) {
|
|
26
|
+
const related = item.related_acs.length > 0 ? item.related_acs : Object.keys(acceptanceCriteria);
|
|
27
|
+
const hasUiLayer = related.some((acId) => acceptanceCriteria[acId]?.required_proof_layers.includes("ui_browser"));
|
|
28
|
+
if (!hasUiLayer) {
|
|
29
|
+
errors.push(compileError(`${planId} owner_surfaces requires a ui_browser proof layer`, "blocking_missing_owner_boundary", item.source_file, item.source_start_line, "owner_surfaces", "owner-surface work must have browser proof on the owner route", "add ui_browser to a related AC required_proof_layers"));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function validateAcceptanceCriterion(product, acId, ac, planIds, planItems, errors) {
|
|
34
|
+
for (const planId of ac.related_plan_items) {
|
|
35
|
+
if (!planIds.has(planId)) {
|
|
36
|
+
errors.push(compileError(`${acId} references unknown related_plan_items ${planId}`, "blocking_unparseable_object", ac.source_file, ac.source_start_line, "related_plan_items", "dangling AC-to-plan references make the graph ambiguous", "fix related_plan_items to existing PI ids"));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const layer of ac.required_proof_layers) {
|
|
40
|
+
if (!ALLOWED_PROOF_LAYERS.has(layer)) {
|
|
41
|
+
errors.push(compileError(`${acId} has invalid required_proof_layers ${layer}`, "blocking_unparseable_object", ac.source_file, ac.source_start_line, "required_proof_layers", "unknown proof layers cannot be evaluated", "use code or a supported machine-verifiable proof layer"));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (ac.ac_validates.length === 0) {
|
|
45
|
+
errors.push(compileError(`${acId} missing observable acceptance result`, "blocking_missing_observable_result", ac.source_file, ac.source_start_line, "ac_validates", "ACs need an observable result before evidence can prove completion", "add concrete ac_validates outcomes"));
|
|
46
|
+
}
|
|
47
|
+
const machineRequirements = (ac.assertion_requirements ?? []).filter((item) => item.machine_blocking);
|
|
48
|
+
if (machineRequirements.some((item) => item.positive_assertions.length === 0)) {
|
|
49
|
+
errors.push(compileError(`${acId} machine-verifiable proof layer lacks positive assertion requirements`, "blocking_missing_assertion_spec", ac.source_file, ac.source_start_line, "assertion_requirements", "machine-verifiable ACs need observable positive assertions", "add ac_validates, required_test_ids, test_cases or final_evidence_expected"));
|
|
50
|
+
}
|
|
51
|
+
if (machineRequirements.some((item) => item.negative_assertions.length === 0)) {
|
|
52
|
+
errors.push(compileError(`${acId} machine-verifiable proof layer lacks negative assertion requirements`, "blocking_missing_invalid_evidence", ac.source_file, ac.source_start_line, "invalid_evidence", "negative assertions prevent forbidden completion shortcuts", "add ac_does_not_validate, fail_conditions or invalid_evidence"));
|
|
53
|
+
}
|
|
54
|
+
const relatedPlans = ac.related_plan_items.map((planId) => planItems[planId]).filter(Boolean);
|
|
55
|
+
if (ac.full_population_required === true && relatedPlans.length > 0 && relatedPlans.every((item) => item.delivery_scope === "representative_sample_validation")) {
|
|
56
|
+
errors.push(compileError(`${acId} full-population AC is backed only by representative sample plan items`, "blocking_scope_conflict", ac.source_file, ac.source_start_line, "full_population_required", "sample-only plan items cannot prove full-population acceptance", "add a full_population_operation plan item or change the AC boundary"));
|
|
57
|
+
}
|
|
58
|
+
const productNonCompleting = product.representative_samples_do_not_validate.join("\n");
|
|
59
|
+
if (productNonCompleting && !containsAny(ac.ac_does_not_validate, product.representative_samples_do_not_validate)) {
|
|
60
|
+
errors.push(compileError(`${acId} does not represent Product non-completing outcomes`, "blocking_missing_invalid_evidence", ac.source_file, ac.source_start_line, "ac_does_not_validate", "Product non-completing outcomes must remain visible in AC invalid evidence", "add matching ac_does_not_validate or invalid_evidence entries"));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function containsAny(values, needles) {
|
|
64
|
+
const text = values.join("\n").toLowerCase();
|
|
65
|
+
return needles.some((needle) => text.includes(needle.toLowerCase()));
|
|
66
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { readText } from "./fs.js";
|
|
2
|
+
import { pathExists, readText } from "./fs.js";
|
|
3
3
|
import { appendSuperpowersEvent } from "./superpowers-task-events.js";
|
|
4
|
+
import { compileError, compileReportSuffix, throwCompileErrors } from "./superpowers-task-compile-diagnostics.js";
|
|
5
|
+
import { validateCompiledSources } from "./superpowers-task-compile-guards.js";
|
|
4
6
|
import { loadSuperpowersState, recomputeStatuses, saveSuperpowersState, refreshSourceHashes } from "./superpowers-task-state.js";
|
|
5
7
|
import { DEFAULT_LAYERS, parseAcceptanceCriteria, parsePlanItems, parseProductArchitectureScope } from "./superpowers-task-source-compile.js";
|
|
6
8
|
export async function compileSuperpowersTask(workdir) {
|
|
7
9
|
const state = await loadSuperpowersState(workdir);
|
|
8
10
|
await refreshSourceHashes(workdir, state);
|
|
11
|
+
await assertRequiredSourcesExist(workdir, state);
|
|
9
12
|
const productSource = await readText(path.join(workdir, state.sources.product_architecture_source.path));
|
|
10
13
|
const technicalPlan = await readText(path.join(workdir, state.sources.technical_realization_plan.path));
|
|
11
14
|
const checklist = await readText(path.join(workdir, state.sources.acceptance_checklist.path));
|
|
@@ -15,6 +18,7 @@ export async function compileSuperpowersTask(workdir) {
|
|
|
15
18
|
};
|
|
16
19
|
const planItems = parsePlanItems(technicalPlan, state.sources.technical_realization_plan.path);
|
|
17
20
|
const acceptanceCriteria = parseAcceptanceCriteria(checklist, state.sources.acceptance_checklist.path);
|
|
21
|
+
validateCompiledSources(state.delivery.product_architecture_scope, planItems, acceptanceCriteria);
|
|
18
22
|
const acIds = Object.keys(acceptanceCriteria);
|
|
19
23
|
for (const [planId, item] of Object.entries(planItems)) {
|
|
20
24
|
if (item.related_acs.length === 0) {
|
|
@@ -46,6 +50,22 @@ export async function compileSuperpowersTask(workdir) {
|
|
|
46
50
|
});
|
|
47
51
|
return state;
|
|
48
52
|
}
|
|
53
|
+
async function assertRequiredSourcesExist(workdir, state) {
|
|
54
|
+
const errors = [];
|
|
55
|
+
const checks = [
|
|
56
|
+
["product_architecture_source", "blocking_missing_source", "Product / Architecture Source"],
|
|
57
|
+
["technical_realization_plan", "blocking_missing_plan", "Technical Realization Plan"],
|
|
58
|
+
["acceptance_checklist", "blocking_missing_checklist", "Acceptance Checklist"]
|
|
59
|
+
];
|
|
60
|
+
for (const [key, category, label] of checks) {
|
|
61
|
+
const source = state.sources[key];
|
|
62
|
+
const file = source?.path ?? `${key}.md`;
|
|
63
|
+
if (!source || !(await pathExists(path.join(workdir, file)))) {
|
|
64
|
+
errors.push(compileError(`${label} input is missing: ${file}`, category, file, 1, key, "all three authority inputs are required", "restore the missing source file and rerun compile"));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throwCompileErrors(errors);
|
|
68
|
+
}
|
|
49
69
|
export function computeScopeConflicts(state) {
|
|
50
70
|
const conflicts = [];
|
|
51
71
|
const product = state.delivery?.product_architecture_scope;
|
|
@@ -56,18 +76,18 @@ export function computeScopeConflicts(state) {
|
|
|
56
76
|
product?.full_population_required === false;
|
|
57
77
|
for (const [planId, item] of Object.entries(state.graph?.plan_items ?? {})) {
|
|
58
78
|
if (productRequiresFullPopulation && item.delivery_scope !== "full_population_operation" && item.delivery_scope !== "out_of_scope_backlog") {
|
|
59
|
-
conflicts.push(`scope_conflict_requires_decision: Product / Architecture Source requires full_population_operation but ${planId} delivery_scope=${item.delivery_scope || "missing"}`);
|
|
79
|
+
conflicts.push(`scope_conflict_requires_decision: Product / Architecture Source requires full_population_operation but ${planId} delivery_scope=${item.delivery_scope || "missing"}${compileReportSuffix("blocking_scope_conflict", item.source_file, item.source_start_line, "delivery_scope", "Product source and plan delivery scopes disagree", "align the source, plan and checklist delivery scope")}`);
|
|
60
80
|
}
|
|
61
81
|
if (productIsCapabilityOnly && item.delivery_scope === "full_population_operation" && productScope !== "mixed_scope_requires_boundary") {
|
|
62
|
-
conflicts.push(`scope_conflict_requires_decision: Product / Architecture Source delivery_scope=${productScope || "missing"} but ${planId} delivery_scope=full_population_operation`);
|
|
82
|
+
conflicts.push(`scope_conflict_requires_decision: Product / Architecture Source delivery_scope=${productScope || "missing"} but ${planId} delivery_scope=full_population_operation${compileReportSuffix("blocking_scope_conflict", item.source_file, item.source_start_line, "delivery_scope", "Product source and plan delivery scopes disagree", "align the source, plan and checklist delivery scope")}`);
|
|
63
83
|
}
|
|
64
84
|
}
|
|
65
85
|
for (const [acId, ac] of Object.entries(state.graph?.acceptance_criteria ?? {})) {
|
|
66
86
|
if (productRequiresFullPopulation && (ac.acceptance_scope === "full_population_not_required" || ac.full_population_required === false)) {
|
|
67
|
-
conflicts.push(`scope_conflict_requires_decision: Product / Architecture Source requires full_population_operation but ${acId} full_population_required=false`);
|
|
87
|
+
conflicts.push(`scope_conflict_requires_decision: Product / Architecture Source requires full_population_operation but ${acId} full_population_required=false${compileReportSuffix("blocking_scope_conflict", ac.source_file, ac.source_start_line, "full_population_required", "Product source and checklist full-population requirements disagree", "align the source, plan and checklist delivery scope")}`);
|
|
68
88
|
}
|
|
69
89
|
if (productIsCapabilityOnly && (ac.acceptance_scope === "full_population_operation" || ac.full_population_required === true) && productScope !== "mixed_scope_requires_boundary") {
|
|
70
|
-
conflicts.push(`scope_conflict_requires_decision: Product / Architecture Source delivery_scope=${productScope || "missing"} but ${acId} acceptance_scope=full_population_operation`);
|
|
90
|
+
conflicts.push(`scope_conflict_requires_decision: Product / Architecture Source delivery_scope=${productScope || "missing"} but ${acId} acceptance_scope=full_population_operation${compileReportSuffix("blocking_scope_conflict", ac.source_file, ac.source_start_line, "acceptance_scope", "Product source and checklist delivery scopes disagree", "align the source, plan and checklist delivery scope")}`);
|
|
71
91
|
}
|
|
72
92
|
}
|
|
73
93
|
return [...new Set(conflicts)];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { primitiveText } from "./plan-validator-common.js";
|
|
2
|
+
export function validatePlanCompletionConformance(state, errors) {
|
|
3
|
+
for (const [planId, item] of Object.entries(state.graph?.plan_items ?? {})) {
|
|
4
|
+
if (item.status !== "complete") {
|
|
5
|
+
continue;
|
|
6
|
+
}
|
|
7
|
+
if (item.implementation_paths.length === 0 && item.delivery_scope !== "out_of_scope_backlog") {
|
|
8
|
+
errors.push(`plan item ${planId} is complete but has no implementation_paths`);
|
|
9
|
+
}
|
|
10
|
+
if ((item.owner_surfaces ?? []).length > 0 && !item.required_proof_layers.some((layer) => layer.endsWith(".ui_browser"))) {
|
|
11
|
+
errors.push(`plan item ${planId} is complete but owner_surfaces has no related ui_browser proof layer`);
|
|
12
|
+
}
|
|
13
|
+
if ((item.required_tests ?? []).length === 0 && item.explicit_no_test_scope !== true) {
|
|
14
|
+
errors.push(`plan item ${planId} is complete but required_tests is empty and no explicit_no_test_scope is recorded`);
|
|
15
|
+
}
|
|
16
|
+
const shortcuts = item.non_completing_shortcuts ?? [];
|
|
17
|
+
const evidenceText = primitiveText(state.evidence.filter((evidence) => evidence.proves.some((layer) => item.required_proof_layers.includes(layer))));
|
|
18
|
+
for (const shortcut of shortcuts) {
|
|
19
|
+
if (shortcut && evidenceText.toLowerCase().includes(shortcut.toLowerCase())) {
|
|
20
|
+
errors.push(`plan item ${planId} is complete but evidence uses forbidden shortcut: ${shortcut}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
import { computeScopeConflicts } from "./superpowers-task-compile.js";
|
|
2
|
+
import { ACCEPTANCE_SCOPES, isSelectedScopeFitSlice, PLAN_DELIVERY_SCOPES, PRODUCT_DELIVERY_SCOPES, SCOPE_FIT_DECISIONS } from "./superpowers-task-fields.js";
|
|
2
3
|
import { isRecord } from "./superpowers-task-state-schema.js";
|
|
3
|
-
const PRODUCT_DELIVERY_SCOPES = new Set([
|
|
4
|
-
"system_capability_build",
|
|
5
|
-
"representative_sample_validation",
|
|
6
|
-
"full_population_operation",
|
|
7
|
-
"mixed_scope_requires_boundary"
|
|
8
|
-
]);
|
|
9
|
-
const PLAN_DELIVERY_SCOPES = new Set([
|
|
10
|
-
"system_capability_build",
|
|
11
|
-
"representative_sample_validation",
|
|
12
|
-
"full_population_operation",
|
|
13
|
-
"out_of_scope_backlog"
|
|
14
|
-
]);
|
|
15
|
-
const ACCEPTANCE_SCOPES = new Set([
|
|
16
|
-
"system_capability_build",
|
|
17
|
-
"representative_sample_validation",
|
|
18
|
-
"full_population_operation",
|
|
19
|
-
"full_population_not_required"
|
|
20
|
-
]);
|
|
21
4
|
export function validateDeliveryContract(state, errors) {
|
|
22
5
|
const product = state.delivery?.product_architecture_scope;
|
|
23
6
|
if (!isRecord(product)) {
|
|
@@ -29,6 +12,17 @@ export function validateDeliveryContract(state, errors) {
|
|
|
29
12
|
requireArray(errors, "Product / Architecture Source representative_samples_validate", product.representative_samples_validate);
|
|
30
13
|
requireArray(errors, "Product / Architecture Source representative_samples_do_not_validate", product.representative_samples_do_not_validate);
|
|
31
14
|
requireArray(errors, "Product / Architecture Source out_of_scope_backlog", product.out_of_scope_backlog);
|
|
15
|
+
requireEnum(errors, "Product / Architecture Source scope_fit_decision", product.scope_fit_decision, SCOPE_FIT_DECISIONS);
|
|
16
|
+
requireText(errors, "Product / Architecture Source selected_scope_fit_slice", product.selected_scope_fit_slice);
|
|
17
|
+
if (typeof product.selected_scope_fit_slice === "string" && product.selected_scope_fit_slice && !isSelectedScopeFitSlice(product.selected_scope_fit_slice)) {
|
|
18
|
+
errors.push(`Product / Architecture Source selected_scope_fit_slice must be none or SFC-###: ${product.selected_scope_fit_slice}`);
|
|
19
|
+
}
|
|
20
|
+
requireText(errors, "Product / Architecture Source owner_boundary", product.owner_boundary);
|
|
21
|
+
requireText(errors, "Product / Architecture Source primary_capability_path", product.primary_capability_path);
|
|
22
|
+
requireArray(errors, "Product / Architecture Source non_completing_outcomes", product.non_completing_outcomes);
|
|
23
|
+
requireText(errors, "Product / Architecture Source assertion_policy", product.assertion_policy);
|
|
24
|
+
requireText(errors, "Product / Architecture Source source_authority", product.source_authority);
|
|
25
|
+
requireText(errors, "Product / Architecture Source product_goal", product.product_goal);
|
|
32
26
|
}
|
|
33
27
|
for (const [planId, item] of Object.entries(state.graph?.plan_items ?? {})) {
|
|
34
28
|
requireEnum(errors, `${planId} delivery_scope`, item.delivery_scope, PLAN_DELIVERY_SCOPES);
|
|
@@ -36,6 +30,15 @@ export function validateDeliveryContract(state, errors) {
|
|
|
36
30
|
requireArray(errors, `${planId} representative_samples`, item.representative_samples);
|
|
37
31
|
requireText(errors, `${planId} full_population_boundary`, item.full_population_boundary);
|
|
38
32
|
requireArray(errors, `${planId} non_required_population`, item.non_required_population);
|
|
33
|
+
requireText(errors, `${planId} owner_boundary`, item.owner_boundary);
|
|
34
|
+
requireText(errors, `${planId} primary_capability_path`, item.primary_capability_path);
|
|
35
|
+
requireText(errors, `${planId} trigger_contract`, item.trigger_contract);
|
|
36
|
+
requireText(errors, `${planId} state_transition_contract`, item.state_transition_contract);
|
|
37
|
+
requireText(errors, `${planId} observable_result_contract`, item.observable_result_contract);
|
|
38
|
+
requireText(errors, `${planId} assertion_support`, item.assertion_support);
|
|
39
|
+
requireArray(errors, `${planId} required_assertion_commands`, item.required_assertion_commands);
|
|
40
|
+
requireArray(errors, `${planId} invalid_implementation_shortcuts`, item.invalid_implementation_shortcuts);
|
|
41
|
+
requireArray(errors, `${planId} implementation_paths`, item.implementation_paths);
|
|
39
42
|
}
|
|
40
43
|
for (const [acId, ac] of Object.entries(state.graph?.acceptance_criteria ?? {})) {
|
|
41
44
|
requireEnum(errors, `${acId} acceptance_scope`, ac.acceptance_scope, ACCEPTANCE_SCOPES);
|
|
@@ -43,6 +46,15 @@ export function validateDeliveryContract(state, errors) {
|
|
|
43
46
|
requireArray(errors, `${acId} ac_does_not_validate`, ac.ac_does_not_validate);
|
|
44
47
|
requireText(errors, `${acId} sample_boundary`, ac.sample_boundary);
|
|
45
48
|
requireBoolean(errors, `${acId} full_population_required`, ac.full_population_required);
|
|
49
|
+
requireArray(errors, `${acId} related_plan_items`, ac.related_plan_items);
|
|
50
|
+
requireArray(errors, `${acId} required_proof_layers`, ac.required_proof_layers);
|
|
51
|
+
requireText(errors, `${acId} assertion_command`, ac.assertion_command);
|
|
52
|
+
requireArray(errors, `${acId} assertion_artifacts`, ac.assertion_artifacts);
|
|
53
|
+
requireArray(errors, `${acId} positive_assertions`, ac.positive_assertions);
|
|
54
|
+
requireArray(errors, `${acId} negative_assertions`, ac.negative_assertions);
|
|
55
|
+
requireBoolean(errors, `${acId} machine_blocking`, ac.machine_blocking);
|
|
56
|
+
requireArray(errors, `${acId} invalid_completion_signals`, ac.invalid_completion_signals);
|
|
57
|
+
requireBoolean(errors, `${acId} assertion_result_required`, ac.assertion_result_required);
|
|
46
58
|
}
|
|
47
59
|
}
|
|
48
60
|
export function validateScopeConflicts(state, errors) {
|
|
@@ -11,3 +11,4 @@ export declare function deriveObjects(state: SuperpowersTaskState): {
|
|
|
11
11
|
progress: Record<string, unknown>;
|
|
12
12
|
};
|
|
13
13
|
export declare function derivedMatchesState(workdir: string, state: SuperpowersTaskState): Promise<string[]>;
|
|
14
|
+
export declare function deriveEvidenceIndex(state: SuperpowersTaskState): Record<string, unknown>;
|