@forgeax/engine-pack 0.1.23 → 0.1.24

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 (81) hide show
  1. package/README.md +12 -6
  2. package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts +2 -0
  3. package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts.map +1 -0
  4. package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts +2 -0
  5. package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/pack-authoring.unit.test.d.ts +2 -0
  7. package/dist/__tests__/pack-authoring.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/pack-scanner.unit.test.d.ts +2 -0
  9. package/dist/__tests__/pack-scanner.unit.test.d.ts.map +1 -0
  10. package/dist/build.d.ts +3 -3
  11. package/dist/build.d.ts.map +1 -1
  12. package/dist/build.mjs +331 -290
  13. package/dist/build.mjs.map +1 -1
  14. package/dist/cli-asset.mjs +33 -35
  15. package/dist/cli-asset.mjs.map +1 -1
  16. package/dist/evidence/material-cook.d.ts +11 -0
  17. package/dist/evidence/material-cook.d.ts.map +1 -1
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.mjs +69 -26
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/material-cook.d.ts +1 -1
  23. package/dist/material-cook.d.ts.map +1 -1
  24. package/dist/material-cook.mjs +50 -3
  25. package/dist/material-cook.mjs.map +1 -1
  26. package/dist/{parameterized-pack-node.d.ts → pack-authoring-node.d.ts} +7 -7
  27. package/dist/pack-authoring-node.d.ts.map +1 -0
  28. package/dist/{parameterized-pack-node.mjs → pack-authoring-node.mjs} +59 -63
  29. package/dist/pack-authoring-node.mjs.map +1 -0
  30. package/dist/{parameterized-pack.d.ts → pack-authoring.d.ts} +28 -31
  31. package/dist/pack-authoring.d.ts.map +1 -0
  32. package/dist/pack-authoring.mjs +1137 -0
  33. package/dist/pack-authoring.mjs.map +1 -0
  34. package/dist/runtime.mjs +1 -1
  35. package/dist/runtime.mjs.map +1 -1
  36. package/dist/scanner.d.ts +8 -8
  37. package/dist/scanner.d.ts.map +1 -1
  38. package/dist/scanner.mjs +29 -31
  39. package/dist/scanner.mjs.map +1 -1
  40. package/dist/schema.mjs +1 -1
  41. package/dist/schema.mjs.map +1 -1
  42. package/dist/scriptable-pack-node.d.ts +4 -4
  43. package/dist/scriptable-pack-node.d.ts.map +1 -1
  44. package/dist/scriptable-pack-node.mjs +18 -20
  45. package/dist/scriptable-pack-node.mjs.map +1 -1
  46. package/dist/scriptable-pack.d.ts +3 -2
  47. package/dist/scriptable-pack.d.ts.map +1 -1
  48. package/dist/scriptable-pack.mjs +19 -23
  49. package/dist/scriptable-pack.mjs.map +1 -1
  50. package/package.json +2 -7
  51. package/schema/pack.schema.json +1 -1
  52. package/src/__tests__/material-cook-receipt-layer-plan.unit.test.ts +16 -0
  53. package/src/__tests__/material-cook-schema.unit.test.ts +62 -0
  54. package/src/__tests__/{parameterized-pack-authoring-gateway.unit.test.ts → pack-authoring-gateway.unit.test.ts} +9 -9
  55. package/src/__tests__/{parameterized-pack.unit.test.ts → pack-authoring.unit.test.ts} +10 -10
  56. package/src/__tests__/{parameterized-pack-scanner.unit.test.ts → pack-scanner.unit.test.ts} +6 -6
  57. package/src/__tests__/scanner-inventory.contract.test.ts +2 -4
  58. package/src/__tests__/scanner-inventory.test.ts +7 -6
  59. package/src/__tests__/scriptable-pack-cli.integration.test.ts +1 -1
  60. package/src/build.ts +8 -8
  61. package/src/catalog-builder.ts +3 -3
  62. package/src/cli-asset.ts +5 -5
  63. package/src/evidence/material-cook.ts +79 -3
  64. package/src/evidence/source-inventory.ts +3 -3
  65. package/src/index.ts +4 -1
  66. package/src/material-cook.ts +1 -0
  67. package/src/{parameterized-pack-node.ts → pack-authoring-node.ts} +71 -73
  68. package/src/{parameterized-pack.ts → pack-authoring.ts} +59 -74
  69. package/src/scanner.ts +26 -28
  70. package/src/schema/material-cook.schema.json +4 -1
  71. package/src/scriptable-pack-node.ts +25 -25
  72. package/src/scriptable-pack.ts +3 -2
  73. package/dist/__tests__/parameterized-pack-authoring-gateway.unit.test.d.ts +0 -2
  74. package/dist/__tests__/parameterized-pack-authoring-gateway.unit.test.d.ts.map +0 -1
  75. package/dist/__tests__/parameterized-pack-scanner.unit.test.d.ts +0 -2
  76. package/dist/__tests__/parameterized-pack-scanner.unit.test.d.ts.map +0 -1
  77. package/dist/__tests__/parameterized-pack.unit.test.d.ts +0 -2
  78. package/dist/__tests__/parameterized-pack.unit.test.d.ts.map +0 -1
  79. package/dist/parameterized-pack-node.d.ts.map +0 -1
  80. package/dist/parameterized-pack-node.mjs.map +0 -1
  81. package/dist/parameterized-pack.d.ts.map +0 -1
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { afterEach, describe, expect, it } from 'vitest';
5
5
  import { AssetGuid, PackageId } from '../guid.js';
6
- import { createFileSystemParameterizedPackAuthoringGateway } from '../parameterized-pack-node.js';
6
+ import { createFileSystemPackAuthoringGateway } from '../pack-authoring-node.js';
7
7
 
8
8
  const DIRECT_PACKAGE = '01900000-0000-7000-8000-000000000070';
9
9
  const CLONE_PACKAGE = '01900000-0000-7000-8000-000000000071';
@@ -23,11 +23,11 @@ afterEach(async () => {
23
23
  if (root !== undefined) await rm(root, { recursive: true, force: true });
24
24
  });
25
25
 
26
- describe('parameterized Pack authoring gateway', () => {
26
+ describe('Pack authoring gateway', () => {
27
27
  it('writes guid-less direct JSON, derives identity, and makes mutations idempotent', async () => {
28
28
  const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-direct-'));
29
29
  roots.push(root);
30
- const gateway = createFileSystemParameterizedPackAuthoringGateway({ gameRoot: root });
30
+ const gateway = createFileSystemPackAuthoringGateway({ gameRoot: root });
31
31
  const operation = {
32
32
  operation: 'asset-source.create' as const,
33
33
  requestId: 'create-direct',
@@ -72,7 +72,7 @@ describe('parameterized Pack authoring gateway', () => {
72
72
  });
73
73
  expect(unresolved).toMatchObject({ ok: false, error: { code: 'asset-not-ready' } });
74
74
 
75
- const publishedGateway = createFileSystemParameterizedPackAuthoringGateway({
75
+ const publishedGateway = createFileSystemPackAuthoringGateway({
76
76
  gameRoot: root,
77
77
  materialized: () => [
78
78
  {
@@ -121,10 +121,10 @@ describe('parameterized Pack authoring gateway', () => {
121
121
  expect(cloneResolved).toMatchObject({ ok: true, value: { status: 'identity' } });
122
122
  });
123
123
 
124
- it('creates parameterized source and instance documents with CAS-protected value edits', async () => {
124
+ it('creates ScriptablePack source and Pack instance documents with CAS-protected value edits', async () => {
125
125
  const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-instance-'));
126
126
  roots.push(root);
127
- const gateway = createFileSystemParameterizedPackAuthoringGateway({ gameRoot: root });
127
+ const gateway = createFileSystemPackAuthoringGateway({ gameRoot: root });
128
128
  const source = await gateway.execute({
129
129
  operation: 'asset-source.create',
130
130
  requestId: 'create-source',
@@ -188,7 +188,7 @@ describe('parameterized Pack authoring gateway', () => {
188
188
  it('fails closed when the current materialized projection violates derived identity', async () => {
189
189
  const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-projection-'));
190
190
  roots.push(root);
191
- const gateway = createFileSystemParameterizedPackAuthoringGateway({
191
+ const gateway = createFileSystemPackAuthoringGateway({
192
192
  gameRoot: root,
193
193
  materialized: () => [
194
194
  {
@@ -216,7 +216,7 @@ describe('parameterized Pack authoring gateway', () => {
216
216
  it('does not fall back from a missing sourcePath to another package locator', async () => {
217
217
  const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-locator-'));
218
218
  roots.push(root);
219
- const gateway = createFileSystemParameterizedPackAuthoringGateway({ gameRoot: root });
219
+ const gateway = createFileSystemPackAuthoringGateway({ gameRoot: root });
220
220
  await gateway.execute({
221
221
  operation: 'asset-source.create',
222
222
  requestId: 'create-locator-source',
@@ -237,7 +237,7 @@ describe('parameterized Pack authoring gateway', () => {
237
237
  it('converts a broken materialized projection callback into a structured error', async () => {
238
238
  const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-projection-callback-'));
239
239
  roots.push(root);
240
- const gateway = createFileSystemParameterizedPackAuthoringGateway({
240
+ const gateway = createFileSystemPackAuthoringGateway({
241
241
  gameRoot: root,
242
242
  materialized: () => {
243
243
  throw new Error('projection unavailable');
@@ -5,12 +5,12 @@ import {
5
5
  definePack,
6
6
  definePackageId,
7
7
  type PackParameterInheritanceSubject,
8
- parseParameterizedPackJson,
8
+ parsePackSourceJson,
9
9
  projectDirectPackJson,
10
10
  resolvePackParameterInheritance,
11
11
  resolvePackParameterValues,
12
12
  validatePackDefinition,
13
- } from '../parameterized-pack.js';
13
+ } from '../pack-authoring.js';
14
14
 
15
15
  const NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
16
16
 
@@ -20,7 +20,7 @@ function packageId(value: string) {
20
20
  return result.value;
21
21
  }
22
22
 
23
- describe('parameterized Pack authoring', () => {
23
+ describe('ScriptablePack and Pack authoring', () => {
24
24
  it('derives the RFC 4122 UUIDv5 vector and keeps PackageId branded separately', () => {
25
25
  const namespace = packageId(NAMESPACE);
26
26
  expect(AssetGuid.format(AssetGuid.derive(namespace, 'www.widgets.com'))).toBe(
@@ -30,7 +30,7 @@ describe('parameterized Pack authoring', () => {
30
30
  expect(AssetGuid.derive(namespace, 'mesh/a')).not.toBe(namespace);
31
31
  });
32
32
 
33
- it('derives an explicit context shape for zero-parameter and parameterized Packs', async () => {
33
+ it('derives an explicit context shape for zero-parameter and parameter-bearing ScriptablePacks', async () => {
34
34
  const zero = definePack({
35
35
  schemaVersion: '2.0.0',
36
36
  packageId: definePackageId('01900000-0000-7000-8000-000000000001'),
@@ -51,7 +51,7 @@ describe('parameterized Pack authoring', () => {
51
51
  });
52
52
  expect(zeroResult.ok).toBe(true);
53
53
 
54
- const parameterized = definePack({
54
+ const withParameters = definePack({
55
55
  schemaVersion: '2.0.0',
56
56
  packageId: definePackageId('01900000-0000-7000-8000-000000000002'),
57
57
  parameters: [
@@ -67,9 +67,9 @@ describe('parameterized Pack authoring', () => {
67
67
  },
68
68
  }),
69
69
  });
70
- const values = resolvePackParameterValues(parameterized, { segments: 24 });
70
+ const values = resolvePackParameterValues(withParameters, { segments: 24 });
71
71
  expect(values).toMatchObject({ ok: true, value: { segments: 24, style: 'oak' } });
72
- expect(resolvePackParameterValues(parameterized, { unknown: true })).toMatchObject({
72
+ expect(resolvePackParameterValues(withParameters, { unknown: true })).toMatchObject({
73
73
  ok: false,
74
74
  error: { code: 'pack-parameter-invalid' },
75
75
  });
@@ -148,7 +148,7 @@ describe('parameterized Pack authoring', () => {
148
148
  });
149
149
 
150
150
  it('parses direct and instance JSON without accepting GUID fields', () => {
151
- const direct = parseParameterizedPackJson({
151
+ const direct = parsePackSourceJson({
152
152
  schemaVersion: '3.0.0',
153
153
  packageId: '01900000-0000-7000-8000-000000000010',
154
154
  assets: {
@@ -167,14 +167,14 @@ describe('parameterized Pack authoring', () => {
167
167
  });
168
168
  }
169
169
  expect(
170
- parseParameterizedPackJson({
170
+ parsePackSourceJson({
171
171
  schemaVersion: '3.0.0',
172
172
  packageId: '01900000-0000-7000-8000-000000000010',
173
173
  assets: { 'scene/main': { guid: NAMESPACE, kind: 'scene', payload: {}, refs: [] } },
174
174
  }),
175
175
  ).toMatchObject({ ok: false, error: { code: 'pack-parameter-invalid' } });
176
176
  expect(
177
- parseParameterizedPackJson({
177
+ parsePackSourceJson({
178
178
  schemaVersion: '3.0.0',
179
179
  packageId: '01900000-0000-7000-8000-000000000020',
180
180
  parent: '01900000-0000-7000-8000-000000000002',
@@ -16,9 +16,9 @@ afterEach(async () => {
16
16
  if (root !== undefined) await rm(root, { recursive: true, force: true });
17
17
  });
18
18
 
19
- describe('parameterized Pack scanner', () => {
19
+ describe('ScriptablePack and Pack source scanner', () => {
20
20
  it('indexes direct v3 output keys as derived GUIDs without requiring entry GUIDs', async () => {
21
- const root = await mkdtemp(join(tmpdir(), 'forgeax-parameterized-scan-'));
21
+ const root = await mkdtemp(join(tmpdir(), 'forgeax-scriptable-pack-scan-'));
22
22
  roots.push(root);
23
23
  const path = join(root, 'direct.pack.json');
24
24
  await writeFile(
@@ -65,8 +65,8 @@ describe('parameterized Pack scanner', () => {
65
65
  expect(stderr).toEqual([]);
66
66
  });
67
67
 
68
- it('captures a parameterized source as metadata and resolves an instance parent', async () => {
69
- const root = await mkdtemp(join(tmpdir(), 'forgeax-parameterized-source-scan-'));
68
+ it('captures a ScriptablePack source as metadata and resolves an instance parent', async () => {
69
+ const root = await mkdtemp(join(tmpdir(), 'forgeax-scriptable-pack-source-scan-'));
70
70
  roots.push(root);
71
71
  await writeFile(
72
72
  join(root, 'source.pack.ts'),
@@ -96,12 +96,12 @@ describe('parameterized Pack scanner', () => {
96
96
  expect(result.ok).toBe(true);
97
97
  if (!result.ok) return;
98
98
  const declaration = result.value.declarations.get(join(root, 'source.pack.ts'));
99
- expect(declaration?.format).toBe('pack.ts-parameterized');
99
+ expect(declaration?.format).toBe('pack.ts');
100
100
  expect(result.value.inventory).toEqual([]);
101
101
  });
102
102
 
103
103
  it('fails closed when an instance parent is missing', async () => {
104
- const root = await mkdtemp(join(tmpdir(), 'forgeax-parameterized-parent-scan-'));
104
+ const root = await mkdtemp(join(tmpdir(), 'forgeax-scriptable-pack-parent-scan-'));
105
105
  roots.push(root);
106
106
  await writeFile(
107
107
  join(root, 'missing.pack.json'),
@@ -117,16 +117,14 @@ describe('engine-pack scanner inventory contract', () => {
117
117
  });
118
118
  const scriptableDeclaration = result.value.declarations.get(scriptablePath);
119
119
  expect(scriptableDeclaration).toMatchObject({
120
- format: 'pack.ts-parameterized',
120
+ format: 'pack.ts',
121
121
  sourcePath: scriptablePath,
122
122
  value: {
123
123
  packageId: '019e3969-1d48-7c3b-ac24-6d68f4570662',
124
124
  },
125
125
  });
126
126
  expect(
127
- scriptableDeclaration?.format === 'pack.ts-parameterized'
128
- ? scriptableDeclaration.value
129
- : undefined,
127
+ scriptableDeclaration?.format === 'pack.ts' ? scriptableDeclaration.value : undefined,
130
128
  ).not.toHaveProperty('subAssets');
131
129
  });
132
130
 
@@ -44,11 +44,11 @@ describe('AssetInventory', () => {
44
44
  const { inventory, sourcePath } = getFixture();
45
45
  const declaration = inventory.declarations.get(sourcePath);
46
46
  expect(declaration).toMatchObject({
47
- format: 'pack.ts-parameterized',
47
+ format: 'pack.ts',
48
48
  sourcePath,
49
49
  value: {
50
50
  schemaVersion: '2.0.0',
51
- kind: 'parameterized-pack-source',
51
+ kind: 'scriptable-pack-source',
52
52
  packageId: '00000000-0000-0000-0000-000000000001',
53
53
  source: sourcePath,
54
54
  },
@@ -59,7 +59,7 @@ describe('AssetInventory', () => {
59
59
  const { inventory, sourcePath } = getFixture();
60
60
  const declaration = inventory.declarations.get(sourcePath);
61
61
  expect(
62
- declaration?.format === 'pack.ts-parameterized'
62
+ declaration?.format === 'pack.ts'
63
63
  ? declaration.sourceClosure.map((entry) => entry.path)
64
64
  : undefined,
65
65
  ).toEqual([await realpath(sourcePath)]);
@@ -69,9 +69,10 @@ describe('AssetInventory', () => {
69
69
  const { inventory, sourcePath } = getFixture();
70
70
  const declaration = inventory.declarations.get(sourcePath);
71
71
  expect(inventory.inventory).toEqual([]);
72
- expect(
73
- declaration?.format === 'pack.ts-parameterized' ? declaration.definition : undefined,
74
- ).toMatchObject({ packageId: expect.any(Uint8Array), schemaVersion: '2.0.0' });
72
+ expect(declaration?.format === 'pack.ts' ? declaration.definition : undefined).toMatchObject({
73
+ packageId: expect.any(Uint8Array),
74
+ schemaVersion: '2.0.0',
75
+ });
75
76
  });
76
77
 
77
78
  it('keeps semantic identity stable when the source path is renamed', () => {
@@ -31,7 +31,7 @@ describe('ScriptablePack CLI Meta inspection', () => {
31
31
  expect(code).toBe(0);
32
32
  expect(stderr).toEqual([]);
33
33
  expect(JSON.parse(stdout[0] ?? '{}')).toMatchObject({
34
- kind: 'parameterized-pack-source',
34
+ kind: 'scriptable-pack-source',
35
35
  source,
36
36
  packageId: '019ffa97-8b39-7ad2-84cc-273268591ab4',
37
37
  });
package/src/build.ts CHANGED
@@ -94,6 +94,13 @@ export {
94
94
  type MeshBinHeaderV4,
95
95
  writeMeshBinHeader,
96
96
  } from './mesh-bin-contract.js';
97
+ export * from './pack-authoring.js';
98
+ export {
99
+ createFileSystemPackAuthoringGateway,
100
+ createFileSystemPackAuthoringPort,
101
+ type FileSystemPackAuthoringOptions,
102
+ type PackAuthoringMaterializedAsset,
103
+ } from './pack-authoring-node.js';
97
104
  export {
98
105
  type FinalizedPackageProduct,
99
106
  finalizePackageProduct,
@@ -106,13 +113,6 @@ export {
106
113
  type PackageProductAsset,
107
114
  packageTransportRevision,
108
115
  } from './package-finalizer.js';
109
- export * from './parameterized-pack.js';
110
- export {
111
- createFileSystemParameterizedPackAuthoringGateway,
112
- createFileSystemParameterizedPackAuthoringPort,
113
- type FileSystemParameterizedPackAuthoringOptions,
114
- type ParameterizedPackMaterializedAsset,
115
- } from './parameterized-pack-node.js';
116
116
  export { validateProducerContract, validateProducerOutputs } from './producer-contract.js';
117
117
  export { resolveAssetSource } from './resolve-asset-source.js';
118
118
  export { parsePackV2, validateMeta, validatePack, validatePackV2 } from './runtime.js';
@@ -129,7 +129,7 @@ export {
129
129
  type LegacyPackInventoryDocument,
130
130
  type PackInventoryAsset,
131
131
  type PackInventoryDocument,
132
- type ParameterizedPackInventoryDocument,
132
+ type PackSourceInventoryDocument,
133
133
  type ScanInventory,
134
134
  type ScanOptions,
135
135
  type ScanSourceDeclaration,
@@ -6,7 +6,7 @@ import {
6
6
  projectPackageCatalog,
7
7
  } from './catalog-projection.js';
8
8
  import { deriveAssetName } from './deriveAssetName.js';
9
- import { parseParameterizedPackJson, projectDirectPackJson } from './parameterized-pack.js';
9
+ import { parsePackSourceJson, projectDirectPackJson } from './pack-authoring.js';
10
10
  import { resolveAssetSource } from './resolve-asset-source.js';
11
11
  import {
12
12
  type MetaInventoryDocument,
@@ -201,7 +201,7 @@ function projectSource(
201
201
  declarations: Map<string, CatalogImportMeta>,
202
202
  sourceIdentityFor: CatalogBuildProjectionOptions['sourceIdentityFor'],
203
203
  ): { readonly entries: PackIndexEntry[]; readonly error?: CatalogBuildError } {
204
- if (sourceDeclaration.format === 'pack.ts-parameterized') {
204
+ if (sourceDeclaration.format === 'pack.ts') {
205
205
  // A source declaration is an authoring identity and parameter contract,
206
206
  // not a materialized output set. Dynamic build owns publication after its
207
207
  // fixed-point worklist converges; Catalog scanning must not invent rows.
@@ -233,7 +233,7 @@ function projectSource(
233
233
  const parsed = sourceDeclaration.value;
234
234
  const sourcePath = catalogSourcePathFor(cwd, path, sourceIdentityFor);
235
235
  if (parsed.schemaVersion === '3.0.0') {
236
- const authoring = parseParameterizedPackJson(parsed);
236
+ const authoring = parsePackSourceJson(parsed);
237
237
  if (!authoring.ok) {
238
238
  return {
239
239
  entries: [],
package/src/cli-asset.ts CHANGED
@@ -26,9 +26,9 @@ import {
26
26
  import { readSourceInventory } from './evidence/source-inventory.js';
27
27
  import { isValidAssetGuidString } from './guid.js';
28
28
  import { parsePackV2 } from './index.js';
29
- import { projectParameterizedPackMeta } from './parameterized-pack.js';
29
+ import { projectScriptablePackMeta } from './pack-authoring.js';
30
30
  import { type ScanSourceDeclaration, scanInventory } from './scanner.js';
31
- import { loadParameterizedScriptablePack } from './scriptable-pack-node.js';
31
+ import { loadScriptablePack } from './scriptable-pack-node.js';
32
32
 
33
33
  export interface PackEntry {
34
34
  readonly guid: string;
@@ -81,7 +81,7 @@ export async function scanEntries(
81
81
  return { ok: false };
82
82
  }
83
83
  const entries: PackEntry[] = [];
84
- // The scanner is the single identity projection for Pack JSON. Parameterized
84
+ // The scanner is the single identity projection for Pack JSON. ScriptablePack
85
85
  // source modules deliberately contribute no rows until a build generation.
86
86
  for (const declaration of result.value.declarations.values()) {
87
87
  if (declaration.format !== 'meta.json') continue;
@@ -233,11 +233,11 @@ async function runMeta(rest: string[], ctx: AssetCtx): Promise<number> {
233
233
  });
234
234
  }
235
235
  const cwd = ctx.cwd ?? process.cwd();
236
- const loaded = await loadParameterizedScriptablePack(absolutePath(source, cwd), {
236
+ const loaded = await loadScriptablePack(absolutePath(source, cwd), {
237
237
  metadataOnly: true,
238
238
  });
239
239
  if (!loaded.ok) return emitError(ctx, loaded.error);
240
- ctx.stdoutWrite(JSON.stringify(projectParameterizedPackMeta(loaded.value, source)));
240
+ ctx.stdoutWrite(JSON.stringify(projectScriptablePackMeta(loaded.value, source)));
241
241
  return 0;
242
242
  }
243
243
 
@@ -7,7 +7,7 @@ import type {
7
7
  MaterialValue,
8
8
  Result,
9
9
  } from '@forgeax/engine-types';
10
- import { err, MATERIAL_TEXTURE_SLOTS, ok } from '@forgeax/engine-types';
10
+ import { deriveStandardLayerPlan, err, MATERIAL_TEXTURE_SLOTS, ok } from '@forgeax/engine-types';
11
11
  import { sha256 } from '@noble/hashes/sha2.js';
12
12
  import { bytesToHex } from '@noble/hashes/utils.js';
13
13
 
@@ -56,7 +56,11 @@ export interface MaterialCookReceipt {
56
56
  readonly profile: string;
57
57
  readonly compilerVersion: string;
58
58
  readonly identity: MaterialCookIdentity;
59
- readonly derivedInterface: { readonly layoutIdentity: string };
59
+ readonly derivedInterface: {
60
+ readonly layoutIdentity: string;
61
+ /** Build-time Standard physical lowering identity, when the root is Standard. */
62
+ readonly layerPlanIdentity?: string;
63
+ };
60
64
  }
61
65
 
62
66
  export interface MaterialParameterContract {
@@ -101,6 +105,36 @@ export interface MaterialCookIdentityExpectation {
101
105
  readonly inputDigest?: string;
102
106
  }
103
107
 
108
+ const STANDARD_ROOT_MODULES = new Set([
109
+ 'forgeax::default-standard-pbr',
110
+ 'forgeax::pbr-skin',
111
+ 'forgeax_material::standard',
112
+ 'forgeax_material::pbr-skin',
113
+ ]);
114
+
115
+ /** Shared ownership predicate for the built-in Standard material root. */
116
+ export function isStandardRootModule(module: string): boolean {
117
+ return STANDARD_ROOT_MODULES.has(module);
118
+ }
119
+
120
+ export function isStandardMaterialRecord(record: Pick<CookedMaterialRecord, 'resolved'>): boolean {
121
+ return record.resolved.passes.some((pass) => isStandardRootModule(pass.program.module));
122
+ }
123
+
124
+ /**
125
+ * Recompute the Standard layer identity from the admitted resolved root.
126
+ * Material admission and runtime loading share this owner so a stale receipt
127
+ * cannot select a different physical layer plan after cooking.
128
+ */
129
+ export function materialLayerPlanIdentity(
130
+ record: Pick<CookedMaterialRecord, 'resolved'>,
131
+ ): string | undefined {
132
+ if (!isStandardMaterialRecord(record)) {
133
+ return undefined;
134
+ }
135
+ return deriveStandardLayerPlan(record.resolved.parameters, record.resolved.passes).identity;
136
+ }
137
+
104
138
  function unique(values: readonly string[]): readonly string[] {
105
139
  return [...new Set(values)].sort();
106
140
  }
@@ -286,6 +320,16 @@ export function validateMaterialCookReceipt(
286
320
  if (derivedInterface.layoutIdentity !== identity.layoutIdentity) {
287
321
  return invalid('receipt.derivedInterface.layoutIdentity', derivedInterface.layoutIdentity);
288
322
  }
323
+ if (
324
+ derivedInterface.layerPlanIdentity !== undefined &&
325
+ (typeof derivedInterface.layerPlanIdentity !== 'string' ||
326
+ derivedInterface.layerPlanIdentity.length === 0)
327
+ ) {
328
+ return invalid(
329
+ 'receipt.derivedInterface.layerPlanIdentity',
330
+ derivedInterface.layerPlanIdentity,
331
+ );
332
+ }
289
333
  for (const field of ['sourceClosure', 'profile', 'compilerVersion']) {
290
334
  const fieldValue = candidate[field];
291
335
  if (
@@ -320,7 +364,12 @@ export function validateMaterialCookReceipt(
320
364
  profile: candidate.profile as string,
321
365
  compilerVersion: candidate.compilerVersion as string,
322
366
  identity,
323
- derivedInterface: { layoutIdentity: derivedInterface.layoutIdentity as string },
367
+ derivedInterface: {
368
+ layoutIdentity: derivedInterface.layoutIdentity as string,
369
+ ...(derivedInterface.layerPlanIdentity === undefined
370
+ ? {}
371
+ : { layerPlanIdentity: derivedInterface.layerPlanIdentity as string }),
372
+ },
324
373
  });
325
374
  }
326
375
 
@@ -380,6 +429,33 @@ export function validateCookedMaterialRecord(
380
429
  artifactDigest: artifact.digest as string,
381
430
  });
382
431
  if (!receiptResult.ok) return receiptResult;
432
+ const resolved = candidate.resolved as Record<string, unknown>;
433
+ if (!Array.isArray(resolved.passes)) return invalid('resolved.passes', resolved.passes);
434
+ if (!Array.isArray(resolved.parameters))
435
+ return invalid('resolved.parameters', resolved.parameters);
436
+ if (
437
+ resolved.values === null ||
438
+ typeof resolved.values !== 'object' ||
439
+ Array.isArray(resolved.values)
440
+ )
441
+ return invalid('resolved.values', resolved.values);
442
+ let expectedLayerPlanIdentity: string | undefined;
443
+ try {
444
+ expectedLayerPlanIdentity = materialLayerPlanIdentity({
445
+ resolved: resolved as unknown as CookedMaterialRecord['resolved'],
446
+ });
447
+ } catch (error) {
448
+ return invalid('resolved.layerPlanIdentity', error instanceof Error ? error.message : error);
449
+ }
450
+ if (
451
+ expectedLayerPlanIdentity !== undefined &&
452
+ receiptResult.value.derivedInterface.layerPlanIdentity !== expectedLayerPlanIdentity
453
+ ) {
454
+ return invalid(
455
+ 'receipt.derivedInterface.layerPlanIdentity',
456
+ receiptResult.value.derivedInterface.layerPlanIdentity,
457
+ );
458
+ }
383
459
  if (candidate.artifactDigest !== undefined && candidate.artifactDigest !== artifact.digest)
384
460
  return invalid('artifactDigest', candidate.artifactDigest);
385
461
  if (
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { readdir, readFile } from 'node:fs/promises';
3
3
  import { basename, dirname, join, relative, resolve, sep } from 'node:path';
4
4
  import type { SourceDeclarationEvidence } from '@forgeax/engine-types';
5
- import { parseParameterizedPackJson, projectDirectPackJson } from '../parameterized-pack.js';
5
+ import { parsePackSourceJson, projectDirectPackJson } from '../pack-authoring.js';
6
6
  import { SCANNER_BLACKLIST, scan } from '../scanner.js';
7
7
 
8
8
  /** Project root and GUID used to locate an authoritative source declaration. */
@@ -149,7 +149,7 @@ export async function readSourceInventory(
149
149
  let authored: SourceDeclarationEvidence | undefined;
150
150
  for (const path of paths) {
151
151
  if (path.endsWith('.pack.ts')) {
152
- // Parameterized source identity is a build result, not a declaration
152
+ // ScriptablePack source identity is a build result, not a declaration
153
153
  // table. The source index therefore records the source only after the
154
154
  // producer publishes its materialized Pack output.
155
155
  continue;
@@ -166,7 +166,7 @@ export async function readSourceInventory(
166
166
  }
167
167
  if (path.endsWith('.pack.json')) {
168
168
  if (parsed.schemaVersion === '3.0.0') {
169
- const direct = parseParameterizedPackJson(parsed);
169
+ const direct = parsePackSourceJson(parsed);
170
170
  if (direct.ok && direct.value.format === 'direct') {
171
171
  const projected = projectDirectPackJson(direct.value);
172
172
  if (
package/src/index.ts CHANGED
@@ -7,6 +7,8 @@ export {
7
7
  collectMaterialCookRefs,
8
8
  createMaterialArtifactDigest,
9
9
  createMaterialCookIdentity,
10
+ isStandardMaterialRecord,
11
+ isStandardRootModule,
10
12
  type MaterialCookArtifact,
11
13
  type MaterialCookIdentity,
12
14
  type MaterialCookIdentityExpectation,
@@ -15,6 +17,7 @@ export {
15
17
  type MaterialCookRecordError,
16
18
  type MaterialCookRefs,
17
19
  type MaterialCookWasmProvenance,
20
+ materialLayerPlanIdentity,
18
21
  projectCookedMaterialRecord,
19
22
  serializeCookedMaterialRecord,
20
23
  serializeMaterialCookReceipt,
@@ -40,7 +43,7 @@ export {
40
43
  type MeshBinHeaderV4,
41
44
  writeMeshBinHeader,
42
45
  } from './mesh-bin-contract.js';
43
- export * from './parameterized-pack.js';
46
+ export * from './pack-authoring.js';
44
47
  export { validateProducerContract, validateProducerOutputs } from './producer-contract.js';
45
48
  export {
46
49
  type AssetReader,
@@ -8,6 +8,7 @@ export {
8
8
  type MaterialCookRecordError,
9
9
  type MaterialCookRefs,
10
10
  type MaterialCookWasmProvenance,
11
+ materialLayerPlanIdentity,
11
12
  projectCookedMaterialRecord,
12
13
  serializeCookedMaterialRecord,
13
14
  serializeMaterialCookReceipt,