Skip to content

Commit 74727bf

Browse files
author
DavidQ
committed
BUILD PR: centralize runtime asset validation/registry helpers across exact tools/shared batch.
1 parent 6bbebdb commit 74727bf

8 files changed

Lines changed: 173 additions & 105 deletions

docs/dev/CODEX_COMMANDS.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
MODEL: GPT-5.3-codex
22
REASONING: high
33
COMMAND:
4-
Execute docs/pr/BUILD_PR_SHARED_EXTRACTION_47_PROJECT_SYSTEM_VALUE_HELPERS_BATCH.md exactly.
4+
Execute docs/pr/BUILD_PR_SHARED_EXTRACTION_48_RUNTIME_ASSET_VALIDATION_BATCH.md exactly.
55
Edit only these files:
6-
- tools/shared/projectSystemValueUtils.js (new file)
7-
- tools/shared/projectManifestContract.js
8-
- tools/shared/projectSystem.js
9-
- tools/shared/projectSystemAdapters.js
6+
- tools/shared/runtimeAssetValidationUtils.js (new file)
7+
- tools/shared/runtimeAssetLoader.js
8+
- tools/shared/runtimeStreaming.js
109
Do not expand scope.
11-
Package the delta output to <project folder>/tmp/BUILD_PR_SHARED_EXTRACTION_47_PROJECT_SYSTEM_VALUE_HELPERS_BATCH_delta.zip
10+
Package the delta output to <project folder>/tmp/BUILD_PR_SHARED_EXTRACTION_48_RUNTIME_ASSET_VALIDATION_BATCH_delta.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
BUILD PR: centralize project-system cloneValue/safeString helpers across exact tools/shared batch.
1+
BUILD PR: centralize runtime asset validation/registry helpers across exact tools/shared batch.

docs/dev/NEXT_COMMAND.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Next: run BUILD_PR_SHARED_EXTRACTION_48_RUNTIME_ASSET_VALIDATION_BATCH after this batch.
1+
Next: run BUILD_PR_SHARED_EXTRACTION_49_VECTOR_TO_FINITE_NUMBER_ROUND_BATCH after this batch.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Execution-grade batch for cloneValue/safeString using the exact duplicate-report file list.
1+
Execution-grade batch for validatePackageManifest/createRegistryDefinition using the exact duplicate-report file list.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# BUILD_PR_SHARED_EXTRACTION_48_RUNTIME_ASSET_VALIDATION_BATCH
2+
3+
## Purpose
4+
Centralize duplicated runtime asset validation/registry helpers across the exact tools/shared batch identified in the duplicate report.
5+
6+
## Single PR Purpose
7+
Normalize ONLY these helpers:
8+
9+
- `validatePackageManifest(manifest)`
10+
- `createRegistryDefinition(asset, source)`
11+
12+
## Exact Files Allowed
13+
14+
### New shared file
15+
1. `tools/shared/runtimeAssetValidationUtils.js`
16+
17+
### Consumer files
18+
2. `tools/shared/runtimeAssetLoader.js`
19+
3. `tools/shared/runtimeStreaming.js`
20+
21+
Do not edit any other file.
22+
23+
## Source of Truth
24+
This exact scope comes from the provided duplicate report entries for:
25+
- `function validatePackageManifest(manifest)`
26+
- `function createRegistryDefinition(asset, source)`
27+
28+
Only the 2 listed consumer files are in scope.
29+
30+
## Exact Shared Helper Creation
31+
Create:
32+
33+
`tools/shared/runtimeAssetValidationUtils.js`
34+
35+
Export exactly:
36+
- `validatePackageManifest`
37+
- `createRegistryDefinition`
38+
39+
Implementation rules:
40+
- use ONE existing local implementation as source-of-truth for each helper
41+
- do NOT merge logic
42+
- do NOT generalize behavior
43+
- preserve signatures exactly:
44+
- `validatePackageManifest(manifest)`
45+
- `createRegistryDefinition(asset, source)`
46+
47+
## Exact Consumer Changes
48+
For each of the 2 listed consumer files:
49+
50+
If the file contains local function definitions matching:
51+
```js
52+
function validatePackageManifest(manifest)
53+
function createRegistryDefinition(asset, source)
54+
```
55+
then:
56+
- remove the local function definition(s)
57+
- import the helper(s) from the correct relative path to:
58+
- `./runtimeAssetValidationUtils.js`
59+
- if the file already imports from that module, add the needed helper(s) with the minimum safe edit
60+
- do not duplicate imports
61+
- do not touch unrelated helpers
62+
- do not change logic
63+
64+
If a listed file already imports and uses shared versions, leave it unchanged.
65+
66+
## Relative Import Rule
67+
Use exactly:
68+
69+
`./runtimeAssetValidationUtils.js`
70+
71+
Do not use aliases.
72+
Do not change `.js` extension usage.
73+
74+
## Hard Constraints
75+
- no files outside the 2 listed consumers plus the one new shared file
76+
- no repo-wide runtime-asset cleanup
77+
- no behavior changes
78+
- keep one PR purpose only
79+
80+
## Validation Checklist
81+
1. Confirm no more than the 3 listed files changed
82+
2. Confirm `tools/shared/runtimeAssetValidationUtils.js` exists and exports:
83+
- `validatePackageManifest`
84+
- `createRegistryDefinition`
85+
3. Confirm local function definitions no longer exist in changed listed consumer files
86+
4. Confirm changed consumer files import the helpers from `./runtimeAssetValidationUtils.js`
87+
5. Confirm no unrelated files changed
88+
6. Confirm no behavior changes were made
89+
90+
## Non-Goals
91+
- no cleanup outside the 2 listed consumer files
92+
- no refactor beyond this exact duplicate-removal batch

tools/shared/runtimeAssetLoader.js

Lines changed: 1 addition & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import AssetRegistry from "../../engine/assets/AssetRegistry.js";
22
import AssetLoaderSystem from "../../engine/assets/AssetLoaderSystem.js";
33
import ImageAssetLoader from "../../engine/assets/ImageAssetLoader.js";
44
import { prepareVectorGeometryRuntimeAsset } from "./vectorGeometryRuntime.js";
5+
import { validatePackageManifest, createRegistryDefinition } from "./runtimeAssetValidationUtils.js";
56
import { ensureArray } from "../../src/shared/utils/arrayUtils.js";
67
import { cloneJson } from "../../src/shared/utils/jsonUtils.js";
78
import { trimSafe } from "../../src/shared/utils/stringUtils.js";
@@ -25,76 +26,6 @@ function createLoaderState(status, reports, extra = {}) {
2526
};
2627
}
2728

28-
function validatePackageManifest(manifest) {
29-
const pkg = manifest?.package;
30-
if (!pkg || typeof pkg !== "object") {
31-
throw new Error("Invalid packaged input: missing package root.");
32-
}
33-
if (!Number.isFinite(pkg.version)) {
34-
throw new Error("Invalid packaged input: package.version must be numeric.");
35-
}
36-
if (!trimSafe(pkg.projectId)) {
37-
throw new Error("Invalid packaged input: package.projectId is required.");
38-
}
39-
if (!Array.isArray(pkg.assets) || pkg.assets.length === 0) {
40-
throw new Error("Invalid packaged input: package.assets must contain at least one asset.");
41-
}
42-
if (!Array.isArray(pkg.dependencies)) {
43-
throw new Error("Invalid packaged input: package.dependencies must be an array.");
44-
}
45-
if (!Array.isArray(pkg.roots) || pkg.roots.length === 0) {
46-
throw new Error("Invalid packaged input: package.roots must contain at least one startup root.");
47-
}
48-
49-
const seenIds = new Set();
50-
pkg.assets.forEach((asset, index) => {
51-
const id = trimSafe(asset?.id);
52-
const type = trimSafe(asset?.type);
53-
if (!id) {
54-
throw new Error(`Invalid packaged input: asset at index ${index} is missing id.`);
55-
}
56-
if (!type) {
57-
throw new Error(`Invalid packaged input: asset ${id} is missing type.`);
58-
}
59-
if (seenIds.has(id)) {
60-
throw new Error(`Invalid packaged input: duplicate packaged asset ${id}.`);
61-
}
62-
seenIds.add(id);
63-
});
64-
65-
pkg.roots.forEach((root, index) => {
66-
const id = trimSafe(root?.id);
67-
if (!id || !seenIds.has(id)) {
68-
throw new Error(`Invalid packaged input: startup root at index ${index} does not resolve to a packaged asset.`);
69-
}
70-
});
71-
72-
pkg.dependencies.forEach((dependency, index) => {
73-
const from = trimSafe(dependency?.from);
74-
const to = trimSafe(dependency?.to);
75-
const type = trimSafe(dependency?.type);
76-
if (!from || !to || !type) {
77-
throw new Error(`Invalid packaged input: dependency at index ${index} is incomplete.`);
78-
}
79-
if (!seenIds.has(from) || !seenIds.has(to)) {
80-
throw new Error(`Invalid packaged input: dependency ${from} -> ${to} references a missing packaged asset.`);
81-
}
82-
});
83-
84-
return pkg;
85-
}
86-
87-
function createRegistryDefinition(asset, source) {
88-
const type = trimSafe(asset?.type);
89-
const sourceType = type === "image" ? "image" : "data";
90-
return {
91-
id: trimSafe(asset?.id),
92-
type: sourceType,
93-
path: trimSafe(asset?.path),
94-
source
95-
};
96-
}
97-
9829
function toBootstrapData(packageManifest, loadedAssets) {
9930
const pkg = packageManifest.package;
10031
return {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { trimSafe } from "../../src/shared/utils/stringUtils.js";
2+
3+
export function validatePackageManifest(manifest) {
4+
const pkg = manifest?.package;
5+
if (!pkg || typeof pkg !== "object") {
6+
throw new Error("Invalid packaged input: missing package root.");
7+
}
8+
if (!Number.isFinite(pkg.version)) {
9+
throw new Error("Invalid packaged input: package.version must be numeric.");
10+
}
11+
if (!trimSafe(pkg.projectId)) {
12+
throw new Error("Invalid packaged input: package.projectId is required.");
13+
}
14+
if (!Array.isArray(pkg.assets) || pkg.assets.length === 0) {
15+
throw new Error("Invalid packaged input: package.assets must contain at least one asset.");
16+
}
17+
if (!Array.isArray(pkg.dependencies)) {
18+
throw new Error("Invalid packaged input: package.dependencies must be an array.");
19+
}
20+
if (!Array.isArray(pkg.roots) || pkg.roots.length === 0) {
21+
throw new Error("Invalid packaged input: package.roots must contain at least one startup root.");
22+
}
23+
24+
const seenIds = new Set();
25+
pkg.assets.forEach((asset, index) => {
26+
const id = trimSafe(asset?.id);
27+
const type = trimSafe(asset?.type);
28+
if (!id) {
29+
throw new Error(`Invalid packaged input: asset at index ${index} is missing id.`);
30+
}
31+
if (!type) {
32+
throw new Error(`Invalid packaged input: asset ${id} is missing type.`);
33+
}
34+
if (seenIds.has(id)) {
35+
throw new Error(`Invalid packaged input: duplicate packaged asset ${id}.`);
36+
}
37+
seenIds.add(id);
38+
});
39+
40+
pkg.roots.forEach((root, index) => {
41+
const id = trimSafe(root?.id);
42+
if (!id || !seenIds.has(id)) {
43+
throw new Error(`Invalid packaged input: startup root at index ${index} does not resolve to a packaged asset.`);
44+
}
45+
});
46+
47+
pkg.dependencies.forEach((dependency, index) => {
48+
const from = trimSafe(dependency?.from);
49+
const to = trimSafe(dependency?.to);
50+
const type = trimSafe(dependency?.type);
51+
if (!from || !to || !type) {
52+
throw new Error(`Invalid packaged input: dependency at index ${index} is incomplete.`);
53+
}
54+
if (!seenIds.has(from) || !seenIds.has(to)) {
55+
throw new Error(`Invalid packaged input: dependency ${from} -> ${to} references a missing packaged asset.`);
56+
}
57+
});
58+
59+
return pkg;
60+
}
61+
62+
export function createRegistryDefinition(asset, source) {
63+
const type = trimSafe(asset?.type);
64+
const sourceType = type === "image" ? "image" : "data";
65+
return {
66+
id: trimSafe(asset?.id),
67+
type: sourceType,
68+
path: trimSafe(asset?.path),
69+
source
70+
};
71+
}

tools/shared/runtimeStreaming.js

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import AssetRegistry from "../../engine/assets/AssetRegistry.js";
22
import AssetLoaderSystem from "../../engine/assets/AssetLoaderSystem.js";
33
import ImageAssetLoader from "../../engine/assets/ImageAssetLoader.js";
44
import { cloneJson } from "../../src/shared/utils/jsonUtils.js";
5+
import { validatePackageManifest, createRegistryDefinition } from "./runtimeAssetValidationUtils.js";
56

67
function sanitizeText(value) {
78
return typeof value === "string" ? value.trim() : "";
@@ -15,23 +16,6 @@ function createReport(level, code, message) {
1516
};
1617
}
1718

18-
function validatePackageManifest(manifest) {
19-
const pkg = manifest?.package;
20-
if (!pkg || typeof pkg !== "object") {
21-
throw new Error("Invalid packaged input: missing package root.");
22-
}
23-
if (!Array.isArray(pkg.assets) || pkg.assets.length === 0) {
24-
throw new Error("Invalid packaged input: package.assets must contain at least one asset.");
25-
}
26-
if (!Array.isArray(pkg.dependencies)) {
27-
throw new Error("Invalid packaged input: package.dependencies must be an array.");
28-
}
29-
if (!Array.isArray(pkg.roots) || pkg.roots.length === 0) {
30-
throw new Error("Invalid packaged input: package.roots must contain at least one startup root.");
31-
}
32-
return pkg;
33-
}
34-
3519
function collectBootAssetIds(pkg) {
3620
const adjacency = new Map();
3721
pkg.dependencies.forEach((dependency) => {
@@ -68,15 +52,6 @@ function collectBootAssetIds(pkg) {
6852
.filter((assetId) => boot.has(assetId));
6953
}
7054

71-
function createRegistryDefinition(asset, source) {
72-
return {
73-
id: sanitizeText(asset?.id),
74-
type: sanitizeText(asset?.type) === "image" ? "image" : "data",
75-
path: sanitizeText(asset?.path),
76-
source
77-
};
78-
}
79-
8055
export function summarizeRuntimeStreaming(result) {
8156
if (result?.streaming?.status === "ready") {
8257
return `Runtime streaming ready with ${result.streaming.chunks.length} chunks.`;

0 commit comments

Comments
 (0)