project-tiny-context-harness 0.7.3 → 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 +2 -2
- package/assets/README.md +44 -11
- package/assets/README.zh-CN.md +21 -10
- package/assets/agents/AGENTS_CORE.md +2 -2
- package/assets/skills/long-task-workflow/SKILL.md +9 -9
- package/assets/skills/long-task-workflow/references/authority-lifecycle.md +7 -5
- package/assets/skills/long-task-workflow/references/contract-authoring.md +17 -3
- package/assets/skills/long-task-workflow/references/evidence-design.md +12 -1
- package/dist/commands/long-task-authoring.js +25 -0
- package/dist/commands/long-task.js +3 -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,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,
|
|
@@ -74,18 +74,28 @@ export async function runDeliveryFinalGate(workdirInput) {
|
|
|
74
74
|
const blocked = globalBlocked ||
|
|
75
75
|
Object.values(outcomeResults).includes("blocked_external") ||
|
|
76
76
|
run.findings.some((finding) => finding.code === "blocked_external");
|
|
77
|
+
const targetBlocked = compiled.global.acceptance.external_confirmations.some((confirmation) => confirmation.blocks_target);
|
|
77
78
|
const workflowStatus = failed
|
|
78
79
|
? "needs_work"
|
|
79
|
-
: blocked
|
|
80
|
+
: blocked || targetBlocked
|
|
80
81
|
? "blocked_external"
|
|
81
82
|
: compiled.global.acceptance.external_confirmations.length
|
|
82
83
|
? "machine_accepted_external_pending"
|
|
83
84
|
: "machine_accepted";
|
|
85
|
+
const targetState = workflowStatus === "machine_accepted" ||
|
|
86
|
+
workflowStatus === "machine_accepted_external_pending"
|
|
87
|
+
? compiled.task.target_profile.required_state
|
|
88
|
+
: workflowStatus === "blocked_external"
|
|
89
|
+
? "blocked_external"
|
|
90
|
+
: "not_accepted";
|
|
84
91
|
return writeFinalReceipt(repository, active.workdir, {
|
|
85
92
|
schema_version: "long-task-final-receipt-v2",
|
|
86
93
|
authority_scope: "audit_only",
|
|
87
94
|
reusable_for_acceptance: false,
|
|
88
95
|
workflow_status: workflowStatus,
|
|
96
|
+
target_profile: compiled.task.target_profile,
|
|
97
|
+
target_state: targetState,
|
|
98
|
+
stage_results: projectStages(compiled, outcomeResults),
|
|
89
99
|
compiled_identity: compiled.compiled_identity,
|
|
90
100
|
contract_sha256: compiled.contract_sha256,
|
|
91
101
|
snapshot_sha256: snapshot.manifest.snapshot_sha256,
|
|
@@ -107,6 +117,37 @@ export async function runDeliveryFinalGate(workdirInput) {
|
|
|
107
117
|
await snapshot.dispose();
|
|
108
118
|
}
|
|
109
119
|
}
|
|
120
|
+
function projectStages(compiled, outcomes) {
|
|
121
|
+
const result = {};
|
|
122
|
+
const remaining = new Set(compiled.stages.map((stage) => stage.key));
|
|
123
|
+
while (remaining.size) {
|
|
124
|
+
let advanced = false;
|
|
125
|
+
for (const stage of compiled.stages) {
|
|
126
|
+
if (!remaining.has(stage.key))
|
|
127
|
+
continue;
|
|
128
|
+
if (stage.depends_on.some((dependency) => remaining.has(dependency)))
|
|
129
|
+
continue;
|
|
130
|
+
const owned = compiled.outcomes
|
|
131
|
+
.filter((outcome) => outcome.stage === stage.key)
|
|
132
|
+
.map((outcome) => outcomes[outcome.key]);
|
|
133
|
+
result[stage.key] = owned.includes("failed")
|
|
134
|
+
? "failed"
|
|
135
|
+
: owned.includes("blocked_external")
|
|
136
|
+
? "blocked_external"
|
|
137
|
+
: stage.depends_on.some((dependency) => result[dependency] !== "passed")
|
|
138
|
+
? "blocked_dependency"
|
|
139
|
+
: "passed";
|
|
140
|
+
remaining.delete(stage.key);
|
|
141
|
+
advanced = true;
|
|
142
|
+
}
|
|
143
|
+
if (!advanced) {
|
|
144
|
+
for (const stage of remaining)
|
|
145
|
+
result[stage] = "blocked_dependency";
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
110
151
|
function projectOutcomes(outcomeKeys, checks, findings) {
|
|
111
152
|
return Object.fromEntries(outcomeKeys.map((outcomeKey) => {
|
|
112
153
|
const owned = checks.filter((check) => check.outcome_key === outcomeKey);
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { parseCheck } from "./long-task-check-shape.js";
|
|
2
2
|
import { normalizeRepositoryFile } from "./long-task-paths.js";
|
|
3
3
|
import { parseRequirements } from "./long-task-requirement-shape.js";
|
|
4
|
-
import { array, key, literal, nullable, object, parseBindings, parseControls, parseCounterfactuals, parseKeyedStatements, parseObligations, parseOwner, parsePopulation, parseRollback, repositoryPatterns, string, strings, } from "./long-task-delivery-shape.js";
|
|
4
|
+
import { array, boolean, key, literal, nullable, object, parseBindings, parseControls, parseCounterfactuals, parseKeyedStatements, parseObligations, parseOwner, parsePopulation, parseRollback, repositoryPatterns, string, strings, } from "./long-task-delivery-shape.js";
|
|
5
5
|
import { parseStrictYaml } from "./strict-codec.js";
|
|
6
|
+
import { assertNoSemanticDriftMigration, semanticDriftOutcomeMigrationFields, } from "./long-task-semantic-drift-migration.js";
|
|
6
7
|
export function parseOutcome(value, label) {
|
|
7
|
-
|
|
8
|
-
const
|
|
8
|
+
assertNoSemanticDriftMigration(semanticDriftOutcomeMigrationFields(value, label));
|
|
9
|
+
const row = object(value, label, ["key", "title", "stage", "product", "technical", "acceptance"], ["depends_on"]);
|
|
10
|
+
const product = object(row.product, `${label}.product`, [
|
|
11
|
+
"observable_result",
|
|
12
|
+
"success_path_required",
|
|
13
|
+
"degradation_path_required",
|
|
14
|
+
"owner",
|
|
15
|
+
], ["requirements", "owner_surfaces", "controls", "non_completing_outcomes"]);
|
|
9
16
|
const technical = object(row.technical, `${label}.technical`, ["expected_change_paths"], [
|
|
10
17
|
"obligations",
|
|
11
18
|
"allowed_support_paths",
|
|
@@ -18,11 +25,14 @@ export function parseOutcome(value, label) {
|
|
|
18
25
|
return {
|
|
19
26
|
key: key(row.key, `${label}.key`),
|
|
20
27
|
title: string(row.title, `${label}.title`),
|
|
28
|
+
stage: key(row.stage, `${label}.stage`),
|
|
21
29
|
depends_on: Object.hasOwn(row, "depends_on")
|
|
22
30
|
? strings(row.depends_on, `${label}.depends_on`).map((item, index) => key(item, `${label}.depends_on[${index}]`))
|
|
23
31
|
: [],
|
|
24
32
|
product: {
|
|
25
33
|
observable_result: string(product.observable_result, `${label}.product.observable_result`),
|
|
34
|
+
success_path_required: boolean(product.success_path_required, `${label}.product.success_path_required`),
|
|
35
|
+
degradation_path_required: boolean(product.degradation_path_required, `${label}.product.degradation_path_required`),
|
|
26
36
|
owner: parseOwner(product.owner, `${label}.product.owner`),
|
|
27
37
|
requirements: Object.hasOwn(product, "requirements")
|
|
28
38
|
? parseRequirements(product.requirements, `${label}.product.requirements`)
|
|
@@ -78,6 +88,7 @@ export function parseOutcomeFragment(raw, relative) {
|
|
|
78
88
|
"outcomes",
|
|
79
89
|
"key",
|
|
80
90
|
"title",
|
|
91
|
+
"stage",
|
|
81
92
|
"depends_on",
|
|
82
93
|
"product",
|
|
83
94
|
"technical",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { CompiledCheckV2 } from "./long-task-delivery-types.js";
|
|
1
|
+
import type { CompiledCheckV2, EvidenceCapabilityRecordV2 } from "./long-task-delivery-types.js";
|
|
2
2
|
export interface PlaywrightEvidence {
|
|
3
3
|
observations: Record<string, unknown>;
|
|
4
|
+
evidence_records: EvidenceCapabilityRecordV2[];
|
|
4
5
|
error: string | null;
|
|
5
6
|
}
|
|
6
7
|
export declare function extractPlaywrightEvidence(check: CompiledCheckV2, report: Record<string, unknown>, exitCode: number): PlaywrightEvidence;
|
|
@@ -61,7 +61,33 @@ export function extractPlaywrightEvidence(check, report, exitCode) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
observations["playwright.case_ids"] = cases.map((item) => item.id).sort();
|
|
64
|
-
|
|
64
|
+
const evidenceRecords = [];
|
|
65
|
+
for (const item of cases) {
|
|
66
|
+
const assertion = [
|
|
67
|
+
...check.positive_assertions,
|
|
68
|
+
...check.negative_assertions,
|
|
69
|
+
].find((candidate) => candidate.key === item.id);
|
|
70
|
+
if (assertion?.evidence_capabilities.includes("interaction_trace") &&
|
|
71
|
+
item.executed)
|
|
72
|
+
evidenceRecords.push({
|
|
73
|
+
assertion_key: assertion.key,
|
|
74
|
+
capability: "interaction_trace",
|
|
75
|
+
target_ref: check.execution_target.target_ref,
|
|
76
|
+
given_keys: item.given_keys,
|
|
77
|
+
action_keys: item.action_keys,
|
|
78
|
+
});
|
|
79
|
+
if (assertion?.evidence_capabilities.includes("target_runtime") &&
|
|
80
|
+
item.executed)
|
|
81
|
+
evidenceRecords.push({
|
|
82
|
+
assertion_key: assertion.key,
|
|
83
|
+
capability: "target_runtime",
|
|
84
|
+
target_ref: check.execution_target.target_ref,
|
|
85
|
+
root_entrypoint: check.execution_target_definition.root_entrypoint,
|
|
86
|
+
session_id: `playwright:${item.id}:${item.project_ids.join(",")}`,
|
|
87
|
+
cold_start: check.execution_target.entrypoint === "root",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return { observations, evidence_records: evidenceRecords, error: null };
|
|
65
91
|
}
|
|
66
92
|
function collectCases(report, declaredIds) {
|
|
67
93
|
const cases = [];
|
|
@@ -139,6 +165,10 @@ function playwrightCase(id, test) {
|
|
|
139
165
|
status === "expected" &&
|
|
140
166
|
resultStatuses.length > 0 &&
|
|
141
167
|
resultStatuses.at(-1) === "passed";
|
|
168
|
+
const traces = results.map((result) => scenarioTrace(result));
|
|
169
|
+
const givenKeys = traces[0]?.given_keys ?? [];
|
|
170
|
+
const actionKeys = traces[0]?.action_keys ?? [];
|
|
171
|
+
const traceConsistent = traces.every((trace) => same(trace.given_keys, givenKeys) && same(trace.action_keys, actionKeys));
|
|
142
172
|
return {
|
|
143
173
|
id,
|
|
144
174
|
project_id: projectId,
|
|
@@ -150,6 +180,8 @@ function playwrightCase(id, test) {
|
|
|
150
180
|
timed_out: timedOut,
|
|
151
181
|
interrupted,
|
|
152
182
|
status,
|
|
183
|
+
given_keys: traceConsistent ? givenKeys : [],
|
|
184
|
+
action_keys: traceConsistent ? actionKeys : [],
|
|
153
185
|
};
|
|
154
186
|
}
|
|
155
187
|
function declaredCaseIds(check) {
|
|
@@ -227,6 +259,14 @@ function aggregateCases(instances) {
|
|
|
227
259
|
unexpected_instances: unexpectedInstances,
|
|
228
260
|
timed_out_instances: timedOutInstances,
|
|
229
261
|
interrupted_instances: interruptedInstances,
|
|
262
|
+
given_keys: rows.length &&
|
|
263
|
+
rows.every((row) => same(row.given_keys, rows[0].given_keys))
|
|
264
|
+
? rows[0].given_keys
|
|
265
|
+
: [],
|
|
266
|
+
action_keys: rows.length &&
|
|
267
|
+
rows.every((row) => same(row.action_keys, rows[0].action_keys))
|
|
268
|
+
? rows[0].action_keys
|
|
269
|
+
: [],
|
|
230
270
|
};
|
|
231
271
|
})
|
|
232
272
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
@@ -239,6 +279,34 @@ function record(value) {
|
|
|
239
279
|
function integer(value) {
|
|
240
280
|
return Number.isInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
241
281
|
}
|
|
282
|
+
function scenarioTrace(result) {
|
|
283
|
+
const givenKeys = [];
|
|
284
|
+
const actionKeys = [];
|
|
285
|
+
const visit = (value) => {
|
|
286
|
+
if (!Array.isArray(value))
|
|
287
|
+
return;
|
|
288
|
+
for (const item of value) {
|
|
289
|
+
const step = record(item);
|
|
290
|
+
if (!step)
|
|
291
|
+
continue;
|
|
292
|
+
if (typeof step.title === "string") {
|
|
293
|
+
const given = /^\[given:([a-z0-9][a-z0-9-]*)\]$/u.exec(step.title);
|
|
294
|
+
const action = /^\[action:([a-z0-9][a-z0-9-]*)\]$/u.exec(step.title);
|
|
295
|
+
if (given)
|
|
296
|
+
givenKeys.push(given[1]);
|
|
297
|
+
if (action)
|
|
298
|
+
actionKeys.push(action[1]);
|
|
299
|
+
}
|
|
300
|
+
visit(step.steps);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
visit(result.steps);
|
|
304
|
+
return { given_keys: givenKeys, action_keys: actionKeys };
|
|
305
|
+
}
|
|
306
|
+
function same(left, right) {
|
|
307
|
+
return (left.length === right.length &&
|
|
308
|
+
left.every((value, index) => value === right[index]));
|
|
309
|
+
}
|
|
242
310
|
function invalid(error) {
|
|
243
|
-
return { observations: {}, error };
|
|
311
|
+
return { observations: {}, evidence_records: [], error };
|
|
244
312
|
}
|
|
@@ -76,6 +76,10 @@ export function checkIdentity(check) {
|
|
|
76
76
|
artifact_globs: check.artifact_globs,
|
|
77
77
|
positive_assertions: check.positive_assertions,
|
|
78
78
|
negative_assertions: check.negative_assertions,
|
|
79
|
+
journey_roles: check.journey_roles,
|
|
80
|
+
execution_target: check.execution_target,
|
|
81
|
+
execution_target_definition: check.execution_target_definition,
|
|
82
|
+
scenario: check.scenario,
|
|
79
83
|
environment_requirements: check.environment_requirements,
|
|
80
84
|
}));
|
|
81
85
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
2
2
|
export declare function parseTask(value: unknown): DeliveryContractV2["task"];
|
|
3
|
+
export declare function parseStages(value: unknown): DeliveryContractV2["stages"];
|
|
3
4
|
export declare function parseRisk(value: unknown): DeliveryContractV2["risk"];
|
|
4
5
|
export declare function parseGlobal(value: unknown): DeliveryContractV2["global"];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseGlobalCounterfactuals } from "./long-task-acceptance-shape.js";
|
|
2
2
|
import { parseCheck } from "./long-task-check-shape.js";
|
|
3
|
-
import { array, key, literal, object, parseKeyedPaths, parseKeyedStatements, repositoryFiles, string, strings, } from "./long-task-delivery-shape.js";
|
|
3
|
+
import { array, boolean, key, literal, object, parseKeyedPaths, parseKeyedStatements, repositoryFiles, string, strings, } from "./long-task-delivery-shape.js";
|
|
4
4
|
const RISK_FACTS = [
|
|
5
5
|
"public_api_or_schema_change",
|
|
6
6
|
"persistent_data_change",
|
|
@@ -14,11 +14,59 @@ const RISK_FACTS = [
|
|
|
14
14
|
"weak_observability",
|
|
15
15
|
];
|
|
16
16
|
export function parseTask(value) {
|
|
17
|
-
const row = object(value, "task", [
|
|
17
|
+
const row = object(value, "task", [
|
|
18
|
+
"id",
|
|
19
|
+
"title",
|
|
20
|
+
"goal",
|
|
21
|
+
"target_profile",
|
|
22
|
+
"execution_targets",
|
|
23
|
+
"source_paths",
|
|
24
|
+
"context_refs",
|
|
25
|
+
], ["context_snapshot_mode"]);
|
|
26
|
+
const targetProfile = object(row.target_profile, "task.target_profile", [
|
|
27
|
+
"key",
|
|
28
|
+
"description",
|
|
29
|
+
"required_state",
|
|
30
|
+
"required_target_refs",
|
|
31
|
+
]);
|
|
18
32
|
return {
|
|
19
33
|
id: key(row.id, "task.id"),
|
|
20
34
|
title: string(row.title, "task.title"),
|
|
21
35
|
goal: string(row.goal, "task.goal"),
|
|
36
|
+
target_profile: {
|
|
37
|
+
key: key(targetProfile.key, "task.target_profile.key"),
|
|
38
|
+
description: string(targetProfile.description, "task.target_profile.description"),
|
|
39
|
+
required_state: literal(targetProfile.required_state, [
|
|
40
|
+
"implementation_complete",
|
|
41
|
+
"target_profile_usable",
|
|
42
|
+
"production_release_ready",
|
|
43
|
+
], "task.target_profile.required_state"),
|
|
44
|
+
required_target_refs: strings(targetProfile.required_target_refs, "task.target_profile.required_target_refs").map((item, index) => key(item, `task.target_profile.required_target_refs[${index}]`)),
|
|
45
|
+
},
|
|
46
|
+
execution_targets: array(row.execution_targets, "task.execution_targets").map((item, index) => {
|
|
47
|
+
const label = `task.execution_targets[${index}]`;
|
|
48
|
+
const target = object(item, label, [
|
|
49
|
+
"key",
|
|
50
|
+
"description",
|
|
51
|
+
"role",
|
|
52
|
+
"runtime_family",
|
|
53
|
+
"root_entrypoint",
|
|
54
|
+
]);
|
|
55
|
+
return {
|
|
56
|
+
key: key(target.key, `${label}.key`),
|
|
57
|
+
description: string(target.description, `${label}.description`),
|
|
58
|
+
role: literal(target.role, ["product", "support", "observer"], `${label}.role`),
|
|
59
|
+
runtime_family: literal(target.runtime_family, [
|
|
60
|
+
"browser",
|
|
61
|
+
"native",
|
|
62
|
+
"desktop",
|
|
63
|
+
"service",
|
|
64
|
+
"process",
|
|
65
|
+
"external",
|
|
66
|
+
], `${label}.runtime_family`),
|
|
67
|
+
root_entrypoint: string(target.root_entrypoint, `${label}.root_entrypoint`),
|
|
68
|
+
};
|
|
69
|
+
}),
|
|
22
70
|
source_paths: repositoryFiles(row.source_paths, "task.source_paths"),
|
|
23
71
|
context_refs: repositoryFiles(row.context_refs, "task.context_refs"),
|
|
24
72
|
context_snapshot_mode: Object.hasOwn(row, "context_snapshot_mode")
|
|
@@ -26,6 +74,23 @@ export function parseTask(value) {
|
|
|
26
74
|
: "referenced",
|
|
27
75
|
};
|
|
28
76
|
}
|
|
77
|
+
export function parseStages(value) {
|
|
78
|
+
return array(value, "stages").map((item, index) => {
|
|
79
|
+
const label = `stages[${index}]`;
|
|
80
|
+
const row = object(item, label, [
|
|
81
|
+
"key",
|
|
82
|
+
"title",
|
|
83
|
+
"depends_on",
|
|
84
|
+
"gate_outcome",
|
|
85
|
+
]);
|
|
86
|
+
return {
|
|
87
|
+
key: key(row.key, `${label}.key`),
|
|
88
|
+
title: string(row.title, `${label}.title`),
|
|
89
|
+
depends_on: strings(row.depends_on, `${label}.depends_on`).map((entry, dependencyIndex) => key(entry, `${label}.depends_on[${dependencyIndex}]`)),
|
|
90
|
+
gate_outcome: key(row.gate_outcome, `${label}.gate_outcome`),
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
29
94
|
export function parseRisk(value) {
|
|
30
95
|
const row = object(value, "risk", ["facts"], ["requested_level"]);
|
|
31
96
|
const facts = object(row.facts, "risk.facts", [], [...RISK_FACTS]);
|
|
@@ -73,11 +138,27 @@ export function parseGlobal(value) {
|
|
|
73
138
|
external_confirmations: Object.hasOwn(acceptance, "external_confirmations")
|
|
74
139
|
? array(acceptance.external_confirmations, "global.acceptance.external_confirmations").map((item, index) => {
|
|
75
140
|
const label = `global.acceptance.external_confirmations[${index}]`;
|
|
76
|
-
const entry = object(item, label, [
|
|
141
|
+
const entry = object(item, label, [
|
|
142
|
+
"key",
|
|
143
|
+
"description",
|
|
144
|
+
"owner",
|
|
145
|
+
"kind",
|
|
146
|
+
"impact_claims",
|
|
147
|
+
"blocks_target",
|
|
148
|
+
]);
|
|
77
149
|
return {
|
|
78
150
|
key: key(entry.key, `${label}.key`),
|
|
79
151
|
description: string(entry.description, `${label}.description`),
|
|
80
152
|
owner: string(entry.owner, `${label}.owner`),
|
|
153
|
+
kind: literal(entry.kind, [
|
|
154
|
+
"functional_prerequisite",
|
|
155
|
+
"field_validation",
|
|
156
|
+
"production_release_gate",
|
|
157
|
+
"commercial_activation",
|
|
158
|
+
"expert_authority",
|
|
159
|
+
], `${label}.kind`),
|
|
160
|
+
impact_claims: strings(entry.impact_claims, `${label}.impact_claims`),
|
|
161
|
+
blocks_target: boolean(entry.blocks_target, `${label}.blocks_target`),
|
|
81
162
|
};
|
|
82
163
|
})
|
|
83
164
|
: [],
|
|
@@ -23,7 +23,7 @@ const BASE_ENVIRONMENT_KEYS = [
|
|
|
23
23
|
];
|
|
24
24
|
export function runnerEnvironment(requirements) {
|
|
25
25
|
const environment = {
|
|
26
|
-
TY_CONTEXT_CHECK_PROTOCOL: "long-task-check-result-
|
|
26
|
+
TY_CONTEXT_CHECK_PROTOCOL: "long-task-check-result-v3",
|
|
27
27
|
};
|
|
28
28
|
for (const key of BASE_ENVIRONMENT_KEYS)
|
|
29
29
|
copyEnvironmentValue(environment, key);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { CompiledCheckV2, DeliveryCheckV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
|
|
2
|
-
export declare function freezeDeliveryCheck(check: DeliveryCheckV2, outcomeKey: string | null, repository: string, baseline: WorkspaceManifestV2): Promise<CompiledCheckV2>;
|
|
1
|
+
import type { CompiledCheckV2, DeliveryCheckV2, ExecutionTargetV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
|
|
2
|
+
export declare function freezeDeliveryCheck(check: DeliveryCheckV2, outcomeKey: string | null, repository: string, baseline: WorkspaceManifestV2, executionTarget: ExecutionTargetV2, knownExecutionTargets: ExecutionTargetV2[]): Promise<CompiledCheckV2>;
|
|
@@ -7,7 +7,7 @@ import { nearestRunnerFile, npmCliPath, npxCliPath, } from "./long-task-runner-f
|
|
|
7
7
|
import { canonicalValueJson, sha256Hex } from "./strict-codec.js";
|
|
8
8
|
import { repoRelative, resolveInsideRepository, } from "./long-task-workspace.js";
|
|
9
9
|
import { evidenceAdapterForRunner } from "./long-task-evidence-adapter-policy.js";
|
|
10
|
-
export async function freezeDeliveryCheck(check, outcomeKey, repository, baseline) {
|
|
10
|
+
export async function freezeDeliveryCheck(check, outcomeKey, repository, baseline, executionTarget, knownExecutionTargets) {
|
|
11
11
|
const prefix = outcomeKey ? `CHECK.${outcomeKey}` : "CHECK.GLOBAL";
|
|
12
12
|
const expectedOutputs = check.expected_output_paths.map((pattern, index) => assertRepositoryPattern(repository, pattern, `${check.key}.expected_output_paths[${index}]`));
|
|
13
13
|
for (const [index, pattern] of check.input_paths.entries()) {
|
|
@@ -30,6 +30,8 @@ export async function freezeDeliveryCheck(check, outcomeKey, repository, baselin
|
|
|
30
30
|
evidence_adapter: evidenceAdapterForRunner(check.runner.type),
|
|
31
31
|
runner: await freezeRunner(check, repository, cwd, target, verificationInputHashes),
|
|
32
32
|
verification_input_hashes: verificationInputHashes,
|
|
33
|
+
execution_target_definition: executionTarget,
|
|
34
|
+
known_execution_targets: knownExecutionTargets,
|
|
33
35
|
};
|
|
34
36
|
return {
|
|
35
37
|
...compiled,
|