project-tiny-context-harness 0.7.2 → 0.7.4
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 +7 -5
- package/assets/README.md +49 -14
- package/assets/README.zh-CN.md +26 -13
- package/assets/agents/AGENTS_CORE.md +5 -5
- package/assets/context_templates/global.md +1 -0
- package/assets/skills/context_development_engineer/SKILL.md +4 -1
- package/assets/skills/context_uiux_design/SKILL.md +19 -6
- package/assets/skills/long-task-workflow/SKILL.md +10 -9
- package/assets/skills/long-task-workflow/references/authority-lifecycle.md +7 -5
- package/assets/skills/long-task-workflow/references/contract-authoring.md +24 -6
- package/assets/skills/long-task-workflow/references/evidence-design.md +14 -1
- package/assets/skills/source-plan-authoring/SKILL.md +2 -2
- package/dist/commands/long-task-authoring.js +25 -0
- package/dist/commands/long-task.js +3 -0
- package/dist/lib/design-md.d.ts +3 -0
- package/dist/lib/design-md.js +48 -55
- package/dist/lib/doctor.js +8 -0
- package/dist/lib/long-task-activation-validation.js +13 -2
- package/dist/lib/long-task-authoring-authority-preview.js +1 -0
- package/dist/lib/long-task-authority-material-diff.js +26 -0
- package/dist/lib/long-task-authority-materials.d.ts +2 -2
- package/dist/lib/long-task-authority-materials.js +6 -0
- package/dist/lib/long-task-authority-policy.d.ts +12 -0
- package/dist/lib/long-task-authority-policy.js +9 -0
- package/dist/lib/long-task-authority-revision-analysis.d.ts +33 -0
- package/dist/lib/long-task-authority-revision-analysis.js +91 -0
- package/dist/lib/long-task-authority-revision.js +93 -158
- package/dist/lib/long-task-authority-types.d.ts +7 -0
- package/dist/lib/long-task-authority.js +32 -3
- package/dist/lib/long-task-check-evidence-decoder.d.ts +2 -2
- package/dist/lib/long-task-check-evidence-decoder.js +11 -1
- package/dist/lib/long-task-check-execution-policy.d.ts +3 -0
- package/dist/lib/long-task-check-execution-policy.js +5 -0
- package/dist/lib/long-task-check-runner.js +2 -0
- package/dist/lib/long-task-check-shape.js +33 -3
- package/dist/lib/long-task-conformance-policy.d.ts +5 -0
- package/dist/lib/long-task-conformance-policy.js +35 -0
- package/dist/lib/long-task-contract-types.d.ts +14 -4
- package/dist/lib/long-task-delivery-compiler.js +1 -0
- package/dist/lib/long-task-delivery-parser.js +5 -2
- package/dist/lib/long-task-delivery-types.d.ts +2 -0
- package/dist/lib/long-task-delivery-types.js +2 -0
- package/dist/lib/long-task-delivery-validation.js +11 -0
- package/dist/lib/long-task-evidence-capability-codec.d.ts +2 -0
- package/dist/lib/long-task-evidence-capability-codec.js +231 -0
- package/dist/lib/long-task-evidence-capability-policy.d.ts +8 -0
- package/dist/lib/long-task-evidence-capability-policy.js +147 -0
- package/dist/lib/long-task-evidence-capability-runtime.d.ts +2 -0
- package/dist/lib/long-task-evidence-capability-runtime.js +86 -0
- package/dist/lib/long-task-evidence-capability-types.d.ts +74 -0
- package/dist/lib/long-task-evidence-capability-types.js +1 -0
- package/dist/lib/long-task-evidence-findings.js +5 -1
- package/dist/lib/long-task-evidence-v2.js +38 -23
- package/dist/lib/long-task-final-v2.js +42 -1
- package/dist/lib/long-task-outcome-parser.js +14 -3
- package/dist/lib/long-task-playwright-evidence.d.ts +2 -1
- package/dist/lib/long-task-playwright-evidence.js +70 -2
- package/dist/lib/long-task-progress.js +4 -0
- package/dist/lib/long-task-root-shape.d.ts +1 -0
- package/dist/lib/long-task-root-shape.js +84 -3
- package/dist/lib/long-task-runner-environment.js +1 -1
- package/dist/lib/long-task-runner-freeze.d.ts +2 -2
- package/dist/lib/long-task-runner-freeze.js +3 -1
- package/dist/lib/long-task-runtime-types.d.ts +13 -0
- package/dist/lib/long-task-semantic-contract-types.d.ts +36 -0
- package/dist/lib/long-task-semantic-contract-types.js +1 -0
- package/dist/lib/long-task-semantic-drift-migration.d.ts +3 -0
- package/dist/lib/long-task-semantic-drift-migration.js +68 -0
- package/dist/lib/long-task-shape-primitives.d.ts +3 -0
- package/dist/lib/long-task-shape-primitives.js +25 -0
- package/dist/lib/long-task-stage-policy.d.ts +4 -0
- package/dist/lib/long-task-stage-policy.js +120 -0
- package/dist/lib/long-task-status-projection.d.ts +4 -1
- package/dist/lib/long-task-status-projection.js +63 -3
- package/dist/lib/long-task-status-v2.d.ts +11 -1
- package/dist/lib/long-task-status-v2.js +25 -3
- package/dist/lib/long-task-target-policy.d.ts +6 -0
- package/dist/lib/long-task-target-policy.js +127 -0
- package/dist/lib/migrations.js +29 -0
- package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +61 -7
- package/migrations/README.md +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
export function decodeEvidenceCapabilityRecords(value) {
|
|
2
|
+
if (!Array.isArray(value))
|
|
3
|
+
throw invalidRecord("must_be_array");
|
|
4
|
+
return value.map((item, index) => decodeRecord(item, index));
|
|
5
|
+
}
|
|
6
|
+
function decodeRecord(value, index) {
|
|
7
|
+
const label = `evidence_records[${index}]`;
|
|
8
|
+
const row = record(value, label);
|
|
9
|
+
const assertionKey = key(row.assertion_key, `${label}.assertion_key`);
|
|
10
|
+
const capability = nonEmpty(row.capability, `${label}.capability`);
|
|
11
|
+
const base = { assertion_key: assertionKey };
|
|
12
|
+
switch (capability) {
|
|
13
|
+
case "interaction_trace":
|
|
14
|
+
exact(row, label, [
|
|
15
|
+
"assertion_key",
|
|
16
|
+
"capability",
|
|
17
|
+
"target_ref",
|
|
18
|
+
"given_keys",
|
|
19
|
+
"action_keys",
|
|
20
|
+
]);
|
|
21
|
+
return {
|
|
22
|
+
...base,
|
|
23
|
+
capability,
|
|
24
|
+
target_ref: key(row.target_ref, `${label}.target_ref`),
|
|
25
|
+
given_keys: keys(row.given_keys, `${label}.given_keys`),
|
|
26
|
+
action_keys: keys(row.action_keys, `${label}.action_keys`),
|
|
27
|
+
};
|
|
28
|
+
case "state_delta":
|
|
29
|
+
exact(row, label, [
|
|
30
|
+
"assertion_key",
|
|
31
|
+
"capability",
|
|
32
|
+
"before_sha256",
|
|
33
|
+
"after_sha256",
|
|
34
|
+
"changed_fields",
|
|
35
|
+
]);
|
|
36
|
+
return {
|
|
37
|
+
...base,
|
|
38
|
+
capability,
|
|
39
|
+
before_sha256: sha(row.before_sha256, `${label}.before_sha256`),
|
|
40
|
+
after_sha256: sha(row.after_sha256, `${label}.after_sha256`),
|
|
41
|
+
changed_fields: strings(row.changed_fields, `${label}.changed_fields`),
|
|
42
|
+
};
|
|
43
|
+
case "cross_surface_consistency":
|
|
44
|
+
exact(row, label, ["assertion_key", "capability", "surfaces"]);
|
|
45
|
+
return {
|
|
46
|
+
...base,
|
|
47
|
+
capability,
|
|
48
|
+
surfaces: array(row.surfaces, `${label}.surfaces`).map((item, surfaceIndex) => {
|
|
49
|
+
const surfaceLabel = `${label}.surfaces[${surfaceIndex}]`;
|
|
50
|
+
const surface = record(item, surfaceLabel);
|
|
51
|
+
exact(surface, surfaceLabel, [
|
|
52
|
+
"surface_ref",
|
|
53
|
+
"target_ref",
|
|
54
|
+
"state_sha256",
|
|
55
|
+
]);
|
|
56
|
+
return {
|
|
57
|
+
surface_ref: key(surface.surface_ref, `${surfaceLabel}.surface_ref`),
|
|
58
|
+
target_ref: key(surface.target_ref, `${surfaceLabel}.target_ref`),
|
|
59
|
+
state_sha256: sha(surface.state_sha256, `${surfaceLabel}.state_sha256`),
|
|
60
|
+
};
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
case "durable_readback":
|
|
64
|
+
exact(row, label, [
|
|
65
|
+
"assertion_key",
|
|
66
|
+
"capability",
|
|
67
|
+
"write_session_id",
|
|
68
|
+
"read_session_id",
|
|
69
|
+
"written_sha256",
|
|
70
|
+
"read_sha256",
|
|
71
|
+
]);
|
|
72
|
+
return {
|
|
73
|
+
...base,
|
|
74
|
+
capability,
|
|
75
|
+
write_session_id: nonEmpty(row.write_session_id, `${label}.write_session_id`),
|
|
76
|
+
read_session_id: nonEmpty(row.read_session_id, `${label}.read_session_id`),
|
|
77
|
+
written_sha256: sha(row.written_sha256, `${label}.written_sha256`),
|
|
78
|
+
read_sha256: sha(row.read_sha256, `${label}.read_sha256`),
|
|
79
|
+
};
|
|
80
|
+
case "boundary_invocation":
|
|
81
|
+
exact(row, label, [
|
|
82
|
+
"assertion_key",
|
|
83
|
+
"capability",
|
|
84
|
+
"boundary",
|
|
85
|
+
"invocation_id",
|
|
86
|
+
"request_sha256",
|
|
87
|
+
"observer_target_ref",
|
|
88
|
+
]);
|
|
89
|
+
return {
|
|
90
|
+
...base,
|
|
91
|
+
capability,
|
|
92
|
+
boundary: nonEmpty(row.boundary, `${label}.boundary`),
|
|
93
|
+
invocation_id: nonEmpty(row.invocation_id, `${label}.invocation_id`),
|
|
94
|
+
request_sha256: sha(row.request_sha256, `${label}.request_sha256`),
|
|
95
|
+
observer_target_ref: key(row.observer_target_ref, `${label}.observer_target_ref`),
|
|
96
|
+
};
|
|
97
|
+
case "external_side_effect":
|
|
98
|
+
exact(row, label, [
|
|
99
|
+
"assertion_key",
|
|
100
|
+
"capability",
|
|
101
|
+
"boundary",
|
|
102
|
+
"effect_id",
|
|
103
|
+
"effect_sha256",
|
|
104
|
+
"observer_target_ref",
|
|
105
|
+
]);
|
|
106
|
+
return {
|
|
107
|
+
...base,
|
|
108
|
+
capability,
|
|
109
|
+
boundary: nonEmpty(row.boundary, `${label}.boundary`),
|
|
110
|
+
effect_id: nonEmpty(row.effect_id, `${label}.effect_id`),
|
|
111
|
+
effect_sha256: sha(row.effect_sha256, `${label}.effect_sha256`),
|
|
112
|
+
observer_target_ref: key(row.observer_target_ref, `${label}.observer_target_ref`),
|
|
113
|
+
};
|
|
114
|
+
case "failure_injection":
|
|
115
|
+
exact(row, label, [
|
|
116
|
+
"assertion_key",
|
|
117
|
+
"capability",
|
|
118
|
+
"fault",
|
|
119
|
+
"failure_observed",
|
|
120
|
+
"recovery_state_sha256",
|
|
121
|
+
]);
|
|
122
|
+
if (row.failure_observed !== true)
|
|
123
|
+
throw invalidRecord(`${label}.failure_observed`);
|
|
124
|
+
return {
|
|
125
|
+
...base,
|
|
126
|
+
capability,
|
|
127
|
+
fault: nonEmpty(row.fault, `${label}.fault`),
|
|
128
|
+
failure_observed: true,
|
|
129
|
+
recovery_state_sha256: sha(row.recovery_state_sha256, `${label}.recovery_state_sha256`),
|
|
130
|
+
};
|
|
131
|
+
case "visual_render":
|
|
132
|
+
exact(row, label, [
|
|
133
|
+
"assertion_key",
|
|
134
|
+
"capability",
|
|
135
|
+
"artifact_path",
|
|
136
|
+
"artifact_sha256",
|
|
137
|
+
]);
|
|
138
|
+
return {
|
|
139
|
+
...base,
|
|
140
|
+
capability,
|
|
141
|
+
artifact_path: nonEmpty(row.artifact_path, `${label}.artifact_path`),
|
|
142
|
+
artifact_sha256: sha(row.artifact_sha256, `${label}.artifact_sha256`),
|
|
143
|
+
};
|
|
144
|
+
case "target_runtime":
|
|
145
|
+
exact(row, label, [
|
|
146
|
+
"assertion_key",
|
|
147
|
+
"capability",
|
|
148
|
+
"target_ref",
|
|
149
|
+
"root_entrypoint",
|
|
150
|
+
"session_id",
|
|
151
|
+
"cold_start",
|
|
152
|
+
]);
|
|
153
|
+
if (typeof row.cold_start !== "boolean")
|
|
154
|
+
throw invalidRecord(`${label}.cold_start`);
|
|
155
|
+
return {
|
|
156
|
+
...base,
|
|
157
|
+
capability,
|
|
158
|
+
target_ref: key(row.target_ref, `${label}.target_ref`),
|
|
159
|
+
root_entrypoint: nonEmpty(row.root_entrypoint, `${label}.root_entrypoint`),
|
|
160
|
+
session_id: nonEmpty(row.session_id, `${label}.session_id`),
|
|
161
|
+
cold_start: row.cold_start,
|
|
162
|
+
};
|
|
163
|
+
case "input_variation":
|
|
164
|
+
exact(row, label, [
|
|
165
|
+
"assertion_key",
|
|
166
|
+
"capability",
|
|
167
|
+
"cases",
|
|
168
|
+
"failure_case_observed",
|
|
169
|
+
]);
|
|
170
|
+
if (typeof row.failure_case_observed !== "boolean")
|
|
171
|
+
throw invalidRecord(`${label}.failure_case_observed`);
|
|
172
|
+
return {
|
|
173
|
+
...base,
|
|
174
|
+
capability,
|
|
175
|
+
cases: array(row.cases, `${label}.cases`).map((item, caseIndex) => {
|
|
176
|
+
const caseLabel = `${label}.cases[${caseIndex}]`;
|
|
177
|
+
const entry = record(item, caseLabel);
|
|
178
|
+
exact(entry, caseLabel, ["input_sha256", "output_sha256"]);
|
|
179
|
+
return {
|
|
180
|
+
input_sha256: sha(entry.input_sha256, `${caseLabel}.input_sha256`),
|
|
181
|
+
output_sha256: sha(entry.output_sha256, `${caseLabel}.output_sha256`),
|
|
182
|
+
};
|
|
183
|
+
}),
|
|
184
|
+
failure_case_observed: row.failure_case_observed,
|
|
185
|
+
};
|
|
186
|
+
default:
|
|
187
|
+
throw invalidRecord(`${label}.capability_unsupported:${capability}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function invalidRecord(detail) {
|
|
191
|
+
return new Error(`check_evidence_records_invalid:${detail}`);
|
|
192
|
+
}
|
|
193
|
+
function record(value, label) {
|
|
194
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
195
|
+
throw invalidRecord(label);
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
function exact(row, label, fields) {
|
|
199
|
+
const allowed = new Set(fields);
|
|
200
|
+
if (fields.some((field) => !Object.hasOwn(row, field)) ||
|
|
201
|
+
Object.keys(row).some((field) => !allowed.has(field)))
|
|
202
|
+
throw invalidRecord(`${label}.shape`);
|
|
203
|
+
}
|
|
204
|
+
function array(value, label) {
|
|
205
|
+
if (!Array.isArray(value))
|
|
206
|
+
throw invalidRecord(label);
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
function nonEmpty(value, label) {
|
|
210
|
+
if (typeof value !== "string" || !value.trim())
|
|
211
|
+
throw invalidRecord(label);
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
function key(value, label) {
|
|
215
|
+
const result = nonEmpty(value, label);
|
|
216
|
+
if (!/^[a-z0-9][a-z0-9-]*$/u.test(result))
|
|
217
|
+
throw invalidRecord(label);
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
function strings(value, label) {
|
|
221
|
+
return array(value, label).map((item, index) => nonEmpty(item, `${label}[${index}]`));
|
|
222
|
+
}
|
|
223
|
+
function keys(value, label) {
|
|
224
|
+
return array(value, label).map((item, index) => key(item, `${label}[${index}]`));
|
|
225
|
+
}
|
|
226
|
+
function sha(value, label) {
|
|
227
|
+
const result = nonEmpty(value, label);
|
|
228
|
+
if (!/^[a-f0-9]{64}$/u.test(result))
|
|
229
|
+
throw invalidRecord(label);
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CompiledCheckV2, DeliveryContractV2, EvidenceCapabilityRecordV2, LongTaskFindingV2 } from "./long-task-delivery-types.js";
|
|
2
|
+
export { decodeEvidenceCapabilityRecords } from "./long-task-evidence-capability-codec.js";
|
|
3
|
+
type Reporter = (message: string) => void;
|
|
4
|
+
export declare function validateEvidenceCapabilityDeclarations(contract: DeliveryContractV2, report?: Reporter): void;
|
|
5
|
+
export declare function evaluateEvidenceCapabilities(check: CompiledCheckV2, records: EvidenceCapabilityRecordV2[], artifactHashes: Record<string, string>): {
|
|
6
|
+
complete: Record<string, boolean>;
|
|
7
|
+
findings: LongTaskFindingV2[];
|
|
8
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { validateRuntimeEvidenceRecord } from "./long-task-evidence-capability-runtime.js";
|
|
2
|
+
import { checkFinding } from "./long-task-evidence-findings.js";
|
|
3
|
+
export { decodeEvidenceCapabilityRecords } from "./long-task-evidence-capability-codec.js";
|
|
4
|
+
export function validateEvidenceCapabilityDeclarations(contract, report) {
|
|
5
|
+
const targets = new Map(contract.task.execution_targets.map((target) => [target.key, target]));
|
|
6
|
+
for (const [outcomeKey, check] of allChecks(contract)) {
|
|
7
|
+
unique(check.journey_roles, "journey_role_duplicate", checkLabel(outcomeKey, check.key), report);
|
|
8
|
+
if (!check.journey_roles.length)
|
|
9
|
+
issue(report, "journey_role_required", checkLabel(outcomeKey, check.key));
|
|
10
|
+
validateScenario(check, outcomeKey, report);
|
|
11
|
+
for (const assertion of [
|
|
12
|
+
...check.positive_assertions,
|
|
13
|
+
...check.negative_assertions,
|
|
14
|
+
]) {
|
|
15
|
+
const label = `${checkLabel(outcomeKey, check.key)}:${assertion.key}`;
|
|
16
|
+
unique(assertion.evidence_capabilities, "evidence_capability_duplicate", label, report);
|
|
17
|
+
if (!assertion.evidence_capabilities.length)
|
|
18
|
+
issue(report, "evidence_capability_required", label);
|
|
19
|
+
if (assertion.claims.length &&
|
|
20
|
+
check.proof_surface !== "implementation_structure" &&
|
|
21
|
+
assertion.evidence_capabilities.every((capability) => capability === "presence"))
|
|
22
|
+
issue(report, "presence_cannot_prove_behavior", label);
|
|
23
|
+
if (assertion.operator === "exists" &&
|
|
24
|
+
!assertion.evidence_capabilities.includes("presence"))
|
|
25
|
+
issue(report, "exists_requires_presence_capability", label);
|
|
26
|
+
if (assertion.evidence_capabilities.includes("visual_render") &&
|
|
27
|
+
!check.artifact_globs.length)
|
|
28
|
+
issue(report, "visual_render_artifact_required", label);
|
|
29
|
+
if (assertion.evidence_capabilities.includes("interaction_trace") &&
|
|
30
|
+
!check.scenario.when.length)
|
|
31
|
+
issue(report, "interaction_trace_actions_required", label);
|
|
32
|
+
if (check.runner.type === "playwright_test" &&
|
|
33
|
+
assertion.evidence_capabilities.some((capability) => capability !== "presence" &&
|
|
34
|
+
capability !== "interaction_trace" &&
|
|
35
|
+
capability !== "target_runtime"))
|
|
36
|
+
issue(report, "playwright_evidence_capability_unsupported", label);
|
|
37
|
+
if (["boundary_invocation", "external_side_effect"].some((capability) => assertion.evidence_capabilities.includes(capability)) &&
|
|
38
|
+
targets.get(check.execution_target.target_ref)?.role !== "observer")
|
|
39
|
+
issue(report, "observer_check_target_required", label);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
validateJourneySeparation(contract, report);
|
|
43
|
+
}
|
|
44
|
+
function validateScenario(check, outcomeKey, report) {
|
|
45
|
+
const label = checkLabel(outcomeKey, check.key);
|
|
46
|
+
if (!check.scenario.given.length)
|
|
47
|
+
issue(report, "scenario_given_required", label);
|
|
48
|
+
if (!check.scenario.when.length)
|
|
49
|
+
issue(report, "scenario_when_required", label);
|
|
50
|
+
unique(check.scenario.given.map((step) => step.key), "scenario_given_key_duplicate", label, report);
|
|
51
|
+
unique(check.scenario.when.map((step) => step.key), "scenario_when_key_duplicate", label, report);
|
|
52
|
+
}
|
|
53
|
+
function validateJourneySeparation(contract, report) {
|
|
54
|
+
for (const outcome of contract.outcomes) {
|
|
55
|
+
for (const check of outcome.acceptance.checks)
|
|
56
|
+
if (check.journey_roles.includes("success") &&
|
|
57
|
+
check.journey_roles.includes("degradation"))
|
|
58
|
+
issue(report, "success_degradation_check_must_be_distinct", `${outcome.key}:${check.key}`);
|
|
59
|
+
if (outcome.product.success_path_required &&
|
|
60
|
+
!outcome.acceptance.checks.some((check) => check.journey_roles.includes("success")))
|
|
61
|
+
issue(report, "success_path_check_required", outcome.key);
|
|
62
|
+
if (outcome.product.degradation_path_required &&
|
|
63
|
+
!outcome.acceptance.checks.some((check) => check.journey_roles.includes("degradation")))
|
|
64
|
+
issue(report, "degradation_path_check_required", outcome.key);
|
|
65
|
+
for (const check of outcome.acceptance.checks) {
|
|
66
|
+
const provesResult = [
|
|
67
|
+
...check.positive_assertions,
|
|
68
|
+
...check.negative_assertions,
|
|
69
|
+
].some((assertion) => assertion.claims.includes("result"));
|
|
70
|
+
if (provesResult && !check.journey_roles.includes("success"))
|
|
71
|
+
issue(report, "result_claim_requires_success_journey", `${outcome.key}:${check.key}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function evaluateEvidenceCapabilities(check, records, artifactHashes) {
|
|
76
|
+
const runtimeRecords = records ?? [];
|
|
77
|
+
const complete = {};
|
|
78
|
+
const findings = [];
|
|
79
|
+
const assertions = [
|
|
80
|
+
...check.positive_assertions,
|
|
81
|
+
...check.negative_assertions,
|
|
82
|
+
];
|
|
83
|
+
const assertionsByKey = new Map(assertions.map((assertion) => [assertion.key, assertion]));
|
|
84
|
+
for (const record of runtimeRecords) {
|
|
85
|
+
const assertion = assertionsByKey.get(record.assertion_key);
|
|
86
|
+
const reason = !assertion
|
|
87
|
+
? "assertion_unknown"
|
|
88
|
+
: !assertion.evidence_capabilities.includes(record.capability)
|
|
89
|
+
? "capability_undeclared"
|
|
90
|
+
: null;
|
|
91
|
+
if (!reason)
|
|
92
|
+
continue;
|
|
93
|
+
findings.push({
|
|
94
|
+
...checkFinding(check, "evidence_capability_invalid", `Evidence record ${record.assertion_key}:${record.capability} is not bound to a declared Assertion capability: ${reason}.`, "Emit records only for capabilities declared by Assertions in this Check."),
|
|
95
|
+
assertion_key: record.assertion_key,
|
|
96
|
+
claim_keys: assertion?.claims ?? [],
|
|
97
|
+
expected: "declared_assertion_capability",
|
|
98
|
+
actual: reason,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
for (const assertion of assertions) {
|
|
102
|
+
let assertionComplete = true;
|
|
103
|
+
for (const capability of assertion.evidence_capabilities) {
|
|
104
|
+
if (capability === "presence")
|
|
105
|
+
continue;
|
|
106
|
+
const matches = runtimeRecords.filter((record) => record.assertion_key === assertion.key &&
|
|
107
|
+
record.capability === capability);
|
|
108
|
+
const reason = matches.length === 1
|
|
109
|
+
? validateRuntimeEvidenceRecord(check, matches[0], artifactHashes)
|
|
110
|
+
: matches.length === 0
|
|
111
|
+
? "record_missing"
|
|
112
|
+
: "record_duplicate";
|
|
113
|
+
if (!reason)
|
|
114
|
+
continue;
|
|
115
|
+
assertionComplete = false;
|
|
116
|
+
findings.push({
|
|
117
|
+
...checkFinding(check, "evidence_capability_invalid", `Assertion ${assertion.key} did not provide valid ${capability} evidence: ${reason}.`, "Make the current Check execution emit one valid typed evidence record for the declared capability."),
|
|
118
|
+
assertion_key: assertion.key,
|
|
119
|
+
claim_keys: assertion.claims,
|
|
120
|
+
expected: capability,
|
|
121
|
+
actual: reason,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
complete[assertion.key] = assertionComplete;
|
|
125
|
+
}
|
|
126
|
+
return { complete, findings };
|
|
127
|
+
}
|
|
128
|
+
function allChecks(contract) {
|
|
129
|
+
return [
|
|
130
|
+
...contract.global.acceptance.checks.map((check) => [null, check]),
|
|
131
|
+
...contract.outcomes.flatMap((outcome) => outcome.acceptance.checks.map((check) => [outcome.key, check])),
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
function checkLabel(outcomeKey, checkKey) {
|
|
135
|
+
return `${outcomeKey ?? "GLOBAL"}:${checkKey}`;
|
|
136
|
+
}
|
|
137
|
+
function unique(values, code, detail, report) {
|
|
138
|
+
if (new Set(values).size !== values.length)
|
|
139
|
+
issue(report, code, detail);
|
|
140
|
+
}
|
|
141
|
+
function issue(report, code, detail) {
|
|
142
|
+
const message = `delivery_contract_invalid:${code}:${detail}`;
|
|
143
|
+
if (report)
|
|
144
|
+
report(message);
|
|
145
|
+
else
|
|
146
|
+
throw new Error(message);
|
|
147
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export function validateRuntimeEvidenceRecord(check, record, artifactHashes) {
|
|
2
|
+
switch (record.capability) {
|
|
3
|
+
case "interaction_trace":
|
|
4
|
+
return validateInteractionTrace(check, record);
|
|
5
|
+
case "state_delta":
|
|
6
|
+
if (record.before_sha256 === record.after_sha256)
|
|
7
|
+
return "state_unchanged";
|
|
8
|
+
if (!record.changed_fields.length)
|
|
9
|
+
return "changed_fields_empty";
|
|
10
|
+
return null;
|
|
11
|
+
case "cross_surface_consistency":
|
|
12
|
+
return validateCrossSurfaceConsistency(check, record);
|
|
13
|
+
case "durable_readback":
|
|
14
|
+
if (record.write_session_id === record.read_session_id)
|
|
15
|
+
return "independent_session_required";
|
|
16
|
+
if (record.written_sha256 !== record.read_sha256)
|
|
17
|
+
return "readback_mismatch";
|
|
18
|
+
return null;
|
|
19
|
+
case "boundary_invocation":
|
|
20
|
+
case "external_side_effect":
|
|
21
|
+
return validateObserverEvidence(check, record.observer_target_ref);
|
|
22
|
+
case "failure_injection":
|
|
23
|
+
return record.failure_observed ? null : "failure_not_observed";
|
|
24
|
+
case "visual_render":
|
|
25
|
+
return artifactHashes[record.artifact_path] === record.artifact_sha256
|
|
26
|
+
? null
|
|
27
|
+
: "artifact_hash_mismatch";
|
|
28
|
+
case "target_runtime":
|
|
29
|
+
return validateTargetRuntime(check, record);
|
|
30
|
+
case "input_variation":
|
|
31
|
+
return validateInputVariation(record);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function validateInteractionTrace(check, record) {
|
|
35
|
+
if (record.target_ref !== check.execution_target.target_ref)
|
|
36
|
+
return "target_mismatch";
|
|
37
|
+
if (!same(record.given_keys, check.scenario.given.map((step) => step.key)))
|
|
38
|
+
return "given_trace_mismatch";
|
|
39
|
+
if (!same(record.action_keys, check.scenario.when.map((step) => step.key)))
|
|
40
|
+
return "action_trace_mismatch";
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function validateCrossSurfaceConsistency(check, record) {
|
|
44
|
+
const surfaces = new Set(record.surfaces.map((surface) => surface.surface_ref));
|
|
45
|
+
const targets = new Set(record.surfaces.map((surface) => surface.target_ref));
|
|
46
|
+
const states = new Set(record.surfaces.map((surface) => surface.state_sha256));
|
|
47
|
+
if (surfaces.size < 2)
|
|
48
|
+
return "two_surfaces_required";
|
|
49
|
+
if (states.size !== 1)
|
|
50
|
+
return "state_hash_mismatch";
|
|
51
|
+
if ([...targets].some((target) => !check.known_execution_targets.some((item) => item.key === target)))
|
|
52
|
+
return "target_unknown";
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function validateObserverEvidence(check, observerTargetRef) {
|
|
56
|
+
const observer = check.known_execution_targets.find((target) => target.key === observerTargetRef);
|
|
57
|
+
if (!observer || observer.role !== "observer")
|
|
58
|
+
return "observer_target_invalid";
|
|
59
|
+
if (observer.key !== check.execution_target.target_ref)
|
|
60
|
+
return "check_must_execute_on_observer";
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function validateTargetRuntime(check, record) {
|
|
64
|
+
if (record.target_ref !== check.execution_target.target_ref)
|
|
65
|
+
return "target_mismatch";
|
|
66
|
+
if (record.root_entrypoint !== check.execution_target_definition.root_entrypoint)
|
|
67
|
+
return "root_entrypoint_mismatch";
|
|
68
|
+
if (check.execution_target.entrypoint === "root" && !record.cold_start)
|
|
69
|
+
return "cold_start_required";
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function validateInputVariation(record) {
|
|
73
|
+
const inputs = new Set(record.cases.map((item) => item.input_sha256));
|
|
74
|
+
const outputs = new Set(record.cases.map((item) => item.output_sha256));
|
|
75
|
+
if (inputs.size < 2)
|
|
76
|
+
return "distinct_inputs_required";
|
|
77
|
+
if (outputs.size < 2)
|
|
78
|
+
return "input_must_reach_output";
|
|
79
|
+
if (!record.failure_case_observed)
|
|
80
|
+
return "failure_case_required";
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function same(left, right) {
|
|
84
|
+
return (left.length === right.length &&
|
|
85
|
+
left.every((value, index) => value === right[index]));
|
|
86
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { EvidenceCapabilityV2 } from "./long-task-semantic-contract-types.js";
|
|
2
|
+
interface EvidenceRecordBaseV2 {
|
|
3
|
+
assertion_key: string;
|
|
4
|
+
capability: EvidenceCapabilityV2;
|
|
5
|
+
}
|
|
6
|
+
export interface InteractionTraceEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
7
|
+
capability: "interaction_trace";
|
|
8
|
+
target_ref: string;
|
|
9
|
+
given_keys: string[];
|
|
10
|
+
action_keys: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface StateDeltaEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
13
|
+
capability: "state_delta";
|
|
14
|
+
before_sha256: string;
|
|
15
|
+
after_sha256: string;
|
|
16
|
+
changed_fields: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface CrossSurfaceConsistencyEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
19
|
+
capability: "cross_surface_consistency";
|
|
20
|
+
surfaces: Array<{
|
|
21
|
+
surface_ref: string;
|
|
22
|
+
target_ref: string;
|
|
23
|
+
state_sha256: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export interface DurableReadbackEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
27
|
+
capability: "durable_readback";
|
|
28
|
+
write_session_id: string;
|
|
29
|
+
read_session_id: string;
|
|
30
|
+
written_sha256: string;
|
|
31
|
+
read_sha256: string;
|
|
32
|
+
}
|
|
33
|
+
export interface BoundaryInvocationEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
34
|
+
capability: "boundary_invocation";
|
|
35
|
+
boundary: string;
|
|
36
|
+
invocation_id: string;
|
|
37
|
+
request_sha256: string;
|
|
38
|
+
observer_target_ref: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ExternalSideEffectEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
41
|
+
capability: "external_side_effect";
|
|
42
|
+
boundary: string;
|
|
43
|
+
effect_id: string;
|
|
44
|
+
effect_sha256: string;
|
|
45
|
+
observer_target_ref: string;
|
|
46
|
+
}
|
|
47
|
+
export interface FailureInjectionEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
48
|
+
capability: "failure_injection";
|
|
49
|
+
fault: string;
|
|
50
|
+
failure_observed: true;
|
|
51
|
+
recovery_state_sha256: string;
|
|
52
|
+
}
|
|
53
|
+
export interface VisualRenderEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
54
|
+
capability: "visual_render";
|
|
55
|
+
artifact_path: string;
|
|
56
|
+
artifact_sha256: string;
|
|
57
|
+
}
|
|
58
|
+
export interface TargetRuntimeEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
59
|
+
capability: "target_runtime";
|
|
60
|
+
target_ref: string;
|
|
61
|
+
root_entrypoint: string;
|
|
62
|
+
session_id: string;
|
|
63
|
+
cold_start: boolean;
|
|
64
|
+
}
|
|
65
|
+
export interface InputVariationEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
66
|
+
capability: "input_variation";
|
|
67
|
+
cases: Array<{
|
|
68
|
+
input_sha256: string;
|
|
69
|
+
output_sha256: string;
|
|
70
|
+
}>;
|
|
71
|
+
failure_case_observed: boolean;
|
|
72
|
+
}
|
|
73
|
+
export type EvidenceCapabilityRecordV2 = InteractionTraceEvidenceV2 | StateDeltaEvidenceV2 | CrossSurfaceConsistencyEvidenceV2 | DurableReadbackEvidenceV2 | BoundaryInvocationEvidenceV2 | ExternalSideEffectEvidenceV2 | FailureInjectionEvidenceV2 | VisualRenderEvidenceV2 | TargetRuntimeEvidenceV2 | InputVariationEvidenceV2;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -19,6 +19,8 @@ export function evaluateAssertionResults(check, observations) {
|
|
|
19
19
|
passed: evaluation.passed,
|
|
20
20
|
claims: assertion.claims,
|
|
21
21
|
observation: assertion.observation,
|
|
22
|
+
evidence_capabilities: assertion.evidence_capabilities,
|
|
23
|
+
evidence_complete: false,
|
|
22
24
|
status: evaluation.status,
|
|
23
25
|
...(Object.hasOwn(evaluation, "expected")
|
|
24
26
|
? { expected: evaluation.expected }
|
|
@@ -35,7 +37,9 @@ export function classifyCheckStatus(raw, findings) {
|
|
|
35
37
|
if (raw.exit_code !== 0 ||
|
|
36
38
|
findings.some((item) => item.code === "test_failed"))
|
|
37
39
|
return "test_failed";
|
|
38
|
-
if (findings.some((item) => item.code === "invalid_evidence" ||
|
|
40
|
+
if (findings.some((item) => item.code === "invalid_evidence" ||
|
|
41
|
+
item.code === "artifact_missing" ||
|
|
42
|
+
item.code === "evidence_capability_invalid"))
|
|
39
43
|
return "invalid_evidence";
|
|
40
44
|
if (findings.some((item) => item.assertion_key) ||
|
|
41
45
|
findings.some((item) => item.code === "population_coverage_failed"))
|
|
@@ -5,28 +5,13 @@ import { executeCheckRunner } from "./long-task-check-runner.js";
|
|
|
5
5
|
import { createCounterfactualSandbox } from "./long-task-counterfactual-sandbox.js";
|
|
6
6
|
import { assertionFinding, checkFinding, classifyCheckStatus, evaluateAssertionResults, } from "./long-task-evidence-findings.js";
|
|
7
7
|
import { classifyPlaywrightCounterfactual } from "./long-task-playwright-counterfactual-policy.js";
|
|
8
|
+
import { evaluateEvidenceCapabilities } from "./long-task-evidence-capability-policy.js";
|
|
8
9
|
import { resolveInsideRepository } from "./long-task-workspace.js";
|
|
9
10
|
export async function evaluateCheckEvidence(check, raw, snapshotRoot, outcome) {
|
|
10
11
|
const artifacts = await collectCheckArtifacts(check, snapshotRoot);
|
|
11
12
|
const assertionResults = evaluateAssertionResults(check, raw.observations);
|
|
12
13
|
const executionCompleted = raw.execution_status === "completed";
|
|
13
|
-
const findings =
|
|
14
|
-
if (!executionCompleted)
|
|
15
|
-
findings.push(checkFinding(check, raw.execution_status, raw.error ?? raw.execution_status, raw.execution_status === "blocked_external"
|
|
16
|
-
? "Satisfy the declared Environment Requirement and rerun this Check."
|
|
17
|
-
: "Repair the declared runner or evidence protocol and rerun this Check."));
|
|
18
|
-
if (executionCompleted && raw.exit_code !== 0)
|
|
19
|
-
findings.push(checkFinding(check, "test_failed", `command exited ${raw.exit_code}`, "Fix the implementation or declared verification command, then rerun this Check."));
|
|
20
|
-
if (executionCompleted &&
|
|
21
|
-
raw.observations["playwright.zero_or_all_skipped"] === true)
|
|
22
|
-
findings.push(checkFinding(check, "test_failed", "Playwright executed zero tests or skipped every test.", "Make the declared Playwright target execute at least one non-skipped test."));
|
|
23
|
-
if (executionCompleted) {
|
|
24
|
-
for (const error of artifacts.errors)
|
|
25
|
-
findings.push(checkFinding(check, "artifact_missing", error, "Produce the artifact declared for this Check and rerun it."));
|
|
26
|
-
for (const result of assertionResults)
|
|
27
|
-
if (!result.passed)
|
|
28
|
-
findings.push(assertionFinding(check, result, raw.observations, outcome));
|
|
29
|
-
}
|
|
14
|
+
const findings = collectExecutionFindings(check, raw, outcome, artifacts, assertionResults);
|
|
30
15
|
const population = outcome?.acceptance.population;
|
|
31
16
|
let populationPassed = true;
|
|
32
17
|
if (executionCompleted && population?.check_key === check.key) {
|
|
@@ -42,12 +27,14 @@ export async function evaluateCheckEvidence(check, raw, snapshotRoot, outcome) {
|
|
|
42
27
|
}
|
|
43
28
|
const status = classifyCheckStatus(raw, findings);
|
|
44
29
|
const claimProofs = status === "passed"
|
|
45
|
-
? assertionResults.flatMap((result) => result.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
30
|
+
? assertionResults.flatMap((result) => result.evidence_complete
|
|
31
|
+
? result.claims.map((claim) => ({
|
|
32
|
+
check_key: check.key,
|
|
33
|
+
assertion_key: result.key,
|
|
34
|
+
polarity: result.polarity,
|
|
35
|
+
proof_surface: check.proof_surface,
|
|
36
|
+
}))
|
|
37
|
+
: [])
|
|
51
38
|
: [];
|
|
52
39
|
if (status === "passed" &&
|
|
53
40
|
population &&
|
|
@@ -68,6 +55,7 @@ export async function evaluateCheckEvidence(check, raw, snapshotRoot, outcome) {
|
|
|
68
55
|
execution_identity: raw.raw_execution_identity,
|
|
69
56
|
assertion_results: assertionResults,
|
|
70
57
|
observations: raw.observations,
|
|
58
|
+
evidence_records: raw.evidence_records ?? [],
|
|
71
59
|
artifact_hashes: artifacts.hashes,
|
|
72
60
|
claim_proofs: claimProofs,
|
|
73
61
|
findings,
|
|
@@ -75,6 +63,33 @@ export async function evaluateCheckEvidence(check, raw, snapshotRoot, outcome) {
|
|
|
75
63
|
duration_ms: raw.duration_ms,
|
|
76
64
|
};
|
|
77
65
|
}
|
|
66
|
+
function collectExecutionFindings(check, raw, outcome, artifacts, assertionResults) {
|
|
67
|
+
const findings = [];
|
|
68
|
+
if (raw.execution_status !== "completed") {
|
|
69
|
+
findings.push(checkFinding(check, raw.execution_status, raw.error ?? raw.execution_status, raw.execution_status === "blocked_external"
|
|
70
|
+
? "Satisfy the declared Environment Requirement and rerun this Check."
|
|
71
|
+
: "Repair the declared runner or evidence protocol and rerun this Check."));
|
|
72
|
+
return findings;
|
|
73
|
+
}
|
|
74
|
+
if (raw.exit_code !== 0)
|
|
75
|
+
findings.push(checkFinding(check, "test_failed", `command exited ${raw.exit_code}`, "Fix the implementation or declared verification command, then rerun this Check."));
|
|
76
|
+
if (raw.observations["playwright.zero_or_all_skipped"] === true)
|
|
77
|
+
findings.push(checkFinding(check, "test_failed", "Playwright executed zero tests or skipped every test.", "Make the declared Playwright target execute at least one non-skipped test."));
|
|
78
|
+
for (const error of artifacts.errors)
|
|
79
|
+
findings.push(checkFinding(check, "artifact_missing", error, "Produce the artifact declared for this Check and rerun it."));
|
|
80
|
+
for (const result of assertionResults)
|
|
81
|
+
if (!result.passed)
|
|
82
|
+
findings.push(assertionFinding(check, result, raw.observations, outcome));
|
|
83
|
+
const capabilityEvaluation = evaluateEvidenceCapabilities(check, raw.evidence_records, artifacts.hashes);
|
|
84
|
+
const passedAssertions = new Set(assertionResults
|
|
85
|
+
.filter((result) => result.passed)
|
|
86
|
+
.map((result) => result.key));
|
|
87
|
+
findings.push(...capabilityEvaluation.findings.filter((finding) => !finding.assertion_key || passedAssertions.has(finding.assertion_key)));
|
|
88
|
+
for (const result of assertionResults)
|
|
89
|
+
result.evidence_complete =
|
|
90
|
+
result.passed && capabilityEvaluation.complete[result.key] === true;
|
|
91
|
+
return findings;
|
|
92
|
+
}
|
|
78
93
|
export async function evaluateOutcomeCounterfactuals(outcome, snapshotRoot, manifest) {
|
|
79
94
|
return evaluateCounterfactualSet(outcome.acceptance.counterfactual_controls.map((control) => ({
|
|
80
95
|
control,
|