skill-family-engineering-kit 0.3.0 → 0.4.0
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/CHANGELOG.md +21 -0
- package/CHANGELOG.zh-CN.md +21 -0
- package/README.md +11 -12
- package/README.zh-CN.md +11 -12
- package/candidate/adoption-cli.mjs +67 -0
- package/candidate/adoption-mechanisms.mjs +69 -0
- package/candidate/index.mjs +3 -0
- package/candidate/profile-bundle.mjs +464 -15
- package/candidate/projection-bundle-cli.mjs +18 -0
- package/docs/agents/capability-catalog.en.json +1746 -1562
- package/docs/agents/capability-catalog.json +1029 -930
- package/docs/agents/capability-catalog.zh-CN.json +1746 -1562
- package/docs/architecture/index.html +10 -3
- package/docs/en/architecture/index.html +12 -3
- package/docs/public/status/index.html +3 -3
- package/docs/reference/api/harness/index.html +396 -1
- package/docs/search/search_index.json +1 -1
- package/package.json +3 -3
- package/release-notes/0.4.0.yaml +23 -0
- package/src/adopt-plan.mjs +26 -1
- package/src/index.mjs +5 -2
- package/src/migration.mjs +121 -1
- package/src/projection.mjs +1152 -266
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, realpath, stat } from "node:fs/promises";
|
|
1
|
+
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
QUICKSTART_PROFILE_VERSION,
|
|
12
12
|
} from "skill-family-contracts/candidate/quickstart-profile";
|
|
13
13
|
import { digestBytes } from "skill-family-harness-node";
|
|
14
|
+
import { verifyConsumerSchemaInventory } from "skill-family-harness-node/candidate/quickstart-profile";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Candidate Quickstart Profile v2 offline bundle builder (unstable).
|
|
@@ -47,6 +48,10 @@ const PROVENANCE_FILE = "foundation-projection.json";
|
|
|
47
48
|
// survives into the final bundle bytes — rewriteGeneratedRequires replaces
|
|
48
49
|
// every occurrence with a hoisted bundle-relative ESM import.
|
|
49
50
|
const FORMAT_RUNTIME_SPECIFIER = "__SKILL_FAMILY_BUNDLE_FORMAT_RUNTIME__";
|
|
51
|
+
// Ajv owns short generated bindings such as validate20 inside each standalone
|
|
52
|
+
// module. Public exports therefore live in a Foundation-reserved namespace
|
|
53
|
+
// that is independent of Ajv's current counter values and internal prefixes.
|
|
54
|
+
const STANDALONE_EXPORT_PREFIX = "__skillFamilyFoundationValidator_";
|
|
50
55
|
|
|
51
56
|
// The Ajv `_` codegen tag serializes interpolated values, so the formats
|
|
52
57
|
// require below is written literally; this guard keeps the literal and the
|
|
@@ -60,6 +65,8 @@ const FOUNDATION_SCHEMA_FILES = Object.freeze([
|
|
|
60
65
|
["resource.schema.json", "schemas/foundation/resource.schema.json"],
|
|
61
66
|
["task.schema.json", "schemas/foundation/task.schema.json"],
|
|
62
67
|
["result.schema.json", "schemas/foundation/result.schema.json"],
|
|
68
|
+
["consumer-schema-inventory.schema.json", "schemas/foundation/consumer-schema-inventory.schema.json"],
|
|
69
|
+
["harness-surface-inventory.schema.json", "schemas/foundation/harness-surface-inventory.schema.json"],
|
|
63
70
|
]);
|
|
64
71
|
|
|
65
72
|
const HARNESS_IMPORT_MAP = new Map([
|
|
@@ -67,6 +74,27 @@ const HARNESS_IMPORT_MAP = new Map([
|
|
|
67
74
|
["skill-family-contracts/candidate/quickstart-profile", "../contracts-candidate/index.mjs"],
|
|
68
75
|
["../src/closure.mjs", "./closure.mjs"],
|
|
69
76
|
["../src/errors.mjs", "./errors.mjs"],
|
|
77
|
+
["../src/paths.mjs", "./paths.mjs"],
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const HARNESS_CLI_IMPORT_MAP = new Map([
|
|
81
|
+
["./quickstart-profile.mjs", "./runner.mjs"],
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const HARNESS_ATOMIC_IMPORT_MAP = new Map([
|
|
85
|
+
["./errors.mjs", "./errors.mjs"],
|
|
86
|
+
["./paths.mjs", "./paths.mjs"],
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const HARNESS_TOKEN_LOCK_IMPORT_MAP = new Map([
|
|
90
|
+
["./atomic.mjs", "./atomic.mjs"],
|
|
91
|
+
["./errors.mjs", "./errors.mjs"],
|
|
92
|
+
["./paths.mjs", "./paths.mjs"],
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
const ADOPTION_IMPORT_MAP = new Map([
|
|
96
|
+
["../src/migration.mjs", "./runtime/kit/migration.mjs"],
|
|
97
|
+
["skill-family-harness-node/candidate/quickstart-profile", "./runtime/harness/quickstart-profile.mjs"],
|
|
70
98
|
]);
|
|
71
99
|
|
|
72
100
|
const DIALECT_URIS = Object.freeze({
|
|
@@ -76,6 +104,20 @@ const DIALECT_URIS = Object.freeze({
|
|
|
76
104
|
|
|
77
105
|
const SUPPORTED_FORMATS = Object.freeze(["date-time"]);
|
|
78
106
|
|
|
107
|
+
const FIXED_SET_CANDIDATE_FILES = Object.freeze([
|
|
108
|
+
"NOTICE",
|
|
109
|
+
"loader.mjs",
|
|
110
|
+
"prebuild-manifest.json",
|
|
111
|
+
"prebuild-release-receipt.json",
|
|
112
|
+
"prebuild-sbom.json",
|
|
113
|
+
"rename-directory-no-replace.mjs",
|
|
114
|
+
"prebuilds/darwin-arm64/rename_directory_no_replace.darwin-arm64.node",
|
|
115
|
+
"prebuilds/darwin-x64/rename_directory_no_replace.darwin-x64.node",
|
|
116
|
+
"prebuilds/linux-arm64-gnu/rename_directory_no_replace.linux-arm64-gnu.node",
|
|
117
|
+
"prebuilds/linux-x64-gnu/rename_directory_no_replace.linux-x64-gnu.node",
|
|
118
|
+
]);
|
|
119
|
+
const FIXED_SET_BINARY_PREFIX = "prebuilds/";
|
|
120
|
+
|
|
79
121
|
// These fragments are the exact case-sensitive words rejected by the
|
|
80
122
|
// bundle-wide byte scan. Caller-provided source identities enter provenance
|
|
81
123
|
// verbatim, so they must pass the same boundary before bundle assembly.
|
|
@@ -95,6 +137,13 @@ function buildError(message) {
|
|
|
95
137
|
return new TypeError(`buildQuickstartProfileProjection: ${message}`);
|
|
96
138
|
}
|
|
97
139
|
|
|
140
|
+
function standaloneExportName(index) {
|
|
141
|
+
if (!Number.isSafeInteger(index) || index < 0) {
|
|
142
|
+
throw new TypeError("standalone export index must be a non-negative safe integer");
|
|
143
|
+
}
|
|
144
|
+
return `${STANDALONE_EXPORT_PREFIX}${String(index).padStart(5, "0")}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
98
147
|
// ---------------------------------------------------------------------------
|
|
99
148
|
// Input classification.
|
|
100
149
|
// ---------------------------------------------------------------------------
|
|
@@ -168,6 +217,168 @@ async function readSourceText(root, relPath) {
|
|
|
168
217
|
return readFile(path.join(root, relPath), "utf8");
|
|
169
218
|
}
|
|
170
219
|
|
|
220
|
+
async function readCanonicalRegularFile(filePath, label) {
|
|
221
|
+
if (typeof filePath !== "string" || !path.isAbsolute(filePath) || path.normalize(filePath) !== filePath) {
|
|
222
|
+
throw buildError(`${label} must be a normalized absolute path`);
|
|
223
|
+
}
|
|
224
|
+
const before = await lstat(filePath);
|
|
225
|
+
if (before.isSymbolicLink() || !before.isFile() || before.nlink !== 1) {
|
|
226
|
+
throw buildError(`${label} must be one ordinary non-symlink file`);
|
|
227
|
+
}
|
|
228
|
+
if (await realpath(filePath) !== filePath) {
|
|
229
|
+
throw buildError(`${label} must already be its canonical realpath`);
|
|
230
|
+
}
|
|
231
|
+
const bytes = await readFile(filePath);
|
|
232
|
+
const after = await lstat(filePath);
|
|
233
|
+
if (
|
|
234
|
+
before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode ||
|
|
235
|
+
before.size !== after.size || before.nlink !== after.nlink
|
|
236
|
+
) {
|
|
237
|
+
throw buildError(`${label} changed while it was read`);
|
|
238
|
+
}
|
|
239
|
+
return { bytes, mode: before.mode & 0o777, sha256: digestBytes(bytes) };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function readFixedSetCandidate(input, harnessVersion) {
|
|
243
|
+
if (
|
|
244
|
+
input === null || typeof input !== "object" || Array.isArray(input) ||
|
|
245
|
+
Object.keys(input).sort().join(",") !== "releaseReceiptPath,root"
|
|
246
|
+
) {
|
|
247
|
+
throw buildError("fixedSetCandidate must carry exactly releaseReceiptPath and root");
|
|
248
|
+
}
|
|
249
|
+
const receiptRecord = await readCanonicalRegularFile(
|
|
250
|
+
input.releaseReceiptPath,
|
|
251
|
+
"fixedSetCandidate.releaseReceiptPath",
|
|
252
|
+
);
|
|
253
|
+
let releaseReceipt;
|
|
254
|
+
try {
|
|
255
|
+
releaseReceipt = JSON.parse(receiptRecord.bytes.toString("utf8"));
|
|
256
|
+
} catch {
|
|
257
|
+
throw buildError("fixedSetCandidate release receipt is not valid JSON");
|
|
258
|
+
}
|
|
259
|
+
if (
|
|
260
|
+
releaseReceipt?.kind !== "skill-family.release-artifacts-manifest" ||
|
|
261
|
+
releaseReceipt?.schemaVersion !== 1 || !Array.isArray(releaseReceipt.tarballs)
|
|
262
|
+
) {
|
|
263
|
+
throw buildError("fixedSetCandidate release receipt has an unexpected contract");
|
|
264
|
+
}
|
|
265
|
+
const harnessTarballs = releaseReceipt.tarballs.filter(
|
|
266
|
+
(entry) => entry?.package === "skill-family-harness-node",
|
|
267
|
+
);
|
|
268
|
+
if (
|
|
269
|
+
harnessTarballs.length !== 1 || harnessTarballs[0].version !== harnessVersion ||
|
|
270
|
+
!/^[0-9a-f]{64}$/u.test(harnessTarballs[0].sha256 ?? "") ||
|
|
271
|
+
typeof harnessTarballs[0].filename !== "string" ||
|
|
272
|
+
path.basename(harnessTarballs[0].filename) !== harnessTarballs[0].filename ||
|
|
273
|
+
!Number.isSafeInteger(harnessTarballs[0].size) || harnessTarballs[0].size < 1
|
|
274
|
+
) {
|
|
275
|
+
throw buildError("fixedSetCandidate release receipt does not bind the exact Harness tarball");
|
|
276
|
+
}
|
|
277
|
+
const tarballPath = path.join(path.dirname(input.releaseReceiptPath), harnessTarballs[0].filename);
|
|
278
|
+
const tarball = await readCanonicalRegularFile(tarballPath, "fixedSetCandidate Harness tarball");
|
|
279
|
+
if (tarball.sha256 !== harnessTarballs[0].sha256 || tarball.bytes.length !== harnessTarballs[0].size) {
|
|
280
|
+
throw buildError("fixedSetCandidate Harness tarball differs from the release receipt");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (typeof input.root !== "string" || !path.isAbsolute(input.root) || path.normalize(input.root) !== input.root) {
|
|
284
|
+
throw buildError("fixedSetCandidate.root must be a normalized absolute path");
|
|
285
|
+
}
|
|
286
|
+
const rootBefore = await lstat(input.root);
|
|
287
|
+
if (rootBefore.isSymbolicLink() || !rootBefore.isDirectory() || await realpath(input.root) !== input.root) {
|
|
288
|
+
throw buildError("fixedSetCandidate.root must be a canonical real directory");
|
|
289
|
+
}
|
|
290
|
+
const files = new Map();
|
|
291
|
+
for (const relative of FIXED_SET_CANDIDATE_FILES) {
|
|
292
|
+
const record = await readCanonicalRegularFile(
|
|
293
|
+
path.join(input.root, ...relative.split("/")),
|
|
294
|
+
`fixedSetCandidate member ${relative}`,
|
|
295
|
+
);
|
|
296
|
+
files.set(relative, record);
|
|
297
|
+
}
|
|
298
|
+
const parse = (relative) => {
|
|
299
|
+
try { return JSON.parse(files.get(relative).bytes.toString("utf8")); }
|
|
300
|
+
catch { throw buildError(`fixedSetCandidate ${relative} is not valid JSON`); }
|
|
301
|
+
};
|
|
302
|
+
const manifest = parse("prebuild-manifest.json");
|
|
303
|
+
const prebuildReceipt = parse("prebuild-release-receipt.json");
|
|
304
|
+
const sbom = parse("prebuild-sbom.json");
|
|
305
|
+
const platformKeys = ["darwin-arm64", "darwin-x64", "linux-arm64-gnu", "linux-x64-gnu"];
|
|
306
|
+
const platformFacts = [
|
|
307
|
+
["darwin-arm64", "darwin", "arm64", "none"],
|
|
308
|
+
["darwin-x64", "darwin", "x64", "none"],
|
|
309
|
+
["linux-arm64-gnu", "linux", "arm64", "glibc"],
|
|
310
|
+
["linux-x64-gnu", "linux", "x64", "glibc"],
|
|
311
|
+
];
|
|
312
|
+
const nativeExports = [
|
|
313
|
+
"closeParentDirectory",
|
|
314
|
+
"openParentDirectory",
|
|
315
|
+
"platform",
|
|
316
|
+
"renameDirectoryNoReplace",
|
|
317
|
+
];
|
|
318
|
+
if (
|
|
319
|
+
manifest?.kind !== "skill-family.rename-directory-no-replace-prebuild-manifest" ||
|
|
320
|
+
manifest?.status !== "candidate" || manifest?.schemaVersion !== 2 ||
|
|
321
|
+
JSON.stringify(manifest.entries?.map((entry) => entry.platformKey)) !== JSON.stringify(platformKeys)
|
|
322
|
+
) {
|
|
323
|
+
throw buildError("fixedSetCandidate prebuild manifest is not the fixed candidate matrix");
|
|
324
|
+
}
|
|
325
|
+
if (
|
|
326
|
+
prebuildReceipt?.kind !== "skill-family.rename-directory-no-replace-prebuild-release-receipt" ||
|
|
327
|
+
prebuildReceipt?.status !== "candidate" || prebuildReceipt?.node?.napi !== 10 ||
|
|
328
|
+
prebuildReceipt.manifestSha256 !== files.get("prebuild-manifest.json").sha256
|
|
329
|
+
) {
|
|
330
|
+
throw buildError("fixedSetCandidate prebuild release receipt does not bind the manifest");
|
|
331
|
+
}
|
|
332
|
+
if (
|
|
333
|
+
sbom?.kind !== "skill-family.rename-directory-no-replace-prebuild-sbom" ||
|
|
334
|
+
sbom?.status !== "candidate"
|
|
335
|
+
) {
|
|
336
|
+
throw buildError("fixedSetCandidate SBOM has an unexpected contract");
|
|
337
|
+
}
|
|
338
|
+
if (
|
|
339
|
+
JSON.stringify(prebuildReceipt.inputs?.map((entry) => entry.platformKey)) !== JSON.stringify(platformKeys) ||
|
|
340
|
+
JSON.stringify(sbom.files?.map((entry) => entry.platformKey)) !== JSON.stringify(platformKeys)
|
|
341
|
+
) {
|
|
342
|
+
throw buildError("fixedSetCandidate receipt/SBOM platform matrix differs from the manifest");
|
|
343
|
+
}
|
|
344
|
+
for (const [index, entry] of manifest.entries.entries()) {
|
|
345
|
+
const [platformKey, os, arch, libc] = platformFacts[index];
|
|
346
|
+
const binary = files.get(entry.binary);
|
|
347
|
+
const receiptInput = prebuildReceipt.inputs?.find((item) => item.platformKey === entry.platformKey);
|
|
348
|
+
const sbomInput = sbom.files?.find((item) => item.platformKey === entry.platformKey);
|
|
349
|
+
if (
|
|
350
|
+
entry.platformKey !== platformKey || entry.os !== os || entry.arch !== arch || entry.libc !== libc ||
|
|
351
|
+
JSON.stringify(entry.exports) !== JSON.stringify(nativeExports) ||
|
|
352
|
+
!binary || entry.mode !== binary.mode || entry.sha256 !== binary.sha256 || entry.napi !== 10 ||
|
|
353
|
+
receiptInput?.binarySha256 !== binary.sha256 || sbomInput?.binary !== entry.binary ||
|
|
354
|
+
sbomInput?.sha256 !== binary.sha256
|
|
355
|
+
) {
|
|
356
|
+
throw buildError(`fixedSetCandidate prebuild binding mismatch: ${entry.platformKey}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
files,
|
|
361
|
+
provenance: {
|
|
362
|
+
status: "candidate",
|
|
363
|
+
stableRegistry: false,
|
|
364
|
+
releaseReceiptSha256: receiptRecord.sha256,
|
|
365
|
+
harnessTarball: {
|
|
366
|
+
filename: harnessTarballs[0].filename,
|
|
367
|
+
sha256: tarball.sha256,
|
|
368
|
+
size: tarball.bytes.length,
|
|
369
|
+
},
|
|
370
|
+
prebuildManifestSha256: files.get("prebuild-manifest.json").sha256,
|
|
371
|
+
prebuildReleaseReceiptSha256: files.get("prebuild-release-receipt.json").sha256,
|
|
372
|
+
files: FIXED_SET_CANDIDATE_FILES.map((relative) => ({
|
|
373
|
+
path: relative,
|
|
374
|
+
type: "regular",
|
|
375
|
+
mode: files.get(relative).mode,
|
|
376
|
+
sha256: files.get(relative).sha256,
|
|
377
|
+
})),
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
171
382
|
/**
|
|
172
383
|
* Reads one consumer schema file. The absolute root is only a read anchor and
|
|
173
384
|
* never enters any output byte; containment is re-proven lexically and by
|
|
@@ -478,6 +689,7 @@ async function generateStandaloneModule({
|
|
|
478
689
|
codegenTemplate,
|
|
479
690
|
standaloneCode,
|
|
480
691
|
isValidDateTime,
|
|
692
|
+
schemaIds,
|
|
481
693
|
}) {
|
|
482
694
|
// Both supported dialects register the same mechanically projected
|
|
483
695
|
// date-time implementation; the generated ESM references the same
|
|
@@ -494,7 +706,15 @@ async function generateStandaloneModule({
|
|
|
494
706
|
throw new Error(`standalone generation failed to compile schema: ${schema.$id}`);
|
|
495
707
|
}
|
|
496
708
|
}
|
|
497
|
-
const
|
|
709
|
+
const ids = [...(schemaIds ?? ordered.map((schema) => schema.$id))].sort();
|
|
710
|
+
for (const schemaId of ids) {
|
|
711
|
+
if (!ajv.getSchema(schemaId)) {
|
|
712
|
+
throw new Error(`standalone generation failed to compile schema: ${schemaId}`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
const moduleMap = Object.fromEntries(
|
|
716
|
+
ids.map((schemaId, index) => [standaloneExportName(index), schemaId]),
|
|
717
|
+
);
|
|
498
718
|
const raw = standaloneCode(ajv, moduleMap);
|
|
499
719
|
return rewriteGeneratedRequires(raw, runtimeDependencyMap);
|
|
500
720
|
}
|
|
@@ -598,21 +818,95 @@ function formatsSource(candidateIndexSourceText) {
|
|
|
598
818
|
].join("\n");
|
|
599
819
|
}
|
|
600
820
|
|
|
821
|
+
function migrationWorkspaceSource(workspaceSourceText) {
|
|
822
|
+
return [
|
|
823
|
+
'import { readFile } from "node:fs/promises";',
|
|
824
|
+
'import path from "node:path";',
|
|
825
|
+
"",
|
|
826
|
+
`export async ${extractFunctionBlock(workspaceSourceText, "readOptionalFile")}`,
|
|
827
|
+
"",
|
|
828
|
+
`export async ${extractFunctionBlock(workspaceSourceText, "readOptionalJson")}`,
|
|
829
|
+
"",
|
|
830
|
+
`export ${extractFunctionBlock(workspaceSourceText, "normalizeRelPath")}`,
|
|
831
|
+
"",
|
|
832
|
+
].join("\n");
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function projectMigrationSource(sourceText, migrationSchemaId) {
|
|
836
|
+
let projected = sourceText;
|
|
837
|
+
const applyOnce = (anchor, replacement, label) => {
|
|
838
|
+
const hits = projected.split(anchor).length - 1;
|
|
839
|
+
if (hits !== 1) {
|
|
840
|
+
throw new Error(
|
|
841
|
+
`migration bundle projection failed: the ${label} anchor matched ${hits} times (expected exactly 1)`,
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
projected = projected.replace(anchor, () => replacement);
|
|
845
|
+
};
|
|
846
|
+
applyOnce(
|
|
847
|
+
'import { findSchemaByObject } from "skill-family-contracts";\n',
|
|
848
|
+
"",
|
|
849
|
+
"contracts registry import",
|
|
850
|
+
);
|
|
851
|
+
applyOnce(
|
|
852
|
+
'import { HarnessError, readFileContained, resolveContained, validateContractDocument } from "skill-family-harness-node";',
|
|
853
|
+
[
|
|
854
|
+
'import { HarnessError } from "../harness/errors.mjs";',
|
|
855
|
+
'import { readFileContained, resolveContained } from "../harness/paths.mjs";',
|
|
856
|
+
'import { validateBySchemaId } from "../../validators.mjs";',
|
|
857
|
+
].join("\n"),
|
|
858
|
+
"harness import",
|
|
859
|
+
);
|
|
860
|
+
applyOnce(
|
|
861
|
+
'export const MIGRATION_MANIFEST_SCHEMA_ID = findSchemaByObject("migration-manifest").$id;',
|
|
862
|
+
`export const MIGRATION_MANIFEST_SCHEMA_ID = ${JSON.stringify(migrationSchemaId)};`,
|
|
863
|
+
"migration schema id",
|
|
864
|
+
);
|
|
865
|
+
const importBoundary = 'import { normalizeRelPath, readOptionalJson } from "./workspace.mjs";';
|
|
866
|
+
applyOnce(
|
|
867
|
+
importBoundary,
|
|
868
|
+
`${importBoundary}\n\nfunction validateContractDocument(document, { schemaId } = {}) {\n` +
|
|
869
|
+
` const outcome = validateBySchemaId(schemaId, document);\n` +
|
|
870
|
+
` return {\n` +
|
|
871
|
+
` valid: outcome.valid,\n` +
|
|
872
|
+
` errorCode: outcome.valid ? null : "SFC1001",\n` +
|
|
873
|
+
` errors: outcome.errors,\n` +
|
|
874
|
+
` data: outcome.valid ? structuredClone(document) : undefined,\n` +
|
|
875
|
+
` };\n` +
|
|
876
|
+
`}`,
|
|
877
|
+
"standalone validation adapter",
|
|
878
|
+
);
|
|
879
|
+
for (const leftover of [
|
|
880
|
+
'from "skill-family-contracts"',
|
|
881
|
+
'from "skill-family-harness-node"',
|
|
882
|
+
"findSchemaByObject(",
|
|
883
|
+
]) {
|
|
884
|
+
if (projected.includes(leftover)) {
|
|
885
|
+
throw new Error(`migration bundle projection left an external binding behind: ${leftover}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return projected;
|
|
889
|
+
}
|
|
890
|
+
|
|
601
891
|
// The standalone-validator binding transform replaces the frozen Ajv-backed
|
|
602
892
|
// getCollection implementation. The digest pins every byte of that source
|
|
603
893
|
// function: a rename, an added statement, or any other body drift fails the
|
|
604
894
|
// projection closed instead of being silently discarded by the replacement.
|
|
605
895
|
const GET_COLLECTION_AJV_SOURCE_SHA256 =
|
|
606
|
-
"
|
|
896
|
+
"11ec924269b95b69924b1cb4935856551ee04793ed394cdac02df99b31bcdba2";
|
|
607
897
|
|
|
608
898
|
const GET_COLLECTION_STANDALONE_BINDING = `function getCollection() {
|
|
609
899
|
if (collection) return collection;
|
|
610
900
|
const bound = {};
|
|
611
|
-
for (const kind of
|
|
612
|
-
|
|
901
|
+
for (const [kind, schemaId] of [
|
|
902
|
+
...[...VALIDATE_KINDS, INVENTORY_KIND, SURFACE_INVENTORY_KIND]
|
|
903
|
+
.map((kind) => [kind, documents[kind].$id]),
|
|
904
|
+
[SURFACE_DETECTORS_KIND, SURFACE_DETECTORS_SCHEMA_ID],
|
|
905
|
+
]) {
|
|
906
|
+
const validate = standaloneValidators[schemaId];
|
|
613
907
|
if (typeof validate !== "function") {
|
|
614
908
|
throw new Error(
|
|
615
|
-
\`standalone validator is missing the quickstart profile schema: \${
|
|
909
|
+
\`standalone validator is missing the quickstart profile schema: \${schemaId}\`,
|
|
616
910
|
);
|
|
617
911
|
}
|
|
618
912
|
bound[kind] = validate;
|
|
@@ -766,6 +1060,14 @@ function runnerSource() {
|
|
|
766
1060
|
"// mechanisms and the standalone-backed candidate validation entry.",
|
|
767
1061
|
'export * from "./runtime/harness/quickstart-profile.mjs";',
|
|
768
1062
|
'export { validateQuickstartProfileDocument } from "./runtime/contracts-candidate/index.mjs";',
|
|
1063
|
+
'export { validateBySchemaId } from "./validators.mjs";',
|
|
1064
|
+
'export { publishFileExclusive, publishFileOrReplace, replaceFileAtomic } from "./runtime/harness/atomic.mjs";',
|
|
1065
|
+
'export { acquireFilesystemLock, inspectFilesystemLock, releaseFilesystemLock, recoverFilesystemLock } from "./runtime/harness/token-lock.mjs";',
|
|
1066
|
+
'import { invokeFoundationMechanism as invokeHarnessMechanism } from "./runtime/harness/quickstart-profile.mjs";',
|
|
1067
|
+
'import { validateBySchemaId } from "./validators.mjs";',
|
|
1068
|
+
'export function invokeFoundationMechanism(request) {',
|
|
1069
|
+
' return invokeHarnessMechanism(request, { validateBySchemaId });',
|
|
1070
|
+
'}',
|
|
769
1071
|
"",
|
|
770
1072
|
].join("\n");
|
|
771
1073
|
}
|
|
@@ -823,6 +1125,7 @@ export async function buildQuickstartProfileProjection({
|
|
|
823
1125
|
consumerSchemaPaths,
|
|
824
1126
|
sourceRepository,
|
|
825
1127
|
sourceBaseCommit,
|
|
1128
|
+
fixedSetCandidate,
|
|
826
1129
|
} = {}) {
|
|
827
1130
|
const prefix = assertContainedPosixPath(targetPrefix, "targetPrefix");
|
|
828
1131
|
if (typeof consumerSchemaRoot !== "string" || !path.isAbsolute(consumerSchemaRoot)) {
|
|
@@ -854,6 +1157,10 @@ export async function buildQuickstartProfileProjection({
|
|
|
854
1157
|
sources.errorCodes = await readSourceText(contractsRoot, "src/error-codes.json");
|
|
855
1158
|
sources.operationRequest = await readSourceText(contractsRoot, "src/schemas/operation-request.schema.json");
|
|
856
1159
|
sources.operationResult = await readSourceText(contractsRoot, "src/schemas/operation-result.schema.json");
|
|
1160
|
+
sources.migrationManifestSchema = await readSourceText(
|
|
1161
|
+
contractsRoot,
|
|
1162
|
+
"src/schemas/migration-manifest.schema.json",
|
|
1163
|
+
);
|
|
857
1164
|
sources.candidateIndex = await readSourceText(contractsRoot, "candidate/quickstart-profile/index.mjs");
|
|
858
1165
|
const candidateSchemaTexts = {};
|
|
859
1166
|
for (const [fileName] of FOUNDATION_SCHEMA_FILES) {
|
|
@@ -864,17 +1171,31 @@ export async function buildQuickstartProfileProjection({
|
|
|
864
1171
|
}
|
|
865
1172
|
sources.harnessIndex = await readSourceText(harnessRoot, "src/index.mjs");
|
|
866
1173
|
sources.harnessCandidate = await readSourceText(harnessRoot, "candidate/quickstart-profile.mjs");
|
|
1174
|
+
sources.harnessMechanismsCli = await readSourceText(harnessRoot, "candidate/mechanisms-cli.mjs");
|
|
867
1175
|
sources.harnessClosure = await readSourceText(harnessRoot, "src/closure.mjs");
|
|
868
1176
|
sources.harnessErrors = await readSourceText(harnessRoot, "src/errors.mjs");
|
|
869
1177
|
sources.harnessPaths = await readSourceText(harnessRoot, "src/paths.mjs");
|
|
1178
|
+
sources.harnessAtomic = await readSourceText(harnessRoot, "src/atomic.mjs");
|
|
1179
|
+
sources.harnessTokenLock = await readSourceText(harnessRoot, "src/token-lock.mjs");
|
|
870
1180
|
// The package manifests are recorded as complete original bytes below, so
|
|
871
1181
|
// every provenance path digest recomputes from the real file.
|
|
872
1182
|
const contractsPackageJsonText = await readSourceText(contractsRoot, "package.json");
|
|
873
1183
|
const harnessPackageJsonText = await readSourceText(harnessRoot, "package.json");
|
|
874
1184
|
const kitPackageJsonText = await readSourceText(kitRoot, "package.json");
|
|
875
1185
|
const contractsVersion = JSON.parse(contractsPackageJsonText).version;
|
|
1186
|
+
const harnessVersion = JSON.parse(harnessPackageJsonText).version;
|
|
1187
|
+
const fixedSet = fixedSetCandidate === undefined
|
|
1188
|
+
? null
|
|
1189
|
+
: await readFixedSetCandidate(fixedSetCandidate, harnessVersion);
|
|
876
1190
|
const kitBuilderSource = await readSourceText(kitRoot, "candidate/profile-bundle.mjs");
|
|
877
1191
|
const kitCliSource = await readSourceText(kitRoot, "candidate/projection-bundle-cli.mjs");
|
|
1192
|
+
const kitAdoptionCliSource = await readSourceText(kitRoot, "candidate/adoption-cli.mjs");
|
|
1193
|
+
const kitAdoptionMechanismsSource = await readSourceText(
|
|
1194
|
+
kitRoot,
|
|
1195
|
+
"candidate/adoption-mechanisms.mjs",
|
|
1196
|
+
);
|
|
1197
|
+
const kitMigrationSource = await readSourceText(kitRoot, "src/migration.mjs");
|
|
1198
|
+
const kitWorkspaceSource = await readSourceText(kitRoot, "src/workspace.mjs");
|
|
878
1199
|
|
|
879
1200
|
// --- Consumer schema intake (contained reads; the absolute root never
|
|
880
1201
|
// --- enters any output byte). ---
|
|
@@ -893,7 +1214,14 @@ export async function buildQuickstartProfileProjection({
|
|
|
893
1214
|
const foundationSchemaDocuments = [
|
|
894
1215
|
{ name: "operation-request", text: sources.operationRequest },
|
|
895
1216
|
{ name: "operation-result", text: sources.operationResult },
|
|
896
|
-
|
|
1217
|
+
{ name: "migration-manifest", text: sources.migrationManifestSchema },
|
|
1218
|
+
...[
|
|
1219
|
+
"resource.schema.json",
|
|
1220
|
+
"task.schema.json",
|
|
1221
|
+
"result.schema.json",
|
|
1222
|
+
"consumer-schema-inventory.schema.json",
|
|
1223
|
+
"harness-surface-inventory.schema.json",
|
|
1224
|
+
].map((fileName) => ({
|
|
897
1225
|
name: fileName,
|
|
898
1226
|
text: candidateSchemaTexts[fileName],
|
|
899
1227
|
})),
|
|
@@ -938,6 +1266,10 @@ export async function buildQuickstartProfileProjection({
|
|
|
938
1266
|
codegenTemplate,
|
|
939
1267
|
standaloneCode,
|
|
940
1268
|
isValidDateTime,
|
|
1269
|
+
schemaIds: [
|
|
1270
|
+
...schemas2020.map((schema) => schema.$id),
|
|
1271
|
+
"https://contracts.skill-family.example/candidate/quickstart-profile/v2/harness-surface-detectors.json",
|
|
1272
|
+
],
|
|
941
1273
|
});
|
|
942
1274
|
const schemasDraft07 = graph["draft-07"].map((record) => record.document);
|
|
943
1275
|
const withDraft07 = schemasDraft07.length > 0;
|
|
@@ -973,14 +1305,55 @@ export async function buildQuickstartProfileProjection({
|
|
|
973
1305
|
if (files.has(relPath)) throw new Error(`duplicate bundle member: ${relPath}`);
|
|
974
1306
|
files.set(relPath, text);
|
|
975
1307
|
};
|
|
1308
|
+
const setBytes = (relPath, bytes) => {
|
|
1309
|
+
if (files.has(relPath)) throw new Error(`duplicate bundle member: ${relPath}`);
|
|
1310
|
+
files.set(relPath, Buffer.from(bytes));
|
|
1311
|
+
};
|
|
976
1312
|
|
|
977
1313
|
setText("runner.mjs", runnerSource());
|
|
1314
|
+
setText(
|
|
1315
|
+
"mechanisms-cli.mjs",
|
|
1316
|
+
projectModuleWithImportMap(
|
|
1317
|
+
sources.harnessMechanismsCli,
|
|
1318
|
+
HARNESS_CLI_IMPORT_MAP,
|
|
1319
|
+
"harness candidate/mechanisms-cli.mjs",
|
|
1320
|
+
),
|
|
1321
|
+
);
|
|
1322
|
+
if (fixedSet) {
|
|
1323
|
+
for (const [relative, record] of fixedSet.files) {
|
|
1324
|
+
const bundlePath = `rename-directory-no-replace/${relative}`;
|
|
1325
|
+
if (relative === "rename-directory-no-replace.mjs") {
|
|
1326
|
+
setText(
|
|
1327
|
+
bundlePath,
|
|
1328
|
+
projectModuleWithImportMap(
|
|
1329
|
+
record.bytes.toString("utf8"),
|
|
1330
|
+
new Map([["skill-family-contracts", "../runtime/contracts/index.mjs"]]),
|
|
1331
|
+
"fixed-set publication mechanism",
|
|
1332
|
+
),
|
|
1333
|
+
);
|
|
1334
|
+
} else if (relative.startsWith(FIXED_SET_BINARY_PREFIX)) {
|
|
1335
|
+
setBytes(bundlePath, record.bytes);
|
|
1336
|
+
} else {
|
|
1337
|
+
setText(bundlePath, record.bytes.toString("utf8"));
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
setText("adoption-cli.mjs", kitAdoptionCliSource);
|
|
1342
|
+
setText(
|
|
1343
|
+
"adoption-mechanisms.mjs",
|
|
1344
|
+
projectModuleWithImportMap(
|
|
1345
|
+
kitAdoptionMechanismsSource,
|
|
1346
|
+
ADOPTION_IMPORT_MAP,
|
|
1347
|
+
"engineering kit candidate/adoption-mechanisms.mjs",
|
|
1348
|
+
),
|
|
1349
|
+
);
|
|
978
1350
|
setText("validators.mjs", validatorsSource());
|
|
979
1351
|
for (const [fileName, bundlePath] of FOUNDATION_SCHEMA_FILES) {
|
|
980
1352
|
setText(bundlePath, candidateSchemaTexts[fileName]);
|
|
981
1353
|
}
|
|
982
1354
|
setText("schemas/foundation/operation-request.schema.json", sources.operationRequest);
|
|
983
1355
|
setText("schemas/foundation/operation-result.schema.json", sources.operationResult);
|
|
1356
|
+
setText("schemas/foundation/migration-manifest.schema.json", sources.migrationManifestSchema);
|
|
984
1357
|
for (const record of consumerRecords) {
|
|
985
1358
|
setText(`schemas/consumer/${record.path}`, `${JSON.stringify(record.document, null, 2)}\n`);
|
|
986
1359
|
}
|
|
@@ -994,6 +1367,20 @@ export async function buildQuickstartProfileProjection({
|
|
|
994
1367
|
projectModuleWithImportMap(sources.harnessErrors, new Map([["skill-family-contracts", "../contracts/index.mjs"]]), "harness src/errors.mjs"),
|
|
995
1368
|
);
|
|
996
1369
|
setText("runtime/harness/paths.mjs", projectModuleWithImportMap(sources.harnessPaths, new Map(), "harness src/paths.mjs"));
|
|
1370
|
+
setText(
|
|
1371
|
+
"runtime/harness/atomic.mjs",
|
|
1372
|
+
projectModuleWithImportMap(sources.harnessAtomic, HARNESS_ATOMIC_IMPORT_MAP, "harness src/atomic.mjs"),
|
|
1373
|
+
);
|
|
1374
|
+
setText(
|
|
1375
|
+
"runtime/harness/token-lock.mjs",
|
|
1376
|
+
projectModuleWithImportMap(sources.harnessTokenLock, HARNESS_TOKEN_LOCK_IMPORT_MAP, "harness src/token-lock.mjs"),
|
|
1377
|
+
);
|
|
1378
|
+
const migrationManifestSchemaId = JSON.parse(sources.migrationManifestSchema).$id;
|
|
1379
|
+
setText(
|
|
1380
|
+
"runtime/kit/migration.mjs",
|
|
1381
|
+
projectMigrationSource(kitMigrationSource, migrationManifestSchemaId),
|
|
1382
|
+
);
|
|
1383
|
+
setText("runtime/kit/workspace.mjs", migrationWorkspaceSource(kitWorkspaceSource));
|
|
997
1384
|
setText("runtime/contracts/index.mjs", contractsIndexSource());
|
|
998
1385
|
setText("runtime/contracts/errors.mjs", sources.contractsErrors);
|
|
999
1386
|
setText("runtime/contracts/error-codes.json", sources.errorCodes);
|
|
@@ -1005,12 +1392,15 @@ export async function buildQuickstartProfileProjection({
|
|
|
1005
1392
|
setText("runtime/generated/validate-draft-07.mjs", generatedDraft07);
|
|
1006
1393
|
}
|
|
1007
1394
|
const sortById = (a, b) => (a.$id < b.$id ? -1 : 1);
|
|
1008
|
-
const entries2020 = [
|
|
1009
|
-
.
|
|
1010
|
-
.
|
|
1395
|
+
const entries2020 = [
|
|
1396
|
+
...schemas2020.map((schema) => schema.$id),
|
|
1397
|
+
"https://contracts.skill-family.example/candidate/quickstart-profile/v2/harness-surface-detectors.json",
|
|
1398
|
+
]
|
|
1399
|
+
.sort()
|
|
1400
|
+
.map((schemaId, index) => ({ schemaId, exportName: standaloneExportName(index) }));
|
|
1011
1401
|
const entriesDraft07 = [...schemasDraft07]
|
|
1012
1402
|
.sort(sortById)
|
|
1013
|
-
.map((schema, index) => ({ schemaId: schema.$id, exportName:
|
|
1403
|
+
.map((schema, index) => ({ schemaId: schema.$id, exportName: standaloneExportName(index) }));
|
|
1014
1404
|
setText("runtime/generated/standalone-map.mjs", standaloneMapSource({ entries2020, entriesDraft07 }));
|
|
1015
1405
|
setText("runtime/generated/formats.mjs", formatsModuleSource);
|
|
1016
1406
|
if (carriesUcs2length) {
|
|
@@ -1049,7 +1439,10 @@ export async function buildQuickstartProfileProjection({
|
|
|
1049
1439
|
|
|
1050
1440
|
// --- Provenance (payload digest excludes the provenance file itself). ---
|
|
1051
1441
|
const payloadFiles = [...files.entries()]
|
|
1052
|
-
.map(([filePath,
|
|
1442
|
+
.map(([filePath, content]) => ({
|
|
1443
|
+
path: filePath,
|
|
1444
|
+
sha256: digestBytes(Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8")),
|
|
1445
|
+
}))
|
|
1053
1446
|
.sort((a, b) => (a.path < b.path ? -1 : 1));
|
|
1054
1447
|
const sha256Of = (text) => digestBytes(Buffer.from(text, "utf8"));
|
|
1055
1448
|
const foundationRecords = [
|
|
@@ -1057,6 +1450,7 @@ export async function buildQuickstartProfileProjection({
|
|
|
1057
1450
|
["packages/skill-family-contracts/src/index.mjs", sources.contractsIndex, "imported-surface"],
|
|
1058
1451
|
["packages/skill-family-contracts/src/schemas/operation-request.schema.json", sources.operationRequest, "projected"],
|
|
1059
1452
|
["packages/skill-family-contracts/src/schemas/operation-result.schema.json", sources.operationResult, "projected"],
|
|
1453
|
+
["packages/skill-family-contracts/src/schemas/migration-manifest.schema.json", sources.migrationManifestSchema, "projected"],
|
|
1060
1454
|
["packages/skill-family-contracts/src/errors.mjs", sources.contractsErrors, "projected"],
|
|
1061
1455
|
["packages/skill-family-contracts/src/error-codes.json", sources.errorCodes, "projected"],
|
|
1062
1456
|
["packages/skill-family-contracts/src/audit-surface.mjs", sources.auditSurface, "extraction-input"],
|
|
@@ -1064,16 +1458,25 @@ export async function buildQuickstartProfileProjection({
|
|
|
1064
1458
|
["packages/skill-family-contracts/candidate/quickstart-profile/resource.schema.json", candidateSchemaTexts["resource.schema.json"], "projected"],
|
|
1065
1459
|
["packages/skill-family-contracts/candidate/quickstart-profile/task.schema.json", candidateSchemaTexts["task.schema.json"], "projected"],
|
|
1066
1460
|
["packages/skill-family-contracts/candidate/quickstart-profile/result.schema.json", candidateSchemaTexts["result.schema.json"], "projected"],
|
|
1461
|
+
["packages/skill-family-contracts/candidate/quickstart-profile/consumer-schema-inventory.schema.json", candidateSchemaTexts["consumer-schema-inventory.schema.json"], "projected"],
|
|
1462
|
+
["packages/skill-family-contracts/candidate/quickstart-profile/harness-surface-inventory.schema.json", candidateSchemaTexts["harness-surface-inventory.schema.json"], "projected"],
|
|
1067
1463
|
["packages/skill-family-contracts/candidate/quickstart-profile/index.mjs", sources.candidateIndex, "extraction-input"],
|
|
1068
1464
|
["packages/skill-family-harness-node/package.json", harnessPackageJsonText, "identity"],
|
|
1069
1465
|
["packages/skill-family-harness-node/src/index.mjs", sources.harnessIndex, "imported-surface"],
|
|
1070
1466
|
["packages/skill-family-harness-node/candidate/quickstart-profile.mjs", sources.harnessCandidate, "projected"],
|
|
1467
|
+
["packages/skill-family-harness-node/candidate/mechanisms-cli.mjs", sources.harnessMechanismsCli, "projected"],
|
|
1071
1468
|
["packages/skill-family-harness-node/src/closure.mjs", sources.harnessClosure, "projected"],
|
|
1072
1469
|
["packages/skill-family-harness-node/src/errors.mjs", sources.harnessErrors, "projected"],
|
|
1073
1470
|
["packages/skill-family-harness-node/src/paths.mjs", sources.harnessPaths, "projected"],
|
|
1471
|
+
["packages/skill-family-harness-node/src/atomic.mjs", sources.harnessAtomic, "projected"],
|
|
1472
|
+
["packages/skill-family-harness-node/src/token-lock.mjs", sources.harnessTokenLock, "projected"],
|
|
1074
1473
|
["packages/skill-family-engineering-kit/package.json", kitPackageJsonText, "identity"],
|
|
1075
1474
|
["packages/skill-family-engineering-kit/candidate/profile-bundle.mjs", kitBuilderSource, "builder"],
|
|
1076
1475
|
["packages/skill-family-engineering-kit/candidate/projection-bundle-cli.mjs", kitCliSource, "builder"],
|
|
1476
|
+
["packages/skill-family-engineering-kit/candidate/adoption-cli.mjs", kitAdoptionCliSource, "projected"],
|
|
1477
|
+
["packages/skill-family-engineering-kit/candidate/adoption-mechanisms.mjs", kitAdoptionMechanismsSource, "projected"],
|
|
1478
|
+
["packages/skill-family-engineering-kit/src/migration.mjs", kitMigrationSource, "projected"],
|
|
1479
|
+
["packages/skill-family-engineering-kit/src/workspace.mjs", kitWorkspaceSource, "extraction-input"],
|
|
1077
1480
|
]
|
|
1078
1481
|
.map(([recordPath, text, role]) => ({ path: recordPath, sha256: sha256Of(text), role }))
|
|
1079
1482
|
.sort((a, b) => (a.path < b.path ? -1 : 1));
|
|
@@ -1105,6 +1508,7 @@ export async function buildQuickstartProfileProjection({
|
|
|
1105
1508
|
baseCommit,
|
|
1106
1509
|
foundation: foundationRecords,
|
|
1107
1510
|
thirdParty: thirdPartyRecords,
|
|
1511
|
+
...(fixedSet ? { fixedSetCandidate: fixedSet.provenance } : {}),
|
|
1108
1512
|
consumerSchemas: consumerRecords
|
|
1109
1513
|
.map((record) => ({
|
|
1110
1514
|
path: record.path,
|
|
@@ -1130,11 +1534,13 @@ export async function buildQuickstartProfileProjection({
|
|
|
1130
1534
|
setText(PROVENANCE_FILE, `${JSON.stringify(provenance, null, 2)}\n`);
|
|
1131
1535
|
|
|
1132
1536
|
const entries = [...files.entries()]
|
|
1133
|
-
.map(([filePath,
|
|
1537
|
+
.map(([filePath, content]) => ({ path: `${prefix}/${filePath}`, content }))
|
|
1134
1538
|
.sort((a, b) => (a.path < b.path ? -1 : 1))
|
|
1135
|
-
.map(({ path: entryPath,
|
|
1539
|
+
.map(({ path: entryPath, content }) => ({
|
|
1136
1540
|
path: entryPath,
|
|
1137
|
-
content:
|
|
1541
|
+
content: Buffer.isBuffer(content)
|
|
1542
|
+
? { base64: content.toString("base64") }
|
|
1543
|
+
: { text: content },
|
|
1138
1544
|
expect: { state: "absent" },
|
|
1139
1545
|
}));
|
|
1140
1546
|
return {
|
|
@@ -1147,4 +1553,47 @@ export async function buildQuickstartProfileProjection({
|
|
|
1147
1553
|
};
|
|
1148
1554
|
}
|
|
1149
1555
|
|
|
1556
|
+
/**
|
|
1557
|
+
* Inventory-gated projection entry point.
|
|
1558
|
+
*
|
|
1559
|
+
* The inventory is the consumer's complete local Schema ownership statement.
|
|
1560
|
+
* Only records assigned to `foundation-bundle` enter the offline validator;
|
|
1561
|
+
* retained records remain consumer-owned but must carry an explicit reason and
|
|
1562
|
+
* exit criterion. The returned ownership proof is deliberately outside the
|
|
1563
|
+
* Bundle payload so consumer domain explanations never enter Foundation code.
|
|
1564
|
+
*/
|
|
1565
|
+
export async function buildQuickstartProfileProjectionFromInventory({
|
|
1566
|
+
targetPrefix = DEFAULT_TARGET_PREFIX,
|
|
1567
|
+
consumerRoot,
|
|
1568
|
+
inventory,
|
|
1569
|
+
sourceRepository,
|
|
1570
|
+
sourceBaseCommit,
|
|
1571
|
+
fixedSetCandidate,
|
|
1572
|
+
} = {}) {
|
|
1573
|
+
if (typeof consumerRoot !== "string" || !path.isAbsolute(consumerRoot)) {
|
|
1574
|
+
throw buildError("consumerRoot must be an absolute build-time directory");
|
|
1575
|
+
}
|
|
1576
|
+
const ownership = await verifyConsumerSchemaInventory({ root: consumerRoot, inventory });
|
|
1577
|
+
if (ownership.bundleSchemaPaths.length === 0) {
|
|
1578
|
+
throw buildError("inventory must assign at least one schema to foundation-bundle");
|
|
1579
|
+
}
|
|
1580
|
+
const projection = await buildQuickstartProfileProjection({
|
|
1581
|
+
targetPrefix,
|
|
1582
|
+
consumerSchemaRoot: consumerRoot,
|
|
1583
|
+
consumerSchemaPaths: ownership.bundleSchemaPaths,
|
|
1584
|
+
sourceRepository,
|
|
1585
|
+
sourceBaseCommit,
|
|
1586
|
+
fixedSetCandidate,
|
|
1587
|
+
});
|
|
1588
|
+
return {
|
|
1589
|
+
...projection,
|
|
1590
|
+
consumerSchemaOwnership: Object.freeze({
|
|
1591
|
+
inventoryDigest: digestBytes(Buffer.from(canonicalJson(inventory), "utf8")),
|
|
1592
|
+
schemaPaths: Object.freeze([...ownership.schemaPaths]),
|
|
1593
|
+
bundleSchemaPaths: Object.freeze([...ownership.bundleSchemaPaths]),
|
|
1594
|
+
retainedSchemas: Object.freeze(ownership.retainedSchemas.map((record) => Object.freeze(record))),
|
|
1595
|
+
}),
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1150
1599
|
export const QUICKSTART_PROFILE_TARGET_PREFIX = DEFAULT_TARGET_PREFIX;
|