project-tiny-context-harness 0.8.8 → 0.8.10
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 +149 -123
- package/assets/README.md +279 -251
- package/assets/README.zh-CN.md +172 -144
- package/assets/skills/design-resource-authoring/SKILL.md +12 -12
- package/assets/skills/design-resource-authoring/references/downstream-handoff.md +57 -48
- package/dist/commands/design-resource.js +62 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/design-resource-fact-manifest-validation.d.ts +3 -2
- package/dist/lib/design-resource-fact-manifest-validation.js +23 -18
- package/dist/lib/design-resource-fact-universe-catalog.js +18 -8
- package/dist/lib/design-resource-fact-universe-helpers.js +9 -5
- package/dist/lib/design-resource-handoff-bundle.d.ts +31 -0
- package/dist/lib/design-resource-handoff-bundle.js +171 -0
- package/dist/lib/design-resource-handoff-file-validation.d.ts +1 -1
- package/dist/lib/design-resource-handoff-file-validation.js +1 -9
- package/dist/lib/design-resource-handoff-manifest-projection.d.ts +3 -0
- package/dist/lib/design-resource-handoff-manifest-projection.js +37 -0
- package/dist/lib/design-resource-handoff-parser.d.ts +7 -2
- package/dist/lib/design-resource-handoff-parser.js +32 -12
- package/dist/lib/design-resource-handoff-set-integrity.d.ts +8 -0
- package/dist/lib/design-resource-handoff-set-integrity.js +93 -0
- package/dist/lib/design-resource-handoff-shape.d.ts +2 -1
- package/dist/lib/design-resource-handoff-shape.js +62 -0
- package/dist/lib/design-resource-handoff-snapshot.d.ts +6 -0
- package/dist/lib/design-resource-handoff-snapshot.js +21 -0
- package/dist/lib/design-resource-handoff-types.d.ts +23 -1
- package/dist/lib/design-resource-handoff-validation.d.ts +2 -1
- package/dist/lib/design-resource-handoff-validation.js +45 -22
- package/dist/lib/long-task-design-resource-handoff.d.ts +15 -0
- package/dist/lib/long-task-design-resource-handoff.js +63 -173
- package/dist/lib/long-task-design-resource-method-binding.d.ts +8 -0
- package/dist/lib/long-task-design-resource-method-binding.js +135 -0
- package/dist/lib/long-task-source-item-parser.d.ts +8 -0
- package/dist/lib/long-task-source-item-parser.js +29 -107
- package/dist/lib/long-task-source-owned-sections.d.ts +29 -0
- package/dist/lib/long-task-source-owned-sections.js +90 -0
- package/dist/lib/source-line-scanner.d.ts +3 -0
- package/dist/lib/source-line-scanner.js +42 -0
- package/package.json +84 -84
|
@@ -2,26 +2,59 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parseDesignResourceHandoffMarkdown } from "./design-resource-handoff-parser.js";
|
|
4
4
|
import { validateDesignResourceFiles } from "./design-resource-handoff-file-validation.js";
|
|
5
|
-
import {
|
|
5
|
+
import { parseDesignResourceFactManifests, validateDesignResourceFactManifests, } from "./design-resource-fact-manifest-validation.js";
|
|
6
|
+
import { hydrateManifestBackedDesignResourceHandoff } from "./design-resource-handoff-manifest-projection.js";
|
|
7
|
+
import { readDesignResourceSnapshot } from "./design-resource-handoff-snapshot.js";
|
|
6
8
|
import { validateDesignResourceFacts } from "./design-resource-handoff-validation-facts.js";
|
|
7
9
|
import { validateDesignResourceBlockers, validateDesignResourceCoverage, validateDesignResourceReachability, } from "./design-resource-handoff-validation-coverage.js";
|
|
8
10
|
import { indexDesignResourceItems, invalidDesignResourceHandoff, requireNonemptyDesignResourceValues, requireUniqueDesignResourceObjects, requireUniqueDesignResourceValues, } from "./design-resource-handoff-validation-primitives.js";
|
|
9
11
|
import { validateDesignResourceConditions, validateDesignResourceEvidence, validateDesignResourceScope, validateDesignResourceSubjects, validateDesignResourceTargets, } from "./design-resource-handoff-validation-structure.js";
|
|
10
12
|
import { assertProtectedRepositoryFile } from "./long-task-protected-files.js";
|
|
11
|
-
import { sha256Hex } from "./strict-codec.js";
|
|
12
13
|
export async function preflightDesignResourceHandoff(repository, handoffPath) {
|
|
13
14
|
const handoffFile = await assertProtectedRepositoryFile(repository, path.resolve(repository, ...handoffPath.split("/")), "design_resource_handoff");
|
|
14
15
|
const parsed = parseDesignResourceHandoffMarkdown(handoffPath, await readFile(handoffFile, "utf8"));
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
16
|
+
return preflightParsedDesignResourceHandoff(repository, parsed);
|
|
17
|
+
}
|
|
18
|
+
export async function preflightParsedDesignResourceHandoff(repository, parsed) {
|
|
19
|
+
const inputHandoff = parsed.handoff;
|
|
20
|
+
if (isManifestBacked(inputHandoff) && inputHandoff.targets.length !== 1)
|
|
21
|
+
invalidDesignResourceHandoff("manifest_backed_one_target_required", String(inputHandoff.targets.length));
|
|
22
|
+
if (!isManifestBacked(inputHandoff))
|
|
23
|
+
validateDesignResourceHandoffSemantics({
|
|
24
|
+
...parsed,
|
|
25
|
+
handoff: inputHandoff,
|
|
26
|
+
});
|
|
27
|
+
const snapshot = await readDesignResourceSnapshot(repository, parsed);
|
|
28
|
+
const manifests = parseDesignResourceFactManifests(parsed, snapshot.contents);
|
|
29
|
+
const handoff = isManifestBacked(inputHandoff)
|
|
30
|
+
? hydrateManifestBackedDesignResourceHandoff(inputHandoff, manifests)
|
|
31
|
+
: inputHandoff;
|
|
32
|
+
const normalized = { ...parsed, handoff };
|
|
33
|
+
if (isManifestBacked(inputHandoff))
|
|
34
|
+
validateDesignResourceHandoffSemantics(normalized);
|
|
35
|
+
validateDesignResourceFiles(normalized, snapshot.contents);
|
|
36
|
+
validateDesignResourceFactManifests(normalized, snapshot.contents, manifests, !isManifestBacked(inputHandoff));
|
|
37
|
+
const resources = new Map(handoff.resources.map((resource) => [resource.key, resource]));
|
|
20
38
|
return {
|
|
21
39
|
schema_version: "design-resource-handoff-preflight-v1",
|
|
22
40
|
status: "ready",
|
|
23
|
-
...
|
|
24
|
-
resource_hashes:
|
|
41
|
+
...normalized,
|
|
42
|
+
resource_hashes: snapshot.hashes,
|
|
43
|
+
manifest_identities: handoff.targets.map((target) => {
|
|
44
|
+
const resourceRef = target.source_profile.fact_manifest_resource_ref;
|
|
45
|
+
const resource = resources.get(resourceRef);
|
|
46
|
+
const manifest = manifests.get(target.key);
|
|
47
|
+
return {
|
|
48
|
+
resource_ref: resourceRef,
|
|
49
|
+
path: resource.path,
|
|
50
|
+
sha256: snapshot.hashes[resourceRef],
|
|
51
|
+
scope_key: manifest.scope_key,
|
|
52
|
+
target_key: manifest.target_key,
|
|
53
|
+
collections: manifest.generation.collections.map((collection) => ({
|
|
54
|
+
...collection,
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
}),
|
|
25
58
|
counts: {
|
|
26
59
|
resources: handoff.resources.length,
|
|
27
60
|
manifests: manifests.size,
|
|
@@ -45,6 +78,9 @@ export async function preflightDesignResourceHandoff(repository, handoffPath) {
|
|
|
45
78
|
},
|
|
46
79
|
};
|
|
47
80
|
}
|
|
81
|
+
function isManifestBacked(handoff) {
|
|
82
|
+
return "representation" in handoff;
|
|
83
|
+
}
|
|
48
84
|
export function validateDesignResourceHandoffSemantics(parsed) {
|
|
49
85
|
const { handoff } = parsed;
|
|
50
86
|
requireNonemptyDesignResourceValues(handoff.scope.surface_keys, "scope_surface_keys_required");
|
|
@@ -104,16 +140,3 @@ export function validateDesignResourceHandoffSemantics(parsed) {
|
|
|
104
140
|
validateDesignResourceBlockers(handoff, subjects, targets, sourceItems);
|
|
105
141
|
validateDesignResourceReachability(handoff);
|
|
106
142
|
}
|
|
107
|
-
async function validateResourceIntegrity(repository, parsed) {
|
|
108
|
-
const hashes = {};
|
|
109
|
-
for (const resource of parsed.handoff.resources) {
|
|
110
|
-
if (resource.path === parsed.handoff_path)
|
|
111
|
-
invalidDesignResourceHandoff("resource_must_not_be_handoff", resource.key);
|
|
112
|
-
const file = await assertProtectedRepositoryFile(repository, path.resolve(repository, ...resource.path.split("/")), `design_resource:${resource.key}`);
|
|
113
|
-
const digest = sha256Hex(await readFile(file));
|
|
114
|
-
if (digest !== resource.sha256)
|
|
115
|
-
invalidDesignResourceHandoff("resource_digest_mismatch", `${resource.key}:${resource.sha256}:${digest}`);
|
|
116
|
-
hashes[resource.key] = digest;
|
|
117
|
-
}
|
|
118
|
-
return hashes;
|
|
119
|
-
}
|
|
@@ -1,2 +1,17 @@
|
|
|
1
|
+
import type { DesignResourceHandoffPreflightV1, DesignResourceHandoffTargetV1 } from "./design-resource-handoff-types.js";
|
|
1
2
|
import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
3
|
+
export interface ContractDesignTarget {
|
|
4
|
+
outcome_key: string;
|
|
5
|
+
binding: DeliveryContractV2["outcomes"][number]["product"]["surface_bindings"][number];
|
|
6
|
+
target: DeliveryContractV2["outcomes"][number]["product"]["surface_bindings"][number]["design_targets"][number];
|
|
7
|
+
}
|
|
8
|
+
export interface IndexedHandoffTarget {
|
|
9
|
+
preflight: DesignResourceHandoffPreflightV1;
|
|
10
|
+
target: DesignResourceHandoffTargetV1;
|
|
11
|
+
}
|
|
12
|
+
export interface LongTaskDesignHandoffConsumer {
|
|
13
|
+
consume(preflight: DesignResourceHandoffPreflightV1): void;
|
|
14
|
+
finish(): void;
|
|
15
|
+
}
|
|
2
16
|
export declare function validateLongTaskDesignResourceHandoffs(contract: DeliveryContractV2, repository: string): Promise<void>;
|
|
17
|
+
export declare function createLongTaskDesignHandoffConsumer(contract: DeliveryContractV2): LongTaskDesignHandoffConsumer;
|
|
@@ -1,63 +1,78 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { containsDesignResourceHandoff } from "./design-resource-handoff-parser.js";
|
|
4
|
-
import {
|
|
3
|
+
import { containsDesignResourceHandoff, parseDesignResourceHandoffMarkdown, } from "./design-resource-handoff-parser.js";
|
|
4
|
+
import { createDesignResourceHandoffSetIntegrity } from "./design-resource-handoff-set-integrity.js";
|
|
5
|
+
import { preflightParsedDesignResourceHandoff } from "./design-resource-handoff-validation.js";
|
|
6
|
+
import { assertSameSet, designSourceItemClaims, invalid, validateTargetIdentity, validateVerificationMethodBindings, } from "./long-task-design-resource-method-binding.js";
|
|
5
7
|
import { validateLongTaskDesignTargetCapabilities } from "./long-task-design-target-capabilities.js";
|
|
6
8
|
import { assertProtectedRepositoryFile } from "./long-task-protected-files.js";
|
|
7
9
|
export async function validateLongTaskDesignResourceHandoffs(contract, repository) {
|
|
8
|
-
const
|
|
9
|
-
const indexed = indexHandoffTargets(handoffs);
|
|
10
|
-
const contractTargets = contract.outcomes.flatMap((outcome) => outcome.product.surface_bindings.flatMap((binding) => binding.design_targets.map((target) => ({
|
|
11
|
-
outcome_key: outcome.key,
|
|
12
|
-
binding,
|
|
13
|
-
target,
|
|
14
|
-
}))));
|
|
15
|
-
const consumed = new Set();
|
|
16
|
-
for (const contractTarget of contractTargets) {
|
|
17
|
-
const indexedTarget = indexed.get(contractTarget.target.key);
|
|
18
|
-
if (!indexedTarget)
|
|
19
|
-
invalid("target_handoff_missing", contractTarget.target.key);
|
|
20
|
-
consumed.add(contractTarget.target.key);
|
|
21
|
-
validateTargetIdentity(contractTarget, indexedTarget);
|
|
22
|
-
validateLongTaskDesignTargetCapabilities(contract, contractTarget, indexedTarget);
|
|
23
|
-
validateCoverageClaims(contract, contractTarget, indexedTarget);
|
|
24
|
-
validateBlockerBindings(contract, contractTarget, indexedTarget);
|
|
25
|
-
}
|
|
26
|
-
for (const key of indexed.keys())
|
|
27
|
-
if (!consumed.has(key))
|
|
28
|
-
invalid("handoff_target_unbound", key);
|
|
29
|
-
}
|
|
30
|
-
async function loadHandoffs(contract, repository) {
|
|
31
|
-
const results = [];
|
|
10
|
+
const consumer = createLongTaskDesignHandoffConsumer(contract);
|
|
32
11
|
for (const sourcePath of contract.task.source_paths) {
|
|
33
12
|
if (!sourcePath.toLowerCase().endsWith(".md"))
|
|
34
13
|
continue;
|
|
35
14
|
const file = await assertProtectedRepositoryFile(repository, path.resolve(repository, ...sourcePath.split("/")), `design_resource_handoff_source:${sourcePath}`);
|
|
36
15
|
const content = await readFile(file, "utf8");
|
|
37
|
-
if (containsDesignResourceHandoff(content))
|
|
38
|
-
|
|
16
|
+
if (!containsDesignResourceHandoff(content))
|
|
17
|
+
continue;
|
|
18
|
+
consumer.consume(await preflightParsedDesignResourceHandoff(repository, parseDesignResourceHandoffMarkdown(sourcePath, content)));
|
|
39
19
|
}
|
|
40
|
-
|
|
20
|
+
consumer.finish();
|
|
41
21
|
}
|
|
42
|
-
function
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
22
|
+
export function createLongTaskDesignHandoffConsumer(contract) {
|
|
23
|
+
const contractTargets = contract.outcomes.flatMap((outcome) => outcome.product.surface_bindings.flatMap((binding) => binding.design_targets.map((target) => ({
|
|
24
|
+
outcome_key: outcome.key,
|
|
25
|
+
binding,
|
|
26
|
+
target,
|
|
27
|
+
}))));
|
|
28
|
+
const contractTargetsByKey = new Map(contractTargets.map((item) => [item.target.key, item]));
|
|
29
|
+
const indexedTargetKeys = new Set();
|
|
30
|
+
const consumed = new Set();
|
|
31
|
+
const validationErrors = new Map();
|
|
32
|
+
let duplicate = null;
|
|
33
|
+
const unbound = [];
|
|
34
|
+
const handoffSetIntegrity = createDesignResourceHandoffSetIntegrity(invalid);
|
|
35
|
+
return {
|
|
36
|
+
consume(preflight) {
|
|
37
|
+
handoffSetIntegrity.consume(preflight);
|
|
38
|
+
for (const target of preflight.handoff.targets) {
|
|
39
|
+
if (indexedTargetKeys.has(target.key)) {
|
|
40
|
+
duplicate ??= target.key;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
indexedTargetKeys.add(target.key);
|
|
44
|
+
const contractTarget = contractTargetsByKey.get(target.key);
|
|
45
|
+
if (!contractTarget) {
|
|
46
|
+
unbound.push(target.key);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const indexedTarget = { preflight, target };
|
|
50
|
+
consumed.add(target.key);
|
|
51
|
+
try {
|
|
52
|
+
validateTargetIdentity(contractTarget, indexedTarget);
|
|
53
|
+
validateLongTaskDesignTargetCapabilities(contract, contractTarget, indexedTarget);
|
|
54
|
+
validateCoverageClaims(contract, contractTarget, indexedTarget);
|
|
55
|
+
validateBlockerBindings(contract, contractTarget, indexedTarget);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
validationErrors.set(target.key, error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
finish() {
|
|
63
|
+
if (duplicate)
|
|
64
|
+
invalid("handoff_target_duplicate", duplicate);
|
|
65
|
+
handoffSetIntegrity.finish();
|
|
66
|
+
for (const contractTarget of contractTargets) {
|
|
67
|
+
if (!consumed.has(contractTarget.target.key))
|
|
68
|
+
invalid("target_handoff_missing", contractTarget.target.key);
|
|
69
|
+
if (validationErrors.has(contractTarget.target.key))
|
|
70
|
+
throw validationErrors.get(contractTarget.target.key);
|
|
71
|
+
}
|
|
72
|
+
if (unbound.length)
|
|
73
|
+
invalid("handoff_target_unbound", unbound[0]);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
61
76
|
}
|
|
62
77
|
function validateCoverageClaims(contract, contractTarget, indexed) {
|
|
63
78
|
const target = contractTarget.target;
|
|
@@ -128,128 +143,3 @@ function validateBlockerBindings(contract, contractTarget, indexed) {
|
|
|
128
143
|
}
|
|
129
144
|
}
|
|
130
145
|
}
|
|
131
|
-
function designSourceItemClaims(contract, contractTarget, indexed, sourceItemRef, claims = new Map(contract.source_claims.map((item) => [item.key, item]))) {
|
|
132
|
-
const claim = claims.get(sourceItemRef);
|
|
133
|
-
if (!claim)
|
|
134
|
-
invalid("coverage_source_claim_unknown", `${contractTarget.target.key}:${sourceItemRef}`);
|
|
135
|
-
if (claim.source_ref.split("#")[0] !== indexed.preflight.handoff_path)
|
|
136
|
-
invalid("coverage_source_claim_file_mismatch", `${contractTarget.target.key}:${sourceItemRef}`);
|
|
137
|
-
if (claim.disposition.type !== "claim")
|
|
138
|
-
invalid("coverage_source_claim_disposition_required", `${contractTarget.target.key}:${sourceItemRef}:${claim.disposition.type}`);
|
|
139
|
-
const prefix = `${contractTarget.outcome_key}.`;
|
|
140
|
-
return claim.disposition.refs.map((claimRef) => {
|
|
141
|
-
if (!claimRef.startsWith(prefix))
|
|
142
|
-
invalid("coverage_claim_outcome_mismatch", `${contractTarget.target.key}:${sourceItemRef}:${claimRef}`);
|
|
143
|
-
return claimRef.slice(prefix.length);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
function validateVerificationMethodBindings(target, check, facts, proofs, indexed, claimsBySourceItem) {
|
|
147
|
-
const expectedMethods = new Set(proofs.map((proof) => proof.method));
|
|
148
|
-
const bindings = target.verification_method_bindings;
|
|
149
|
-
assertSameSet(bindings.map((item) => item.method), [...expectedMethods], "verification_methods_mismatch", target.key);
|
|
150
|
-
const assertionRefs = bindings.map((item) => item.assertion_ref);
|
|
151
|
-
if (new Set(assertionRefs).size !== assertionRefs.length)
|
|
152
|
-
invalid("verification_method_assertion_duplicate", target.key);
|
|
153
|
-
if (assertionRefs.includes(target.conformance_assertion_ref))
|
|
154
|
-
invalid("verification_method_assertion_must_be_independent", target.key);
|
|
155
|
-
const boundFactRefs = [];
|
|
156
|
-
for (const binding of bindings) {
|
|
157
|
-
const assertion = check.positive_assertions.find((item) => item.key === binding.assertion_ref);
|
|
158
|
-
if (!assertion)
|
|
159
|
-
invalid("verification_method_assertion_unknown", `${target.key}:${binding.method}:${binding.assertion_ref}`);
|
|
160
|
-
const sourceItems = new Set(facts
|
|
161
|
-
.filter((fact) => proofs.some((proof) => proof.fact_ref === fact.key && proof.method === binding.method))
|
|
162
|
-
.flatMap((fact) => fact.source_item_refs));
|
|
163
|
-
for (const sourceItemRef of sourceItems)
|
|
164
|
-
for (const claimRef of claimsBySourceItem.get(sourceItemRef) ?? [])
|
|
165
|
-
if (!assertion.claims.includes(claimRef))
|
|
166
|
-
invalid("verification_method_claim_not_asserted", `${target.key}:${binding.method}:${sourceItemRef}:${claimRef}`);
|
|
167
|
-
for (const capability of requiredCapabilities(binding.method))
|
|
168
|
-
if (!assertion.evidence_capabilities.includes(capability))
|
|
169
|
-
invalid("verification_method_capability_required", `${target.key}:${binding.method}:${capability}`);
|
|
170
|
-
assertSameSet(binding.evidence_artifacts.map((item) => item.condition_key), target.condition_keys, "verification_method_evidence_conditions_mismatch", `${target.key}:${binding.method}`);
|
|
171
|
-
for (const artifact of binding.evidence_artifacts) {
|
|
172
|
-
const expectedFactRefs = facts
|
|
173
|
-
.filter((fact) => proofs.some((proof) => proof.fact_ref === fact.key && proof.method === binding.method) && fact.condition_ref === artifact.condition_key)
|
|
174
|
-
.map((fact) => fact.key);
|
|
175
|
-
assertSameSet(artifact.fact_refs, expectedFactRefs, "design_method_fact_refs_mismatch", `${target.key}:${binding.method}:${artifact.condition_key}`);
|
|
176
|
-
const expectedFactExpectations = proofs
|
|
177
|
-
.filter((proof) => proof.method === binding.method &&
|
|
178
|
-
facts.some((fact) => fact.key === proof.fact_ref &&
|
|
179
|
-
fact.condition_ref === artifact.condition_key))
|
|
180
|
-
.map((proof) => designFactExpectation(indexed, proof.fact_ref, proof.key));
|
|
181
|
-
assertSameSet(artifact.fact_expectations.map((item) => item.fact_ref), expectedFactRefs, "design_method_fact_expectation_refs_mismatch", `${target.key}:${binding.method}:${artifact.condition_key}`);
|
|
182
|
-
assertCanonicalRows(artifact.fact_expectations, expectedFactExpectations, "design_method_fact_expectations_mismatch", `${target.key}:${binding.method}:${artifact.condition_key}`);
|
|
183
|
-
boundFactRefs.push(...artifact.fact_refs);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
assertSameSet(boundFactRefs, facts.map((fact) => fact.key), "design_method_fact_refs_mismatch", target.key);
|
|
187
|
-
}
|
|
188
|
-
function designFactExpectation(indexed, factRef, proofRef) {
|
|
189
|
-
const handoff = indexed.preflight.handoff;
|
|
190
|
-
const fact = handoff.facts.find((item) => item.key === factRef);
|
|
191
|
-
const proof = handoff.proof_obligations.find((item) => item.key === proofRef);
|
|
192
|
-
const oracle = handoff.oracles.find((item) => item.key === proof.oracle_ref);
|
|
193
|
-
const environment = handoff.environments.find((item) => item.key === proof.environment_ref);
|
|
194
|
-
return {
|
|
195
|
-
fact_ref: fact.key,
|
|
196
|
-
subject_ref: fact.subject_ref,
|
|
197
|
-
variation_ref: fact.variation_ref,
|
|
198
|
-
property_ref: fact.property_ref,
|
|
199
|
-
observation_sensitivity: fact.observation_sensitivity,
|
|
200
|
-
expected: fact.value,
|
|
201
|
-
comparison: proof.comparison,
|
|
202
|
-
oracle: {
|
|
203
|
-
key: oracle.key,
|
|
204
|
-
trust: oracle.trust,
|
|
205
|
-
identity: oracle.identity,
|
|
206
|
-
version: oracle.version,
|
|
207
|
-
sha256: oracle.sha256,
|
|
208
|
-
},
|
|
209
|
-
environment: {
|
|
210
|
-
key: environment.key,
|
|
211
|
-
identity: environment.identity,
|
|
212
|
-
definition: environment.definition,
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
function assertCanonicalRows(actual, expected, code, detail) {
|
|
217
|
-
const left = new Map(actual.map((item) => [item.fact_ref, canonicalJson(item)]));
|
|
218
|
-
const right = new Map(expected.map((item) => [item.fact_ref, canonicalJson(item)]));
|
|
219
|
-
assertSameSet([...left.keys()], [...right.keys()], code, detail);
|
|
220
|
-
for (const [key, value] of right)
|
|
221
|
-
if (left.get(key) !== value)
|
|
222
|
-
invalid(code, `${detail}:${key}`);
|
|
223
|
-
}
|
|
224
|
-
function canonicalJson(value) {
|
|
225
|
-
if (Array.isArray(value))
|
|
226
|
-
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
227
|
-
if (value && typeof value === "object")
|
|
228
|
-
return `{${Object.entries(value)
|
|
229
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
230
|
-
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
231
|
-
.join(",")}}`;
|
|
232
|
-
return JSON.stringify(value);
|
|
233
|
-
}
|
|
234
|
-
function requiredCapabilities(method) {
|
|
235
|
-
if (method === "interaction_trace")
|
|
236
|
-
return ["design_method", "interaction_trace", "target_runtime"];
|
|
237
|
-
if (method === "component_state")
|
|
238
|
-
return [
|
|
239
|
-
"design_method",
|
|
240
|
-
"design_conformance",
|
|
241
|
-
"interaction_trace",
|
|
242
|
-
"target_runtime",
|
|
243
|
-
];
|
|
244
|
-
return ["design_method", "design_conformance", "target_runtime"];
|
|
245
|
-
}
|
|
246
|
-
function assertSameSet(actual, expected, code, detail) {
|
|
247
|
-
const left = [...new Set(actual)].sort();
|
|
248
|
-
const right = [...new Set(expected)].sort();
|
|
249
|
-
if (left.length !== right.length ||
|
|
250
|
-
left.some((item, index) => item !== right[index]))
|
|
251
|
-
invalid(code, `${detail}:${left.join(",")}:${right.join(",")}`);
|
|
252
|
-
}
|
|
253
|
-
function invalid(code, detail) {
|
|
254
|
-
throw new Error(`delivery_contract_invalid:design_resource_${code}:${detail}`);
|
|
255
|
-
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DesignResourceHandoffPreflightV1 } from "./design-resource-handoff-types.js";
|
|
2
|
+
import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
3
|
+
import type { ContractDesignTarget, IndexedHandoffTarget } from "./long-task-design-resource-handoff.js";
|
|
4
|
+
export declare function validateTargetIdentity(contractTarget: ContractDesignTarget, indexed: IndexedHandoffTarget): void;
|
|
5
|
+
export declare function designSourceItemClaims(contract: DeliveryContractV2, contractTarget: ContractDesignTarget, indexed: IndexedHandoffTarget, sourceItemRef: string, claims?: Map<string, import("./long-task-contract-types.js").SourceClaimV2>): string[];
|
|
6
|
+
export declare function validateVerificationMethodBindings(target: ContractDesignTarget["target"], check: DeliveryContractV2["outcomes"][number]["acceptance"]["checks"][number], facts: DesignResourceHandoffPreflightV1["handoff"]["facts"], proofs: DesignResourceHandoffPreflightV1["handoff"]["proof_obligations"], indexed: IndexedHandoffTarget, claimsBySourceItem: Map<string, string[]>): void;
|
|
7
|
+
export declare function assertSameSet(actual: string[], expected: string[], code: string, detail: string): void;
|
|
8
|
+
export declare function invalid(code: string, detail: string): never;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export function validateTargetIdentity(contractTarget, indexed) {
|
|
2
|
+
const { target } = contractTarget;
|
|
3
|
+
const handoffTarget = indexed.target;
|
|
4
|
+
if (target.interpretation !== handoffTarget.interpretation)
|
|
5
|
+
invalid("target_interpretation_mismatch", `${target.key}:${target.interpretation}:${handoffTarget.interpretation}`);
|
|
6
|
+
assertSameSet(target.condition_keys, handoffTarget.condition_refs, "target_conditions_mismatch", target.key);
|
|
7
|
+
const resourcePaths = handoffTarget.resource_refs.map((ref) => indexed.preflight.handoff.resources.find((item) => item.key === ref)
|
|
8
|
+
.path);
|
|
9
|
+
assertSameSet(target.source_paths, [indexed.preflight.handoff_path, ...resourcePaths], "target_source_paths_mismatch", target.key);
|
|
10
|
+
}
|
|
11
|
+
export function designSourceItemClaims(contract, contractTarget, indexed, sourceItemRef, claims = new Map(contract.source_claims.map((item) => [item.key, item]))) {
|
|
12
|
+
const claim = claims.get(sourceItemRef);
|
|
13
|
+
if (!claim)
|
|
14
|
+
invalid("coverage_source_claim_unknown", `${contractTarget.target.key}:${sourceItemRef}`);
|
|
15
|
+
if (claim.source_ref.split("#")[0] !== indexed.preflight.handoff_path)
|
|
16
|
+
invalid("coverage_source_claim_file_mismatch", `${contractTarget.target.key}:${sourceItemRef}`);
|
|
17
|
+
if (claim.disposition.type !== "claim")
|
|
18
|
+
invalid("coverage_source_claim_disposition_required", `${contractTarget.target.key}:${sourceItemRef}:${claim.disposition.type}`);
|
|
19
|
+
const prefix = `${contractTarget.outcome_key}.`;
|
|
20
|
+
return claim.disposition.refs.map((claimRef) => {
|
|
21
|
+
if (!claimRef.startsWith(prefix))
|
|
22
|
+
invalid("coverage_claim_outcome_mismatch", `${contractTarget.target.key}:${sourceItemRef}:${claimRef}`);
|
|
23
|
+
return claimRef.slice(prefix.length);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function validateVerificationMethodBindings(target, check, facts, proofs, indexed, claimsBySourceItem) {
|
|
27
|
+
const expectedMethods = new Set(proofs.map((proof) => proof.method));
|
|
28
|
+
const bindings = target.verification_method_bindings;
|
|
29
|
+
assertSameSet(bindings.map((item) => item.method), [...expectedMethods], "verification_methods_mismatch", target.key);
|
|
30
|
+
const assertionRefs = bindings.map((item) => item.assertion_ref);
|
|
31
|
+
if (new Set(assertionRefs).size !== assertionRefs.length)
|
|
32
|
+
invalid("verification_method_assertion_duplicate", target.key);
|
|
33
|
+
if (assertionRefs.includes(target.conformance_assertion_ref))
|
|
34
|
+
invalid("verification_method_assertion_must_be_independent", target.key);
|
|
35
|
+
const boundFactRefs = [];
|
|
36
|
+
for (const binding of bindings) {
|
|
37
|
+
const assertion = check.positive_assertions.find((item) => item.key === binding.assertion_ref);
|
|
38
|
+
if (!assertion)
|
|
39
|
+
invalid("verification_method_assertion_unknown", `${target.key}:${binding.method}:${binding.assertion_ref}`);
|
|
40
|
+
const sourceItems = new Set(facts
|
|
41
|
+
.filter((fact) => proofs.some((proof) => proof.fact_ref === fact.key && proof.method === binding.method))
|
|
42
|
+
.flatMap((fact) => fact.source_item_refs));
|
|
43
|
+
for (const sourceItemRef of sourceItems)
|
|
44
|
+
for (const claimRef of claimsBySourceItem.get(sourceItemRef) ?? [])
|
|
45
|
+
if (!assertion.claims.includes(claimRef))
|
|
46
|
+
invalid("verification_method_claim_not_asserted", `${target.key}:${binding.method}:${sourceItemRef}:${claimRef}`);
|
|
47
|
+
for (const capability of requiredCapabilities(binding.method))
|
|
48
|
+
if (!assertion.evidence_capabilities.includes(capability))
|
|
49
|
+
invalid("verification_method_capability_required", `${target.key}:${binding.method}:${capability}`);
|
|
50
|
+
assertSameSet(binding.evidence_artifacts.map((item) => item.condition_key), target.condition_keys, "verification_method_evidence_conditions_mismatch", `${target.key}:${binding.method}`);
|
|
51
|
+
for (const artifact of binding.evidence_artifacts) {
|
|
52
|
+
const expectedFactRefs = facts
|
|
53
|
+
.filter((fact) => proofs.some((proof) => proof.fact_ref === fact.key && proof.method === binding.method) && fact.condition_ref === artifact.condition_key)
|
|
54
|
+
.map((fact) => fact.key);
|
|
55
|
+
assertSameSet(artifact.fact_refs, expectedFactRefs, "design_method_fact_refs_mismatch", `${target.key}:${binding.method}:${artifact.condition_key}`);
|
|
56
|
+
const expectedFactExpectations = proofs
|
|
57
|
+
.filter((proof) => proof.method === binding.method &&
|
|
58
|
+
facts.some((fact) => fact.key === proof.fact_ref &&
|
|
59
|
+
fact.condition_ref === artifact.condition_key))
|
|
60
|
+
.map((proof) => designFactExpectation(indexed, proof.fact_ref, proof.key));
|
|
61
|
+
assertSameSet(artifact.fact_expectations.map((item) => item.fact_ref), expectedFactRefs, "design_method_fact_expectation_refs_mismatch", `${target.key}:${binding.method}:${artifact.condition_key}`);
|
|
62
|
+
assertCanonicalRows(artifact.fact_expectations, expectedFactExpectations, "design_method_fact_expectations_mismatch", `${target.key}:${binding.method}:${artifact.condition_key}`);
|
|
63
|
+
boundFactRefs.push(...artifact.fact_refs);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
assertSameSet(boundFactRefs, facts.map((fact) => fact.key), "design_method_fact_refs_mismatch", target.key);
|
|
67
|
+
}
|
|
68
|
+
function designFactExpectation(indexed, factRef, proofRef) {
|
|
69
|
+
const handoff = indexed.preflight.handoff;
|
|
70
|
+
const fact = handoff.facts.find((item) => item.key === factRef);
|
|
71
|
+
const proof = handoff.proof_obligations.find((item) => item.key === proofRef);
|
|
72
|
+
const oracle = handoff.oracles.find((item) => item.key === proof.oracle_ref);
|
|
73
|
+
const environment = handoff.environments.find((item) => item.key === proof.environment_ref);
|
|
74
|
+
return {
|
|
75
|
+
fact_ref: fact.key,
|
|
76
|
+
subject_ref: fact.subject_ref,
|
|
77
|
+
variation_ref: fact.variation_ref,
|
|
78
|
+
property_ref: fact.property_ref,
|
|
79
|
+
observation_sensitivity: fact.observation_sensitivity,
|
|
80
|
+
expected: fact.value,
|
|
81
|
+
comparison: proof.comparison,
|
|
82
|
+
oracle: {
|
|
83
|
+
key: oracle.key,
|
|
84
|
+
trust: oracle.trust,
|
|
85
|
+
identity: oracle.identity,
|
|
86
|
+
version: oracle.version,
|
|
87
|
+
sha256: oracle.sha256,
|
|
88
|
+
},
|
|
89
|
+
environment: {
|
|
90
|
+
key: environment.key,
|
|
91
|
+
identity: environment.identity,
|
|
92
|
+
definition: environment.definition,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function assertCanonicalRows(actual, expected, code, detail) {
|
|
97
|
+
const left = new Map(actual.map((item) => [item.fact_ref, canonicalJson(item)]));
|
|
98
|
+
const right = new Map(expected.map((item) => [item.fact_ref, canonicalJson(item)]));
|
|
99
|
+
assertSameSet([...left.keys()], [...right.keys()], code, detail);
|
|
100
|
+
for (const [key, value] of right)
|
|
101
|
+
if (left.get(key) !== value)
|
|
102
|
+
invalid(code, `${detail}:${key}`);
|
|
103
|
+
}
|
|
104
|
+
function canonicalJson(value) {
|
|
105
|
+
if (Array.isArray(value))
|
|
106
|
+
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
107
|
+
if (value && typeof value === "object")
|
|
108
|
+
return `{${Object.entries(value)
|
|
109
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
110
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
111
|
+
.join(",")}}`;
|
|
112
|
+
return JSON.stringify(value);
|
|
113
|
+
}
|
|
114
|
+
function requiredCapabilities(method) {
|
|
115
|
+
if (method === "interaction_trace")
|
|
116
|
+
return ["design_method", "interaction_trace", "target_runtime"];
|
|
117
|
+
if (method === "component_state")
|
|
118
|
+
return [
|
|
119
|
+
"design_method",
|
|
120
|
+
"design_conformance",
|
|
121
|
+
"interaction_trace",
|
|
122
|
+
"target_runtime",
|
|
123
|
+
];
|
|
124
|
+
return ["design_method", "design_conformance", "target_runtime"];
|
|
125
|
+
}
|
|
126
|
+
export function assertSameSet(actual, expected, code, detail) {
|
|
127
|
+
const left = [...new Set(actual)].sort();
|
|
128
|
+
const right = [...new Set(expected)].sort();
|
|
129
|
+
if (left.length !== right.length ||
|
|
130
|
+
left.some((item, index) => item !== right[index]))
|
|
131
|
+
invalid(code, `${detail}:${left.join(",")}:${right.join(",")}`);
|
|
132
|
+
}
|
|
133
|
+
export function invalid(code, detail) {
|
|
134
|
+
throw new Error(`delivery_contract_invalid:design_resource_${code}:${detail}`);
|
|
135
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { CompiledSourceItemV2 } from "./long-task-delivery-types.js";
|
|
2
|
+
import type { DesignResourceHandoffInputV1 } from "./design-resource-handoff-types.js";
|
|
3
|
+
import type { SemanticFactManifestV1 } from "./semantic-fact-types.js";
|
|
4
|
+
export interface ParsedSourceDocument {
|
|
5
|
+
items: CompiledSourceItemV2[];
|
|
6
|
+
designResourceHandoff: DesignResourceHandoffInputV1 | null;
|
|
7
|
+
semanticFactManifest: SemanticFactManifestV1 | null;
|
|
8
|
+
}
|
|
2
9
|
export declare function parseSourceItems(sourcePath: string, content: string): CompiledSourceItemV2[];
|
|
10
|
+
export declare function parseSourceDocument(sourcePath: string, content: string): ParsedSourceDocument;
|
|
3
11
|
export { normalizeSourceItemText } from "./long-task-source-markers.js";
|