project-tiny-context-harness 0.8.0 → 0.8.2
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 +40 -32
- package/assets/README.md +141 -60
- package/assets/README.zh-CN.md +48 -40
- package/assets/agents/AGENTS_CORE.md +16 -12
- package/assets/context_templates/architecture.md +5 -5
- package/assets/context_templates/area.md +5 -5
- package/assets/context_templates/global.md +6 -6
- package/assets/context_templates/screen-contract.md +18 -18
- package/assets/github/harness.yml +2 -2
- package/assets/skills/context_development_engineer/SKILL.md +7 -5
- package/assets/skills/context_full_project_export/SKILL.md +42 -42
- package/assets/skills/context_uiux_design/SKILL.md +27 -13
- package/assets/skills/design-resource-authoring/SKILL.md +7 -7
- package/assets/skills/design-resource-authoring/references/downstream-handoff.md +12 -12
- package/assets/skills/design-resource-authoring/references/open-design-provider.md +21 -21
- package/assets/skills/design-resource-authoring/references/resource-selection.md +3 -3
- package/assets/skills/long-task-workflow/SKILL.md +17 -17
- package/assets/skills/long-task-workflow/agents/openai.yaml +4 -4
- package/assets/skills/long-task-workflow/references/authority-lifecycle.md +11 -9
- package/assets/skills/long-task-workflow/references/contract-authoring.md +19 -15
- package/assets/skills/long-task-workflow/references/evidence-design.md +18 -17
- package/assets/skills/long-task-workflow/references/source-authoring.md +15 -12
- package/assets/skills/normal-long-task/SKILL.md +12 -12
- package/dist/commands/design-resource.js +2 -2
- package/dist/commands/index.js +26 -26
- package/dist/commands/long-task-authoring.js +83 -9
- package/dist/commands/long-task.js +15 -15
- package/dist/lib/context-graph-snapshot.js +7 -0
- package/dist/lib/design-md.js +5 -4
- package/dist/lib/design-resource-handoff-shape-evidence.js +3 -0
- package/dist/lib/design-resource-handoff-shape-primitives.js +1 -3
- package/dist/lib/design-resource-handoff-types.d.ts +2 -0
- package/dist/lib/design-resource-handoff-validation-coverage.js +1 -0
- package/dist/lib/design-resource-handoff-validation-structure.js +2 -0
- package/dist/lib/doctor.js +2 -2
- package/dist/lib/execution-target-capabilities.d.ts +2 -0
- package/dist/lib/execution-target-capabilities.js +31 -0
- package/dist/lib/long-task-acceptance-shape.js +81 -20
- package/dist/lib/long-task-applicability-shape.d.ts +4 -0
- package/dist/lib/long-task-applicability-shape.js +54 -0
- package/dist/lib/long-task-assertions-v2.js +8 -0
- package/dist/lib/long-task-authoring-preflight-diagnostics.js +5 -0
- package/dist/lib/long-task-authoring-preflight-repair-order.js +3 -0
- package/dist/lib/long-task-authoring-preflight.js +12 -8
- package/dist/lib/long-task-authority-materials.js +46 -4
- package/dist/lib/long-task-authority-policy.d.ts +54 -0
- package/dist/lib/long-task-authority-policy.js +61 -0
- package/dist/lib/long-task-authority-types.d.ts +12 -2
- package/dist/lib/long-task-authority.js +9 -2
- package/dist/lib/long-task-check-shape.js +6 -1
- package/dist/lib/long-task-claim-definitions.js +27 -42
- package/dist/lib/long-task-claims.js +275 -146
- package/dist/lib/long-task-contract-types.d.ts +42 -8
- package/dist/lib/long-task-control-fields.d.ts +12 -0
- package/dist/lib/long-task-control-fields.js +66 -0
- package/dist/lib/long-task-control-types.d.ts +1 -0
- package/dist/lib/long-task-control-types.js +1 -0
- package/dist/lib/long-task-counterfactual-claim-policy.js +7 -0
- package/dist/lib/long-task-counterfactual-sandbox.js +1 -2
- package/dist/lib/long-task-counterfactual-types.d.ts +12 -0
- package/dist/lib/long-task-delivery-compiler.js +1 -0
- package/dist/lib/long-task-delivery-preflight.js +24 -0
- package/dist/lib/long-task-delivery-shape.d.ts +1 -0
- package/dist/lib/long-task-delivery-shape.js +1 -0
- package/dist/lib/long-task-delivery-types.d.ts +1 -0
- package/dist/lib/long-task-delivery-types.js +1 -0
- package/dist/lib/long-task-delivery-validation.js +46 -0
- package/dist/lib/long-task-design-resource-handoff.js +48 -7
- package/dist/lib/long-task-evidence-capability-codec.js +37 -0
- package/dist/lib/long-task-evidence-capability-policy.js +80 -38
- package/dist/lib/long-task-evidence-capability-runtime.js +36 -0
- package/dist/lib/long-task-evidence-capability-types.d.ts +13 -1
- package/dist/lib/long-task-evidence-findings.js +3 -0
- package/dist/lib/long-task-evidence-sensitivity-policy.js +38 -0
- package/dist/lib/long-task-evidence-v2.js +7 -1
- package/dist/lib/long-task-final-integrity.d.ts +16 -0
- package/dist/lib/long-task-final-integrity.js +98 -0
- package/dist/lib/long-task-final-v2.js +17 -34
- package/dist/lib/long-task-outcome-parser.js +22 -4
- package/dist/lib/long-task-playwright-case-evidence.d.ts +2 -0
- package/dist/lib/long-task-playwright-case-evidence.js +10 -2
- package/dist/lib/long-task-playwright-evidence.d.ts +1 -0
- package/dist/lib/long-task-playwright-evidence.js +32 -0
- package/dist/lib/long-task-product-shape.d.ts +3 -1
- package/dist/lib/long-task-product-shape.js +100 -4
- package/dist/lib/long-task-requirement-shape.js +3 -0
- package/dist/lib/long-task-root-shape.js +12 -6
- package/dist/lib/long-task-runner-freeze.js +16 -0
- package/dist/lib/long-task-runtime-types.d.ts +11 -2
- package/dist/lib/long-task-semantic-assurance-policy.d.ts +4 -0
- package/dist/lib/long-task-semantic-assurance-policy.js +52 -0
- package/dist/lib/long-task-semantic-contract-types.d.ts +18 -1
- package/dist/lib/long-task-semantic-drift-migration.js +33 -2
- package/dist/lib/long-task-semantic-mutation.d.ts +7 -0
- package/dist/lib/long-task-semantic-mutation.js +81 -0
- package/dist/lib/long-task-shape-primitives.d.ts +1 -1
- package/dist/lib/long-task-shape-primitives.js +1 -0
- package/dist/lib/long-task-source-authority-types.d.ts +2 -0
- package/dist/lib/long-task-source-item-parser.d.ts +1 -1
- package/dist/lib/long-task-source-item-parser.js +150 -104
- package/dist/lib/long-task-source-markers.d.ts +12 -0
- package/dist/lib/long-task-source-markers.js +146 -0
- package/dist/lib/long-task-source-target-continuity.d.ts +1 -1
- package/dist/lib/long-task-source-target-continuity.js +80 -55
- package/dist/lib/long-task-source-target-index.js +7 -29
- package/dist/lib/long-task-status-v2.d.ts +1 -0
- package/dist/lib/long-task-status-v2.js +2 -2
- package/dist/lib/long-task-target-policy.js +20 -0
- package/dist/lib/long-task-ui-design-policy.js +27 -1
- package/dist/lib/long-task-ui-surface-shape.js +21 -1
- package/dist/lib/long-task-ui-surface-types.d.ts +7 -0
- package/dist/lib/long-task-verification-preview.d.ts +10 -0
- package/dist/lib/long-task-verifier-dependency-closure.d.ts +3 -0
- package/dist/lib/long-task-verifier-dependency-closure.js +179 -0
- package/dist/lib/long-task-verifier-v2.js +12 -2
- package/dist/lib/long-task-workspace.js +5 -1
- package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +99 -25
- package/migrations/README.md +8 -8
- package/package.json +84 -84
|
@@ -104,8 +104,7 @@ export async function removeCounterfactualSandboxRoot(root, removeTree = rm, wai
|
|
|
104
104
|
const code = error && typeof error === "object" && "code" in error
|
|
105
105
|
? String(error.code)
|
|
106
106
|
: "";
|
|
107
|
-
if (!TRANSIENT_REMOVE_CODES.has(code) ||
|
|
108
|
-
attempt >= REMOVE_RETRY_LIMIT)
|
|
107
|
+
if (!TRANSIENT_REMOVE_CODES.has(code) || attempt >= REMOVE_RETRY_LIMIT)
|
|
109
108
|
throw error;
|
|
110
109
|
await wait(REMOVE_RETRY_DELAY_MS * (attempt + 1));
|
|
111
110
|
}
|
|
@@ -5,6 +5,16 @@ export type CounterfactualMutationV2 = {
|
|
|
5
5
|
type: "replace_file";
|
|
6
6
|
path: string;
|
|
7
7
|
fixture_path: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: "replace_json_value";
|
|
10
|
+
path: string;
|
|
11
|
+
pointer: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
} | {
|
|
14
|
+
type: "replace_text";
|
|
15
|
+
path: string;
|
|
16
|
+
match: string;
|
|
17
|
+
replacement: string;
|
|
8
18
|
};
|
|
9
19
|
export interface CounterfactualControlV2 {
|
|
10
20
|
key: string;
|
|
@@ -13,6 +23,7 @@ export interface CounterfactualControlV2 {
|
|
|
13
23
|
check_key: string;
|
|
14
24
|
mutation: CounterfactualMutationV2;
|
|
15
25
|
expected_assertion_failures: string[];
|
|
26
|
+
preserved_assertions: string[];
|
|
16
27
|
}
|
|
17
28
|
export interface GlobalCounterfactualControlV2 {
|
|
18
29
|
key: string;
|
|
@@ -21,4 +32,5 @@ export interface GlobalCounterfactualControlV2 {
|
|
|
21
32
|
check_key: string;
|
|
22
33
|
mutation: CounterfactualMutationV2;
|
|
23
34
|
expected_assertion_failures: string[];
|
|
35
|
+
preserved_assertions: string[];
|
|
24
36
|
}
|
|
@@ -134,6 +134,7 @@ export async function compileDeliveryContract(workdirInput, projectRootInput = p
|
|
|
134
134
|
source_claims: contract.source_claims,
|
|
135
135
|
stages: contract.stages,
|
|
136
136
|
global: {
|
|
137
|
+
applicability: contract.global.applicability,
|
|
137
138
|
product: contract.global.product,
|
|
138
139
|
technical: contract.global.technical,
|
|
139
140
|
acceptance: {
|
|
@@ -4,6 +4,7 @@ import { assertRepositoryPattern, matchesRepoPattern, proveRepositoryPatternSubs
|
|
|
4
4
|
import { assertProtectedRepositoryFile } from "./long-task-protected-files.js";
|
|
5
5
|
import { sha256Hex } from "./strict-codec.js";
|
|
6
6
|
import { repoRelative, resolveInsideRepository, } from "./long-task-workspace.js";
|
|
7
|
+
import { mutateSemanticText } from "./long-task-semantic-mutation.js";
|
|
7
8
|
export function validateVerificationInputSeparation(contract, checks, workdirRelative) {
|
|
8
9
|
const implementation = contract.outcomes.flatMap((outcome) => [
|
|
9
10
|
...outcome.technical.expected_change_paths,
|
|
@@ -133,12 +134,35 @@ async function validateScopeCounterfactualPaths(options) {
|
|
|
133
134
|
!(await stat(target).catch(() => null)))
|
|
134
135
|
throw new Error(`counterfactual_path_not_found:${options.scope}:${relative}`);
|
|
135
136
|
}
|
|
137
|
+
if (options.mutation.type === "replace_json_value" ||
|
|
138
|
+
options.mutation.type === "replace_text") {
|
|
139
|
+
const replacementTarget = resolveInsideRepository(options.repository, options.mutation.path, `${options.scope}.counterfactual.path`);
|
|
140
|
+
if (!(await stat(replacementTarget).catch(() => null)))
|
|
141
|
+
return;
|
|
142
|
+
try {
|
|
143
|
+
mutateSemanticText(await readFile(replacementTarget, "utf8"), options.mutation, false);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
+
throw new Error(`counterfactual_semantic_mutation_invalid:${options.scope}:${options.controlKey}:${message}`);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
136
151
|
if (options.mutation.type !== "replace_file")
|
|
137
152
|
return;
|
|
138
153
|
const fixtureTarget = resolveInsideRepository(options.repository, options.mutation.fixture_path, `${options.scope}.counterfactual.fixture`);
|
|
139
154
|
const fixture = repoRelative(options.repository, await assertProtectedRepositoryFile(options.repository, fixtureTarget, `${options.scope}.counterfactual.fixture`));
|
|
140
155
|
if (!Object.hasOwn(options.check.verification_input_hashes, fixture))
|
|
141
156
|
throw new Error(`counterfactual_fixture_must_be_verification_input:${options.scope}:${fixture}`);
|
|
157
|
+
const replacementTarget = resolveInsideRepository(options.repository, options.mutation.path, `${options.scope}.counterfactual.path`);
|
|
158
|
+
if (await stat(replacementTarget).catch(() => null)) {
|
|
159
|
+
const [fixtureBytes, targetBytes] = await Promise.all([
|
|
160
|
+
readFile(fixtureTarget),
|
|
161
|
+
readFile(replacementTarget),
|
|
162
|
+
]);
|
|
163
|
+
if (fixtureBytes.equals(targetBytes))
|
|
164
|
+
throw new Error(`counterfactual_replacement_must_change_carrier:${options.scope}:${options.controlKey}`);
|
|
165
|
+
}
|
|
142
166
|
}
|
|
143
167
|
export async function hashDeclaredFiles(repository, files, label) {
|
|
144
168
|
const result = {};
|
|
@@ -8,9 +8,12 @@ import { validateEvidenceCapabilityDeclarations } from "./long-task-evidence-cap
|
|
|
8
8
|
import { validateDeliveryStages } from "./long-task-stage-policy.js";
|
|
9
9
|
import { validateExecutionTargets, validateExternalConfirmationImpacts, } from "./long-task-target-policy.js";
|
|
10
10
|
import { validateUiSurfaceBindings } from "./long-task-ui-surface-policy.js";
|
|
11
|
+
import { controlFieldFacts } from "./long-task-control-fields.js";
|
|
12
|
+
import { validateSemanticAssuranceShape } from "./long-task-semantic-assurance-policy.js";
|
|
11
13
|
export function validateDeliveryContractStructure(contract) {
|
|
12
14
|
validateUniqueKeys(contract);
|
|
13
15
|
validateDependencies(contract);
|
|
16
|
+
validateControlClosure(contract);
|
|
14
17
|
validateOwnerAndBindings(contract);
|
|
15
18
|
validateDeclaredCheckSafety(contract);
|
|
16
19
|
validateEvidenceAdapters(contract);
|
|
@@ -22,6 +25,7 @@ export function validateDeliveryContractStructure(contract) {
|
|
|
22
25
|
assertCompiledClaimsCovered(claims);
|
|
23
26
|
validateDeliveryStages(contract);
|
|
24
27
|
validateExecutionTargets(contract);
|
|
28
|
+
validateSemanticAssuranceShape(contract);
|
|
25
29
|
validateEvidenceCapabilityDeclarations(contract);
|
|
26
30
|
validateExternalConfirmationImpacts(contract, claims);
|
|
27
31
|
}
|
|
@@ -30,8 +34,10 @@ export function deliveryContractStructureDiagnostics(contract) {
|
|
|
30
34
|
const report = (message) => diagnostics.push(message);
|
|
31
35
|
validateUniqueKeys(contract, report);
|
|
32
36
|
validateDependencies(contract, report);
|
|
37
|
+
validateControlClosure(contract, report);
|
|
33
38
|
validateDeliveryStages(contract, report);
|
|
34
39
|
validateExecutionTargets(contract, report);
|
|
40
|
+
validateSemanticAssuranceShape(contract, report);
|
|
35
41
|
validateEvidenceCapabilityDeclarations(contract, report);
|
|
36
42
|
validateOwnerAndBindings(contract, report);
|
|
37
43
|
validateDeclaredCheckSafety(contract, report);
|
|
@@ -73,6 +79,7 @@ function validateUniqueKeys(contract, report) {
|
|
|
73
79
|
unique(outcome.acceptance.checks.map((check) => check.key), `check_key_duplicate:${outcome.key}`, report);
|
|
74
80
|
unique(outcome.product.requirements.map((item) => item.key), `requirement_key_duplicate:${outcome.key}`, report);
|
|
75
81
|
unique(outcome.product.controls.map((control) => control.key), `control_key_duplicate:${outcome.key}`, report);
|
|
82
|
+
unique(outcome.product.control_relations.map((relation) => relation.key), `control_relation_key_duplicate:${outcome.key}`, report);
|
|
76
83
|
unique(outcome.product.non_completing_outcomes.map((item) => item.key), `non_completing_key_duplicate:${outcome.key}`, report);
|
|
77
84
|
unique(outcome.technical.obligations.map((item) => item.key), `obligation_key_duplicate:${outcome.key}`, report);
|
|
78
85
|
unique(outcome.technical.forbidden_shortcuts.map((item) => item.key), `forbidden_shortcut_key_duplicate:${outcome.key}`, report);
|
|
@@ -87,6 +94,45 @@ function validateUniqueKeys(contract, report) {
|
|
|
87
94
|
for (const check of contract.global.acceptance.checks)
|
|
88
95
|
unique([...check.positive_assertions, ...check.negative_assertions].map((assertion) => assertion.observation), `assertion_observation_duplicate:GLOBAL:${check.key}`, report);
|
|
89
96
|
}
|
|
97
|
+
function validateControlClosure(contract, report) {
|
|
98
|
+
for (const outcome of contract.outcomes) {
|
|
99
|
+
const controls = new Set(outcome.product.controls.map((control) => control.key));
|
|
100
|
+
const closure = outcome.product.control_relation_closure;
|
|
101
|
+
const relations = outcome.product.control_relations;
|
|
102
|
+
if (closure.state === "unresolved")
|
|
103
|
+
issue(report, "control_relation_closure_unresolved", outcome.key);
|
|
104
|
+
if (closure.state === "specified" && !relations.length)
|
|
105
|
+
issue(report, "control_relation_required", outcome.key);
|
|
106
|
+
if (closure.state === "not_applicable" && relations.length)
|
|
107
|
+
issue(report, "control_relation_forbidden_when_not_applicable", outcome.key);
|
|
108
|
+
if (!controls.size && closure.state !== "not_applicable")
|
|
109
|
+
issue(report, "control_relation_closure_without_controls", outcome.key);
|
|
110
|
+
for (const control of outcome.product.controls) {
|
|
111
|
+
const facts = controlFieldFacts(control);
|
|
112
|
+
const unresolved = facts
|
|
113
|
+
.filter((fact) => fact.state === "unresolved")
|
|
114
|
+
.map((fact) => fact.contract_field);
|
|
115
|
+
if (unresolved.length)
|
|
116
|
+
issue(report, "control_field_unresolved", `${outcome.key}:${control.key}:${unresolved.join(",")}`);
|
|
117
|
+
if (!facts.some((fact) => [
|
|
118
|
+
"navigation_result",
|
|
119
|
+
"interaction",
|
|
120
|
+
"trigger",
|
|
121
|
+
"location",
|
|
122
|
+
].includes(fact.contract_field) && fact.state === "specified"))
|
|
123
|
+
issue(report, "control_root_behavior_required", `${outcome.key}:${control.key}`);
|
|
124
|
+
}
|
|
125
|
+
for (const relation of relations) {
|
|
126
|
+
if (relation.control_refs.length < 2)
|
|
127
|
+
issue(report, "control_relation_control_refs_required", `${outcome.key}:${relation.key}`);
|
|
128
|
+
if (new Set(relation.control_refs).size !== relation.control_refs.length)
|
|
129
|
+
issue(report, "control_relation_control_ref_duplicate", `${outcome.key}:${relation.key}`);
|
|
130
|
+
for (const reference of relation.control_refs)
|
|
131
|
+
if (!controls.has(reference))
|
|
132
|
+
issue(report, "control_relation_control_ref_unknown", `${outcome.key}:${relation.key}:${reference}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
90
136
|
function validateOwnerAndBindings(contract, report) {
|
|
91
137
|
const contextRefs = new Set(contract.task.context_refs);
|
|
92
138
|
for (const outcome of contract.outcomes) {
|
|
@@ -18,6 +18,7 @@ export async function validateLongTaskDesignResourceHandoffs(contract, repositor
|
|
|
18
18
|
invalid("target_handoff_missing", contractTarget.target.key);
|
|
19
19
|
consumed.add(contractTarget.target.key);
|
|
20
20
|
validateTargetIdentity(contractTarget, indexedTarget);
|
|
21
|
+
validateTargetCapabilities(contract, contractTarget, indexedTarget);
|
|
21
22
|
validateCoverageClaims(contract, contractTarget, indexedTarget);
|
|
22
23
|
validateBlockerBindings(contract, contractTarget, indexedTarget);
|
|
23
24
|
}
|
|
@@ -62,7 +63,6 @@ function validateCoverageClaims(contract, contractTarget, indexed) {
|
|
|
62
63
|
const check = contract.outcomes
|
|
63
64
|
.find((item) => item.key === contractTarget.outcome_key)
|
|
64
65
|
.acceptance.checks.find((item) => item.key === target.conformance_check_ref);
|
|
65
|
-
const assertion = check.positive_assertions.find((item) => item.key === target.conformance_assertion_ref);
|
|
66
66
|
const claims = new Map(contract.source_claims.map((item) => [item.key, item]));
|
|
67
67
|
const rows = indexed.preflight.handoff.coverage.filter((row) => row.disposition === "covered" && row.target_refs.includes(target.key));
|
|
68
68
|
if (!rows.length)
|
|
@@ -72,9 +72,6 @@ function validateCoverageClaims(contract, contractTarget, indexed) {
|
|
|
72
72
|
for (const sourceItemRef of row.source_item_refs) {
|
|
73
73
|
const localClaims = designSourceItemClaims(contract, contractTarget, indexed, sourceItemRef, claims);
|
|
74
74
|
claimsBySourceItem.set(sourceItemRef, localClaims);
|
|
75
|
-
for (const localClaimRef of localClaims)
|
|
76
|
-
if (!assertion.claims.includes(localClaimRef))
|
|
77
|
-
invalid("coverage_claim_not_in_conformance_assertion", `${target.key}:${sourceItemRef}:${localClaimRef}`);
|
|
78
75
|
}
|
|
79
76
|
validateVerificationMethodBindings(target, check, rows, claimsBySourceItem);
|
|
80
77
|
}
|
|
@@ -94,6 +91,7 @@ function validateBlockerBindings(contract, contractTarget, indexed) {
|
|
|
94
91
|
invalid("acceptance_blocker_unbound", `${contractTarget.target.key}:${blocker.key}`);
|
|
95
92
|
assertSameSet(contractBlocker.source_item_refs, blocker.source_item_refs, "acceptance_blocker_source_items_mismatch", blocker.key);
|
|
96
93
|
assertSameSet(contractBlocker.verification_methods, blocker.verification_methods, "acceptance_blocker_methods_mismatch", blocker.key);
|
|
94
|
+
assertSameSet(contractBlocker.required_capabilities, blocker.required_capabilities, "acceptance_blocker_capabilities_mismatch", blocker.key);
|
|
97
95
|
for (const method of blocker.verification_methods)
|
|
98
96
|
if (!availableMethods.has(method))
|
|
99
97
|
invalid("acceptance_blocker_method_unbound", `${blocker.key}:${method}`);
|
|
@@ -102,6 +100,12 @@ function validateBlockerBindings(contract, contractTarget, indexed) {
|
|
|
102
100
|
return local.map((claimRef) => `${contractTarget.outcome_key}.${claimRef}`);
|
|
103
101
|
});
|
|
104
102
|
if (contractBlocker.status === "machine_claim") {
|
|
103
|
+
const executionTarget = contract.task.execution_targets.find((item) => item.key === contractTarget.binding.target_ref);
|
|
104
|
+
if (!executionTarget)
|
|
105
|
+
invalid("execution_target_unknown", `${contractTarget.target.key}:${contractTarget.binding.target_ref}`);
|
|
106
|
+
for (const capability of blocker.required_capabilities)
|
|
107
|
+
if (!executionTarget.capabilities.includes(capability))
|
|
108
|
+
invalid("acceptance_blocker_target_capability_missing", `${blocker.key}:${executionTarget.key}:${capability}`);
|
|
105
109
|
const expectedLocal = fullClaimRefs.map((claimRef) => claimRef.slice(contractTarget.outcome_key.length + 1));
|
|
106
110
|
for (const claimRef of expectedLocal)
|
|
107
111
|
if (!contractBlocker.refs.includes(claimRef))
|
|
@@ -156,14 +160,51 @@ function validateVerificationMethodBindings(target, check, rows, claimsBySourceI
|
|
|
156
160
|
for (const capability of requiredCapabilities(binding.method))
|
|
157
161
|
if (!assertion.evidence_capabilities.includes(capability))
|
|
158
162
|
invalid("verification_method_capability_required", `${target.key}:${binding.method}:${capability}`);
|
|
163
|
+
assertSameSet(binding.evidence_artifacts.map((item) => item.condition_key), target.condition_keys, "verification_method_evidence_conditions_mismatch", `${target.key}:${binding.method}`);
|
|
159
164
|
}
|
|
160
165
|
}
|
|
161
166
|
function requiredCapabilities(method) {
|
|
162
167
|
if (method === "interaction_trace")
|
|
163
|
-
return ["interaction_trace", "target_runtime"];
|
|
168
|
+
return ["design_method", "interaction_trace", "target_runtime"];
|
|
164
169
|
if (method === "component_state")
|
|
165
|
-
return [
|
|
166
|
-
|
|
170
|
+
return [
|
|
171
|
+
"design_method",
|
|
172
|
+
"design_conformance",
|
|
173
|
+
"interaction_trace",
|
|
174
|
+
"target_runtime",
|
|
175
|
+
];
|
|
176
|
+
return ["design_method", "design_conformance", "target_runtime"];
|
|
177
|
+
}
|
|
178
|
+
function validateTargetCapabilities(contract, contractTarget, indexed) {
|
|
179
|
+
const executionTarget = contract.task.execution_targets.find((item) => item.key === contractTarget.binding.target_ref);
|
|
180
|
+
if (!executionTarget)
|
|
181
|
+
invalid("execution_target_unknown", `${contractTarget.target.key}:${contractTarget.binding.target_ref}`);
|
|
182
|
+
const required = new Set();
|
|
183
|
+
for (const method of contractTarget.target.verification_method_bindings.map((item) => item.method)) {
|
|
184
|
+
if (method === "responsive_reflow")
|
|
185
|
+
required.add("viewport-control");
|
|
186
|
+
if (method === "motion_timeline")
|
|
187
|
+
required.add("motion-observation");
|
|
188
|
+
if (method === "accessibility_semantics")
|
|
189
|
+
required.add("assistive-technology");
|
|
190
|
+
}
|
|
191
|
+
for (const conditionRef of indexed.target.condition_refs) {
|
|
192
|
+
const condition = indexed.preflight.handoff.conditions.find((item) => item.key === conditionRef);
|
|
193
|
+
const input = condition.input_methods[0].toLowerCase();
|
|
194
|
+
if (["mouse", "pointer", "trackpad"].includes(input))
|
|
195
|
+
required.add("pointer-input");
|
|
196
|
+
if (input === "keyboard")
|
|
197
|
+
required.add("keyboard-input");
|
|
198
|
+
if (["touch", "pen", "stylus"].includes(input))
|
|
199
|
+
required.add("touch-input");
|
|
200
|
+
if (["screen-reader", "voice-control", "switch-control"].includes(input))
|
|
201
|
+
required.add("assistive-technology");
|
|
202
|
+
if (condition.motion === "reduced")
|
|
203
|
+
required.add("reduced-motion");
|
|
204
|
+
}
|
|
205
|
+
for (const capability of required)
|
|
206
|
+
if (!executionTarget.capabilities.includes(capability))
|
|
207
|
+
invalid("execution_target_capability_missing", `${contractTarget.target.key}:${executionTarget.key}:${capability}`);
|
|
167
208
|
}
|
|
168
209
|
function assertSameSet(actual, expected, code, detail) {
|
|
169
210
|
const left = [...new Set(actual)].sort();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DESIGN_RESOURCE_VERIFICATION_METHODS } from "./design-resource-handoff-types.js";
|
|
1
2
|
export function decodeEvidenceCapabilityRecords(value) {
|
|
2
3
|
if (!Array.isArray(value))
|
|
3
4
|
throw invalidRecord("must_be_array");
|
|
@@ -179,6 +180,36 @@ function decodeRecord(value, index) {
|
|
|
179
180
|
session_id: nonEmpty(row.session_id, `${label}.session_id`),
|
|
180
181
|
cold_start: row.cold_start,
|
|
181
182
|
};
|
|
183
|
+
case "design_method":
|
|
184
|
+
exact(row, label, [
|
|
185
|
+
"assertion_key",
|
|
186
|
+
"capability",
|
|
187
|
+
"design_target_ref",
|
|
188
|
+
"target_ref",
|
|
189
|
+
"method",
|
|
190
|
+
"cells",
|
|
191
|
+
]);
|
|
192
|
+
return {
|
|
193
|
+
...base,
|
|
194
|
+
capability,
|
|
195
|
+
design_target_ref: key(row.design_target_ref, `${label}.design_target_ref`),
|
|
196
|
+
target_ref: key(row.target_ref, `${label}.target_ref`),
|
|
197
|
+
method: literal(row.method, DESIGN_RESOURCE_VERIFICATION_METHODS, `${label}.method`),
|
|
198
|
+
cells: array(row.cells, `${label}.cells`).map((item, cellIndex) => {
|
|
199
|
+
const cellLabel = `${label}.cells[${cellIndex}]`;
|
|
200
|
+
const cell = record(item, cellLabel);
|
|
201
|
+
exact(cell, cellLabel, [
|
|
202
|
+
"condition_key",
|
|
203
|
+
"artifact_path",
|
|
204
|
+
"observation_artifact_path",
|
|
205
|
+
]);
|
|
206
|
+
return {
|
|
207
|
+
condition_key: key(cell.condition_key, `${cellLabel}.condition_key`),
|
|
208
|
+
artifact_path: nonEmpty(cell.artifact_path, `${cellLabel}.artifact_path`),
|
|
209
|
+
observation_artifact_path: nonEmpty(cell.observation_artifact_path, `${cellLabel}.observation_artifact_path`),
|
|
210
|
+
};
|
|
211
|
+
}),
|
|
212
|
+
};
|
|
182
213
|
case "input_variation":
|
|
183
214
|
exact(row, label, [
|
|
184
215
|
"assertion_key",
|
|
@@ -248,3 +279,9 @@ function sha(value, label) {
|
|
|
248
279
|
throw invalidRecord(label);
|
|
249
280
|
return result;
|
|
250
281
|
}
|
|
282
|
+
function literal(value, allowed, label) {
|
|
283
|
+
const result = nonEmpty(value, label);
|
|
284
|
+
if (!allowed.includes(result))
|
|
285
|
+
throw invalidRecord(label);
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
@@ -4,47 +4,56 @@ export { decodeEvidenceCapabilityRecords } from "./long-task-evidence-capability
|
|
|
4
4
|
export function validateEvidenceCapabilityDeclarations(contract, report) {
|
|
5
5
|
const targets = new Map(contract.task.execution_targets.map((target) => [target.key, target]));
|
|
6
6
|
for (const [outcomeKey, check] of allChecks(contract)) {
|
|
7
|
-
|
|
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("design_conformance") &&
|
|
30
|
-
!check.artifact_globs.length)
|
|
31
|
-
issue(report, "design_conformance_artifact_required", label);
|
|
32
|
-
if (assertion.evidence_capabilities.includes("interaction_trace") &&
|
|
33
|
-
!check.scenario.when.length)
|
|
34
|
-
issue(report, "interaction_trace_actions_required", label);
|
|
35
|
-
if (check.runner.type === "playwright_test" &&
|
|
36
|
-
assertion.evidence_capabilities.some((capability) => capability !== "presence" &&
|
|
37
|
-
capability !== "interaction_trace" &&
|
|
38
|
-
capability !== "design_conformance" &&
|
|
39
|
-
capability !== "target_runtime"))
|
|
40
|
-
issue(report, "playwright_evidence_capability_unsupported", label);
|
|
41
|
-
if (["boundary_invocation", "external_side_effect"].some((capability) => assertion.evidence_capabilities.includes(capability)) &&
|
|
42
|
-
targets.get(check.execution_target.target_ref)?.role !== "observer")
|
|
43
|
-
issue(report, "observer_check_target_required", label);
|
|
44
|
-
}
|
|
7
|
+
validateCheckEvidenceCapabilityDeclarations(check, outcomeKey, targets, report);
|
|
45
8
|
}
|
|
46
9
|
validateJourneySeparation(contract, report);
|
|
47
10
|
}
|
|
11
|
+
function validateCheckEvidenceCapabilityDeclarations(check, outcomeKey, targets, report) {
|
|
12
|
+
const label = checkLabel(outcomeKey, check.key);
|
|
13
|
+
unique(check.journey_roles, "journey_role_duplicate", label, report);
|
|
14
|
+
if (!check.journey_roles.length)
|
|
15
|
+
issue(report, "journey_role_required", label);
|
|
16
|
+
validateScenario(check, outcomeKey, report);
|
|
17
|
+
for (const assertion of [
|
|
18
|
+
...check.positive_assertions,
|
|
19
|
+
...check.negative_assertions,
|
|
20
|
+
])
|
|
21
|
+
validateAssertionEvidenceCapabilityDeclarations(assertion, check, targets, `${label}:${assertion.key}`, report);
|
|
22
|
+
}
|
|
23
|
+
function validateAssertionEvidenceCapabilityDeclarations(assertion, check, targets, label, report) {
|
|
24
|
+
unique(assertion.evidence_capabilities, "evidence_capability_duplicate", label, report);
|
|
25
|
+
if (!assertion.evidence_capabilities.length)
|
|
26
|
+
issue(report, "evidence_capability_required", label);
|
|
27
|
+
if (assertion.claims.length &&
|
|
28
|
+
check.proof_surface !== "implementation_structure" &&
|
|
29
|
+
assertion.evidence_capabilities.every((capability) => capability === "presence"))
|
|
30
|
+
issue(report, "presence_cannot_prove_behavior", label);
|
|
31
|
+
if (assertion.operator === "exists" &&
|
|
32
|
+
!assertion.evidence_capabilities.includes("presence"))
|
|
33
|
+
issue(report, "exists_requires_presence_capability", label);
|
|
34
|
+
if (assertion.evidence_capabilities.includes("visual_render") &&
|
|
35
|
+
!check.artifact_globs.length)
|
|
36
|
+
issue(report, "visual_render_artifact_required", label);
|
|
37
|
+
if (assertion.evidence_capabilities.includes("design_conformance") &&
|
|
38
|
+
!check.artifact_globs.length)
|
|
39
|
+
issue(report, "design_conformance_artifact_required", label);
|
|
40
|
+
if (assertion.evidence_capabilities.includes("design_method") &&
|
|
41
|
+
!check.artifact_globs.length)
|
|
42
|
+
issue(report, "design_method_artifact_required", label);
|
|
43
|
+
if (assertion.evidence_capabilities.includes("interaction_trace") &&
|
|
44
|
+
!check.scenario.when.length)
|
|
45
|
+
issue(report, "interaction_trace_actions_required", label);
|
|
46
|
+
if (check.runner.type === "playwright_test" &&
|
|
47
|
+
assertion.evidence_capabilities.some((capability) => capability !== "presence" &&
|
|
48
|
+
capability !== "interaction_trace" &&
|
|
49
|
+
capability !== "design_conformance" &&
|
|
50
|
+
capability !== "design_method" &&
|
|
51
|
+
capability !== "target_runtime"))
|
|
52
|
+
issue(report, "playwright_evidence_capability_unsupported", label);
|
|
53
|
+
if (["boundary_invocation", "external_side_effect"].some((capability) => assertion.evidence_capabilities.includes(capability)) &&
|
|
54
|
+
targets.get(check.execution_target.target_ref)?.role !== "observer")
|
|
55
|
+
issue(report, "observer_check_target_required", label);
|
|
56
|
+
}
|
|
48
57
|
function validateScenario(check, outcomeKey, report) {
|
|
49
58
|
const label = checkLabel(outcomeKey, check.key);
|
|
50
59
|
if (!check.scenario.given.length)
|
|
@@ -127,8 +136,41 @@ export function evaluateEvidenceCapabilities(check, records, artifactHashes) {
|
|
|
127
136
|
}
|
|
128
137
|
complete[assertion.key] = assertionComplete;
|
|
129
138
|
}
|
|
139
|
+
validateDistinctDesignMethodArtifacts(check, runtimeRecords, artifactHashes, findings);
|
|
130
140
|
return { complete, findings };
|
|
131
141
|
}
|
|
142
|
+
function validateDistinctDesignMethodArtifacts(check, records, artifactHashes, findings) {
|
|
143
|
+
const pathOwners = new Map();
|
|
144
|
+
const digestOwners = new Map();
|
|
145
|
+
for (const record of records) {
|
|
146
|
+
if (record.capability !== "design_method")
|
|
147
|
+
continue;
|
|
148
|
+
for (const cell of record.cells) {
|
|
149
|
+
const currentOwner = `${record.method}:${cell.condition_key}`;
|
|
150
|
+
const pathOwner = pathOwners.get(cell.observation_artifact_path);
|
|
151
|
+
if (pathOwner)
|
|
152
|
+
findings.push(reusedDesignMethodArtifactFinding(check, record.assertion_key, currentOwner, pathOwner, "path", cell.observation_artifact_path));
|
|
153
|
+
else
|
|
154
|
+
pathOwners.set(cell.observation_artifact_path, currentOwner);
|
|
155
|
+
const digest = artifactHashes[cell.observation_artifact_path];
|
|
156
|
+
if (!digest)
|
|
157
|
+
continue;
|
|
158
|
+
const digestOwner = digestOwners.get(digest);
|
|
159
|
+
if (digestOwner)
|
|
160
|
+
findings.push(reusedDesignMethodArtifactFinding(check, record.assertion_key, currentOwner, digestOwner, "sha256", digest));
|
|
161
|
+
else
|
|
162
|
+
digestOwners.set(digest, currentOwner);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function reusedDesignMethodArtifactFinding(check, assertionKey, currentOwner, previousOwner, identityKind, identity) {
|
|
167
|
+
return {
|
|
168
|
+
...checkFinding(check, "design_method_evidence_reused", `Design-method evidence ${identityKind} ${identity} is reused by ${previousOwner} and ${currentOwner}.`, "Emit method-and-condition evidence whose path and current content digest are both distinct for every verification-method cell."),
|
|
169
|
+
assertion_key: assertionKey,
|
|
170
|
+
expected: "distinct_method_condition_artifact_path_and_content",
|
|
171
|
+
actual: { identity_kind: identityKind, identity },
|
|
172
|
+
};
|
|
173
|
+
}
|
|
132
174
|
function allChecks(contract) {
|
|
133
175
|
return [
|
|
134
176
|
...contract.global.acceptance.checks.map((check) => [null, check]),
|
|
@@ -27,12 +27,48 @@ export function validateRuntimeEvidenceRecord(check, record, artifactHashes) {
|
|
|
27
27
|
: "artifact_hash_mismatch";
|
|
28
28
|
case "design_conformance":
|
|
29
29
|
return validateDesignConformance(check, record, artifactHashes);
|
|
30
|
+
case "design_method":
|
|
31
|
+
return validateDesignMethod(check, record, artifactHashes);
|
|
30
32
|
case "target_runtime":
|
|
31
33
|
return validateTargetRuntime(check, record);
|
|
32
34
|
case "input_variation":
|
|
33
35
|
return validateInputVariation(record);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
38
|
+
function validateDesignMethod(check, record, artifactHashes) {
|
|
39
|
+
const target = (check.design_conformance_targets ?? []).find((item) => item.key === record.design_target_ref &&
|
|
40
|
+
item.verification_method_bindings.some((binding) => binding.assertion_ref === record.assertion_key &&
|
|
41
|
+
binding.method === record.method));
|
|
42
|
+
if (!target)
|
|
43
|
+
return "design_method_binding_unknown";
|
|
44
|
+
if (target.target_ref !== record.target_ref ||
|
|
45
|
+
target.target_ref !== check.execution_target.target_ref)
|
|
46
|
+
return "target_mismatch";
|
|
47
|
+
const binding = target.verification_method_bindings.find((item) => item.assertion_ref === record.assertion_key &&
|
|
48
|
+
item.method === record.method);
|
|
49
|
+
if (!same([...record.cells.map((item) => item.condition_key)].sort(), [...target.condition_keys].sort()))
|
|
50
|
+
return "design_method_conditions_mismatch";
|
|
51
|
+
const expected = new Map(binding.evidence_artifacts.map((item) => [
|
|
52
|
+
item.condition_key,
|
|
53
|
+
{
|
|
54
|
+
artifact_path: item.path,
|
|
55
|
+
observation_artifact_path: item.observation_path,
|
|
56
|
+
},
|
|
57
|
+
]));
|
|
58
|
+
if (record.cells.length !== expected.size)
|
|
59
|
+
return "design_method_cell_count_mismatch";
|
|
60
|
+
for (const cell of record.cells) {
|
|
61
|
+
const expectedCell = expected.get(cell.condition_key);
|
|
62
|
+
if (expectedCell?.artifact_path !== cell.artifact_path ||
|
|
63
|
+
expectedCell?.observation_artifact_path !== cell.observation_artifact_path)
|
|
64
|
+
return "design_method_artifact_path_mismatch";
|
|
65
|
+
if (!artifactHashes[cell.artifact_path])
|
|
66
|
+
return "design_method_artifact_missing";
|
|
67
|
+
if (!artifactHashes[cell.observation_artifact_path])
|
|
68
|
+
return "design_method_observation_artifact_missing";
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
36
72
|
function validateDesignConformance(check, record, artifactHashes) {
|
|
37
73
|
const target = (check.design_conformance_targets ?? []).find((item) => item.key === record.design_target_ref &&
|
|
38
74
|
designTargetUsesAssertion(item, record.assertion_key));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EvidenceCapabilityV2 } from "./long-task-semantic-contract-types.js";
|
|
2
|
+
import type { DesignResourceVerificationMethod } from "./design-resource-handoff-types.js";
|
|
2
3
|
interface EvidenceRecordBaseV2 {
|
|
3
4
|
assertion_key: string;
|
|
4
5
|
capability: EvidenceCapabilityV2;
|
|
@@ -70,6 +71,17 @@ export interface TargetRuntimeEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
|
70
71
|
session_id: string;
|
|
71
72
|
cold_start: boolean;
|
|
72
73
|
}
|
|
74
|
+
export interface DesignMethodEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
75
|
+
capability: "design_method";
|
|
76
|
+
design_target_ref: string;
|
|
77
|
+
target_ref: string;
|
|
78
|
+
method: DesignResourceVerificationMethod;
|
|
79
|
+
cells: Array<{
|
|
80
|
+
condition_key: string;
|
|
81
|
+
artifact_path: string;
|
|
82
|
+
observation_artifact_path: string;
|
|
83
|
+
}>;
|
|
84
|
+
}
|
|
73
85
|
export interface InputVariationEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
74
86
|
capability: "input_variation";
|
|
75
87
|
cases: Array<{
|
|
@@ -78,5 +90,5 @@ export interface InputVariationEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
|
78
90
|
}>;
|
|
79
91
|
failure_case_observed: boolean;
|
|
80
92
|
}
|
|
81
|
-
export type EvidenceCapabilityRecordV2 = InteractionTraceEvidenceV2 | StateDeltaEvidenceV2 | CrossSurfaceConsistencyEvidenceV2 | DurableReadbackEvidenceV2 | BoundaryInvocationEvidenceV2 | ExternalSideEffectEvidenceV2 | FailureInjectionEvidenceV2 | VisualRenderEvidenceV2 | DesignConformanceEvidenceV2 | TargetRuntimeEvidenceV2 | InputVariationEvidenceV2;
|
|
93
|
+
export type EvidenceCapabilityRecordV2 = InteractionTraceEvidenceV2 | StateDeltaEvidenceV2 | CrossSurfaceConsistencyEvidenceV2 | DurableReadbackEvidenceV2 | BoundaryInvocationEvidenceV2 | ExternalSideEffectEvidenceV2 | FailureInjectionEvidenceV2 | VisualRenderEvidenceV2 | DesignConformanceEvidenceV2 | DesignMethodEvidenceV2 | TargetRuntimeEvidenceV2 | InputVariationEvidenceV2;
|
|
82
94
|
export {};
|
|
@@ -18,6 +18,9 @@ export function evaluateAssertionResults(check, observations) {
|
|
|
18
18
|
polarity,
|
|
19
19
|
passed: evaluation.passed,
|
|
20
20
|
claims: assertion.claims,
|
|
21
|
+
...(assertion.applicability_ref
|
|
22
|
+
? { applicability_ref: assertion.applicability_ref }
|
|
23
|
+
: {}),
|
|
21
24
|
observation: assertion.observation,
|
|
22
25
|
evidence_capabilities: assertion.evidence_capabilities,
|
|
23
26
|
evidence_complete: false,
|