skill-family-engineering-kit 0.2.0 → 0.3.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/CHANGELOG.zh-CN.md +43 -0
  3. package/NOTICE +10 -0
  4. package/README.md +160 -55
  5. package/README.zh-CN.md +210 -0
  6. package/candidate/index.mjs +4 -0
  7. package/candidate/profile-bundle.mjs +1150 -0
  8. package/candidate/projection-bundle-cli.mjs +64 -0
  9. package/candidate/skill-naming-cli.mjs +59 -0
  10. package/candidate/skill-naming-policy.json +46 -0
  11. package/candidate/skill-naming.mjs +280 -0
  12. package/docs/404.html +1434 -408
  13. package/docs/agents/architecture-routing/index.html +1944 -0
  14. package/docs/agents/capability-catalog.en.json +1564 -0
  15. package/docs/agents/capability-catalog.json +935 -0
  16. package/docs/agents/capability-catalog.schema.json +179 -0
  17. package/docs/agents/capability-catalog.zh-CN.json +1564 -0
  18. package/docs/agents/index.html +1914 -0
  19. package/docs/architecture/index.html +1706 -497
  20. package/docs/assets/javascripts/lunr/tinyseg.js +2 -2
  21. package/docs/assets/javascripts/lunr/wordcut.js +37 -37
  22. package/docs/en/agents/architecture-routing/index.html +1944 -0
  23. package/docs/en/agents/index.html +1925 -0
  24. package/docs/en/architecture/index.html +2187 -0
  25. package/docs/en/examples-and-fixtures/index.html +1888 -0
  26. package/docs/en/help/index.html +2035 -0
  27. package/docs/en/index.html +1895 -0
  28. package/docs/en/licensing/index.html +1917 -0
  29. package/docs/en/migration/index.html +2335 -0
  30. package/docs/en/quickstart/index.html +1919 -0
  31. package/docs/en/recipes/adapter-text-closure/index.html +2000 -0
  32. package/docs/en/recipes/adopt-existing-repository/index.html +1987 -0
  33. package/docs/en/recipes/deterministic-human-report/index.html +1998 -0
  34. package/docs/en/recipes/domain-schema-validation/index.html +2009 -0
  35. package/docs/en/recipes/durable-local-state/index.html +2009 -0
  36. package/docs/en/recipes/host-profile-integration/index.html +2001 -0
  37. package/docs/en/recipes/index.html +1877 -0
  38. package/docs/en/recipes/safe-filesystem-and-atomic-write/index.html +1994 -0
  39. package/docs/en/reference/api/index.html +1941 -0
  40. package/docs/en/reference/compatibility/index.html +2036 -0
  41. package/docs/en/reference/failure-and-side-effect-matrix/index.html +2216 -0
  42. package/docs/examples-and-fixtures/index.html +1871 -0
  43. package/docs/git-lifecycle/index.html +1508 -482
  44. package/docs/help/index.html +1655 -554
  45. package/docs/index.html +1479 -450
  46. package/docs/integration/audit/failure-evidence/index.html +1488 -462
  47. package/docs/integration/audit/independence/index.html +1513 -487
  48. package/docs/integration/audit/index.html +1514 -488
  49. package/docs/integration/audit/mutation-taxonomy/index.html +1548 -522
  50. package/docs/integration/audit/version-compatibility/index.html +1489 -463
  51. package/docs/licensing/index.html +1917 -0
  52. package/docs/migration/index.html +1679 -586
  53. package/docs/public/status/index.html +1542 -516
  54. package/docs/quickstart/index.html +1589 -539
  55. package/docs/recipes/adapter-text-closure/index.html +2000 -0
  56. package/docs/recipes/adopt-existing-repository/index.html +1987 -0
  57. package/docs/recipes/deterministic-human-report/index.html +1998 -0
  58. package/docs/recipes/domain-schema-validation/index.html +2009 -0
  59. package/docs/recipes/durable-local-state/index.html +2009 -0
  60. package/docs/recipes/host-profile-integration/index.html +2001 -0
  61. package/docs/recipes/index.html +1877 -0
  62. package/docs/recipes/safe-filesystem-and-atomic-write/index.html +1994 -0
  63. package/docs/reference/api/contracts/index.html +2558 -0
  64. package/docs/reference/api/engineering-kit/index.html +2577 -0
  65. package/docs/reference/api/harness/index.html +2634 -0
  66. package/docs/reference/api/index.html +1881 -0
  67. package/docs/reference/compatibility/index.html +2036 -0
  68. package/docs/reference/failure-and-side-effect-matrix/index.html +2216 -0
  69. package/docs/search/search_index.json +1 -1
  70. package/docs/setup/index.html +1494 -468
  71. package/docs/sitemap.xml +152 -0
  72. package/package.json +16 -5
  73. package/release-notes/0.2.1.yaml +23 -0
  74. package/release-notes/0.3.0.yaml +25 -0
@@ -0,0 +1,1150 @@
1
+ import { readFile, realpath, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import {
6
+ CONTRACTS_VERSION,
7
+ canonicalJson,
8
+ } from "skill-family-contracts";
9
+ import {
10
+ QUICKSTART_PROFILE_ID,
11
+ QUICKSTART_PROFILE_VERSION,
12
+ } from "skill-family-contracts/candidate/quickstart-profile";
13
+ import { digestBytes } from "skill-family-harness-node";
14
+
15
+ /**
16
+ * Candidate Quickstart Profile v2 offline bundle builder (unstable).
17
+ *
18
+ * The bundle replaces the v1 node_modules half-closure with a self-contained,
19
+ * deterministically generated projection:
20
+ *
21
+ * - the Harness quickstart mechanisms and their minimal source closure are
22
+ * projected verbatim from the real Foundation sources, with imports
23
+ * rewritten through a fixed bundle map;
24
+ * - canonical digest, JSON-boundary probe, and error-normalization code keep
25
+ * their original Foundation bytes (fixed-anchor extraction, never a second
26
+ * hand-written algorithm);
27
+ * - the precisely locked Ajv 8.20.0 generates ESM standalone validators at
28
+ * build time; the runtime carries no Ajv package, only the generated code
29
+ * plus the mechanically projected `ucs2length` / `equal` / fast-deep-equal
30
+ * helpers it actually references;
31
+ * - consumer schemas join the same $id index under strict fail-closed rules
32
+ * (unique $id, supported dialect, no cross-dialect refs, date-time only);
33
+ * - provenance binds repository/base-commit identities, every Foundation and
34
+ * third-party source that influences the output, each consumer schema, and
35
+ * the payload set (excluding the provenance file itself).
36
+ *
37
+ * The builder only reads; it never consults the network, the clock, or Git.
38
+ * The returned entries are ordinary `skill-family.projection-manifest`
39
+ * entries; runProjection remains the only writer and authorization boundary.
40
+ */
41
+
42
+ const DEFAULT_TARGET_PREFIX = "foundation/quickstart-profile";
43
+ const PROVENANCE_FILE = "foundation-projection.json";
44
+ // Internal Ajv standalone-codegen replacement anchor only: it must not parse
45
+ // as an npm scope/package coordinate (no "@" scope form, no valid package
46
+ // name), it is never added to the public coordinate allowlist, and it never
47
+ // survives into the final bundle bytes — rewriteGeneratedRequires replaces
48
+ // every occurrence with a hoisted bundle-relative ESM import.
49
+ const FORMAT_RUNTIME_SPECIFIER = "__SKILL_FAMILY_BUNDLE_FORMAT_RUNTIME__";
50
+
51
+ // The Ajv `_` codegen tag serializes interpolated values, so the formats
52
+ // require below is written literally; this guard keeps the literal and the
53
+ // dependency-map specifier aligned.
54
+ if (!'require("__SKILL_FAMILY_BUNDLE_FORMAT_RUNTIME__").default'.includes(FORMAT_RUNTIME_SPECIFIER)) {
55
+ throw new Error("FORMAT_RUNTIME_SPECIFIER drifted from the formats code literal");
56
+ }
57
+
58
+ const FOUNDATION_SCHEMA_FILES = Object.freeze([
59
+ ["protocol.json", "schemas/foundation/protocol.json"],
60
+ ["resource.schema.json", "schemas/foundation/resource.schema.json"],
61
+ ["task.schema.json", "schemas/foundation/task.schema.json"],
62
+ ["result.schema.json", "schemas/foundation/result.schema.json"],
63
+ ]);
64
+
65
+ const HARNESS_IMPORT_MAP = new Map([
66
+ ["skill-family-contracts", "../contracts/index.mjs"],
67
+ ["skill-family-contracts/candidate/quickstart-profile", "../contracts-candidate/index.mjs"],
68
+ ["../src/closure.mjs", "./closure.mjs"],
69
+ ["../src/errors.mjs", "./errors.mjs"],
70
+ ]);
71
+
72
+ const DIALECT_URIS = Object.freeze({
73
+ "draft-07": "http://json-schema.org/draft-07/schema#",
74
+ "2020-12": "https://json-schema.org/draft/2020-12/schema",
75
+ });
76
+
77
+ const SUPPORTED_FORMATS = Object.freeze(["date-time"]);
78
+
79
+ // These fragments are the exact case-sensitive words rejected by the
80
+ // bundle-wide byte scan. Caller-provided source identities enter provenance
81
+ // verbatim, so they must pass the same boundary before bundle assembly.
82
+ const FORBIDDEN_SOURCE_IDENTITY_FRAGMENTS = Object.freeze([
83
+ "skill-family-audit",
84
+ "conformance",
85
+ "behavior",
86
+ "runtime-audit",
87
+ "release-audit",
88
+ "operation: audit",
89
+ ]);
90
+ const C0_CONTROL_PATTERN = /[\u0000-\u001f]/u;
91
+
92
+ const requireFromKit = createRequire(import.meta.url);
93
+
94
+ function buildError(message) {
95
+ return new TypeError(`buildQuickstartProfileProjection: ${message}`);
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Input classification.
100
+ // ---------------------------------------------------------------------------
101
+
102
+ // Relative POSIX path: one or more segments of [A-Za-z0-9._-]. The segment
103
+ // charset mechanically excludes absolute paths, backslashes, and NUL bytes;
104
+ // the explicit segment check additionally excludes "." and "..".
105
+ const CONTAINED_POSIX_PATH_PATTERN = /^[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*$/;
106
+
107
+ function assertContainedPosixPath(value, label) {
108
+ if (
109
+ typeof value !== "string" ||
110
+ !CONTAINED_POSIX_PATH_PATTERN.test(value) ||
111
+ value.split("/").some((segment) => segment === "." || segment === "..")
112
+ ) {
113
+ throw buildError(`${label} must be a contained relative POSIX path: ${JSON.stringify(value)}`);
114
+ }
115
+ return value;
116
+ }
117
+
118
+ function assertSourceIdentity(value, label) {
119
+ if (typeof value !== "string" || value.trim().length === 0) {
120
+ throw buildError(`${label} must be a non-empty caller-provided identity string`);
121
+ }
122
+ if (C0_CONTROL_PATTERN.test(value)) {
123
+ throw buildError(`${label} must not contain C0 control characters`);
124
+ }
125
+ const classified = value.trim();
126
+ if (
127
+ path.posix.isAbsolute(classified) ||
128
+ path.win32.isAbsolute(classified) ||
129
+ /^file:/iu.test(classified)
130
+ ) {
131
+ throw buildError(`${label} must not be an absolute path or file: URL`);
132
+ }
133
+ if (FORBIDDEN_SOURCE_IDENTITY_FRAGMENTS.some((fragment) => value.includes(fragment))) {
134
+ throw buildError(`${label} must not contain a forbidden bundle word`);
135
+ }
136
+ return value;
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Source location and contained reads.
141
+ // ---------------------------------------------------------------------------
142
+
143
+ async function packageRootOf(entryPath, expectedName) {
144
+ let cursor = path.dirname(entryPath);
145
+ while (true) {
146
+ try {
147
+ const document = JSON.parse(await readFile(path.join(cursor, "package.json"), "utf8"));
148
+ if (document.name === expectedName) return { root: cursor, packageJson: document };
149
+ } catch {
150
+ // Keep walking to the package boundary.
151
+ }
152
+ const parent = path.dirname(cursor);
153
+ if (parent === cursor) throw new Error(`cannot resolve package root for ${expectedName}`);
154
+ cursor = parent;
155
+ }
156
+ }
157
+
158
+ async function foundationPackageRoots() {
159
+ const contractsEntry = requireFromKit.resolve("skill-family-contracts");
160
+ const harnessEntry = requireFromKit.resolve("skill-family-harness-node");
161
+ const contracts = await packageRootOf(contractsEntry, "skill-family-contracts");
162
+ const harness = await packageRootOf(harnessEntry, "skill-family-harness-node");
163
+ const kitRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
164
+ return { contracts, harness, kitRoot };
165
+ }
166
+
167
+ async function readSourceText(root, relPath) {
168
+ return readFile(path.join(root, relPath), "utf8");
169
+ }
170
+
171
+ /**
172
+ * Reads one consumer schema file. The absolute root is only a read anchor and
173
+ * never enters any output byte; containment is re-proven lexically and by
174
+ * realpath so links cannot escape the consumer schema root.
175
+ */
176
+ async function readConsumerSchema(root, relPath) {
177
+ const rootReal = await realpath(root);
178
+ const absPath = path.resolve(rootReal, relPath);
179
+ let stats;
180
+ try {
181
+ stats = await stat(absPath);
182
+ } catch {
183
+ throw buildError(`consumer schema does not exist: ${relPath}`);
184
+ }
185
+ if (!stats.isFile()) {
186
+ throw buildError(`consumer schema is not a regular file: ${relPath}`);
187
+ }
188
+ const fileReal = await realpath(absPath);
189
+ if (fileReal !== rootReal && !fileReal.startsWith(`${rootReal}${path.sep}`)) {
190
+ throw buildError(`consumer schema escapes the consumer schema root: ${relPath}`);
191
+ }
192
+ return readFile(absPath, "utf8");
193
+ }
194
+
195
+ // The pnpm version comes exclusively from the installed kit package's own
196
+ // managed package.json. Real pnpm pack strips packageManager from tarballs,
197
+ // so the tarball-safe pin lives in the managed engines.pnpm field. In a
198
+ // consumer install layout kitRoot is node_modules/skill-family-engineering-kit,
199
+ // so any upward or sibling read would hit consumer-owned files; the builder
200
+ // never does that and never falls back to the consumer package, the workspace
201
+ // root, environment variables, subprocesses, Git, network, or a hardcoded
202
+ // version constant.
203
+ async function readPnpmVersion(kitRoot) {
204
+ let kitPackageJson;
205
+ try {
206
+ kitPackageJson = JSON.parse(await readFile(path.join(kitRoot, "package.json"), "utf8"));
207
+ } catch {
208
+ throw new Error(
209
+ "buildQuickstartProfileProjection: the kit package.json is unreadable, and it alone pins the pnpm version",
210
+ );
211
+ }
212
+ const engines = kitPackageJson.engines;
213
+ const pinned =
214
+ engines !== null && typeof engines === "object" && !Array.isArray(engines)
215
+ ? engines.pnpm
216
+ : undefined;
217
+ if (typeof pinned !== "string" || !/^\d+\.\d+\.\d+$/.test(pinned)) {
218
+ throw new Error(
219
+ "buildQuickstartProfileProjection: the kit package.json must pin engines.pnpm as an exact x.y.z version",
220
+ );
221
+ }
222
+ return pinned;
223
+ }
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Fixed-anchor source extraction (bytes carried unchanged into the bundle).
227
+ // ---------------------------------------------------------------------------
228
+
229
+ function extractFunctionBlock(sourceText, functionName) {
230
+ const marker = `function ${functionName}(`;
231
+ const start = sourceText.indexOf(marker);
232
+ if (start === -1) throw new Error(`bundle source extraction failed: ${functionName} not found`);
233
+ const openBrace = sourceText.indexOf("{", start);
234
+ if (openBrace === -1) throw new Error(`bundle source extraction failed: ${functionName} has no body`);
235
+ let depth = 0;
236
+ for (let index = openBrace; index < sourceText.length; index += 1) {
237
+ const char = sourceText[index];
238
+ if (char === "{") depth += 1;
239
+ else if (char === "}") {
240
+ depth -= 1;
241
+ if (depth === 0) return sourceText.slice(start, index + 1);
242
+ }
243
+ }
244
+ throw new Error(`bundle source extraction failed: ${functionName} body is unbalanced`);
245
+ }
246
+
247
+ function extractRegion(sourceText, startMarker, endMarker) {
248
+ const start = sourceText.indexOf(startMarker);
249
+ if (start === -1) throw new Error(`bundle source extraction failed: start marker not found: ${startMarker}`);
250
+ const end = sourceText.indexOf(endMarker, start + startMarker.length);
251
+ if (end === -1) throw new Error(`bundle source extraction failed: end marker not found: ${endMarker}`);
252
+ return sourceText.slice(start, end + endMarker.length);
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Schema-graph intake (fail-closed).
257
+ // ---------------------------------------------------------------------------
258
+
259
+ // Schema-valued keyword positions per supported dialect. Ordinary-data
260
+ // keywords (const, enum, examples, default, required, title, ...) are never
261
+ // descended into, so data that merely looks like a schema (for example
262
+ // { const: { format: "uri" } } or { examples: [{ $ref: "ordinary-data" }] })
263
+ // stays data. Boolean subschemas are valid schemas with nothing to scan.
264
+ const SCHEMA_POSITION_KEYWORDS = Object.freeze({
265
+ "draft-07": Object.freeze({
266
+ single: Object.freeze([
267
+ "not",
268
+ "contains",
269
+ "additionalProperties",
270
+ "additionalItems",
271
+ "propertyNames",
272
+ "if",
273
+ "then",
274
+ "else",
275
+ ]),
276
+ singleOrArray: Object.freeze(["items"]),
277
+ arrayOfSchemas: Object.freeze(["allOf", "anyOf", "oneOf"]),
278
+ schemaMap: Object.freeze(["properties", "patternProperties", "definitions", "dependencies"]),
279
+ }),
280
+ "2020-12": Object.freeze({
281
+ single: Object.freeze([
282
+ "not",
283
+ "contains",
284
+ "additionalProperties",
285
+ "propertyNames",
286
+ "if",
287
+ "then",
288
+ "else",
289
+ "unevaluatedItems",
290
+ "unevaluatedProperties",
291
+ "contentSchema",
292
+ ]),
293
+ singleOrArray: Object.freeze(["items"]),
294
+ arrayOfSchemas: Object.freeze(["allOf", "anyOf", "oneOf", "prefixItems"]),
295
+ schemaMap: Object.freeze(["properties", "patternProperties", "$defs", "dependentSchemas"]),
296
+ }),
297
+ });
298
+
299
+ function walkSchema(node, dialect, visit) {
300
+ if (typeof node === "boolean") return;
301
+ if (node === null || typeof node !== "object" || Array.isArray(node)) return;
302
+ visit(node);
303
+ const positions = SCHEMA_POSITION_KEYWORDS[dialect];
304
+ for (const keyword of positions.single) {
305
+ const child = node[keyword];
306
+ if (child !== undefined) walkSchema(child, dialect, visit);
307
+ }
308
+ for (const keyword of positions.singleOrArray) {
309
+ const child = node[keyword];
310
+ if (child === undefined) continue;
311
+ if (Array.isArray(child)) {
312
+ for (const item of child) walkSchema(item, dialect, visit);
313
+ } else {
314
+ walkSchema(child, dialect, visit);
315
+ }
316
+ }
317
+ for (const keyword of positions.arrayOfSchemas) {
318
+ const child = node[keyword];
319
+ if (Array.isArray(child)) {
320
+ for (const item of child) walkSchema(item, dialect, visit);
321
+ }
322
+ }
323
+ for (const keyword of positions.schemaMap) {
324
+ const child = node[keyword];
325
+ if (child === null || typeof child !== "object" || Array.isArray(child)) continue;
326
+ for (const item of Object.values(child)) walkSchema(item, dialect, visit);
327
+ }
328
+ }
329
+
330
+ function dialectOf(schema, relPath) {
331
+ const uri = schema.$schema;
332
+ for (const [dialect, dialectUri] of Object.entries(DIALECT_URIS)) {
333
+ if (uri === dialectUri) return dialect;
334
+ }
335
+ throw buildError(
336
+ `consumer schema ${relPath} must declare $schema as one of the supported dialect URIs` +
337
+ ` (${Object.values(DIALECT_URIS).join(", ")}), got ${JSON.stringify(uri)}`,
338
+ );
339
+ }
340
+
341
+ function scanFormats(schema, relPath, dialect) {
342
+ walkSchema(schema, dialect, (node) => {
343
+ const format = node.format;
344
+ if (format === undefined) return;
345
+ if (typeof format !== "string" || !SUPPORTED_FORMATS.includes(format)) {
346
+ throw buildError(
347
+ `consumer schema ${relPath} uses unsupported format ${JSON.stringify(format)};` +
348
+ ` only ${SUPPORTED_FORMATS.join(", ")} is implemented by Foundation`,
349
+ );
350
+ }
351
+ });
352
+ }
353
+
354
+ /**
355
+ * Ref intake is fail-closed but not literal-URL-only: local fragment refs
356
+ * stay allowed, and every other ref is resolved with standard URL resolution
357
+ * against the owning top-level schema $id before matching the known-$id
358
+ * index. A ref such as "detail.json" inside a schema whose $id is
359
+ * https://consumer.example/v1/request.json therefore resolves to
360
+ * https://consumer.example/v1/detail.json.
361
+ */
362
+ function scanRefs(schema, relPath, dialect, idsByDialect, allKnownIds) {
363
+ walkSchema(schema, dialect, (node) => {
364
+ const ref = node.$ref;
365
+ if (typeof ref !== "string" || ref.length === 0) return;
366
+ if (ref.startsWith("#")) return;
367
+ let resolved;
368
+ try {
369
+ resolved = new URL(ref, schema.$id).href;
370
+ } catch {
371
+ throw buildError(
372
+ `consumer schema ${relPath} uses an unresolvable $ref: ${JSON.stringify(ref)}`,
373
+ );
374
+ }
375
+ const hashIndex = resolved.indexOf("#");
376
+ const base = hashIndex === -1 ? resolved : resolved.slice(0, hashIndex);
377
+ if (base.length === 0) return;
378
+ if (!allKnownIds.has(base)) {
379
+ throw buildError(`consumer schema ${relPath} references an unknown $id: ${JSON.stringify(base)}`);
380
+ }
381
+ if (idsByDialect.get(base) !== dialect) {
382
+ throw buildError(`consumer schema ${relPath} crosses JSON Schema dialects via $ref: ${JSON.stringify(base)}`);
383
+ }
384
+ });
385
+ }
386
+
387
+ function buildSchemaGraph(consumerRecords, foundationSchemaDocuments) {
388
+ const idsByDialect = new Map();
389
+ const allKnownIds = new Set();
390
+ const claim = (schemaId, dialect, where) => {
391
+ if (allKnownIds.has(schemaId)) {
392
+ throw buildError(`duplicate schema $id in the bundle graph: ${schemaId} (${where})`);
393
+ }
394
+ allKnownIds.add(schemaId);
395
+ idsByDialect.set(schemaId, dialect);
396
+ };
397
+ for (const { document } of foundationSchemaDocuments) {
398
+ claim(document.$id, "2020-12", "foundation schema");
399
+ }
400
+ const byDialect = { "draft-07": [], "2020-12": [] };
401
+ for (const record of consumerRecords) {
402
+ const schema = record.document;
403
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
404
+ throw buildError(`consumer schema ${record.path} must be a JSON object`);
405
+ }
406
+ if (typeof schema.$id !== "string" || schema.$id.length === 0) {
407
+ throw buildError(`consumer schema ${record.path} must declare a non-empty $id`);
408
+ }
409
+ record.dialect = dialectOf(schema, record.path);
410
+ claim(schema.$id, record.dialect, `consumer schema ${record.path}`);
411
+ byDialect[record.dialect].push(record);
412
+ }
413
+ for (const record of consumerRecords) {
414
+ scanFormats(record.document, record.path, record.dialect);
415
+ scanRefs(record.document, record.path, record.dialect, idsByDialect, allKnownIds);
416
+ }
417
+ const sortById = (a, b) => (a.document.$id < b.document.$id ? -1 : 1);
418
+ byDialect["draft-07"].sort(sortById);
419
+ byDialect["2020-12"].sort(sortById);
420
+ return byDialect;
421
+ }
422
+
423
+ // ---------------------------------------------------------------------------
424
+ // Standalone validator generation.
425
+ // ---------------------------------------------------------------------------
426
+
427
+ function ajvOptions(codegen) {
428
+ return {
429
+ code: { source: true, esm: true, ...(codegen ?? {}) },
430
+ coerceTypes: false,
431
+ useDefaults: false,
432
+ allErrors: true,
433
+ validateFormats: true,
434
+ strict: true,
435
+ };
436
+ }
437
+
438
+ /**
439
+ * Ajv emits CJS-flavoured `require("<specifier>").default` initializers inside
440
+ * the ESM standalone source. Each occurrence is rewritten through the fixed
441
+ * bundle dependency map and hoisted into a real ESM default import; anything
442
+ * outside the map fails the build.
443
+ */
444
+ function rewriteGeneratedRequires(code, runtimeDependencyMap) {
445
+ const imports = [];
446
+ const bindings = new Map();
447
+ const rewritten = code.replace(/require\("([^"]+)"\)\.default/g, (whole, specifier) => {
448
+ const target = runtimeDependencyMap.get(specifier);
449
+ if (!target) {
450
+ throw new Error(`standalone validator generated an unmapped runtime dependency: ${specifier}`);
451
+ }
452
+ let binding = bindings.get(specifier);
453
+ if (!binding) {
454
+ binding = `__bundleDependency${bindings.size}`;
455
+ bindings.set(specifier, binding);
456
+ imports.push(`import ${binding} from "${target}";`);
457
+ }
458
+ return binding;
459
+ });
460
+ if (/require\(/.test(rewritten)) {
461
+ throw new Error(
462
+ `standalone validator generated an unexpected require() form: ` +
463
+ [...rewritten.matchAll(/require\([^)]*\)\.?[a-zA-Z]*/g)].map((m) => m[0]).join(" | "),
464
+ );
465
+ }
466
+ if (imports.length === 0) return rewritten;
467
+ const strictMarker = '"use strict";';
468
+ if (!rewritten.startsWith(strictMarker)) {
469
+ throw new Error("standalone validator output missing the strict marker");
470
+ }
471
+ return `${strictMarker}\n${imports.join("\n")}\n${rewritten.slice(strictMarker.length)}`;
472
+ }
473
+
474
+ async function generateStandaloneModule({
475
+ AjvCtor,
476
+ schemas,
477
+ runtimeDependencyMap,
478
+ codegenTemplate,
479
+ standaloneCode,
480
+ isValidDateTime,
481
+ }) {
482
+ // Both supported dialects register the same mechanically projected
483
+ // date-time implementation; the generated ESM references the same
484
+ // bundle-relative format runtime through FORMAT_RUNTIME_SPECIFIER.
485
+ const options = ajvOptions({
486
+ formats: codegenTemplate`require("__SKILL_FAMILY_BUNDLE_FORMAT_RUNTIME__").default`,
487
+ });
488
+ const ajv = new AjvCtor(options);
489
+ ajv.addFormat("date-time", { type: "string", validate: isValidDateTime });
490
+ const ordered = [...schemas].sort((a, b) => (a.$id < b.$id ? -1 : 1));
491
+ for (const schema of ordered) ajv.addSchema(schema);
492
+ for (const schema of ordered) {
493
+ if (!ajv.getSchema(schema.$id)) {
494
+ throw new Error(`standalone generation failed to compile schema: ${schema.$id}`);
495
+ }
496
+ }
497
+ const moduleMap = Object.fromEntries(ordered.map((schema, index) => [`validate${index}`, schema.$id]));
498
+ const raw = standaloneCode(ajv, moduleMap);
499
+ return rewriteGeneratedRequires(raw, runtimeDependencyMap);
500
+ }
501
+
502
+ // ---------------------------------------------------------------------------
503
+ // Generated runtime module sources.
504
+ // ---------------------------------------------------------------------------
505
+
506
+ const IMPORT_SPECIFIER_PATTERN = /(?:import|export)[^;]*?from\s*"([^"]+)"/g;
507
+
508
+ function projectModuleWithImportMap(sourceText, importMap, sourceLabel) {
509
+ let projected = sourceText;
510
+ for (const [from, to] of importMap) {
511
+ projected = projected.replaceAll(`"${from}"`, `"${to}"`);
512
+ }
513
+ for (const match of projected.matchAll(IMPORT_SPECIFIER_PATTERN)) {
514
+ const specifier = match[1];
515
+ if (specifier.startsWith("node:")) continue;
516
+ if (specifier.startsWith("./") || specifier.startsWith("../")) continue;
517
+ throw new Error(
518
+ `bundle projection of ${sourceLabel} left an unmapped import specifier: ${specifier}`,
519
+ );
520
+ }
521
+ return projected;
522
+ }
523
+
524
+ function contractsIndexSource() {
525
+ return [
526
+ "// Fixed bundle surface: the exact stable contracts exports consumed by the",
527
+ "// projected quickstart harness, re-exported from mechanically projected sources.",
528
+ "export {",
529
+ " ContractsError,",
530
+ " ERROR_CODES,",
531
+ " errorCodeRegistry,",
532
+ " errorCodeInfo,",
533
+ " isRegisteredErrorCode,",
534
+ " assertRegisteredErrorCode,",
535
+ " stableError,",
536
+ '} from "./errors.mjs";',
537
+ 'export { canonicalJson, digestDocument } from "./canonical.mjs";',
538
+ "",
539
+ ].join("\n");
540
+ }
541
+
542
+ function canonicalSource(auditSurfaceSourceText) {
543
+ const algorithmSet = extractRegion(
544
+ auditSurfaceSourceText,
545
+ "/** Frozen digest algorithm set",
546
+ 'Object.freeze(["sha256"]);',
547
+ );
548
+ const region = extractRegion(
549
+ auditSurfaceSourceText,
550
+ "/**\n * Canonical JSON serialization",
551
+ '.digest("hex");\n}',
552
+ );
553
+ if (
554
+ !region.includes("export function canonicalJson(") ||
555
+ !region.includes("export function digestDocument(")
556
+ ) {
557
+ throw new Error("bundle source extraction failed: canonical region is incomplete");
558
+ }
559
+ return `import { createHash } from "node:crypto";\n\n${algorithmSet}\n\n${region}\n`;
560
+ }
561
+
562
+ function jsonBoundarySource(candidateIndexSourceText) {
563
+ const region = extractRegion(
564
+ candidateIndexSourceText,
565
+ "function normalizeErrors(",
566
+ 'return probeJsonValue(value, "", new Set());\n}',
567
+ );
568
+ if (
569
+ !region.includes("function escapePointerSegment(") ||
570
+ !region.includes("export function findNonJsonValue(")
571
+ ) {
572
+ throw new Error("bundle source extraction failed: JSON boundary region is incomplete");
573
+ }
574
+ return [
575
+ "// Verbatim projection of the candidate JSON-boundary probe and the Ajv error",
576
+ "// normalization from the contracts candidate validation entry.",
577
+ region,
578
+ "export { normalizeErrors as normalizeValidationError };",
579
+ "",
580
+ ].join("\n");
581
+ }
582
+
583
+ function formatsSource(candidateIndexSourceText) {
584
+ const patternDeclaration = extractRegion(candidateIndexSourceText, "const DATE_TIME_PATTERN =", ";");
585
+ const isValidDateTime = extractFunctionBlock(candidateIndexSourceText, "isValidDateTime");
586
+ return [
587
+ "// Verbatim projection of the Foundation date-time format implementation.",
588
+ "// Each format entry carries the shape Ajv standalone codegen consumes:",
589
+ "// a record whose validate member is the projected implementation.",
590
+ patternDeclaration,
591
+ "",
592
+ isValidDateTime,
593
+ "",
594
+ 'const FORMATS = Object.freeze({ "date-time": Object.freeze({ validate: isValidDateTime }) });',
595
+ "",
596
+ "export default FORMATS;",
597
+ "",
598
+ ].join("\n");
599
+ }
600
+
601
+ // The standalone-validator binding transform replaces the frozen Ajv-backed
602
+ // getCollection implementation. The digest pins every byte of that source
603
+ // function: a rename, an added statement, or any other body drift fails the
604
+ // projection closed instead of being silently discarded by the replacement.
605
+ const GET_COLLECTION_AJV_SOURCE_SHA256 =
606
+ "21308a4660e3706ff4e9ce1a08ac98b3d1422d779867665a4d6f651b9ddc39ad";
607
+
608
+ const GET_COLLECTION_STANDALONE_BINDING = `function getCollection() {
609
+ if (collection) return collection;
610
+ const bound = {};
611
+ for (const kind of VALIDATE_KINDS) {
612
+ const validate = standaloneValidators[documents[kind].$id];
613
+ if (typeof validate !== "function") {
614
+ throw new Error(
615
+ \`standalone validator is missing the quickstart profile schema: \${documents[kind].$id}\`,
616
+ );
617
+ }
618
+ bound[kind] = validate;
619
+ }
620
+ collection = Object.freeze(bound);
621
+ return collection;
622
+ }`;
623
+
624
+ /**
625
+ * Deterministic projection of the real contracts candidate validation entry.
626
+ * The full source text is the only input; the sole allowed transforms are the
627
+ * Ajv build-time dependency, the registry lookup import, the schema load
628
+ * paths, and the standalone-validator binding. Each transform has a unique
629
+ * anchor and must hit exactly once: a missing or repeated anchor fails the
630
+ * build closed instead of emitting a stale or partial wrapper.
631
+ */
632
+ function projectContractsCandidateIndex(sourceText) {
633
+ let projected = sourceText;
634
+ const applyOnce = (anchor, replacement, label) => {
635
+ const hits = projected.split(anchor).length - 1;
636
+ if (hits !== 1) {
637
+ throw new Error(
638
+ `contracts candidate projection failed: the ${label} anchor matched ${hits} times (expected exactly 1)`,
639
+ );
640
+ }
641
+ projected = projected.replace(anchor, () => replacement);
642
+ };
643
+ applyOnce(
644
+ 'import Ajv2020 from "ajv/dist/2020.js";',
645
+ 'import standaloneValidators from "../generated/standalone-map.mjs";',
646
+ "Ajv build-time dependency",
647
+ );
648
+ applyOnce(
649
+ 'import { findSchemaRegistration } from "../../src/registry.mjs";\n',
650
+ "",
651
+ "stable registry lookup import",
652
+ );
653
+ for (const [fileName] of FOUNDATION_SCHEMA_FILES) {
654
+ applyOnce(
655
+ `load("${fileName}")`,
656
+ `load("../../schemas/foundation/${fileName}")`,
657
+ `schema load path ${fileName}`,
658
+ );
659
+ }
660
+
661
+ const getCollectionMarker = "function getCollection(";
662
+ const getCollectionHits = projected.split(getCollectionMarker).length - 1;
663
+ if (getCollectionHits !== 1) {
664
+ throw new Error(
665
+ `contracts candidate projection failed: getCollection matched ${getCollectionHits} times (expected exactly 1)`,
666
+ );
667
+ }
668
+ const getCollectionSource = extractFunctionBlock(projected, "getCollection");
669
+ const getCollectionDigest = digestBytes(Buffer.from(getCollectionSource, "utf8"));
670
+ if (getCollectionDigest !== GET_COLLECTION_AJV_SOURCE_SHA256) {
671
+ throw new Error(
672
+ `contracts candidate projection failed: getCollection body digest drifted: ${getCollectionDigest}`,
673
+ );
674
+ }
675
+ applyOnce(
676
+ getCollectionSource,
677
+ GET_COLLECTION_STANDALONE_BINDING,
678
+ "standalone validator binding",
679
+ );
680
+ for (const leftover of [
681
+ "Ajv2020",
682
+ "findSchemaRegistration",
683
+ "addFormat",
684
+ "addSchema",
685
+ "ajv/dist/2020.js",
686
+ "../../src/registry.mjs",
687
+ ]) {
688
+ if (projected.includes(leftover)) {
689
+ throw new Error(
690
+ `contracts candidate projection left an Ajv-era binding behind: ${leftover}`,
691
+ );
692
+ }
693
+ }
694
+ return projected;
695
+ }
696
+
697
+ function standaloneMapSource({ entries2020, entriesDraft07 }) {
698
+ const lines = [
699
+ "// Fixed dispatch: every registered schema $id mapped to its generated",
700
+ "// standalone validator function.",
701
+ 'import * as validate202012 from "./validate-2020-12.mjs";',
702
+ ];
703
+ if (entriesDraft07.length > 0) {
704
+ lines.push('import * as validateDraft07 from "./validate-draft-07.mjs";');
705
+ }
706
+ lines.push("", "const STANDALONE_VALIDATORS = Object.freeze({");
707
+ for (const entry of entries2020) {
708
+ lines.push(` ${JSON.stringify(entry.schemaId)}: validate202012.${entry.exportName},`);
709
+ }
710
+ for (const entry of entriesDraft07) {
711
+ lines.push(` ${JSON.stringify(entry.schemaId)}: validateDraft07.${entry.exportName},`);
712
+ }
713
+ lines.push("});", "", "export default STANDALONE_VALIDATORS;", "");
714
+ return lines.join("\n");
715
+ }
716
+
717
+ function validatorsSource() {
718
+ return `import standaloneValidators from "./runtime/generated/standalone-map.mjs";
719
+ import { findNonJsonValue, normalizeValidationError } from "./runtime/json-boundary.mjs";
720
+
721
+ /** Every schema $id compiled into this bundle (Foundation graph + consumer schemas). */
722
+ export function listValidatableSchemaIds() {
723
+ return Object.keys(standaloneValidators).sort();
724
+ }
725
+
726
+ /**
727
+ * Validates one document against the schema registered under schemaId.
728
+ * Unknown $ids are a caller error (TypeError). Validation never mutates the
729
+ * caller input; the returned errors carry only plain JSON fields and expose
730
+ * no validator instance or shared mutable state.
731
+ */
732
+ export function validateBySchemaId(schemaId, document) {
733
+ if (typeof schemaId !== "string" || !Object.hasOwn(standaloneValidators, schemaId)) {
734
+ throw new TypeError(\`validateBySchemaId: unknown schema $id: \${String(schemaId)}\`);
735
+ }
736
+ const target = document === undefined ? null : document;
737
+ const jsonIssue = findNonJsonValue(target);
738
+ if (jsonIssue) {
739
+ return {
740
+ valid: false,
741
+ errors: [
742
+ {
743
+ keyword: "json-value",
744
+ instancePath: jsonIssue.instancePath,
745
+ schemaPath: "#",
746
+ message: \`value is not representable as JSON: \${jsonIssue.reason}\`,
747
+ params: { reason: jsonIssue.reason },
748
+ },
749
+ ],
750
+ };
751
+ }
752
+ const validate = standaloneValidators[schemaId];
753
+ const clone = structuredClone(target);
754
+ const valid = validate(clone) === true;
755
+ return {
756
+ valid,
757
+ errors: valid ? [] : normalizeValidationError(validate.errors),
758
+ };
759
+ }
760
+ `;
761
+ }
762
+
763
+ function runnerSource() {
764
+ return [
765
+ "// Quickstart Profile v2 offline runner: the projected Foundation harness",
766
+ "// mechanisms and the standalone-backed candidate validation entry.",
767
+ 'export * from "./runtime/harness/quickstart-profile.mjs";',
768
+ 'export { validateQuickstartProfileDocument } from "./runtime/contracts-candidate/index.mjs";',
769
+ "",
770
+ ].join("\n");
771
+ }
772
+
773
+ function ucs2lengthSource(ajvRuntimeSourceText) {
774
+ const body = extractFunctionBlock(ajvRuntimeSourceText, "ucs2length");
775
+ return `${body}\nexport default ucs2length;\n`;
776
+ }
777
+
778
+ function equalSource() {
779
+ return 'import equal from "../fast-deep-equal/index.mjs";\nexport default equal;\n';
780
+ }
781
+
782
+ function fastDeepEqualSource(indexJsSourceText) {
783
+ const exportLine = "module.exports = function equal(";
784
+ if (!indexJsSourceText.includes(exportLine)) {
785
+ throw new Error("fast-deep-equal index.js has an unexpected layout");
786
+ }
787
+ return indexJsSourceText.replace(exportLine, "export default function equal(");
788
+ }
789
+
790
+ function noticeSource(licenseRecords) {
791
+ const lines = [
792
+ "Foundation Quickstart Profile offline bundle: third-party notice.",
793
+ "Each entry lists the package actually carried in runtime/, the locked",
794
+ "version, the projected source files, and the license file in licenses/.",
795
+ "",
796
+ ];
797
+ for (const record of licenseRecords) {
798
+ lines.push(
799
+ `- ${record.name} ${record.version}`,
800
+ ` source files: ${record.sourceFiles.join(", ")}`,
801
+ ` license file: ${record.licenseFile}`,
802
+ "",
803
+ );
804
+ }
805
+ return lines.join("\n");
806
+ }
807
+
808
+ // ---------------------------------------------------------------------------
809
+ // Builder.
810
+ // ---------------------------------------------------------------------------
811
+
812
+ /**
813
+ * Build the deterministic, offline Quickstart Profile v2 bundle.
814
+ *
815
+ * Inputs are explicit and caller-frozen: a contained target prefix, the
816
+ * consumer schema root plus relative schema paths (read only at build time),
817
+ * and the caller-provided source repository / base commit identities. The
818
+ * builder fails closed and returns no half manifest on any violation.
819
+ */
820
+ export async function buildQuickstartProfileProjection({
821
+ targetPrefix = DEFAULT_TARGET_PREFIX,
822
+ consumerSchemaRoot,
823
+ consumerSchemaPaths,
824
+ sourceRepository,
825
+ sourceBaseCommit,
826
+ } = {}) {
827
+ const prefix = assertContainedPosixPath(targetPrefix, "targetPrefix");
828
+ if (typeof consumerSchemaRoot !== "string" || !path.isAbsolute(consumerSchemaRoot)) {
829
+ throw buildError("consumerSchemaRoot must be an absolute build-time directory");
830
+ }
831
+ if (!Array.isArray(consumerSchemaPaths) || consumerSchemaPaths.length === 0) {
832
+ throw buildError("consumerSchemaPaths must carry at least one schema");
833
+ }
834
+ const normalizedPaths = consumerSchemaPaths.map((relPath) =>
835
+ assertContainedPosixPath(relPath, "consumer schema path"),
836
+ );
837
+ if (new Set(normalizedPaths).size !== normalizedPaths.length) {
838
+ throw buildError("consumerSchemaPaths contains a duplicate path");
839
+ }
840
+ normalizedPaths.sort();
841
+ const repository = assertSourceIdentity(sourceRepository, "sourceRepository");
842
+ const baseCommit = assertSourceIdentity(sourceBaseCommit, "sourceBaseCommit");
843
+
844
+ const roots = await foundationPackageRoots();
845
+ const contractsRoot = roots.contracts.root;
846
+ const harnessRoot = roots.harness.root;
847
+ const kitRoot = roots.kitRoot;
848
+
849
+ // --- Foundation source reads (every byte that can influence the output). ---
850
+ const sources = {};
851
+ sources.auditSurface = await readSourceText(contractsRoot, "src/audit-surface.mjs");
852
+ sources.contractsIndex = await readSourceText(contractsRoot, "src/index.mjs");
853
+ sources.contractsErrors = await readSourceText(contractsRoot, "src/errors.mjs");
854
+ sources.errorCodes = await readSourceText(contractsRoot, "src/error-codes.json");
855
+ sources.operationRequest = await readSourceText(contractsRoot, "src/schemas/operation-request.schema.json");
856
+ sources.operationResult = await readSourceText(contractsRoot, "src/schemas/operation-result.schema.json");
857
+ sources.candidateIndex = await readSourceText(contractsRoot, "candidate/quickstart-profile/index.mjs");
858
+ const candidateSchemaTexts = {};
859
+ for (const [fileName] of FOUNDATION_SCHEMA_FILES) {
860
+ candidateSchemaTexts[fileName] = await readSourceText(
861
+ contractsRoot,
862
+ `candidate/quickstart-profile/${fileName}`,
863
+ );
864
+ }
865
+ sources.harnessIndex = await readSourceText(harnessRoot, "src/index.mjs");
866
+ sources.harnessCandidate = await readSourceText(harnessRoot, "candidate/quickstart-profile.mjs");
867
+ sources.harnessClosure = await readSourceText(harnessRoot, "src/closure.mjs");
868
+ sources.harnessErrors = await readSourceText(harnessRoot, "src/errors.mjs");
869
+ sources.harnessPaths = await readSourceText(harnessRoot, "src/paths.mjs");
870
+ // The package manifests are recorded as complete original bytes below, so
871
+ // every provenance path digest recomputes from the real file.
872
+ const contractsPackageJsonText = await readSourceText(contractsRoot, "package.json");
873
+ const harnessPackageJsonText = await readSourceText(harnessRoot, "package.json");
874
+ const kitPackageJsonText = await readSourceText(kitRoot, "package.json");
875
+ const contractsVersion = JSON.parse(contractsPackageJsonText).version;
876
+ const kitBuilderSource = await readSourceText(kitRoot, "candidate/profile-bundle.mjs");
877
+ const kitCliSource = await readSourceText(kitRoot, "candidate/projection-bundle-cli.mjs");
878
+
879
+ // --- Consumer schema intake (contained reads; the absolute root never
880
+ // --- enters any output byte). ---
881
+ const consumerRecords = [];
882
+ for (const relPath of normalizedPaths) {
883
+ const text = await readConsumerSchema(consumerSchemaRoot, relPath);
884
+ let document;
885
+ try {
886
+ document = JSON.parse(text);
887
+ } catch {
888
+ throw buildError(`consumer schema is not valid JSON: ${relPath}`);
889
+ }
890
+ consumerRecords.push({ path: relPath, sha256: digestBytes(Buffer.from(text, "utf8")), document });
891
+ }
892
+
893
+ const foundationSchemaDocuments = [
894
+ { name: "operation-request", text: sources.operationRequest },
895
+ { name: "operation-result", text: sources.operationResult },
896
+ ...["resource.schema.json", "task.schema.json", "result.schema.json"].map((fileName) => ({
897
+ name: fileName,
898
+ text: candidateSchemaTexts[fileName],
899
+ })),
900
+ ].map((entry) => ({ name: entry.name, document: JSON.parse(entry.text) }));
901
+
902
+ const graph = buildSchemaGraph(consumerRecords, foundationSchemaDocuments);
903
+
904
+ // --- Standalone validator generation (Ajv build dependency only). ---
905
+ const fromContracts = createRequire(pathToFileURL(path.join(contractsRoot, "package.json")));
906
+ const ajvEntry = fromContracts.resolve("ajv");
907
+ const ajv = await packageRootOf(ajvEntry, "ajv");
908
+ const fromAjv = createRequire(pathToFileURL(path.join(ajv.root, "package.json")));
909
+ const fastDeepEqual = await packageRootOf(fromAjv.resolve("fast-deep-equal"), "fast-deep-equal");
910
+ const importDefault = async (resolved) => (await import(pathToFileURL(resolved).href)).default;
911
+ const AjvDraft07 = await importDefault(ajvEntry);
912
+ const Ajv2020 = await importDefault(fromContracts.resolve("ajv/dist/2020.js"));
913
+ const standaloneCode = await importDefault(fromContracts.resolve("ajv/dist/standalone"));
914
+ const codegenModule = await import(pathToFileURL(fromContracts.resolve("ajv/dist/compile/codegen")).href);
915
+ const codegenTemplate = codegenModule._ ?? codegenModule.default._;
916
+
917
+ // The build-time date-time implementation is imported from the very module
918
+ // text that ships in the bundle (data URL), so build-time and runtime
919
+ // formats are byte-identical without any dynamic code compilation.
920
+ const formatsModuleSource = formatsSource(sources.candidateIndex);
921
+ const formatsModule = await import(`data:text/javascript,${encodeURIComponent(formatsModuleSource)}`);
922
+ const isValidDateTime = formatsModule.default["date-time"].validate;
923
+
924
+ const runtimeDependencyMap = new Map([
925
+ ["ajv/dist/runtime/ucs2length", "../ajv/ucs2length.mjs"],
926
+ ["ajv/dist/runtime/equal", "../ajv/equal.mjs"],
927
+ [FORMAT_RUNTIME_SPECIFIER, "./formats.mjs"],
928
+ ]);
929
+
930
+ const schemas2020 = [
931
+ ...foundationSchemaDocuments.map((entry) => entry.document),
932
+ ...graph["2020-12"].map((record) => record.document),
933
+ ];
934
+ const generated2020 = await generateStandaloneModule({
935
+ AjvCtor: Ajv2020,
936
+ schemas: schemas2020,
937
+ runtimeDependencyMap,
938
+ codegenTemplate,
939
+ standaloneCode,
940
+ isValidDateTime,
941
+ });
942
+ const schemasDraft07 = graph["draft-07"].map((record) => record.document);
943
+ const withDraft07 = schemasDraft07.length > 0;
944
+ const generatedDraft07 = withDraft07
945
+ ? await generateStandaloneModule({
946
+ AjvCtor: AjvDraft07,
947
+ schemas: schemasDraft07,
948
+ runtimeDependencyMap,
949
+ codegenTemplate,
950
+ standaloneCode,
951
+ isValidDateTime,
952
+ })
953
+ : null;
954
+
955
+ // --- Runtime helpers actually referenced by the generated code. ---
956
+ const carriesUcs2length =
957
+ generated2020.includes("../ajv/ucs2length.mjs") || (generatedDraft07 ?? "").includes("../ajv/ucs2length.mjs");
958
+ const carriesEqual =
959
+ generated2020.includes("../ajv/equal.mjs") || (generatedDraft07 ?? "").includes("../ajv/equal.mjs");
960
+ const ajvUcs2lengthSource = carriesUcs2length ? await readSourceText(ajv.root, "dist/runtime/ucs2length.js") : null;
961
+ const ajvLicense = await readSourceText(ajv.root, "LICENSE");
962
+ const ajvPackageJsonText = await readSourceText(ajv.root, "package.json");
963
+ const fastDeepEqualIndex = carriesEqual ? await readSourceText(fastDeepEqual.root, "index.js") : null;
964
+ const fastDeepEqualLicense = carriesEqual ? await readSourceText(fastDeepEqual.root, "LICENSE") : null;
965
+ const fastDeepEqualPackageJsonText = carriesEqual
966
+ ? await readSourceText(fastDeepEqual.root, "package.json")
967
+ : null;
968
+
969
+ // --- Bundle assembly (all members are deterministic functions of the
970
+ // --- frozen inputs; code-unit ordering everywhere). ---
971
+ const files = new Map();
972
+ const setText = (relPath, text) => {
973
+ if (files.has(relPath)) throw new Error(`duplicate bundle member: ${relPath}`);
974
+ files.set(relPath, text);
975
+ };
976
+
977
+ setText("runner.mjs", runnerSource());
978
+ setText("validators.mjs", validatorsSource());
979
+ for (const [fileName, bundlePath] of FOUNDATION_SCHEMA_FILES) {
980
+ setText(bundlePath, candidateSchemaTexts[fileName]);
981
+ }
982
+ setText("schemas/foundation/operation-request.schema.json", sources.operationRequest);
983
+ setText("schemas/foundation/operation-result.schema.json", sources.operationResult);
984
+ for (const record of consumerRecords) {
985
+ setText(`schemas/consumer/${record.path}`, `${JSON.stringify(record.document, null, 2)}\n`);
986
+ }
987
+ setText(
988
+ "runtime/harness/quickstart-profile.mjs",
989
+ projectModuleWithImportMap(sources.harnessCandidate, HARNESS_IMPORT_MAP, "harness candidate/quickstart-profile.mjs"),
990
+ );
991
+ setText("runtime/harness/closure.mjs", projectModuleWithImportMap(sources.harnessClosure, new Map(), "harness src/closure.mjs"));
992
+ setText(
993
+ "runtime/harness/errors.mjs",
994
+ projectModuleWithImportMap(sources.harnessErrors, new Map([["skill-family-contracts", "../contracts/index.mjs"]]), "harness src/errors.mjs"),
995
+ );
996
+ setText("runtime/harness/paths.mjs", projectModuleWithImportMap(sources.harnessPaths, new Map(), "harness src/paths.mjs"));
997
+ setText("runtime/contracts/index.mjs", contractsIndexSource());
998
+ setText("runtime/contracts/errors.mjs", sources.contractsErrors);
999
+ setText("runtime/contracts/error-codes.json", sources.errorCodes);
1000
+ setText("runtime/contracts/canonical.mjs", canonicalSource(sources.auditSurface));
1001
+ setText("runtime/contracts-candidate/index.mjs", projectContractsCandidateIndex(sources.candidateIndex));
1002
+ setText("runtime/json-boundary.mjs", jsonBoundarySource(sources.candidateIndex));
1003
+ setText("runtime/generated/validate-2020-12.mjs", generated2020);
1004
+ if (withDraft07) {
1005
+ setText("runtime/generated/validate-draft-07.mjs", generatedDraft07);
1006
+ }
1007
+ const sortById = (a, b) => (a.$id < b.$id ? -1 : 1);
1008
+ const entries2020 = [...schemas2020]
1009
+ .sort(sortById)
1010
+ .map((schema, index) => ({ schemaId: schema.$id, exportName: `validate${index}` }));
1011
+ const entriesDraft07 = [...schemasDraft07]
1012
+ .sort(sortById)
1013
+ .map((schema, index) => ({ schemaId: schema.$id, exportName: `validate${index}` }));
1014
+ setText("runtime/generated/standalone-map.mjs", standaloneMapSource({ entries2020, entriesDraft07 }));
1015
+ setText("runtime/generated/formats.mjs", formatsModuleSource);
1016
+ if (carriesUcs2length) {
1017
+ setText("runtime/ajv/ucs2length.mjs", ucs2lengthSource(ajvUcs2lengthSource));
1018
+ }
1019
+ if (carriesEqual) {
1020
+ setText("runtime/ajv/equal.mjs", equalSource());
1021
+ setText("runtime/fast-deep-equal/index.mjs", fastDeepEqualSource(fastDeepEqualIndex));
1022
+ }
1023
+
1024
+ const licenseRecords = [
1025
+ {
1026
+ name: "ajv",
1027
+ version: ajv.packageJson.version,
1028
+ licenseFile: "licenses/ajv-LICENSE",
1029
+ sourceFiles: [
1030
+ ...(carriesUcs2length ? ["ajv/dist/runtime/ucs2length.js"] : []),
1031
+ "generated standalone validator code",
1032
+ ],
1033
+ },
1034
+ ];
1035
+ if (carriesEqual) {
1036
+ licenseRecords.push({
1037
+ name: "fast-deep-equal",
1038
+ version: fastDeepEqual.packageJson.version,
1039
+ licenseFile: "licenses/fast-deep-equal-LICENSE",
1040
+ sourceFiles: ["fast-deep-equal/index.js"],
1041
+ });
1042
+ }
1043
+ licenseRecords.sort((a, b) => (a.name < b.name ? -1 : 1));
1044
+ setText("licenses/ajv-LICENSE", ajvLicense);
1045
+ if (carriesEqual) {
1046
+ setText("licenses/fast-deep-equal-LICENSE", fastDeepEqualLicense);
1047
+ }
1048
+ setText("licenses/NOTICE", noticeSource(licenseRecords));
1049
+
1050
+ // --- Provenance (payload digest excludes the provenance file itself). ---
1051
+ const payloadFiles = [...files.entries()]
1052
+ .map(([filePath, text]) => ({ path: filePath, sha256: digestBytes(Buffer.from(text, "utf8")) }))
1053
+ .sort((a, b) => (a.path < b.path ? -1 : 1));
1054
+ const sha256Of = (text) => digestBytes(Buffer.from(text, "utf8"));
1055
+ const foundationRecords = [
1056
+ ["packages/skill-family-contracts/package.json", contractsPackageJsonText, "identity"],
1057
+ ["packages/skill-family-contracts/src/index.mjs", sources.contractsIndex, "imported-surface"],
1058
+ ["packages/skill-family-contracts/src/schemas/operation-request.schema.json", sources.operationRequest, "projected"],
1059
+ ["packages/skill-family-contracts/src/schemas/operation-result.schema.json", sources.operationResult, "projected"],
1060
+ ["packages/skill-family-contracts/src/errors.mjs", sources.contractsErrors, "projected"],
1061
+ ["packages/skill-family-contracts/src/error-codes.json", sources.errorCodes, "projected"],
1062
+ ["packages/skill-family-contracts/src/audit-surface.mjs", sources.auditSurface, "extraction-input"],
1063
+ ["packages/skill-family-contracts/candidate/quickstart-profile/protocol.json", candidateSchemaTexts["protocol.json"], "projected"],
1064
+ ["packages/skill-family-contracts/candidate/quickstart-profile/resource.schema.json", candidateSchemaTexts["resource.schema.json"], "projected"],
1065
+ ["packages/skill-family-contracts/candidate/quickstart-profile/task.schema.json", candidateSchemaTexts["task.schema.json"], "projected"],
1066
+ ["packages/skill-family-contracts/candidate/quickstart-profile/result.schema.json", candidateSchemaTexts["result.schema.json"], "projected"],
1067
+ ["packages/skill-family-contracts/candidate/quickstart-profile/index.mjs", sources.candidateIndex, "extraction-input"],
1068
+ ["packages/skill-family-harness-node/package.json", harnessPackageJsonText, "identity"],
1069
+ ["packages/skill-family-harness-node/src/index.mjs", sources.harnessIndex, "imported-surface"],
1070
+ ["packages/skill-family-harness-node/candidate/quickstart-profile.mjs", sources.harnessCandidate, "projected"],
1071
+ ["packages/skill-family-harness-node/src/closure.mjs", sources.harnessClosure, "projected"],
1072
+ ["packages/skill-family-harness-node/src/errors.mjs", sources.harnessErrors, "projected"],
1073
+ ["packages/skill-family-harness-node/src/paths.mjs", sources.harnessPaths, "projected"],
1074
+ ["packages/skill-family-engineering-kit/package.json", kitPackageJsonText, "identity"],
1075
+ ["packages/skill-family-engineering-kit/candidate/profile-bundle.mjs", kitBuilderSource, "builder"],
1076
+ ["packages/skill-family-engineering-kit/candidate/projection-bundle-cli.mjs", kitCliSource, "builder"],
1077
+ ]
1078
+ .map(([recordPath, text, role]) => ({ path: recordPath, sha256: sha256Of(text), role }))
1079
+ .sort((a, b) => (a.path < b.path ? -1 : 1));
1080
+ const thirdPartyRecords = [
1081
+ ["ajv/package.json", ajvPackageJsonText, "identity"],
1082
+ ["ajv/LICENSE", ajvLicense, "license"],
1083
+ ...(carriesUcs2length ? [["ajv/dist/runtime/ucs2length.js", ajvUcs2lengthSource, "projected"]] : []),
1084
+ ...(carriesEqual
1085
+ ? [
1086
+ ["fast-deep-equal/package.json", fastDeepEqualPackageJsonText, "identity"],
1087
+ ["fast-deep-equal/LICENSE", fastDeepEqualLicense, "license"],
1088
+ ["fast-deep-equal/index.js", fastDeepEqualIndex, "projected"],
1089
+ ]
1090
+ : []),
1091
+ ]
1092
+ .map(([recordPath, text, role]) => ({ path: recordPath, sha256: sha256Of(text), role }))
1093
+ .sort((a, b) => (a.path < b.path ? -1 : 1));
1094
+
1095
+ const provenance = {
1096
+ schemaVersion: 1,
1097
+ kind: "skill-family.foundation-projection",
1098
+ profile: {
1099
+ id: QUICKSTART_PROFILE_ID,
1100
+ version: QUICKSTART_PROFILE_VERSION,
1101
+ contractsVersion: CONTRACTS_VERSION,
1102
+ },
1103
+ source: {
1104
+ repository,
1105
+ baseCommit,
1106
+ foundation: foundationRecords,
1107
+ thirdParty: thirdPartyRecords,
1108
+ consumerSchemas: consumerRecords
1109
+ .map((record) => ({
1110
+ path: record.path,
1111
+ $id: record.document.$id,
1112
+ dialect: record.dialect,
1113
+ sha256: record.sha256,
1114
+ }))
1115
+ .sort((a, b) => (a.path < b.path ? -1 : 1)),
1116
+ },
1117
+ toolchain: {
1118
+ node: process.version,
1119
+ pnpm: await readPnpmVersion(kitRoot),
1120
+ ajv: ajv.packageJson.version,
1121
+ fastDeepEqual: fastDeepEqual.packageJson.version,
1122
+ },
1123
+ licenses: licenseRecords,
1124
+ payload: {
1125
+ digestAlgorithm: "sha256",
1126
+ files: payloadFiles,
1127
+ digest: digestBytes(Buffer.from(canonicalJson(payloadFiles), "utf8")),
1128
+ },
1129
+ };
1130
+ setText(PROVENANCE_FILE, `${JSON.stringify(provenance, null, 2)}\n`);
1131
+
1132
+ const entries = [...files.entries()]
1133
+ .map(([filePath, text]) => ({ path: `${prefix}/${filePath}`, text }))
1134
+ .sort((a, b) => (a.path < b.path ? -1 : 1))
1135
+ .map(({ path: entryPath, text }) => ({
1136
+ path: entryPath,
1137
+ content: { text },
1138
+ expect: { state: "absent" },
1139
+ }));
1140
+ return {
1141
+ manifest: {
1142
+ schemaVersion: 1,
1143
+ kind: "skill-family.projection-manifest",
1144
+ entries,
1145
+ },
1146
+ provenance,
1147
+ };
1148
+ }
1149
+
1150
+ export const QUICKSTART_PROFILE_TARGET_PREFIX = DEFAULT_TARGET_PREFIX;