@plasius/asset-contracts 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +67 -1
- package/dist/index.cjs +213 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -2
- package/dist/index.d.ts +65 -2
- package/dist/index.js +210 -37
- package/dist/index.js.map +1 -1
- package/docs/adrs/adr-0011-reloadable-texture-resource-roles.md +92 -0
- package/docs/adrs/index.md +1 -0
- package/docs/tdrs/index.md +1 -0
- package/docs/tdrs/tdr-0006-reloadable-texture-resource-roles.md +91 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/model-texture-resources.ts +215 -0
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,23 @@
|
|
|
14
14
|
- **Security**
|
|
15
15
|
- (placeholder)
|
|
16
16
|
|
|
17
|
+
## [0.7.0] - 2026-10-24
|
|
18
|
+
|
|
19
|
+
- **Added**
|
|
20
|
+
- Opt-in reloadable canonical texture/material resource qualification, exact
|
|
21
|
+
storage role/MIME mapping and separate GPU upload/atlas source bindings.
|
|
22
|
+
Preserves authored metadata and original resource identity through JSON reload.
|
|
23
|
+
- (placeholder)
|
|
24
|
+
|
|
25
|
+
- **Changed**
|
|
26
|
+
- (placeholder)
|
|
27
|
+
|
|
28
|
+
- **Fixed**
|
|
29
|
+
- (placeholder)
|
|
30
|
+
|
|
31
|
+
- **Security**
|
|
32
|
+
- (placeholder)
|
|
33
|
+
|
|
17
34
|
## [0.6.0] - 2026-10-24
|
|
18
35
|
|
|
19
36
|
- **Added**
|
|
@@ -349,3 +366,4 @@
|
|
|
349
366
|
[0.4.6]: https://github.com/Plasius-LTD/asset-contracts/releases/tag/v0.4.6
|
|
350
367
|
[0.5.0]: https://github.com/Plasius-LTD/asset-contracts/releases/tag/v0.5.0
|
|
351
368
|
[0.6.0]: https://github.com/Plasius-LTD/asset-contracts/releases/tag/v0.6.0
|
|
369
|
+
[0.7.0]: https://github.com/Plasius-LTD/asset-contracts/releases/tag/v0.7.0
|
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ The exported surface covers:
|
|
|
28
28
|
- asset job metadata contracts
|
|
29
29
|
- manifest and file descriptor contracts
|
|
30
30
|
- opt-in canonical model package inventory and exact-byte qualification
|
|
31
|
+
- reloadable canonical texture/material metadata and explicit upload/atlas roles
|
|
31
32
|
- opt-in public-safe canonical conversion diagnostics and resource/source binding
|
|
32
33
|
- screenshot plan definitions
|
|
33
34
|
- review findings and review report contracts
|
|
@@ -91,7 +92,7 @@ Embedded resources remain inside their owning entrypoint; list only actual
|
|
|
91
92
|
exported files. This helper checks bytes and declarations, not internal format
|
|
92
93
|
references, MIME sniffing, malware, rendering, storage or promotion authority.
|
|
93
94
|
Keep source provenance in the canonical document and do not substitute derived
|
|
94
|
-
hashes for original source hashes.
|
|
95
|
+
hashes for original source hashes. Role qualification is available through the additive helper below; animation/source
|
|
95
96
|
relationships and promotion closure remain separately tracked work. The result
|
|
96
97
|
is qualification at call time; callers must preserve the exact bytes in their
|
|
97
98
|
own immutable storage boundary.
|
|
@@ -105,6 +106,71 @@ runtime parser or renderer loading. Downstream adoption must use a released
|
|
|
105
106
|
package. See [ADR 0009](./docs/adrs/adr-0009-model-resource-manifest-qualification.md)
|
|
106
107
|
and [TDR 0004](./docs/tdrs/tdr-0004-model-resource-manifest-qualification.md).
|
|
107
108
|
|
|
109
|
+
## Reloadable Texture and Resource Roles
|
|
110
|
+
|
|
111
|
+
`qualifyModelTextureResources` composes exact byte/inventory qualification with
|
|
112
|
+
published model-core's verified material/texture graph. It keeps authored
|
|
113
|
+
metadata and source image identity separate from derived GPU uploads/atlases.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { qualifyModelTextureResources } from "@plasius/asset-contracts";
|
|
117
|
+
|
|
118
|
+
const reload = await qualifyModelTextureResources({
|
|
119
|
+
manifest, packageManifest, files: exportedFiles,
|
|
120
|
+
graph: verifiedCanonicalGraph,
|
|
121
|
+
resources: [
|
|
122
|
+
{ resourceId: "source-image", filePath: "textures/source.png" },
|
|
123
|
+
{ resourceId: "embedded-buffer", filePath: "model.gltf" },
|
|
124
|
+
], // Exactly one binding per graph resource.
|
|
125
|
+
derived: [
|
|
126
|
+
{ role: "gpu-upload", filePath: "derived/upload.bin", sourceResourceIds: ["source-image"] },
|
|
127
|
+
{ role: "texture-atlas", filePath: "derived/atlas.png", sourceResourceIds: ["source-image"] },
|
|
128
|
+
], // Or [] when no derivative is exported.
|
|
129
|
+
}, { signal: abortController.signal, timeoutMs: 10_000 });
|
|
130
|
+
// Persist JSON.stringify(reload) alongside the qualified immutable bytes.
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Use existing storage roles: canonical texture → texture with PNG/JPEG/WebP/KTX2
|
|
134
|
+
MIME; material/animation → metadata with application/json; mesh → model or
|
|
135
|
+
binary; auxiliary → binary or JSON metadata. Uploads require auxiliary/binary
|
|
136
|
+
buffer files, atlases texture/image files. See TDR 0006 for the exact MIME table.
|
|
137
|
+
Missing/duplicate resources, incompatible roles/MIME, unknown roles, graph
|
|
138
|
+
clones and source/output collisions reject. External files match graph path,
|
|
139
|
+
hash, byte length and MIME; embedded resources name the owning model entrypoint
|
|
140
|
+
and retain their independent resource hash. List only actual exported files.
|
|
141
|
+
|
|
142
|
+
The frozen version-1 result contains manifest, original source provenance,
|
|
143
|
+
resources, canonical materials/textures and derived records. Authored usage,
|
|
144
|
+
color space, sampler, binding transform, embedding policy and sourceMetadata
|
|
145
|
+
survive JSON reload. Original image IDs and bytes are never replaced by atlas
|
|
146
|
+
hashes or upload paths. No Blob payload or renderer allocation is serialized.
|
|
147
|
+
Adapters own model-internal references, derivation algorithms and image content
|
|
148
|
+
inspection; qualification does not attest those operations or grant promotion.
|
|
149
|
+
Task 12 animation schemas, Task 23 shader ABI and Task 51 promotion closure
|
|
150
|
+
remain separate.
|
|
151
|
+
|
|
152
|
+
This is **trusted package metadata**, including names, extensions, original
|
|
153
|
+
paths and authored sourceMetadata. Minimize/sanitize that data before public
|
|
154
|
+
distribution; use the separate public diagnostics API for technical responses.
|
|
155
|
+
Serialized graphs lose their private brand: verify reloaded canonical resources
|
|
156
|
+
and reconstruct a graph through core before qualifying again. Produce graphs
|
|
157
|
+
with the same installed model-core instance and matching module format (ESM
|
|
158
|
+
import with ESM, require with CJS). The normal named guard import preserves that
|
|
159
|
+
identity; unused exports remain tree-shakeable through sideEffects=false.
|
|
160
|
+
|
|
161
|
+
Inputs/bytes are snapshotted before awaiting. Fixed ceilings are 16,384 resource
|
|
162
|
+
bindings, 4,096 derivatives, 65,536 graph nodes, shared 262,144 metadata values,
|
|
163
|
+
2,097,152 UTF-16 characters, depth 16 and 2,048 characters/string. Existing file
|
|
164
|
+
limits also apply. Default deadline 10 seconds, allowed range 1–60,000 ms;
|
|
165
|
+
cancellation/deadline reject without a partial result. In-flight Web Crypto
|
|
166
|
+
cannot be cancelled. ModelTextureResourceError.code is value-free.
|
|
167
|
+
|
|
168
|
+
The backend evaluator gpu.model.conversion.enabled controls caller adoption;
|
|
169
|
+
disabled/rollback paths retain legacy factories. No lookup/mutation or UI
|
|
170
|
+
capability is introduced. Dependencies and legacy factories are unchanged.
|
|
171
|
+
See [ADR 0011](./docs/adrs/adr-0011-reloadable-texture-resource-roles.md) and
|
|
172
|
+
[TDR 0006](./docs/tdrs/tdr-0006-reloadable-texture-resource-roles.md).
|
|
173
|
+
|
|
108
174
|
## Public Conversion Diagnostics
|
|
109
175
|
|
|
110
176
|
`qualifyModelConversionDiagnostics` adds an opt-in, version-1 JSON boundary over
|
package/dist/index.cjs
CHANGED
|
@@ -65,9 +65,11 @@ __export(index_exports, {
|
|
|
65
65
|
MODEL_RESOLUTION_V2_STATES: () => MODEL_RESOLUTION_V2_STATES,
|
|
66
66
|
MODEL_RESOURCE_MANIFEST_LIMITS: () => MODEL_RESOURCE_MANIFEST_LIMITS,
|
|
67
67
|
MODEL_RIGHTS_STATUSES: () => MODEL_RIGHTS_STATUSES,
|
|
68
|
+
MODEL_TEXTURE_RESOURCE_LIMITS: () => MODEL_TEXTURE_RESOURCE_LIMITS,
|
|
68
69
|
MODEL_TEXT_ONLY_ASSURANCE_CEILING_REASON_CODE: () => MODEL_TEXT_ONLY_ASSURANCE_CEILING_REASON_CODE,
|
|
69
70
|
ModelConversionDiagnosticsError: () => ModelConversionDiagnosticsError,
|
|
70
71
|
ModelResourceManifestError: () => ModelResourceManifestError,
|
|
72
|
+
ModelTextureResourceError: () => ModelTextureResourceError,
|
|
71
73
|
PVOX_ASSEMBLY_CLOSURE_HASH_DOMAIN: () => PVOX_ASSEMBLY_CLOSURE_HASH_DOMAIN,
|
|
72
74
|
PVOX_ASSET_MANIFEST_VERSION: () => PVOX_ASSET_MANIFEST_VERSION,
|
|
73
75
|
PVOX_BINARY_CLOSURE_HASH_DOMAIN: () => PVOX_BINARY_CLOSURE_HASH_DOMAIN,
|
|
@@ -260,6 +262,7 @@ __export(index_exports, {
|
|
|
260
262
|
projectPvoxNamedJsonClosureV1: () => projectPvoxNamedJsonClosureV1,
|
|
261
263
|
qualifyModelConversionDiagnostics: () => qualifyModelConversionDiagnostics,
|
|
262
264
|
qualifyModelResourceManifest: () => qualifyModelResourceManifest,
|
|
265
|
+
qualifyModelTextureResources: () => qualifyModelTextureResources,
|
|
263
266
|
toLegacyModelResolutionState: () => toLegacyModelResolutionState,
|
|
264
267
|
validateGpuAssetFiles: () => validateGpuAssetFiles
|
|
265
268
|
});
|
|
@@ -448,11 +451,11 @@ function assertNoDirectUrl(value, fieldName) {
|
|
|
448
451
|
}
|
|
449
452
|
}
|
|
450
453
|
function requiredToken(value, fieldName) {
|
|
451
|
-
const
|
|
452
|
-
if (!TOKEN_PATTERN.test(
|
|
454
|
+
const token3 = requiredString(value, fieldName, 128);
|
|
455
|
+
if (!TOKEN_PATTERN.test(token3)) {
|
|
453
456
|
throw new Error(`${fieldName} must be a token up to 128 characters.`);
|
|
454
457
|
}
|
|
455
|
-
return
|
|
458
|
+
return token3;
|
|
456
459
|
}
|
|
457
460
|
function requiredModelPathSegment(value, fieldName) {
|
|
458
461
|
const segment = requiredString(value, fieldName, 128);
|
|
@@ -6953,9 +6956,9 @@ function snapshot(value, budget, depth = 0) {
|
|
|
6953
6956
|
return value;
|
|
6954
6957
|
}
|
|
6955
6958
|
if (Array.isArray(value)) return Object.freeze(list(value).map((item) => snapshot(item, budget, depth + 1)));
|
|
6956
|
-
const
|
|
6957
|
-
for (const key of Object.keys(
|
|
6958
|
-
return Object.freeze(
|
|
6959
|
+
const fields3 = record(value);
|
|
6960
|
+
for (const key of Object.keys(fields3)) fields3[key] = snapshot(fields3[key], budget, depth + 1);
|
|
6961
|
+
return Object.freeze(fields3);
|
|
6959
6962
|
}
|
|
6960
6963
|
function path(value) {
|
|
6961
6964
|
if (typeof value !== "string" || value.length > 1024 || !value.length) fail();
|
|
@@ -7064,6 +7067,176 @@ async function qualify(input) {
|
|
|
7064
7067
|
return Object.freeze({ ...manifest, files: Object.freeze([...manifest.files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) });
|
|
7065
7068
|
}
|
|
7066
7069
|
|
|
7070
|
+
// src/model-texture-resources.ts
|
|
7071
|
+
var import_gpu_model_core = require("@plasius/gpu-model-core");
|
|
7072
|
+
var MODEL_TEXTURE_RESOURCE_LIMITS = Object.freeze({ resources: 16384, derived: 4096, nodes: 65536, values: 262144, characters: 2097152, depth: 16, timeoutMs: 6e4 });
|
|
7073
|
+
var ModelTextureResourceError = class extends TypeError {
|
|
7074
|
+
constructor(code) {
|
|
7075
|
+
super(`Invalid model texture resources: ${code}`);
|
|
7076
|
+
this.code = code;
|
|
7077
|
+
this.name = "ModelTextureResourceError";
|
|
7078
|
+
}
|
|
7079
|
+
code;
|
|
7080
|
+
};
|
|
7081
|
+
function fail2(code = "invalid-input") {
|
|
7082
|
+
throw new ModelTextureResourceError(code);
|
|
7083
|
+
}
|
|
7084
|
+
function fields(value, allowed) {
|
|
7085
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail2();
|
|
7086
|
+
const keys = Reflect.ownKeys(value);
|
|
7087
|
+
if (keys.length > 1024) fail2("limit-exceeded");
|
|
7088
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
7089
|
+
for (const key of keys) {
|
|
7090
|
+
if (typeof key !== "string" || key.length > 128 || ["__proto__", "prototype", "constructor"].includes(key) || allowed && !allowed.includes(key)) fail2();
|
|
7091
|
+
const property = Object.getOwnPropertyDescriptor(value, key);
|
|
7092
|
+
if (!("value" in property) || !property.enumerable) fail2();
|
|
7093
|
+
result[key] = property.value;
|
|
7094
|
+
}
|
|
7095
|
+
return result;
|
|
7096
|
+
}
|
|
7097
|
+
function array(value, maximum) {
|
|
7098
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) fail2();
|
|
7099
|
+
if (value.length > maximum) fail2("limit-exceeded");
|
|
7100
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail2();
|
|
7101
|
+
const result = [];
|
|
7102
|
+
for (let i = 0; i < value.length; i++) {
|
|
7103
|
+
const property = Object.getOwnPropertyDescriptor(value, String(i));
|
|
7104
|
+
if (!property || !("value" in property) || !property.enumerable) fail2();
|
|
7105
|
+
result.push(property.value);
|
|
7106
|
+
}
|
|
7107
|
+
return result;
|
|
7108
|
+
}
|
|
7109
|
+
function snapshot2(value, budget, depth = 0) {
|
|
7110
|
+
if (--budget.values < 0 || depth > MODEL_TEXTURE_RESOURCE_LIMITS.depth) fail2("limit-exceeded");
|
|
7111
|
+
if (value === null || value === void 0 || typeof value === "boolean") return value;
|
|
7112
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
7113
|
+
if (typeof value === "string") {
|
|
7114
|
+
budget.characters -= value.length;
|
|
7115
|
+
if (value.length > 2048 || budget.characters < 0) fail2("limit-exceeded");
|
|
7116
|
+
return value;
|
|
7117
|
+
}
|
|
7118
|
+
if (Array.isArray(value)) return Object.freeze(array(value, MODEL_TEXTURE_RESOURCE_LIMITS.resources).map((v) => snapshot2(v, budget, depth + 1)));
|
|
7119
|
+
const result = fields(value);
|
|
7120
|
+
for (const key of Object.keys(result)) {
|
|
7121
|
+
budget.characters -= key.length;
|
|
7122
|
+
if (budget.characters < 0) fail2("limit-exceeded");
|
|
7123
|
+
result[key] = snapshot2(result[key], budget, depth + 1);
|
|
7124
|
+
}
|
|
7125
|
+
return Object.freeze(result);
|
|
7126
|
+
}
|
|
7127
|
+
function token2(value) {
|
|
7128
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value)) fail2();
|
|
7129
|
+
return value;
|
|
7130
|
+
}
|
|
7131
|
+
var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
7132
|
+
var imageMime = (value) => ["image/png", "image/jpeg", "image/webp", "image/ktx2"].includes(value);
|
|
7133
|
+
var bufferMime = (value) => ["application/octet-stream", "application/vnd.plasius.gpu-buffer"].includes(value);
|
|
7134
|
+
function storageRole(role, file) {
|
|
7135
|
+
const valid = role === "texture" ? file.role === "texture" && imageMime(file.contentType) : role === "material" || role === "animation" ? file.role === "metadata" && file.contentType === "application/json" : role === "mesh" ? file.role === "binary" && bufferMime(file.contentType) || file.role === "model" && (file.contentType.startsWith("model/") || ["application/json", "application/octet-stream", "text/plain"].includes(file.contentType)) : role === "auxiliary" && (file.role === "binary" && bufferMime(file.contentType) || file.role === "metadata" && file.contentType === "application/json");
|
|
7136
|
+
if (!valid) fail2("role-mismatch");
|
|
7137
|
+
}
|
|
7138
|
+
async function qualifyModelTextureResources(input, options = {}) {
|
|
7139
|
+
let timer;
|
|
7140
|
+
let signal;
|
|
7141
|
+
let onAbort;
|
|
7142
|
+
try {
|
|
7143
|
+
const started = performance.now();
|
|
7144
|
+
const controls = fields(options, ["signal", "timeoutMs"]);
|
|
7145
|
+
const timeoutMs = controls.timeoutMs === void 0 ? 1e4 : controls.timeoutMs;
|
|
7146
|
+
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MODEL_TEXTURE_RESOURCE_LIMITS.timeoutMs) fail2();
|
|
7147
|
+
signal = controls.signal;
|
|
7148
|
+
const aborted = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted").get;
|
|
7149
|
+
const check = () => {
|
|
7150
|
+
if (signal !== void 0 && aborted.call(signal)) fail2("aborted");
|
|
7151
|
+
if (performance.now() - started >= timeoutMs) fail2("deadline-exceeded");
|
|
7152
|
+
};
|
|
7153
|
+
check();
|
|
7154
|
+
const root = fields(input, ["manifest", "packageManifest", "files", "graph", "resources", "derived"]);
|
|
7155
|
+
const graph = root.graph;
|
|
7156
|
+
if (!(0, import_gpu_model_core.isGpuModelResourceGraph)(graph)) fail2("unverified-graph");
|
|
7157
|
+
if (graph.nodes.length > MODEL_TEXTURE_RESOURCE_LIMITS.nodes) fail2("limit-exceeded");
|
|
7158
|
+
const budget = { values: MODEL_TEXTURE_RESOURCE_LIMITS.values, characters: MODEL_TEXTURE_RESOURCE_LIMITS.characters };
|
|
7159
|
+
const inventory = snapshot2(root.packageManifest, budget);
|
|
7160
|
+
const bindings = array(snapshot2(root.resources, budget), MODEL_TEXTURE_RESOURCE_LIMITS.resources).map((value) => {
|
|
7161
|
+
const binding = fields(value, ["resourceId", "filePath"]);
|
|
7162
|
+
return Object.freeze({ resourceId: token2(binding.resourceId), filePath: tokenPath(binding.filePath) });
|
|
7163
|
+
});
|
|
7164
|
+
const derived = array(snapshot2(root.derived, budget), MODEL_TEXTURE_RESOURCE_LIMITS.derived).map((value) => {
|
|
7165
|
+
const record2 = fields(value, ["role", "filePath", "sourceResourceIds"]);
|
|
7166
|
+
if (record2.role !== "gpu-upload" && record2.role !== "texture-atlas") fail2("role-mismatch");
|
|
7167
|
+
const ids = array(record2.sourceResourceIds, MODEL_TEXTURE_RESOURCE_LIMITS.resources).map(token2);
|
|
7168
|
+
if (!ids.length || new Set(ids).size !== ids.length) fail2("resource-mismatch");
|
|
7169
|
+
return Object.freeze({ role: record2.role, filePath: tokenPath(record2.filePath), sourceResourceIds: Object.freeze(ids.sort(compare)) });
|
|
7170
|
+
});
|
|
7171
|
+
check();
|
|
7172
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
7173
|
+
timer = setTimeout(() => reject(new ModelTextureResourceError("deadline-exceeded")), Math.max(1, timeoutMs - (performance.now() - started)));
|
|
7174
|
+
onAbort = () => reject(new ModelTextureResourceError("aborted"));
|
|
7175
|
+
if (signal) AbortSignal.prototype.addEventListener.call(signal, "abort", onAbort, { once: true });
|
|
7176
|
+
});
|
|
7177
|
+
const manifest = await Promise.race([
|
|
7178
|
+
qualifyModelResourceManifest({ manifest: root.manifest, files: root.files, packageManifest: inventory, signal, timeoutMs }),
|
|
7179
|
+
interrupted
|
|
7180
|
+
]);
|
|
7181
|
+
check();
|
|
7182
|
+
const files = new Map(manifest.files.map((file) => [file.path, file]));
|
|
7183
|
+
const roles = new Map(inventory.entries.map((entry) => [entry.path, entry.role]));
|
|
7184
|
+
for (const entry of inventory.entries) storageRole(entry.role, files.get(entry.path));
|
|
7185
|
+
const resources = new Map(graph.nodes.filter((node) => node.kind === "resource").map((node) => [node.id, node.value]));
|
|
7186
|
+
if (bindings.length !== resources.size || new Set(bindings.map((b) => b.resourceId)).size !== bindings.length) fail2("resource-mismatch");
|
|
7187
|
+
const resourceFiles = /* @__PURE__ */ new Set();
|
|
7188
|
+
const qualified = bindings.map((binding) => {
|
|
7189
|
+
const value = resources.get(binding.resourceId);
|
|
7190
|
+
const file = files.get(binding.filePath);
|
|
7191
|
+
if (!value || !file) fail2("resource-mismatch");
|
|
7192
|
+
const { resource, location, exportEmbeddingPolicy } = value;
|
|
7193
|
+
if (location.kind === "external") {
|
|
7194
|
+
if (binding.filePath !== location.relativePath || file.sha256 !== resource.contentHash || file.byteLength !== resource.byteLength || file.contentType !== resource.mimeType) fail2("resource-mismatch");
|
|
7195
|
+
if (resource.kind === "image" ? roles.get(file.path) !== "texture" : file.role !== "binary") fail2("role-mismatch");
|
|
7196
|
+
} else if (binding.filePath !== manifest.entrypoint || file.role !== "model") fail2("resource-mismatch");
|
|
7197
|
+
resourceFiles.add(binding.filePath);
|
|
7198
|
+
return Object.freeze({ ...binding, kind: resource.kind, contentHash: resource.contentHash, byteLength: resource.byteLength, mimeType: resource.mimeType, location, exportEmbeddingPolicy });
|
|
7199
|
+
});
|
|
7200
|
+
const outputFiles = /* @__PURE__ */ new Set();
|
|
7201
|
+
for (const output of derived) {
|
|
7202
|
+
const file = files.get(output.filePath);
|
|
7203
|
+
if (!file || outputFiles.has(output.filePath) || resourceFiles.has(output.filePath)) fail2("resource-mismatch");
|
|
7204
|
+
outputFiles.add(output.filePath);
|
|
7205
|
+
for (const id of output.sourceResourceIds) {
|
|
7206
|
+
const source2 = resources.get(id);
|
|
7207
|
+
if (!source2 || output.role === "texture-atlas" && source2.resource.kind !== "image") fail2("resource-mismatch");
|
|
7208
|
+
}
|
|
7209
|
+
if (output.role === "gpu-upload" ? file.role !== "binary" || !bufferMime(file.contentType) || roles.get(file.path) !== "auxiliary" : file.role !== "texture" || !imageMime(file.contentType) || roles.get(file.path) !== "texture") fail2("role-mismatch");
|
|
7210
|
+
}
|
|
7211
|
+
const materials = graph.nodes.filter((node) => node.kind === "material").map((node) => node.value);
|
|
7212
|
+
const textures = graph.nodes.filter((node) => node.kind === "texture").map((node) => node.value);
|
|
7213
|
+
const source = graph.nodes.find((node) => node.kind === "provenance");
|
|
7214
|
+
if (source.kind !== "provenance") fail2();
|
|
7215
|
+
snapshot2({ materials, textures, source: source.value }, budget);
|
|
7216
|
+
check();
|
|
7217
|
+
return Object.freeze({
|
|
7218
|
+
contractVersion: 1,
|
|
7219
|
+
manifest,
|
|
7220
|
+
source: source.value,
|
|
7221
|
+
resources: Object.freeze(qualified.sort((a, b) => compare(a.resourceId, b.resourceId))),
|
|
7222
|
+
materials: Object.freeze(materials),
|
|
7223
|
+
textures: Object.freeze(textures),
|
|
7224
|
+
derived: Object.freeze(derived.sort((a, b) => compare(a.filePath, b.filePath)))
|
|
7225
|
+
});
|
|
7226
|
+
} catch (error) {
|
|
7227
|
+
if (error instanceof ModelTextureResourceError) throw error;
|
|
7228
|
+
if (error instanceof ModelResourceManifestError) return fail2(error.code === "aborted" || error.code === "deadline-exceeded" || error.code === "limit-exceeded" ? error.code : "package-mismatch");
|
|
7229
|
+
return fail2();
|
|
7230
|
+
} finally {
|
|
7231
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
7232
|
+
if (signal && onAbort) AbortSignal.prototype.removeEventListener.call(signal, "abort", onAbort);
|
|
7233
|
+
}
|
|
7234
|
+
}
|
|
7235
|
+
function tokenPath(value) {
|
|
7236
|
+
if (typeof value !== "string" || !value.length || value.length > 1024) fail2();
|
|
7237
|
+
return value;
|
|
7238
|
+
}
|
|
7239
|
+
|
|
7067
7240
|
// src/model-conversion-diagnostics.ts
|
|
7068
7241
|
var MODEL_CONVERSION_DIAGNOSTIC_LIMITS = Object.freeze({ issues: 100, resources: 100, values: 16384, depth: 12, string: 2048, characters: 262144, timeoutMs: 6e4 });
|
|
7069
7242
|
var ModelConversionDiagnosticsError = class extends TypeError {
|
|
@@ -7074,50 +7247,50 @@ var ModelConversionDiagnosticsError = class extends TypeError {
|
|
|
7074
7247
|
}
|
|
7075
7248
|
code;
|
|
7076
7249
|
};
|
|
7077
|
-
function
|
|
7250
|
+
function fail3(code = "invalid-input") {
|
|
7078
7251
|
throw new ModelConversionDiagnosticsError(code);
|
|
7079
7252
|
}
|
|
7080
|
-
function
|
|
7081
|
-
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
7253
|
+
function fields2(value) {
|
|
7254
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
7082
7255
|
const result = /* @__PURE__ */ Object.create(null);
|
|
7083
7256
|
const keys = Reflect.ownKeys(value);
|
|
7084
|
-
if (keys.length > 1024)
|
|
7257
|
+
if (keys.length > 1024) fail3("limit-exceeded");
|
|
7085
7258
|
for (const key of keys) {
|
|
7086
|
-
if (typeof key !== "string" || key.length > 128 || ["__proto__", "prototype", "constructor"].includes(key))
|
|
7259
|
+
if (typeof key !== "string" || key.length > 128 || ["__proto__", "prototype", "constructor"].includes(key)) fail3();
|
|
7087
7260
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
7088
|
-
if (!("value" in descriptor) || !descriptor.enumerable)
|
|
7261
|
+
if (!("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
7089
7262
|
result[key] = descriptor.value;
|
|
7090
7263
|
}
|
|
7091
7264
|
return result;
|
|
7092
7265
|
}
|
|
7093
7266
|
function only(record2, keys) {
|
|
7094
|
-
if (Object.keys(record2).some((key) => !keys.includes(key)))
|
|
7267
|
+
if (Object.keys(record2).some((key) => !keys.includes(key))) fail3();
|
|
7095
7268
|
}
|
|
7096
|
-
function
|
|
7097
|
-
if (--budget.values < 0 || depth > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.depth)
|
|
7269
|
+
function snapshot3(value, budget, depth = 0) {
|
|
7270
|
+
if (--budget.values < 0 || depth > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.depth) fail3("limit-exceeded");
|
|
7098
7271
|
if (value === null || typeof value === "boolean") return value;
|
|
7099
7272
|
if (typeof value === "string") {
|
|
7100
7273
|
budget.characters -= value.length;
|
|
7101
|
-
if (value.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.string || budget.characters < 0)
|
|
7274
|
+
if (value.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.string || budget.characters < 0) fail3("limit-exceeded");
|
|
7102
7275
|
return value;
|
|
7103
7276
|
}
|
|
7104
7277
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
7105
7278
|
if (Array.isArray(value)) {
|
|
7106
|
-
if (Object.getPrototypeOf(value) !== Array.prototype || value.length > 1024)
|
|
7107
|
-
if (Reflect.ownKeys(value).length !== value.length + 1)
|
|
7279
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || value.length > 1024) fail3("limit-exceeded");
|
|
7280
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail3();
|
|
7108
7281
|
const result = [];
|
|
7109
7282
|
for (let i = 0; i < value.length; i++) {
|
|
7110
7283
|
const descriptor = Object.getOwnPropertyDescriptor(value, String(i));
|
|
7111
|
-
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable)
|
|
7112
|
-
result.push(
|
|
7284
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
7285
|
+
result.push(snapshot3(descriptor.value, budget, depth + 1));
|
|
7113
7286
|
}
|
|
7114
7287
|
return Object.freeze(result);
|
|
7115
7288
|
}
|
|
7116
|
-
const record2 =
|
|
7289
|
+
const record2 = fields2(value);
|
|
7117
7290
|
for (const key of Object.keys(record2)) {
|
|
7118
7291
|
budget.characters -= key.length;
|
|
7119
|
-
if (budget.characters < 0)
|
|
7120
|
-
record2[key] =
|
|
7292
|
+
if (budget.characters < 0) fail3("limit-exceeded");
|
|
7293
|
+
record2[key] = snapshot3(record2[key], budget, depth + 1);
|
|
7121
7294
|
}
|
|
7122
7295
|
return Object.freeze(record2);
|
|
7123
7296
|
}
|
|
@@ -7130,34 +7303,34 @@ async function qualifyModelConversionDiagnostics(input, options = {}) {
|
|
|
7130
7303
|
let onAbort;
|
|
7131
7304
|
try {
|
|
7132
7305
|
const start = performance.now();
|
|
7133
|
-
const controls =
|
|
7306
|
+
const controls = fields2(options);
|
|
7134
7307
|
only(controls, ["signal", "timeoutMs"]);
|
|
7135
7308
|
const timeoutMs = controls.timeoutMs === void 0 ? 1e4 : controls.timeoutMs;
|
|
7136
|
-
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.timeoutMs)
|
|
7309
|
+
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.timeoutMs) fail3();
|
|
7137
7310
|
signal = controls.signal;
|
|
7138
7311
|
const aborted = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted").get;
|
|
7139
7312
|
const check = () => {
|
|
7140
|
-
if (signal !== void 0 && aborted.call(signal))
|
|
7141
|
-
if (performance.now() - start >= timeoutMs)
|
|
7313
|
+
if (signal !== void 0 && aborted.call(signal)) fail3("aborted");
|
|
7314
|
+
if (performance.now() - start >= timeoutMs) fail3("deadline-exceeded");
|
|
7142
7315
|
};
|
|
7143
7316
|
check();
|
|
7144
|
-
const args =
|
|
7317
|
+
const args = fields2(snapshot3(input, { values: MODEL_CONVERSION_DIAGNOSTIC_LIMITS.values, characters: MODEL_CONVERSION_DIAGNOSTIC_LIMITS.characters }));
|
|
7145
7318
|
only(args, ["outcome", "source", "report", "resources"]);
|
|
7146
|
-
if (!["completed", "unsupported", "failed"].includes(args.outcome))
|
|
7319
|
+
if (!["completed", "unsupported", "failed"].includes(args.outcome)) fail3();
|
|
7147
7320
|
const source = createModelProvenance(args.source);
|
|
7148
|
-
const reportInput =
|
|
7321
|
+
const reportInput = fields2(args.report);
|
|
7149
7322
|
const maximumIssues = MODEL_CONVERSION_DIAGNOSTIC_LIMITS.issues - (args.outcome === "completed" ? 0 : 1);
|
|
7150
|
-
if (!Array.isArray(reportInput.issues) || reportInput.issues.length > maximumIssues)
|
|
7151
|
-
if (!Array.isArray(args.resources) || args.resources.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.resources)
|
|
7323
|
+
if (!Array.isArray(reportInput.issues) || reportInput.issues.length > maximumIssues) fail3("limit-exceeded");
|
|
7324
|
+
if (!Array.isArray(args.resources) || args.resources.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.resources) fail3("limit-exceeded");
|
|
7152
7325
|
const resources = /* @__PURE__ */ new Map();
|
|
7153
7326
|
const identities = /* @__PURE__ */ new Map();
|
|
7154
7327
|
for (const value of args.resources) {
|
|
7155
|
-
const binding =
|
|
7328
|
+
const binding = fields2(value);
|
|
7156
7329
|
only(binding, ["path", "resource"]);
|
|
7157
|
-
if (typeof binding.path !== "string" || !binding.path.startsWith("/") || binding.path === "/" || resources.has(binding.path))
|
|
7330
|
+
if (typeof binding.path !== "string" || !binding.path.startsWith("/") || binding.path === "/" || resources.has(binding.path)) fail3();
|
|
7158
7331
|
const resource = createModelResourceRef(binding.resource);
|
|
7159
7332
|
const identity = JSON.stringify(resource);
|
|
7160
|
-
if (identities.has(resource.uri) && identities.get(resource.uri) !== identity)
|
|
7333
|
+
if (identities.has(resource.uri) && identities.get(resource.uri) !== identity) fail3();
|
|
7161
7334
|
identities.set(resource.uri, identity);
|
|
7162
7335
|
resources.set(binding.path, resource);
|
|
7163
7336
|
}
|
|
@@ -7172,7 +7345,7 @@ async function qualifyModelConversionDiagnostics(input, options = {}) {
|
|
|
7172
7345
|
const report = core.evaluateGpuModelDiagnostics(reportInput);
|
|
7173
7346
|
const diagnostics = report.diagnostics.map((diagnostic) => {
|
|
7174
7347
|
const resource = resources.get(diagnostic.path);
|
|
7175
|
-
if (diagnostic.path !== "/" && !resource)
|
|
7348
|
+
if (diagnostic.path !== "/" && !resource) fail3("unbound-resource");
|
|
7176
7349
|
const base = resource ? { resource } : {};
|
|
7177
7350
|
switch (diagnostic.kind) {
|
|
7178
7351
|
case "warning":
|
|
@@ -7204,7 +7377,7 @@ async function qualifyModelConversionDiagnostics(input, options = {}) {
|
|
|
7204
7377
|
});
|
|
7205
7378
|
} catch (error) {
|
|
7206
7379
|
if (error instanceof ModelConversionDiagnosticsError) throw error;
|
|
7207
|
-
return
|
|
7380
|
+
return fail3();
|
|
7208
7381
|
} finally {
|
|
7209
7382
|
if (timer !== void 0) clearTimeout(timer);
|
|
7210
7383
|
if (signal && onAbort) AbortSignal.prototype.removeEventListener.call(signal, "abort", onAbort);
|
|
@@ -8015,9 +8188,11 @@ function assertRequiredString(value, fieldName) {
|
|
|
8015
8188
|
MODEL_RESOLUTION_V2_STATES,
|
|
8016
8189
|
MODEL_RESOURCE_MANIFEST_LIMITS,
|
|
8017
8190
|
MODEL_RIGHTS_STATUSES,
|
|
8191
|
+
MODEL_TEXTURE_RESOURCE_LIMITS,
|
|
8018
8192
|
MODEL_TEXT_ONLY_ASSURANCE_CEILING_REASON_CODE,
|
|
8019
8193
|
ModelConversionDiagnosticsError,
|
|
8020
8194
|
ModelResourceManifestError,
|
|
8195
|
+
ModelTextureResourceError,
|
|
8021
8196
|
PVOX_ASSEMBLY_CLOSURE_HASH_DOMAIN,
|
|
8022
8197
|
PVOX_ASSET_MANIFEST_VERSION,
|
|
8023
8198
|
PVOX_BINARY_CLOSURE_HASH_DOMAIN,
|
|
@@ -8210,6 +8385,7 @@ function assertRequiredString(value, fieldName) {
|
|
|
8210
8385
|
projectPvoxNamedJsonClosureV1,
|
|
8211
8386
|
qualifyModelConversionDiagnostics,
|
|
8212
8387
|
qualifyModelResourceManifest,
|
|
8388
|
+
qualifyModelTextureResources,
|
|
8213
8389
|
toLegacyModelResolutionState,
|
|
8214
8390
|
validateGpuAssetFiles
|
|
8215
8391
|
});
|