project-tiny-context-harness 0.8.1 → 0.8.3

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.
Files changed (111) hide show
  1. package/README.md +26 -20
  2. package/assets/README.md +115 -36
  3. package/assets/README.zh-CN.md +33 -27
  4. package/assets/agents/AGENTS_CORE.md +6 -6
  5. package/assets/skills/context_development_engineer/SKILL.md +6 -6
  6. package/assets/skills/context_uiux_design/SKILL.md +5 -5
  7. package/assets/skills/design-resource-authoring/SKILL.md +4 -3
  8. package/assets/skills/design-resource-authoring/references/downstream-handoff.md +9 -6
  9. package/assets/skills/design-resource-authoring/references/resource-selection.md +3 -2
  10. package/assets/skills/long-task-workflow/SKILL.md +16 -16
  11. package/assets/skills/long-task-workflow/references/authority-lifecycle.md +8 -6
  12. package/assets/skills/long-task-workflow/references/contract-authoring.md +18 -14
  13. package/assets/skills/long-task-workflow/references/evidence-design.md +15 -14
  14. package/assets/skills/long-task-workflow/references/source-authoring.md +12 -9
  15. package/assets/skills/source-plan-authoring/SKILL.md +1 -1
  16. package/dist/commands/long-task-authoring.js +83 -9
  17. package/dist/lib/context-graph-snapshot.js +7 -0
  18. package/dist/lib/design-resource-fact-policy.d.ts +25 -0
  19. package/dist/lib/design-resource-fact-policy.js +35 -0
  20. package/dist/lib/design-resource-handoff-shape-evidence.d.ts +3 -1
  21. package/dist/lib/design-resource-handoff-shape-evidence.js +60 -0
  22. package/dist/lib/design-resource-handoff-shape.js +5 -1
  23. package/dist/lib/design-resource-handoff-types.d.ts +29 -0
  24. package/dist/lib/design-resource-handoff-validation-coverage.d.ts +1 -1
  25. package/dist/lib/design-resource-handoff-validation-coverage.js +42 -14
  26. package/dist/lib/design-resource-handoff-validation-facts.d.ts +2 -0
  27. package/dist/lib/design-resource-handoff-validation-facts.js +97 -0
  28. package/dist/lib/design-resource-handoff-validation-structure.js +2 -0
  29. package/dist/lib/design-resource-handoff-validation.js +10 -1
  30. package/dist/lib/execution-target-capabilities.d.ts +2 -0
  31. package/dist/lib/execution-target-capabilities.js +31 -0
  32. package/dist/lib/long-task-acceptance-shape.js +81 -20
  33. package/dist/lib/long-task-applicability-shape.d.ts +4 -0
  34. package/dist/lib/long-task-applicability-shape.js +54 -0
  35. package/dist/lib/long-task-assertions-v2.js +8 -0
  36. package/dist/lib/long-task-authoring-preflight-diagnostics.js +5 -0
  37. package/dist/lib/long-task-authoring-preflight-repair-order.js +3 -0
  38. package/dist/lib/long-task-authoring-preflight.js +12 -8
  39. package/dist/lib/long-task-authority-materials.js +46 -4
  40. package/dist/lib/long-task-authority-policy.d.ts +54 -0
  41. package/dist/lib/long-task-authority-policy.js +61 -0
  42. package/dist/lib/long-task-authority-types.d.ts +12 -2
  43. package/dist/lib/long-task-authority.js +9 -2
  44. package/dist/lib/long-task-check-shape.js +6 -1
  45. package/dist/lib/long-task-claim-definitions.js +27 -42
  46. package/dist/lib/long-task-claims.js +275 -146
  47. package/dist/lib/long-task-contract-types.d.ts +42 -8
  48. package/dist/lib/long-task-control-fields.d.ts +12 -0
  49. package/dist/lib/long-task-control-fields.js +66 -0
  50. package/dist/lib/long-task-control-types.d.ts +1 -0
  51. package/dist/lib/long-task-control-types.js +1 -0
  52. package/dist/lib/long-task-counterfactual-claim-policy.js +7 -0
  53. package/dist/lib/long-task-counterfactual-types.d.ts +12 -0
  54. package/dist/lib/long-task-delivery-compiler.js +1 -0
  55. package/dist/lib/long-task-delivery-preflight.js +24 -0
  56. package/dist/lib/long-task-delivery-shape.d.ts +1 -0
  57. package/dist/lib/long-task-delivery-shape.js +1 -0
  58. package/dist/lib/long-task-delivery-types.d.ts +1 -0
  59. package/dist/lib/long-task-delivery-types.js +1 -0
  60. package/dist/lib/long-task-delivery-validation.js +46 -0
  61. package/dist/lib/long-task-design-resource-handoff.js +67 -13
  62. package/dist/lib/long-task-evidence-capability-codec.js +47 -0
  63. package/dist/lib/long-task-evidence-capability-policy.js +80 -38
  64. package/dist/lib/long-task-evidence-capability-runtime.js +39 -0
  65. package/dist/lib/long-task-evidence-capability-types.d.ts +14 -1
  66. package/dist/lib/long-task-evidence-findings.js +3 -0
  67. package/dist/lib/long-task-evidence-sensitivity-policy.js +38 -0
  68. package/dist/lib/long-task-evidence-v2.js +7 -1
  69. package/dist/lib/long-task-final-integrity.d.ts +16 -0
  70. package/dist/lib/long-task-final-integrity.js +98 -0
  71. package/dist/lib/long-task-final-v2.js +17 -34
  72. package/dist/lib/long-task-outcome-parser.js +22 -4
  73. package/dist/lib/long-task-playwright-case-evidence.d.ts +2 -0
  74. package/dist/lib/long-task-playwright-case-evidence.js +10 -2
  75. package/dist/lib/long-task-playwright-evidence.d.ts +1 -0
  76. package/dist/lib/long-task-playwright-evidence.js +33 -0
  77. package/dist/lib/long-task-product-shape.d.ts +3 -1
  78. package/dist/lib/long-task-product-shape.js +100 -4
  79. package/dist/lib/long-task-requirement-shape.js +3 -0
  80. package/dist/lib/long-task-root-shape.js +12 -6
  81. package/dist/lib/long-task-runner-freeze.js +16 -0
  82. package/dist/lib/long-task-runtime-types.d.ts +11 -2
  83. package/dist/lib/long-task-semantic-assurance-policy.d.ts +4 -0
  84. package/dist/lib/long-task-semantic-assurance-policy.js +52 -0
  85. package/dist/lib/long-task-semantic-contract-types.d.ts +18 -1
  86. package/dist/lib/long-task-semantic-drift-migration.js +33 -2
  87. package/dist/lib/long-task-semantic-mutation.d.ts +7 -0
  88. package/dist/lib/long-task-semantic-mutation.js +81 -0
  89. package/dist/lib/long-task-shape-primitives.d.ts +1 -1
  90. package/dist/lib/long-task-shape-primitives.js +1 -0
  91. package/dist/lib/long-task-source-authority-types.d.ts +2 -0
  92. package/dist/lib/long-task-source-item-parser.d.ts +1 -1
  93. package/dist/lib/long-task-source-item-parser.js +150 -104
  94. package/dist/lib/long-task-source-markers.d.ts +12 -0
  95. package/dist/lib/long-task-source-markers.js +146 -0
  96. package/dist/lib/long-task-source-target-continuity.d.ts +1 -1
  97. package/dist/lib/long-task-source-target-continuity.js +80 -55
  98. package/dist/lib/long-task-source-target-index.js +7 -29
  99. package/dist/lib/long-task-status-v2.d.ts +1 -0
  100. package/dist/lib/long-task-status-v2.js +2 -2
  101. package/dist/lib/long-task-target-policy.js +20 -0
  102. package/dist/lib/long-task-ui-design-policy.js +34 -1
  103. package/dist/lib/long-task-ui-surface-shape.js +33 -2
  104. package/dist/lib/long-task-ui-surface-types.d.ts +8 -0
  105. package/dist/lib/long-task-verification-preview.d.ts +10 -0
  106. package/dist/lib/long-task-verifier-dependency-closure.d.ts +3 -0
  107. package/dist/lib/long-task-verifier-dependency-closure.js +179 -0
  108. package/dist/lib/long-task-verifier-v2.js +12 -2
  109. package/dist/lib/long-task-workspace.js +5 -1
  110. package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +101 -25
  111. package/package.json +2 -2
@@ -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 = {};
@@ -1,4 +1,5 @@
1
1
  export * from "./long-task-acceptance-shape.js";
2
+ export * from "./long-task-applicability-shape.js";
2
3
  export * from "./long-task-product-shape.js";
3
4
  export * from "./long-task-shape-primitives.js";
4
5
  export * from "./long-task-source-shape.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./long-task-acceptance-shape.js";
2
+ export * from "./long-task-applicability-shape.js";
2
3
  export * from "./long-task-product-shape.js";
3
4
  export * from "./long-task-shape-primitives.js";
4
5
  export * from "./long-task-source-shape.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./long-task-contract-types.js";
2
+ export * from "./long-task-control-types.js";
2
3
  export * from "./long-task-counterfactual-types.js";
3
4
  export * from "./long-task-authority-types.js";
4
5
  export * from "./long-task-evidence-adapter-types.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./long-task-contract-types.js";
2
+ export * from "./long-task-control-types.js";
2
3
  export * from "./long-task-counterfactual-types.js";
3
4
  export * from "./long-task-authority-types.js";
4
5
  export * from "./long-task-evidence-adapter-types.js";
@@ -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,21 +63,20 @@ 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)
69
69
  invalid("target_covered_claims_required", target.key);
70
+ const facts = indexed.preflight.handoff.facts.filter((fact) => fact.target_ref === target.key);
71
+ if (!facts.length)
72
+ invalid("target_facts_required", target.key);
70
73
  const claimsBySourceItem = new Map();
71
74
  for (const row of rows)
72
75
  for (const sourceItemRef of row.source_item_refs) {
73
76
  const localClaims = designSourceItemClaims(contract, contractTarget, indexed, sourceItemRef, claims);
74
77
  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
78
  }
79
- validateVerificationMethodBindings(target, check, rows, claimsBySourceItem);
79
+ validateVerificationMethodBindings(target, check, facts, claimsBySourceItem);
80
80
  }
81
81
  function validateBlockerBindings(contract, contractTarget, indexed) {
82
82
  const bound = new Map(contractTarget.binding.acceptance_blockers.map((item) => [item.key, item]));
@@ -94,6 +94,7 @@ function validateBlockerBindings(contract, contractTarget, indexed) {
94
94
  invalid("acceptance_blocker_unbound", `${contractTarget.target.key}:${blocker.key}`);
95
95
  assertSameSet(contractBlocker.source_item_refs, blocker.source_item_refs, "acceptance_blocker_source_items_mismatch", blocker.key);
96
96
  assertSameSet(contractBlocker.verification_methods, blocker.verification_methods, "acceptance_blocker_methods_mismatch", blocker.key);
97
+ assertSameSet(contractBlocker.required_capabilities, blocker.required_capabilities, "acceptance_blocker_capabilities_mismatch", blocker.key);
97
98
  for (const method of blocker.verification_methods)
98
99
  if (!availableMethods.has(method))
99
100
  invalid("acceptance_blocker_method_unbound", `${blocker.key}:${method}`);
@@ -102,6 +103,12 @@ function validateBlockerBindings(contract, contractTarget, indexed) {
102
103
  return local.map((claimRef) => `${contractTarget.outcome_key}.${claimRef}`);
103
104
  });
104
105
  if (contractBlocker.status === "machine_claim") {
106
+ const executionTarget = contract.task.execution_targets.find((item) => item.key === contractTarget.binding.target_ref);
107
+ if (!executionTarget)
108
+ invalid("execution_target_unknown", `${contractTarget.target.key}:${contractTarget.binding.target_ref}`);
109
+ for (const capability of blocker.required_capabilities)
110
+ if (!executionTarget.capabilities.includes(capability))
111
+ invalid("acceptance_blocker_target_capability_missing", `${blocker.key}:${executionTarget.key}:${capability}`);
105
112
  const expectedLocal = fullClaimRefs.map((claimRef) => claimRef.slice(contractTarget.outcome_key.length + 1));
106
113
  for (const claimRef of expectedLocal)
107
114
  if (!contractBlocker.refs.includes(claimRef))
@@ -133,8 +140,8 @@ function designSourceItemClaims(contract, contractTarget, indexed, sourceItemRef
133
140
  return claimRef.slice(prefix.length);
134
141
  });
135
142
  }
136
- function validateVerificationMethodBindings(target, check, rows, claimsBySourceItem) {
137
- const expectedMethods = new Set(rows.flatMap((row) => row.verification_methods));
143
+ function validateVerificationMethodBindings(target, check, facts, claimsBySourceItem) {
144
+ const expectedMethods = new Set(facts.map((fact) => fact.verification_method));
138
145
  const bindings = target.verification_method_bindings;
139
146
  assertSameSet(bindings.map((item) => item.method), [...expectedMethods], "verification_methods_mismatch", target.key);
140
147
  const assertionRefs = bindings.map((item) => item.assertion_ref);
@@ -142,13 +149,14 @@ function validateVerificationMethodBindings(target, check, rows, claimsBySourceI
142
149
  invalid("verification_method_assertion_duplicate", target.key);
143
150
  if (assertionRefs.includes(target.conformance_assertion_ref))
144
151
  invalid("verification_method_assertion_must_be_independent", target.key);
152
+ const boundFactRefs = [];
145
153
  for (const binding of bindings) {
146
154
  const assertion = check.positive_assertions.find((item) => item.key === binding.assertion_ref);
147
155
  if (!assertion)
148
156
  invalid("verification_method_assertion_unknown", `${target.key}:${binding.method}:${binding.assertion_ref}`);
149
- const sourceItems = new Set(rows
150
- .filter((row) => row.verification_methods.includes(binding.method))
151
- .flatMap((row) => row.source_item_refs));
157
+ const sourceItems = new Set(facts
158
+ .filter((fact) => fact.verification_method === binding.method)
159
+ .flatMap((fact) => fact.source_item_refs));
152
160
  for (const sourceItemRef of sourceItems)
153
161
  for (const claimRef of claimsBySourceItem.get(sourceItemRef) ?? [])
154
162
  if (!assertion.claims.includes(claimRef))
@@ -156,14 +164,60 @@ function validateVerificationMethodBindings(target, check, rows, claimsBySourceI
156
164
  for (const capability of requiredCapabilities(binding.method))
157
165
  if (!assertion.evidence_capabilities.includes(capability))
158
166
  invalid("verification_method_capability_required", `${target.key}:${binding.method}:${capability}`);
167
+ assertSameSet(binding.evidence_artifacts.map((item) => item.condition_key), target.condition_keys, "verification_method_evidence_conditions_mismatch", `${target.key}:${binding.method}`);
168
+ for (const artifact of binding.evidence_artifacts) {
169
+ const expectedFactRefs = facts
170
+ .filter((fact) => fact.verification_method === binding.method &&
171
+ fact.condition_ref === artifact.condition_key)
172
+ .map((fact) => fact.key);
173
+ assertSameSet(artifact.fact_refs, expectedFactRefs, "design_method_fact_refs_mismatch", `${target.key}:${binding.method}:${artifact.condition_key}`);
174
+ boundFactRefs.push(...artifact.fact_refs);
175
+ }
159
176
  }
177
+ assertSameSet(boundFactRefs, facts.map((fact) => fact.key), "design_method_fact_refs_mismatch", target.key);
160
178
  }
161
179
  function requiredCapabilities(method) {
162
180
  if (method === "interaction_trace")
163
- return ["interaction_trace", "target_runtime"];
181
+ return ["design_method", "interaction_trace", "target_runtime"];
164
182
  if (method === "component_state")
165
- return ["design_conformance", "interaction_trace", "target_runtime"];
166
- return ["design_conformance", "target_runtime"];
183
+ return [
184
+ "design_method",
185
+ "design_conformance",
186
+ "interaction_trace",
187
+ "target_runtime",
188
+ ];
189
+ return ["design_method", "design_conformance", "target_runtime"];
190
+ }
191
+ function validateTargetCapabilities(contract, contractTarget, indexed) {
192
+ const executionTarget = contract.task.execution_targets.find((item) => item.key === contractTarget.binding.target_ref);
193
+ if (!executionTarget)
194
+ invalid("execution_target_unknown", `${contractTarget.target.key}:${contractTarget.binding.target_ref}`);
195
+ const required = new Set();
196
+ for (const method of contractTarget.target.verification_method_bindings.map((item) => item.method)) {
197
+ if (method === "responsive_reflow")
198
+ required.add("viewport-control");
199
+ if (method === "motion_timeline")
200
+ required.add("motion-observation");
201
+ if (method === "accessibility_semantics")
202
+ required.add("assistive-technology");
203
+ }
204
+ for (const conditionRef of indexed.target.condition_refs) {
205
+ const condition = indexed.preflight.handoff.conditions.find((item) => item.key === conditionRef);
206
+ const input = condition.input_methods[0].toLowerCase();
207
+ if (["mouse", "pointer", "trackpad"].includes(input))
208
+ required.add("pointer-input");
209
+ if (input === "keyboard")
210
+ required.add("keyboard-input");
211
+ if (["touch", "pen", "stylus"].includes(input))
212
+ required.add("touch-input");
213
+ if (["screen-reader", "voice-control", "switch-control"].includes(input))
214
+ required.add("assistive-technology");
215
+ if (condition.motion === "reduced")
216
+ required.add("reduced-motion");
217
+ }
218
+ for (const capability of required)
219
+ if (!executionTarget.capabilities.includes(capability))
220
+ invalid("execution_target_capability_missing", `${contractTarget.target.key}:${executionTarget.key}:${capability}`);
167
221
  }
168
222
  function assertSameSet(actual, expected, code, detail) {
169
223
  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,38 @@ 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
+ "fact_refs",
206
+ ]);
207
+ return {
208
+ condition_key: key(cell.condition_key, `${cellLabel}.condition_key`),
209
+ artifact_path: nonEmpty(cell.artifact_path, `${cellLabel}.artifact_path`),
210
+ observation_artifact_path: nonEmpty(cell.observation_artifact_path, `${cellLabel}.observation_artifact_path`),
211
+ fact_refs: designFactRefs(cell.fact_refs, `${cellLabel}.fact_refs`),
212
+ };
213
+ }),
214
+ };
182
215
  case "input_variation":
183
216
  exact(row, label, [
184
217
  "assertion_key",
@@ -242,9 +275,23 @@ function strings(value, label) {
242
275
  function keys(value, label) {
243
276
  return array(value, label).map((item, index) => key(item, `${label}[${index}]`));
244
277
  }
278
+ function designFactRefs(value, label) {
279
+ return array(value, label).map((item, index) => {
280
+ const result = nonEmpty(item, `${label}[${index}]`);
281
+ if (!/^[a-z0-9][a-z0-9._-]*$/u.test(result))
282
+ throw invalidRecord(`${label}[${index}]`);
283
+ return result;
284
+ });
285
+ }
245
286
  function sha(value, label) {
246
287
  const result = nonEmpty(value, label);
247
288
  if (!/^[a-f0-9]{64}$/u.test(result))
248
289
  throw invalidRecord(label);
249
290
  return result;
250
291
  }
292
+ function literal(value, allowed, label) {
293
+ const result = nonEmpty(value, label);
294
+ if (!allowed.includes(result))
295
+ throw invalidRecord(label);
296
+ return result;
297
+ }
@@ -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
- 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("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,51 @@ 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
+ fact_refs: item.fact_refs,
57
+ },
58
+ ]));
59
+ if (record.cells.length !== expected.size)
60
+ return "design_method_cell_count_mismatch";
61
+ for (const cell of record.cells) {
62
+ const expectedCell = expected.get(cell.condition_key);
63
+ if (expectedCell?.artifact_path !== cell.artifact_path ||
64
+ expectedCell?.observation_artifact_path !== cell.observation_artifact_path)
65
+ return "design_method_artifact_path_mismatch";
66
+ if (!same([...cell.fact_refs].sort(), [...(expectedCell?.fact_refs ?? [])].sort()))
67
+ return "design_method_fact_refs_mismatch";
68
+ if (!artifactHashes[cell.artifact_path])
69
+ return "design_method_artifact_missing";
70
+ if (!artifactHashes[cell.observation_artifact_path])
71
+ return "design_method_observation_artifact_missing";
72
+ }
73
+ return null;
74
+ }
36
75
  function validateDesignConformance(check, record, artifactHashes) {
37
76
  const target = (check.design_conformance_targets ?? []).find((item) => item.key === record.design_target_ref &&
38
77
  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,18 @@ 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
+ fact_refs: string[];
84
+ }>;
85
+ }
73
86
  export interface InputVariationEvidenceV2 extends EvidenceRecordBaseV2 {
74
87
  capability: "input_variation";
75
88
  cases: Array<{
@@ -78,5 +91,5 @@ export interface InputVariationEvidenceV2 extends EvidenceRecordBaseV2 {
78
91
  }>;
79
92
  failure_case_observed: boolean;
80
93
  }
81
- export type EvidenceCapabilityRecordV2 = InteractionTraceEvidenceV2 | StateDeltaEvidenceV2 | CrossSurfaceConsistencyEvidenceV2 | DurableReadbackEvidenceV2 | BoundaryInvocationEvidenceV2 | ExternalSideEffectEvidenceV2 | FailureInjectionEvidenceV2 | VisualRenderEvidenceV2 | DesignConformanceEvidenceV2 | TargetRuntimeEvidenceV2 | InputVariationEvidenceV2;
94
+ export type EvidenceCapabilityRecordV2 = InteractionTraceEvidenceV2 | StateDeltaEvidenceV2 | CrossSurfaceConsistencyEvidenceV2 | DurableReadbackEvidenceV2 | BoundaryInvocationEvidenceV2 | ExternalSideEffectEvidenceV2 | FailureInjectionEvidenceV2 | VisualRenderEvidenceV2 | DesignConformanceEvidenceV2 | DesignMethodEvidenceV2 | TargetRuntimeEvidenceV2 | InputVariationEvidenceV2;
82
95
  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,