project-tiny-context-harness 0.8.8 → 0.8.11
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/lib/design-resource-fact-manifest-validation.d.ts +3 -1
- 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-draft.d.ts +11 -0
- package/dist/lib/design-resource-handoff-bundle-draft.js +38 -0
- package/dist/lib/design-resource-handoff-bundle.d.ts +31 -0
- package/dist/lib/design-resource-handoff-bundle.js +147 -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-input-types.d.ts +11 -0
- package/dist/lib/design-resource-handoff-input-types.js +1 -0
- package/dist/lib/design-resource-handoff-manifest-projection.d.ts +4 -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 -0
- 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 +13 -1
- package/dist/lib/design-resource-handoff-validation.d.ts +2 -0
- 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
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DesignResourceManifestCollectionName } from "./design-resource-fact-manifest-types.js";
|
|
2
|
+
export interface DesignResourceHandoffBundleOptions {
|
|
3
|
+
repository: string;
|
|
4
|
+
draft_directory: string;
|
|
5
|
+
output_directory: string;
|
|
6
|
+
manifest_paths: string[];
|
|
7
|
+
max_handoff_bytes: number;
|
|
8
|
+
}
|
|
9
|
+
export interface DesignResourceHandoffBundleResult {
|
|
10
|
+
status: "published";
|
|
11
|
+
output_directory: string;
|
|
12
|
+
handoffs: Array<{
|
|
13
|
+
path: string;
|
|
14
|
+
sha256: string;
|
|
15
|
+
bytes: number;
|
|
16
|
+
scope_key: string;
|
|
17
|
+
target_key: string;
|
|
18
|
+
}>;
|
|
19
|
+
manifests: Array<{
|
|
20
|
+
path: string;
|
|
21
|
+
sha256: string;
|
|
22
|
+
scope_key: string;
|
|
23
|
+
target_key: string;
|
|
24
|
+
collections: Array<{
|
|
25
|
+
name: DesignResourceManifestCollectionName;
|
|
26
|
+
expected_count: number;
|
|
27
|
+
identity_sha256: string;
|
|
28
|
+
}>;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
export declare function publishDesignResourceHandoffBundle(options: DesignResourceHandoffBundleOptions): Promise<DesignResourceHandoffBundleResult>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { lstat, mkdtemp, readdir, realpath, rename, rm, } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { stageDesignResourceHandoffDraft } from "./design-resource-handoff-bundle-draft.js";
|
|
4
|
+
import { createDesignResourceHandoffSetIntegrity } from "./design-resource-handoff-set-integrity.js";
|
|
5
|
+
import { preflightParsedDesignResourceHandoff } from "./design-resource-handoff-validation.js";
|
|
6
|
+
import { normalizeRepositoryCwd, normalizeRepositoryFile, } from "./long-task-paths.js";
|
|
7
|
+
import { resolveInsideRepository } from "./long-task-workspace.js";
|
|
8
|
+
import { sha256Hex } from "./strict-codec.js";
|
|
9
|
+
export async function publishDesignResourceHandoffBundle(options) {
|
|
10
|
+
const repository = path.resolve(options.repository);
|
|
11
|
+
const draftDirectory = normalizeBundleDirectory(options.draft_directory, "design_resource_bundle_draft_directory");
|
|
12
|
+
const outputDirectory = normalizeBundleDirectory(options.output_directory, "design_resource_bundle_output_directory");
|
|
13
|
+
if (!Number.isSafeInteger(options.max_handoff_bytes) ||
|
|
14
|
+
options.max_handoff_bytes <= 0)
|
|
15
|
+
invalid("max_handoff_bytes", String(options.max_handoff_bytes));
|
|
16
|
+
const manifestPaths = normalizeManifestPaths(options.manifest_paths);
|
|
17
|
+
const declaredManifestPaths = new Set(manifestPaths);
|
|
18
|
+
const draftAbsolute = await assertSafeDirectory(repository, draftDirectory, "draft_directory");
|
|
19
|
+
const outputAbsolute = resolveInsideRepository(repository, outputDirectory, "design_resource_bundle_output_directory");
|
|
20
|
+
if (await lstat(outputAbsolute).catch(() => null))
|
|
21
|
+
invalid("output_directory_exists", outputDirectory);
|
|
22
|
+
const outputParentRelative = path.posix.dirname(outputDirectory);
|
|
23
|
+
const outputParent = await assertSafeDirectory(repository, outputParentRelative === "." ? "." : outputParentRelative, "output_parent");
|
|
24
|
+
const temporary = await mkdtemp(path.join(outputParent, `.${path.basename(outputAbsolute)}.tmp-`));
|
|
25
|
+
try {
|
|
26
|
+
const draftEntries = (await readdir(draftAbsolute, {
|
|
27
|
+
withFileTypes: true,
|
|
28
|
+
})).sort((left, right) => compareText(left.name, right.name));
|
|
29
|
+
if (!draftEntries.length)
|
|
30
|
+
invalid("draft_directory_empty", draftDirectory);
|
|
31
|
+
for (const entry of draftEntries)
|
|
32
|
+
if (!entry.isFile() ||
|
|
33
|
+
entry.isSymbolicLink() ||
|
|
34
|
+
path.extname(entry.name).toLowerCase() !== ".md")
|
|
35
|
+
invalid("draft_entry_not_markdown_file", entry.name);
|
|
36
|
+
const handoffs = [];
|
|
37
|
+
const seenTargets = new Set();
|
|
38
|
+
const seenManifestPaths = new Set();
|
|
39
|
+
const manifests = new Map();
|
|
40
|
+
const handoffSetIntegrity = createDesignResourceHandoffSetIntegrity((code, detail) => invalid(code, detail));
|
|
41
|
+
for (const entry of draftEntries) {
|
|
42
|
+
const { bytes, parsed, target: declaredTarget, manifest_resource: manifestResource, } = await stageDesignResourceHandoffDraft(repository, draftAbsolute, temporary, entry.name, options.max_handoff_bytes, invalid);
|
|
43
|
+
if (!declaredManifestPaths.has(manifestResource.path))
|
|
44
|
+
invalid("target_manifest_not_declared", `${declaredTarget.key}:${manifestResource.path}`);
|
|
45
|
+
const preflight = await preflightParsedDesignResourceHandoff(repository, parsed);
|
|
46
|
+
handoffSetIntegrity.consume(preflight);
|
|
47
|
+
const targetKey = preflight.handoff.targets[0].key;
|
|
48
|
+
if (seenTargets.has(targetKey))
|
|
49
|
+
invalid("target_duplicate", targetKey);
|
|
50
|
+
seenTargets.add(targetKey);
|
|
51
|
+
const manifestIdentity = preflight.manifest_identities[0];
|
|
52
|
+
if (manifestIdentity.target_key !== targetKey ||
|
|
53
|
+
manifestIdentity.path !== manifestResource.path ||
|
|
54
|
+
manifestIdentity.sha256 !== manifestResource.sha256)
|
|
55
|
+
invalid("target_manifest_identity_mismatch", targetKey);
|
|
56
|
+
seenManifestPaths.add(manifestIdentity.path);
|
|
57
|
+
manifests.set(targetKey, {
|
|
58
|
+
path: manifestIdentity.path,
|
|
59
|
+
sha256: manifestIdentity.sha256,
|
|
60
|
+
scope_key: manifestIdentity.scope_key,
|
|
61
|
+
target_key: manifestIdentity.target_key,
|
|
62
|
+
collections: manifestIdentity.collections.map((collection) => ({
|
|
63
|
+
...collection,
|
|
64
|
+
})),
|
|
65
|
+
});
|
|
66
|
+
handoffs.push({
|
|
67
|
+
path: `${outputDirectory}/${entry.name}`,
|
|
68
|
+
sha256: sha256Hex(bytes),
|
|
69
|
+
bytes: bytes.length,
|
|
70
|
+
scope_key: preflight.handoff.scope.key,
|
|
71
|
+
target_key: targetKey,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
handoffSetIntegrity.finish();
|
|
75
|
+
assertSameSet(seenManifestPaths, declaredManifestPaths, "manifest_path_set_mismatch");
|
|
76
|
+
const result = {
|
|
77
|
+
status: "published",
|
|
78
|
+
output_directory: outputDirectory,
|
|
79
|
+
handoffs,
|
|
80
|
+
manifests: [...manifests.values()]
|
|
81
|
+
.map((baseline) => ({
|
|
82
|
+
path: baseline.path,
|
|
83
|
+
sha256: baseline.sha256,
|
|
84
|
+
scope_key: baseline.scope_key,
|
|
85
|
+
target_key: baseline.target_key,
|
|
86
|
+
collections: baseline.collections.map((collection) => ({
|
|
87
|
+
...collection,
|
|
88
|
+
})),
|
|
89
|
+
}))
|
|
90
|
+
.sort((left, right) => compareText(left.target_key, right.target_key)),
|
|
91
|
+
};
|
|
92
|
+
await rename(temporary, outputAbsolute);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
try {
|
|
97
|
+
await rm(temporary, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
catch (cleanupError) {
|
|
100
|
+
throw new AggregateError([error, cleanupError], "design_resource_handoff_bundle_cleanup_failed");
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function normalizeManifestPaths(values) {
|
|
106
|
+
if (!values.length)
|
|
107
|
+
invalid("manifest_paths_required", "");
|
|
108
|
+
const normalized = values.map((value) => normalizeRepositoryFile(value, "design_resource_bundle_manifest"));
|
|
109
|
+
if (new Set(normalized).size !== normalized.length)
|
|
110
|
+
invalid("manifest_path_duplicate", normalized.join(","));
|
|
111
|
+
return normalized.sort(compareText);
|
|
112
|
+
}
|
|
113
|
+
async function assertSafeDirectory(repository, relative, label) {
|
|
114
|
+
const absolute = resolveInsideRepository(repository, relative, label);
|
|
115
|
+
const info = await lstat(absolute).catch(() => null);
|
|
116
|
+
if (!info)
|
|
117
|
+
invalid(`${label}_not_found`, relative);
|
|
118
|
+
if (info.isSymbolicLink() || !info.isDirectory())
|
|
119
|
+
invalid(`${label}_not_directory`, relative);
|
|
120
|
+
const root = await realpath(repository);
|
|
121
|
+
const resolved = await realpath(absolute);
|
|
122
|
+
const outside = path.relative(root, resolved);
|
|
123
|
+
if (outside === ".." ||
|
|
124
|
+
outside.startsWith(`..${path.sep}`) ||
|
|
125
|
+
path.isAbsolute(outside))
|
|
126
|
+
invalid(`${label}_outside_repository`, relative);
|
|
127
|
+
return resolved;
|
|
128
|
+
}
|
|
129
|
+
function normalizeBundleDirectory(value, label) {
|
|
130
|
+
const normalized = normalizeRepositoryCwd(value, label);
|
|
131
|
+
if (normalized === ".")
|
|
132
|
+
invalid("repository_root_directory_forbidden", label);
|
|
133
|
+
return normalized;
|
|
134
|
+
}
|
|
135
|
+
function assertSameSet(actual, expected, code) {
|
|
136
|
+
const left = [...actual].sort(compareText);
|
|
137
|
+
const right = [...expected].sort(compareText);
|
|
138
|
+
if (left.length !== right.length ||
|
|
139
|
+
left.some((value, index) => value !== right[index]))
|
|
140
|
+
invalid(code, `${left.join(",")}:${right.join(",")}`);
|
|
141
|
+
}
|
|
142
|
+
function invalid(code, detail) {
|
|
143
|
+
throw new Error(`design_resource_handoff_bundle_invalid:${code}${detail ? `:${detail}` : ""}`);
|
|
144
|
+
}
|
|
145
|
+
function compareText(left, right) {
|
|
146
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
147
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ParsedDesignResourceHandoffV1 } from "./design-resource-handoff-types.js";
|
|
2
|
-
export declare function validateDesignResourceFiles(
|
|
2
|
+
export declare function validateDesignResourceFiles(parsed: ParsedDesignResourceHandoffV1, contents: Map<string, Buffer>): void;
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import { isTextResource, } from "./design-resource-handoff-file-primitives.js";
|
|
4
2
|
import { invalidDesignResourceHandoff } from "./design-resource-handoff-validation-primitives.js";
|
|
5
3
|
import { resolveDesignResourceLocatorValue } from "./design-resource-fact-locator-validation.js";
|
|
6
4
|
import { validateDesignResourceImplementationDependencyClosure } from "./design-resource-handoff-web-dependency-validation.js";
|
|
7
|
-
|
|
8
|
-
export async function validateDesignResourceFiles(repository, parsed) {
|
|
5
|
+
export function validateDesignResourceFiles(parsed, contents) {
|
|
9
6
|
const resources = new Map(parsed.handoff.resources.map((resource) => [resource.key, resource]));
|
|
10
|
-
const contents = new Map();
|
|
11
|
-
for (const resource of parsed.handoff.resources) {
|
|
12
|
-
const file = await assertProtectedRepositoryFile(repository, path.resolve(repository, ...resource.path.split("/")), `design_resource:${resource.key}`);
|
|
13
|
-
contents.set(resource.key, await readFile(file));
|
|
14
|
-
}
|
|
15
7
|
for (const evidence of parsed.handoff.evidence)
|
|
16
8
|
validateLocator(evidence, resources.get(evidence.resource_ref), contents.get(evidence.resource_ref));
|
|
17
9
|
for (const target of parsed.handoff.targets)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DesignResourceHandoffV1 } from "./design-resource-handoff-types.js";
|
|
2
|
+
export type DesignResourceHandoffManifestBackedV1 = Pick<DesignResourceHandoffV1, "schema_version" | "intent" | "scope" | "provenance" | "resources" | "targets" | "resource_fact_closure" | "coverage" | "proposal"> & {
|
|
3
|
+
representation: "manifest_backed";
|
|
4
|
+
};
|
|
5
|
+
export type DesignResourceHandoffInputV1 = DesignResourceHandoffV1 | DesignResourceHandoffManifestBackedV1;
|
|
6
|
+
export interface ParsedDesignResourceHandoffInputV1 {
|
|
7
|
+
handoff_path: string;
|
|
8
|
+
handoff: DesignResourceHandoffInputV1;
|
|
9
|
+
source_item_keys: string[];
|
|
10
|
+
source_item_kinds: Record<string, string>;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DesignResourceObservableFactManifestV1 } from "./design-resource-fact-manifest-types.js";
|
|
2
|
+
import type { DesignResourceHandoffManifestBackedV1 } from "./design-resource-handoff-input-types.js";
|
|
3
|
+
import type { DesignResourceHandoffV1 } from "./design-resource-handoff-types.js";
|
|
4
|
+
export declare function hydrateManifestBackedDesignResourceHandoff(descriptor: DesignResourceHandoffManifestBackedV1, manifests: Map<string, DesignResourceObservableFactManifestV1>): DesignResourceHandoffV1;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { invalidDesignResourceHandoff } from "./design-resource-handoff-validation-primitives.js";
|
|
2
|
+
export function hydrateManifestBackedDesignResourceHandoff(descriptor, manifests) {
|
|
3
|
+
if (descriptor.targets.length !== 1)
|
|
4
|
+
invalidDesignResourceHandoff("manifest_backed_one_target_required", String(descriptor.targets.length));
|
|
5
|
+
const target = descriptor.targets[0];
|
|
6
|
+
const manifest = manifests.get(target.key);
|
|
7
|
+
if (!manifest)
|
|
8
|
+
invalidDesignResourceHandoff("fact_manifest_target_missing", target.key);
|
|
9
|
+
return {
|
|
10
|
+
schema_version: descriptor.schema_version,
|
|
11
|
+
intent: descriptor.intent,
|
|
12
|
+
scope: descriptor.scope,
|
|
13
|
+
provenance: descriptor.provenance,
|
|
14
|
+
resources: descriptor.resources,
|
|
15
|
+
axis_dispositions: manifest.axis_dispositions,
|
|
16
|
+
condition_exclusions: manifest.condition_exclusions,
|
|
17
|
+
conditions: manifest.conditions,
|
|
18
|
+
subjects: manifest.subjects,
|
|
19
|
+
variation_axis_dispositions: manifest.variation_axis_dispositions,
|
|
20
|
+
variation_exclusions: manifest.variation_exclusions,
|
|
21
|
+
variations: manifest.variations,
|
|
22
|
+
properties: manifest.properties,
|
|
23
|
+
lineage_nodes: manifest.lineage_nodes,
|
|
24
|
+
targets: descriptor.targets,
|
|
25
|
+
evidence: manifest.evidence,
|
|
26
|
+
fact_cells: manifest.fact_cells,
|
|
27
|
+
facts: manifest.facts,
|
|
28
|
+
proof_obligations: manifest.proof_obligations,
|
|
29
|
+
oracles: manifest.oracles,
|
|
30
|
+
environments: manifest.environments,
|
|
31
|
+
asset_bindings: manifest.asset_bindings,
|
|
32
|
+
resource_fact_closure: descriptor.resource_fact_closure,
|
|
33
|
+
coverage: descriptor.coverage,
|
|
34
|
+
acceptance_blockers: manifest.acceptance_blockers,
|
|
35
|
+
proposal: descriptor.proposal,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ParsedDesignResourceHandoffInputV1 } from "./design-resource-handoff-input-types.js";
|
|
2
|
+
export interface DesignResourceHandoffBlockSpan {
|
|
3
|
+
bodyStartOffset: number;
|
|
4
|
+
bodyEndOffset: number;
|
|
5
|
+
}
|
|
2
6
|
export declare function containsDesignResourceHandoff(content: string): boolean;
|
|
3
|
-
export declare function
|
|
7
|
+
export declare function scanDesignResourceHandoffBlocks(content: string): DesignResourceHandoffBlockSpan[];
|
|
8
|
+
export declare function parseDesignResourceHandoffMarkdown(handoffPath: string, content: string): ParsedDesignResourceHandoffInputV1;
|
|
@@ -1,23 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const
|
|
1
|
+
import { parseSourceDocument } from "./long-task-source-item-parser.js";
|
|
2
|
+
import { forEachSourceLine } from "./source-line-scanner.js";
|
|
3
|
+
const DESIGN_RESOURCE_START = /^```yaml[ \t]+design-resource-handoff-v1[ \t]*$/u;
|
|
4
|
+
const FORMAL_BLOCK_END = /^```[ \t]*$/u;
|
|
5
5
|
export function containsDesignResourceHandoff(content) {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
return scanDesignResourceHandoffBlocks(content).length > 0;
|
|
7
|
+
}
|
|
8
|
+
export function scanDesignResourceHandoffBlocks(content) {
|
|
9
|
+
const blocks = [];
|
|
10
|
+
let bodyStartOffset = null;
|
|
11
|
+
forEachSourceLine(content, (line, startOffset, _endOffset, nextOffset) => {
|
|
12
|
+
if (bodyStartOffset === null) {
|
|
13
|
+
if (DESIGN_RESOURCE_START.test(line))
|
|
14
|
+
bodyStartOffset = nextOffset;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (!FORMAL_BLOCK_END.test(line))
|
|
18
|
+
return;
|
|
19
|
+
blocks.push({
|
|
20
|
+
bodyStartOffset,
|
|
21
|
+
bodyEndOffset: startOffset,
|
|
22
|
+
});
|
|
23
|
+
bodyStartOffset = null;
|
|
24
|
+
});
|
|
25
|
+
return blocks;
|
|
8
26
|
}
|
|
9
27
|
export function parseDesignResourceHandoffMarkdown(handoffPath, content) {
|
|
10
|
-
|
|
11
|
-
const blocks = [...content.matchAll(FENCE)];
|
|
28
|
+
const blocks = scanDesignResourceHandoffBlocks(content);
|
|
12
29
|
if (blocks.length !== 1)
|
|
13
30
|
throw new Error(`design_resource_handoff_invalid:block_count:${handoffPath}:${blocks.length}`);
|
|
14
31
|
try {
|
|
15
|
-
const
|
|
32
|
+
const parsedSource = parseSourceDocument(handoffPath, content);
|
|
33
|
+
const handoff = parsedSource.designResourceHandoff;
|
|
34
|
+
if (!handoff)
|
|
35
|
+
throw new Error("design-resource-handoff-v1 block was not decoded");
|
|
16
36
|
return {
|
|
17
37
|
handoff_path: handoffPath,
|
|
18
|
-
handoff
|
|
19
|
-
source_item_keys:
|
|
20
|
-
source_item_kinds: Object.fromEntries(
|
|
38
|
+
handoff,
|
|
39
|
+
source_item_keys: parsedSource.items.map((item) => item.key),
|
|
40
|
+
source_item_kinds: Object.fromEntries(parsedSource.items.map((item) => [item.key, item.kind])),
|
|
21
41
|
};
|
|
22
42
|
}
|
|
23
43
|
catch (error) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DesignResourceHandoffPreflightV1 } from "./design-resource-handoff-types.js";
|
|
2
|
+
type HandoffSetFailure = (code: string, detail: string) => never;
|
|
3
|
+
export interface DesignResourceHandoffSetIntegrity {
|
|
4
|
+
consume(preflight: DesignResourceHandoffPreflightV1): void;
|
|
5
|
+
finish(): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createDesignResourceHandoffSetIntegrity(fail: HandoffSetFailure): DesignResourceHandoffSetIntegrity;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { canonicalValueJson, sha256Hex } from "./strict-codec.js";
|
|
2
|
+
export function createDesignResourceHandoffSetIntegrity(fail) {
|
|
3
|
+
const scopes = new Map();
|
|
4
|
+
const sourceItems = new Set();
|
|
5
|
+
let issue = null;
|
|
6
|
+
function record(code, detail) {
|
|
7
|
+
issue ??= { code, detail };
|
|
8
|
+
}
|
|
9
|
+
function sharedHeader(scope, name, value) {
|
|
10
|
+
const digest = canonicalDigest(value);
|
|
11
|
+
const previous = scope.headers.get(name);
|
|
12
|
+
if (previous && previous !== digest)
|
|
13
|
+
record("handoff_set_header_conflict", name);
|
|
14
|
+
else
|
|
15
|
+
scope.headers.set(name, digest);
|
|
16
|
+
}
|
|
17
|
+
function sharedCollection(scope, name, rows) {
|
|
18
|
+
const index = scope.sharedRows.get(name) ?? new Map();
|
|
19
|
+
scope.sharedRows.set(name, index);
|
|
20
|
+
for (const row of rows) {
|
|
21
|
+
const digest = canonicalDigest(row);
|
|
22
|
+
const previous = index.get(row.key);
|
|
23
|
+
if (previous && previous !== digest)
|
|
24
|
+
record("handoff_set_shared_row_conflict", `${name}:${row.key}`);
|
|
25
|
+
else
|
|
26
|
+
index.set(row.key, digest);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
consume(preflight) {
|
|
31
|
+
if (issue)
|
|
32
|
+
return;
|
|
33
|
+
const handoff = preflight.handoff;
|
|
34
|
+
const scope = scopeIndex(scopes, handoff.scope.key);
|
|
35
|
+
sharedHeader(scope, "provenance", handoff.provenance);
|
|
36
|
+
sharedHeader(scope, "proposal", handoff.proposal);
|
|
37
|
+
sharedCollection(scope, "resources", handoff.resources);
|
|
38
|
+
sharedCollection(scope, "conditions", handoff.conditions);
|
|
39
|
+
sharedCollection(scope, "properties", handoff.properties);
|
|
40
|
+
sharedCollection(scope, "resource_fact_closure", handoff.resource_fact_closure);
|
|
41
|
+
for (const closure of handoff.resource_fact_closure) {
|
|
42
|
+
const digest = canonicalDigest(closure);
|
|
43
|
+
const previous = scope.resourceClosures.get(closure.resource_ref);
|
|
44
|
+
if (previous &&
|
|
45
|
+
(previous.key !== closure.key || previous.digest !== digest))
|
|
46
|
+
record("handoff_set_resource_closure_conflict", `${closure.resource_ref}:${previous.key}:${closure.key}`);
|
|
47
|
+
else
|
|
48
|
+
scope.resourceClosures.set(closure.resource_ref, {
|
|
49
|
+
key: closure.key,
|
|
50
|
+
digest,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
for (const resource of handoff.resources) {
|
|
54
|
+
const digest = canonicalDigest(resource);
|
|
55
|
+
const previous = scope.resourcePaths.get(resource.path);
|
|
56
|
+
if (previous &&
|
|
57
|
+
(previous.key !== resource.key || previous.digest !== digest))
|
|
58
|
+
record("handoff_set_resource_path_conflict", `${resource.path}:${previous.key}:${resource.key}`);
|
|
59
|
+
else
|
|
60
|
+
scope.resourcePaths.set(resource.path, {
|
|
61
|
+
key: resource.key,
|
|
62
|
+
digest,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
for (const sourceItem of preflight.source_item_keys) {
|
|
66
|
+
if (sourceItems.has(sourceItem))
|
|
67
|
+
record("handoff_set_source_item_duplicate", sourceItem);
|
|
68
|
+
else
|
|
69
|
+
sourceItems.add(sourceItem);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
finish() {
|
|
73
|
+
if (issue)
|
|
74
|
+
fail(issue.code, issue.detail);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function scopeIndex(scopes, key) {
|
|
79
|
+
const existing = scopes.get(key);
|
|
80
|
+
if (existing)
|
|
81
|
+
return existing;
|
|
82
|
+
const created = {
|
|
83
|
+
headers: new Map(),
|
|
84
|
+
sharedRows: new Map(),
|
|
85
|
+
resourcePaths: new Map(),
|
|
86
|
+
resourceClosures: new Map(),
|
|
87
|
+
};
|
|
88
|
+
scopes.set(key, created);
|
|
89
|
+
return created;
|
|
90
|
+
}
|
|
91
|
+
function canonicalDigest(value) {
|
|
92
|
+
return sha256Hex(canonicalValueJson(value));
|
|
93
|
+
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import type { DesignResourceHandoffInputV1 } from "./design-resource-handoff-input-types.js";
|
|
1
2
|
import type { DesignResourceHandoffV1 } from "./design-resource-handoff-types.js";
|
|
3
|
+
export declare function parseDesignResourceHandoffInputShape(value: unknown): DesignResourceHandoffInputV1;
|
|
2
4
|
export declare function parseDesignResourceHandoffShape(value: unknown): DesignResourceHandoffV1;
|
|
@@ -3,6 +3,14 @@ import { parseDesignResourceHandoffBlockers, parseDesignResourceHandoffCoverage,
|
|
|
3
3
|
import { contractKey, stableKeys, } from "./design-resource-handoff-shape-primitives.js";
|
|
4
4
|
import { parseDesignResourceHandoffConditions, parseDesignResourceHandoffResources, parseDesignResourceHandoffSubjects, parseDesignResourceHandoffTargets, } from "./design-resource-handoff-shape-structure.js";
|
|
5
5
|
import { literal, object, repositoryFile, string, strings, } from "./long-task-shape-primitives.js";
|
|
6
|
+
export function parseDesignResourceHandoffInputShape(value) {
|
|
7
|
+
if (value &&
|
|
8
|
+
typeof value === "object" &&
|
|
9
|
+
!Array.isArray(value) &&
|
|
10
|
+
value.representation === "manifest_backed")
|
|
11
|
+
return parseManifestBackedDesignResourceHandoffShape(value);
|
|
12
|
+
return parseDesignResourceHandoffShape(value);
|
|
13
|
+
}
|
|
6
14
|
export function parseDesignResourceHandoffShape(value) {
|
|
7
15
|
const root = object(value, "design_resource_handoff", [
|
|
8
16
|
"schema_version",
|
|
@@ -102,3 +110,57 @@ export function parseDesignResourceHandoffShape(value) {
|
|
|
102
110
|
},
|
|
103
111
|
};
|
|
104
112
|
}
|
|
113
|
+
function parseManifestBackedDesignResourceHandoffShape(value) {
|
|
114
|
+
const root = object(value, "design_resource_handoff", [
|
|
115
|
+
"schema_version",
|
|
116
|
+
"representation",
|
|
117
|
+
"intent",
|
|
118
|
+
"scope",
|
|
119
|
+
"provenance",
|
|
120
|
+
"resources",
|
|
121
|
+
"targets",
|
|
122
|
+
"resource_fact_closure",
|
|
123
|
+
"coverage",
|
|
124
|
+
"proposal",
|
|
125
|
+
]);
|
|
126
|
+
const parsed = parseDesignResourceHandoffShape({
|
|
127
|
+
schema_version: root.schema_version,
|
|
128
|
+
intent: root.intent,
|
|
129
|
+
scope: root.scope,
|
|
130
|
+
provenance: root.provenance,
|
|
131
|
+
resources: root.resources,
|
|
132
|
+
axis_dispositions: [],
|
|
133
|
+
condition_exclusions: [],
|
|
134
|
+
conditions: [],
|
|
135
|
+
subjects: [],
|
|
136
|
+
variation_axis_dispositions: [],
|
|
137
|
+
variation_exclusions: [],
|
|
138
|
+
variations: [],
|
|
139
|
+
properties: [],
|
|
140
|
+
lineage_nodes: [],
|
|
141
|
+
targets: root.targets,
|
|
142
|
+
evidence: [],
|
|
143
|
+
fact_cells: [],
|
|
144
|
+
facts: [],
|
|
145
|
+
proof_obligations: [],
|
|
146
|
+
oracles: [],
|
|
147
|
+
environments: [],
|
|
148
|
+
asset_bindings: [],
|
|
149
|
+
resource_fact_closure: root.resource_fact_closure,
|
|
150
|
+
coverage: root.coverage,
|
|
151
|
+
acceptance_blockers: [],
|
|
152
|
+
proposal: root.proposal,
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
schema_version: parsed.schema_version,
|
|
156
|
+
representation: literal(root.representation, ["manifest_backed"], "design_resource_handoff.representation"),
|
|
157
|
+
intent: parsed.intent,
|
|
158
|
+
scope: parsed.scope,
|
|
159
|
+
provenance: parsed.provenance,
|
|
160
|
+
resources: parsed.resources,
|
|
161
|
+
targets: parsed.targets,
|
|
162
|
+
resource_fact_closure: parsed.resource_fact_closure,
|
|
163
|
+
coverage: parsed.coverage,
|
|
164
|
+
proposal: parsed.proposal,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ParsedDesignResourceHandoffInputV1 } from "./design-resource-handoff-input-types.js";
|
|
2
|
+
export interface DesignResourceSnapshot {
|
|
3
|
+
contents: Map<string, Buffer>;
|
|
4
|
+
hashes: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export declare function readDesignResourceSnapshot(repository: string, parsed: ParsedDesignResourceHandoffInputV1): Promise<DesignResourceSnapshot>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { invalidDesignResourceHandoff } from "./design-resource-handoff-validation-primitives.js";
|
|
4
|
+
import { assertProtectedRepositoryFile } from "./long-task-protected-files.js";
|
|
5
|
+
import { sha256Hex } from "./strict-codec.js";
|
|
6
|
+
export async function readDesignResourceSnapshot(repository, parsed) {
|
|
7
|
+
const contents = new Map();
|
|
8
|
+
const hashes = {};
|
|
9
|
+
for (const resource of parsed.handoff.resources) {
|
|
10
|
+
if (resource.path === parsed.handoff_path)
|
|
11
|
+
invalidDesignResourceHandoff("resource_must_not_be_handoff", resource.key);
|
|
12
|
+
const file = await assertProtectedRepositoryFile(repository, path.resolve(repository, ...resource.path.split("/")), `design_resource:${resource.key}`);
|
|
13
|
+
const bytes = await readFile(file);
|
|
14
|
+
const digest = sha256Hex(bytes);
|
|
15
|
+
if (digest !== resource.sha256)
|
|
16
|
+
invalidDesignResourceHandoff("resource_digest_mismatch", `${resource.key}:${resource.sha256}:${digest}`);
|
|
17
|
+
contents.set(resource.key, bytes);
|
|
18
|
+
hashes[resource.key] = digest;
|
|
19
|
+
}
|
|
20
|
+
return { contents, hashes };
|
|
21
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExecutionTargetCapabilityV2 } from "./execution-target-capabilities.js";
|
|
2
|
-
import type { DesignResourceAssetBindingV1, DesignResourceAxisDispositionV1, DesignResourceConditionCombinationDispositionV1, DesignResourceEnvironmentV1, DesignResourceFactCellV1, DesignResourceFactV1, DesignResourceLineageNodeV1, DesignResourceOracleV1, DesignResourcePropertyDefinitionV1, DesignResourceProofObligationV1, DesignResourceRelationEndpointV1, DesignResourceSubjectKind, DesignResourceSubjectPresenceKind, DesignResourceSubjectVariationV1, DesignResourceVariationAxisDispositionV1, DesignResourceVariationCombinationDispositionV1 } from "./design-resource-fact-manifest-types.js";
|
|
2
|
+
import type { DesignResourceAssetBindingV1, DesignResourceAxisDispositionV1, DesignResourceConditionCombinationDispositionV1, DesignResourceEnvironmentV1, DesignResourceFactCellV1, DesignResourceFactV1, DesignResourceLineageNodeV1, DesignResourceManifestCollectionName, DesignResourceOracleV1, DesignResourcePropertyDefinitionV1, DesignResourceProofObligationV1, DesignResourceRelationEndpointV1, DesignResourceSubjectKind, DesignResourceSubjectPresenceKind, DesignResourceSubjectVariationV1, DesignResourceVariationAxisDispositionV1, DesignResourceVariationCombinationDispositionV1 } from "./design-resource-fact-manifest-types.js";
|
|
3
3
|
export declare const DESIGN_RESOURCE_DIMENSIONS: readonly ["surface_flow", "visual_content", "component_control", "state_interaction", "motion", "adaptation_input", "accessibility", "assets"];
|
|
4
4
|
export type DesignResourceDimension = (typeof DESIGN_RESOURCE_DIMENSIONS)[number];
|
|
5
5
|
export declare const DESIGN_RESOURCE_EVIDENCE_KINDS: readonly ["frame", "component_variant", "prototype_state", "prototype_transition", "motion_spec", "motion_capture", "responsive_spec", "input_spec", "accessibility_spec", "semantic_tree", "token_spec", "asset", "annotation", "localization_spec", "system_ui_spec", "haptic_spec", "sound_spec", "sound_capture", "render_environment", "relation_spec"];
|
|
@@ -220,6 +220,18 @@ export interface DesignResourceHandoffPreflightV1 extends ParsedDesignResourceHa
|
|
|
220
220
|
schema_version: "design-resource-handoff-preflight-v1";
|
|
221
221
|
status: "ready";
|
|
222
222
|
resource_hashes: Record<string, string>;
|
|
223
|
+
manifest_identities: Array<{
|
|
224
|
+
resource_ref: string;
|
|
225
|
+
path: string;
|
|
226
|
+
sha256: string;
|
|
227
|
+
scope_key: string;
|
|
228
|
+
target_key: string;
|
|
229
|
+
collections: Array<{
|
|
230
|
+
name: DesignResourceManifestCollectionName;
|
|
231
|
+
expected_count: number;
|
|
232
|
+
identity_sha256: string;
|
|
233
|
+
}>;
|
|
234
|
+
}>;
|
|
223
235
|
counts: {
|
|
224
236
|
resources: number;
|
|
225
237
|
manifests: number;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ParsedDesignResourceHandoffInputV1 } from "./design-resource-handoff-input-types.js";
|
|
1
2
|
import type { DesignResourceHandoffPreflightV1, ParsedDesignResourceHandoffV1 } from "./design-resource-handoff-types.js";
|
|
2
3
|
export declare function preflightDesignResourceHandoff(repository: string, handoffPath: string): Promise<DesignResourceHandoffPreflightV1>;
|
|
4
|
+
export declare function preflightParsedDesignResourceHandoff(repository: string, parsed: ParsedDesignResourceHandoffInputV1): Promise<DesignResourceHandoffPreflightV1>;
|
|
3
5
|
export declare function validateDesignResourceHandoffSemantics(parsed: ParsedDesignResourceHandoffV1): void;
|