@plasius/asset-contracts 0.6.0 → 0.8.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 +36 -0
- package/README.md +114 -2
- package/dist/index.cjs +474 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -2
- package/dist/index.d.ts +131 -2
- package/dist/index.js +453 -87
- package/dist/index.js.map +1 -1
- package/docs/adrs/adr-0011-reloadable-texture-resource-roles.md +92 -0
- package/docs/adrs/adr-0012-model-resource-promotion-closure.md +92 -0
- package/docs/adrs/index.md +2 -0
- package/docs/tdrs/index.md +2 -0
- package/docs/tdrs/tdr-0006-reloadable-texture-resource-roles.md +91 -0
- package/docs/tdrs/tdr-0007-model-resource-promotion-closure.md +63 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/model-resource-promotion.ts +219 -0
- package/src/model-texture-resources.ts +215 -0
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
SHADER_VALIDATION_EVIDENCE_VERSION,
|
|
6
6
|
SUPPORTED_ADDITIVE_WEBGPU_MATRIX_POLICIES,
|
|
7
7
|
SUPPORTED_STABLE_WEBGPU_MATRIX_POLICIES,
|
|
8
|
-
canonicalizeGpuContract as
|
|
9
|
-
computeSha256 as
|
|
8
|
+
canonicalizeGpuContract as canonicalizeGpuContract4,
|
|
9
|
+
computeSha256 as computeSha2563,
|
|
10
10
|
parseGpuInterfaceManifest,
|
|
11
11
|
parseJsonBytes,
|
|
12
12
|
parseModelGpuCompatibilityDescriptor,
|
|
@@ -196,11 +196,11 @@ function assertNoDirectUrl(value, fieldName) {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
function requiredToken(value, fieldName) {
|
|
199
|
-
const
|
|
200
|
-
if (!TOKEN_PATTERN.test(
|
|
199
|
+
const token3 = requiredString(value, fieldName, 128);
|
|
200
|
+
if (!TOKEN_PATTERN.test(token3)) {
|
|
201
201
|
throw new Error(`${fieldName} must be a token up to 128 characters.`);
|
|
202
202
|
}
|
|
203
|
-
return
|
|
203
|
+
return token3;
|
|
204
204
|
}
|
|
205
205
|
function requiredModelPathSegment(value, fieldName) {
|
|
206
206
|
const segment = requiredString(value, fieldName, 128);
|
|
@@ -2797,7 +2797,7 @@ function requireEnum(value, allowed, fieldName) {
|
|
|
2797
2797
|
}
|
|
2798
2798
|
function requireReasonCodes(value, fieldName) {
|
|
2799
2799
|
const entries = asDenseArray(value, fieldName, 0, 32);
|
|
2800
|
-
const result = entries.map((
|
|
2800
|
+
const result = entries.map((entry2, index) => requireReasonCode(entry2, `${fieldName}[${index}]`));
|
|
2801
2801
|
if (new Set(result).size !== result.length) throw new Error(`${fieldName} must not contain duplicates.`);
|
|
2802
2802
|
return result;
|
|
2803
2803
|
}
|
|
@@ -3119,7 +3119,7 @@ function createPvoxRuntimeRequestProfileV1(value) {
|
|
|
3119
3119
|
const record2 = asRecord(value, fieldName);
|
|
3120
3120
|
exactKeys(record2, ["profileId", "fidelityProfileId", "capabilityProfileId", "geometryMode", "requiredCapabilities", "limits"], fieldName);
|
|
3121
3121
|
const requestedCapabilityEntries = asDenseArray(record2.requiredCapabilities, `${fieldName}.requiredCapabilities`, 1, PVOX_CAPABILITIES.length);
|
|
3122
|
-
const requiredCapabilities = requestedCapabilityEntries.map((
|
|
3122
|
+
const requiredCapabilities = requestedCapabilityEntries.map((entry2, index) => requireEnum(entry2, PVOX_CAPABILITIES, `${fieldName}.requiredCapabilities[${index}]`));
|
|
3123
3123
|
if (new Set(requiredCapabilities).size !== requiredCapabilities.length) {
|
|
3124
3124
|
throw new Error(`${fieldName}.requiredCapabilities must not contain duplicates.`);
|
|
3125
3125
|
}
|
|
@@ -3282,7 +3282,7 @@ function createPvoxAssetManifestV1(input) {
|
|
|
3282
3282
|
}
|
|
3283
3283
|
if (expectedOffset !== artifact.byteLength) throw new Error("PvoxAssetManifestV1 pages must exactly cover the artifact bytes.");
|
|
3284
3284
|
const lodEntries = asDenseArray(record2.lods, "PvoxAssetManifestV1.lods", 1, 4);
|
|
3285
|
-
const lods = lodEntries.map((
|
|
3285
|
+
const lods = lodEntries.map((entry2, index) => createLod(entry2, index, pages.length));
|
|
3286
3286
|
for (const [index, current] of lods.entries()) {
|
|
3287
3287
|
if (current.level !== index) throw new Error("PvoxAssetManifestV1 LOD levels must be contiguous from LOD0.");
|
|
3288
3288
|
const previous = lods[index - 1];
|
|
@@ -3518,7 +3518,7 @@ function createPhysicalPropertyEvidence(input) {
|
|
|
3518
3518
|
const reviewedBy = record2.reviewedBy === void 0 ? void 0 : requireToken(record2.reviewedBy, "PhysicalPropertyEvidence.reviewedBy");
|
|
3519
3519
|
const reviewedAt = record2.reviewedAt === void 0 ? void 0 : requireTimestamp(record2.reviewedAt, "PhysicalPropertyEvidence.reviewedAt");
|
|
3520
3520
|
const reviewToken = record2.reviewToken === void 0 ? void 0 : requireAttestation(record2.reviewToken, "PhysicalPropertyEvidence.reviewToken");
|
|
3521
|
-
if (new Set([reviewedBy, reviewedAt, reviewToken].map((
|
|
3521
|
+
if (new Set([reviewedBy, reviewedAt, reviewToken].map((entry2) => entry2 === void 0)).size !== 1) throw new Error("PhysicalPropertyEvidence signed review identity, timestamp, and token must be supplied together.");
|
|
3522
3522
|
if (provenance === "inferred" && reviewedBy === void 0) throw new Error("Inferred PhysicalPropertyEvidence requires human review.");
|
|
3523
3523
|
return deepFreeze2({
|
|
3524
3524
|
property,
|
|
@@ -3541,9 +3541,9 @@ function createPvoxPhysicalRegionInventoryV1(input) {
|
|
|
3541
3541
|
exactKeys(record2, ["inventoryVersion", "subjectContentHash", "inventoryHash", "inventoryHashAttestation", "validationEvidenceHash", "entries"], fieldName);
|
|
3542
3542
|
if (record2.inventoryVersion !== PVOX_PHYSICAL_REGION_INVENTORY_VERSION) throw new Error(`${fieldName}.inventoryVersion is unsupported.`);
|
|
3543
3543
|
const entriesInput = asDenseArray(record2.entries, `${fieldName}.entries`, 1, PVOX_DEFAULT_LIMITS.maximumMaterialRegions);
|
|
3544
|
-
const entries = entriesInput.map((
|
|
3544
|
+
const entries = entriesInput.map((entry2, index) => {
|
|
3545
3545
|
const entryName = `${fieldName}.entries[${index}]`;
|
|
3546
|
-
const item = asRecord(
|
|
3546
|
+
const item = asRecord(entry2, entryName);
|
|
3547
3547
|
exactKeys(item, ["regionIndex", "physicalPaletteIndex", "regionId", "materialId"], entryName);
|
|
3548
3548
|
return {
|
|
3549
3549
|
regionIndex: requireInteger(item.regionIndex, `${entryName}.regionIndex`, 0, PVOX_DEFAULT_LIMITS.maximumMaterialRegions - 1),
|
|
@@ -3552,7 +3552,7 @@ function createPvoxPhysicalRegionInventoryV1(input) {
|
|
|
3552
3552
|
materialId: requireToken(item.materialId, `${entryName}.materialId`)
|
|
3553
3553
|
};
|
|
3554
3554
|
});
|
|
3555
|
-
if (entries.some((
|
|
3555
|
+
if (entries.some((entry2, index) => entry2.regionIndex !== index)) throw new Error(`${fieldName} regionIndex values must be contiguous and canonically ordered.`);
|
|
3556
3556
|
const pairs = entries.map(({ regionId, materialId }) => `${regionId}\0${materialId}`);
|
|
3557
3557
|
if (new Set(pairs).size !== pairs.length) throw new Error(`${fieldName} region/material pairs must be unique.`);
|
|
3558
3558
|
const inventoryHash = requireSha256(record2.inventoryHash, `${fieldName}.inventoryHash`);
|
|
@@ -3689,9 +3689,9 @@ function createPvoxEditJournalCurrentState(input, baseManifest) {
|
|
|
3689
3689
|
const record2 = asRecord(input, fieldName);
|
|
3690
3690
|
exactKeys(record2, ["baseContentHash", "basePageSetHash", "placementId", "gridVersion", "revision", "rootHash", "latestJournalHash", "pageHashes"], fieldName);
|
|
3691
3691
|
const pageEntries = asDenseArray(record2.pageHashes, `${fieldName}.pageHashes`, baseManifest.pages.length, baseManifest.pages.length);
|
|
3692
|
-
const pageHashes = pageEntries.map((
|
|
3692
|
+
const pageHashes = pageEntries.map((entry2, index) => {
|
|
3693
3693
|
const entryName = `${fieldName}.pageHashes[${index}]`;
|
|
3694
|
-
const page = asRecord(
|
|
3694
|
+
const page = asRecord(entry2, entryName);
|
|
3695
3695
|
exactKeys(page, ["pageIndex", "sha256"], entryName);
|
|
3696
3696
|
const pageIndex = requireInteger(page.pageIndex, `${entryName}.pageIndex`, 0, baseManifest.pages.length - 1);
|
|
3697
3697
|
if (pageIndex !== index) throw new Error(`${fieldName}.pageHashes must be complete and ordered by pageIndex.`);
|
|
@@ -3742,9 +3742,9 @@ function createPvoxEditJournal(input, baseManifestInput, currentStateInput) {
|
|
|
3742
3742
|
}
|
|
3743
3743
|
}
|
|
3744
3744
|
const patchEntries = asDenseArray(record2.patches, "PvoxEditJournal.patches", 1, PVOX_MAX_EDIT_PATCHES);
|
|
3745
|
-
const patches = patchEntries.map((
|
|
3745
|
+
const patches = patchEntries.map((entry2, index) => {
|
|
3746
3746
|
const fieldName = `PvoxEditJournal.patches[${index}]`;
|
|
3747
|
-
const patch = asRecord(
|
|
3747
|
+
const patch = asRecord(entry2, fieldName);
|
|
3748
3748
|
exactKeys(patch, ["fieldKind", "lodLevel", "partitionIndex", "hierarchyDepth", "mortonCode", "expectedPageIndex", "expectedPageHash", "operation", "expectedBrickHash", "resultingBrickHash", "resultingPageHash"], fieldName);
|
|
3749
3749
|
const mortonCode = requireString(patch.mortonCode, `${fieldName}.mortonCode`, 16);
|
|
3750
3750
|
if (!MORTON_CODE.test(mortonCode)) throw new Error(`${fieldName}.mortonCode must be a canonical Morton code.`);
|
|
@@ -3821,7 +3821,7 @@ function createPvoxEditJournalChain(input, baseManifestInput) {
|
|
|
3821
3821
|
const journals = [];
|
|
3822
3822
|
const operationIds = /* @__PURE__ */ new Set();
|
|
3823
3823
|
const currentPageHashes = new Map(baseManifest.pages.map(({ pageIndex, sha256: sha2562 }) => [pageIndex, sha2562]));
|
|
3824
|
-
for (const
|
|
3824
|
+
for (const entry2 of entries) {
|
|
3825
3825
|
const previous = journals.at(-1);
|
|
3826
3826
|
const currentState = previous === void 0 ? void 0 : {
|
|
3827
3827
|
baseContentHash: previous.baseContentHash,
|
|
@@ -3833,7 +3833,7 @@ function createPvoxEditJournalChain(input, baseManifestInput) {
|
|
|
3833
3833
|
latestJournalHash: previous.journalHash,
|
|
3834
3834
|
pageHashes: baseManifest.pages.map(({ pageIndex }) => ({ pageIndex, sha256: currentPageHashes.get(pageIndex) }))
|
|
3835
3835
|
};
|
|
3836
|
-
const journal = createPvoxEditJournal(
|
|
3836
|
+
const journal = createPvoxEditJournal(entry2, baseManifest, currentState);
|
|
3837
3837
|
if (previous === void 0 && (journal.expectedRevision !== 0 || journal.previousJournalHash !== PVOX_EDIT_JOURNAL_GENESIS_HASH)) {
|
|
3838
3838
|
throw new Error("PvoxEditJournalChain must begin at the canonical genesis revision.");
|
|
3839
3839
|
}
|
|
@@ -3891,10 +3891,10 @@ function createTransform2(value, fieldName) {
|
|
|
3891
3891
|
exactKeys(record2, ["translationMetres", "rotationQuaternion", "scale"], fieldName);
|
|
3892
3892
|
const translationMetres = requireVector(record2.translationMetres, `${fieldName}.translationMetres`);
|
|
3893
3893
|
const rotationEntries = asDenseArray(record2.rotationQuaternion, `${fieldName}.rotationQuaternion`, 4, 4);
|
|
3894
|
-
const rotationQuaternion = rotationEntries.map((
|
|
3894
|
+
const rotationQuaternion = rotationEntries.map((entry2, index) => requireNumber(entry2, `${fieldName}.rotationQuaternion[${index}]`, -1, 1));
|
|
3895
3895
|
if (Math.abs(Math.hypot(...rotationQuaternion) - 1) > 1e-4) throw new Error(`${fieldName}.rotationQuaternion must be normalized.`);
|
|
3896
3896
|
const scale = requireVector(record2.scale, `${fieldName}.scale`);
|
|
3897
|
-
if (scale.some((
|
|
3897
|
+
if (scale.some((entry2) => entry2 <= 0)) throw new Error(`${fieldName}.scale values must be positive.`);
|
|
3898
3898
|
return { translationMetres, rotationQuaternion, scale };
|
|
3899
3899
|
}
|
|
3900
3900
|
function createAssemblyChild2(value, index, resolutionId, candidateId) {
|
|
@@ -4062,10 +4062,10 @@ function createModelProcessingManifestV2(input) {
|
|
|
4062
4062
|
}
|
|
4063
4063
|
const capabilityEntries = asDenseArray(record2.capabilities, "ModelProcessingManifestV2.capabilities", PVOX_CAPABILITIES.length, PVOX_CAPABILITIES.length);
|
|
4064
4064
|
const capabilities = capabilityEntries.map(createVoxelCapabilityAssessment);
|
|
4065
|
-
const byCapability = new Map(capabilities.map((
|
|
4065
|
+
const byCapability = new Map(capabilities.map((entry2) => [entry2.capability, entry2]));
|
|
4066
4066
|
if (byCapability.size !== PVOX_CAPABILITIES.length || PVOX_CAPABILITIES.some((name) => !byCapability.has(name))) throw new Error("ModelProcessingManifestV2 must include every PVOX capability exactly once.");
|
|
4067
4067
|
const orderedCapabilities = PVOX_CAPABILITIES.map((name) => byCapability.get(name));
|
|
4068
|
-
if (orderedCapabilities.some((
|
|
4068
|
+
if (orderedCapabilities.some((entry2) => entry2.subjectContentHash !== binaryClosureHash)) throw new Error("ModelProcessingManifestV2 capability evidence must bind the binaryClosureHash.");
|
|
4069
4069
|
const capabilityEvidenceSetHash = requireSha256(record2.capabilityEvidenceSetHash, "ModelProcessingManifestV2.capabilityEvidenceSetHash");
|
|
4070
4070
|
const capabilityEvidenceSetAttestation = createHashAttestation(
|
|
4071
4071
|
record2.capabilityEvidenceSetAttestation,
|
|
@@ -4074,7 +4074,7 @@ function createModelProcessingManifestV2(input) {
|
|
|
4074
4074
|
"ModelProcessingManifestV2.capabilityEvidenceSetAttestation"
|
|
4075
4075
|
);
|
|
4076
4076
|
const requiredCapabilityEntries = asDenseArray(record2.requiredCapabilities, "ModelProcessingManifestV2.requiredCapabilities", 1, PVOX_CAPABILITIES.length);
|
|
4077
|
-
const requiredCapabilities = requiredCapabilityEntries.map((
|
|
4077
|
+
const requiredCapabilities = requiredCapabilityEntries.map((entry2, index) => requireEnum(entry2, PVOX_CAPABILITIES, `ModelProcessingManifestV2.requiredCapabilities[${index}]`));
|
|
4078
4078
|
if (new Set(requiredCapabilities).size !== requiredCapabilities.length) throw new Error("ModelProcessingManifestV2.requiredCapabilities must be unique.");
|
|
4079
4079
|
if (!requiredCapabilities.includes("rendering")) throw new Error("ModelProcessingManifestV2 rendering capability is always required.");
|
|
4080
4080
|
if (technicalProfile.hasCollision !== (byCapability.get("collision")?.status === "supported")) throw new Error("ModelProcessingManifestV2 collision capability must match technicalProfile.hasCollision.");
|
|
@@ -4087,9 +4087,9 @@ function createModelProcessingManifestV2(input) {
|
|
|
4087
4087
|
}
|
|
4088
4088
|
const physicalEntries = asDenseArray(record2.physicalProperties, "ModelProcessingManifestV2.physicalProperties", 0, PVOX_DEFAULT_LIMITS.maximumPhysicalEvidenceEntries);
|
|
4089
4089
|
const physicalProperties = physicalEntries.map(createPhysicalPropertyEvidence);
|
|
4090
|
-
const byRegionProperty = new Map(physicalProperties.map((
|
|
4090
|
+
const byRegionProperty = new Map(physicalProperties.map((entry2) => [`${entry2.regionId}\0${entry2.materialId}\0${entry2.property}`, entry2]));
|
|
4091
4091
|
if (byRegionProperty.size !== physicalProperties.length) throw new Error("ModelProcessingManifestV2 physical properties must be unique per region, material, and property.");
|
|
4092
|
-
if (physicalProperties.some((
|
|
4092
|
+
if (physicalProperties.some((entry2) => entry2.subjectContentHash !== binaryClosureHash)) throw new Error("ModelProcessingManifestV2 physical evidence must bind the PVOX binary closure subject.");
|
|
4093
4093
|
const authoritativePairs = physicalRegionInventory.entries.map(({ regionId, materialId }) => `${regionId}\0${materialId}`);
|
|
4094
4094
|
const authoritativePairSet = new Set(authoritativePairs);
|
|
4095
4095
|
if (physicalProperties.some(({ regionId, materialId }) => !authoritativePairSet.has(`${regionId}\0${materialId}`))) {
|
|
@@ -4171,7 +4171,7 @@ function createModelProcessingManifestV2(input) {
|
|
|
4171
4171
|
throw new Error("ModelProcessingManifestV2 fidelity evidence must exactly match every governed PVOX LOD0 metric.");
|
|
4172
4172
|
}
|
|
4173
4173
|
const childEntries = asDenseArray(record2.children, "ModelProcessingManifestV2.children", 0, PVOX_DEFAULT_LIMITS.maximumAssemblyChildren);
|
|
4174
|
-
const children = childEntries.map((
|
|
4174
|
+
const children = childEntries.map((entry2, index) => createAssemblyChild2(entry2, index, resolutionId, candidateId));
|
|
4175
4175
|
const kind = requireEnum(record2.kind, ["leaf", "assembly"], "ModelProcessingManifestV2.kind");
|
|
4176
4176
|
if (kind === "leaf" && children.length !== 0) throw new Error("A leaf ModelProcessingManifestV2 cannot contain children.");
|
|
4177
4177
|
if (kind === "leaf" && technicalProfile.partitionCount !== 1) throw new Error("A leaf ModelProcessingManifestV2 must have one partition.");
|
|
@@ -4254,9 +4254,9 @@ function createCandidateAssetRef2(value) {
|
|
|
4254
4254
|
}
|
|
4255
4255
|
function createViews(value, resolutionId, candidateId) {
|
|
4256
4256
|
const entries = asDenseArray(value, "ModelCandidateV2.views", 4, 4);
|
|
4257
|
-
const parsed = entries.map((
|
|
4257
|
+
const parsed = entries.map((entry2, index) => {
|
|
4258
4258
|
const fieldName = `ModelCandidateV2.views[${index}]`;
|
|
4259
|
-
const record2 = asRecord(
|
|
4259
|
+
const record2 = asRecord(entry2, fieldName);
|
|
4260
4260
|
exactKeys(record2, ["kind", "imageUri", "sha256", "contentType", "width", "height"], fieldName);
|
|
4261
4261
|
const kind = requireEnum(record2.kind, MODEL_CONFIRMATION_VIEW_KINDS, `${fieldName}.kind`);
|
|
4262
4262
|
const expectedUri = `mcp://models/resolutions/${resolutionId}/candidates/${candidateId}/${kind}.png`;
|
|
@@ -4264,7 +4264,7 @@ function createViews(value, resolutionId, candidateId) {
|
|
|
4264
4264
|
if (record2.contentType !== "image/png" || record2.width !== MODEL_CONFIRMATION_VIEW_SIZE_PX || record2.height !== MODEL_CONFIRMATION_VIEW_SIZE_PX) throw new Error(`${fieldName} must be an authenticated 1024x1024 PNG.`);
|
|
4265
4265
|
return { kind, imageUri: expectedUri, sha256: requireSha256(record2.sha256, `${fieldName}.sha256`), contentType: "image/png", width: MODEL_CONFIRMATION_VIEW_SIZE_PX, height: MODEL_CONFIRMATION_VIEW_SIZE_PX };
|
|
4266
4266
|
});
|
|
4267
|
-
if (parsed.some((
|
|
4267
|
+
if (parsed.some((entry2, index) => entry2.kind !== MODEL_CONFIRMATION_VIEW_KINDS[index])) throw new Error("ModelCandidateV2.views must be ordered front, left, top, isometric.");
|
|
4268
4268
|
return parsed;
|
|
4269
4269
|
}
|
|
4270
4270
|
function createRenderEvidence2(value) {
|
|
@@ -4463,7 +4463,7 @@ function createModelCandidateV2(input) {
|
|
|
4463
4463
|
);
|
|
4464
4464
|
const gateEntries = asDenseArray(record2.hardGates, "ModelCandidateV2.hardGates", PVOX_NON_OVERRIDABLE_GATE_KINDS.length, PVOX_NON_OVERRIDABLE_GATE_KINDS.length);
|
|
4465
4465
|
const gates = gateEntries.map(createPvoxGateEvidence);
|
|
4466
|
-
const byGate = new Map(gates.map((
|
|
4466
|
+
const byGate = new Map(gates.map((entry2) => [entry2.kind, entry2]));
|
|
4467
4467
|
if (byGate.size !== PVOX_NON_OVERRIDABLE_GATE_KINDS.length || PVOX_NON_OVERRIDABLE_GATE_KINDS.some((kind) => !byGate.has(kind))) throw new Error("ModelCandidateV2 must contain every non-overridable hard gate exactly once.");
|
|
4468
4468
|
const orderedGates = PVOX_NON_OVERRIDABLE_GATE_KINDS.map((kind) => byGate.get(kind));
|
|
4469
4469
|
const concreteGateHashes = {
|
|
@@ -4486,7 +4486,7 @@ function createModelCandidateV2(input) {
|
|
|
4486
4486
|
...!match.hardConstraintPass ? ["semantic-hard-constraints-failed"] : [],
|
|
4487
4487
|
...!["allowed", "attribution-required"].includes(rights.status) ? [`rights:${rights.status}`] : [],
|
|
4488
4488
|
...processingManifest.requiredCapabilities.flatMap((capability) => {
|
|
4489
|
-
const evidence = processingManifest.capabilities.find((
|
|
4489
|
+
const evidence = processingManifest.capabilities.find((entry2) => entry2.capability === capability);
|
|
4490
4490
|
return evidence.status === "supported" ? [] : [`capability:${capability}:${evidence.status}`];
|
|
4491
4491
|
}),
|
|
4492
4492
|
...orderedGates.flatMap((gate) => gate.outcome === "passed" ? [] : gate.reasonCodes.map((reason) => `${gate.kind}:${reason}`))
|
|
@@ -4514,7 +4514,7 @@ function createModelCandidateV2(input) {
|
|
|
4514
4514
|
if (record2.confirmationRequired !== false) throw new Error("Diagnostic ModelCandidateV2 confirmationRequired must be false.");
|
|
4515
4515
|
if (record2.blockingReasonCodes !== void 0) {
|
|
4516
4516
|
const suppliedEntries = asDenseArray(record2.blockingReasonCodes, "ModelCandidateV2.blockingReasonCodes", 1, 256);
|
|
4517
|
-
const suppliedBlockingReasons = suppliedEntries.map((
|
|
4517
|
+
const suppliedBlockingReasons = suppliedEntries.map((entry2, index) => requireReasonCode(entry2, `ModelCandidateV2.blockingReasonCodes[${index}]`));
|
|
4518
4518
|
if (JSON.stringify(suppliedBlockingReasons) !== JSON.stringify(blockingReasonCodes)) throw new Error("Diagnostic ModelCandidateV2 blockingReasonCodes must be derived from its evidence.");
|
|
4519
4519
|
}
|
|
4520
4520
|
return deepFreeze2({ ...common, admissionStatus: "diagnostic", blockingReasonCodes, confirmationRequired: false });
|
|
@@ -4568,7 +4568,7 @@ function createModelCandidateV2(input) {
|
|
|
4568
4568
|
});
|
|
4569
4569
|
}
|
|
4570
4570
|
function isModelCandidateV2Confirmable(candidate) {
|
|
4571
|
-
return candidate.admissionStatus === "confirmable" && candidate.confirmationRequired && candidate.match.hardConstraintPass && candidate.match.assurance !== "none" && (candidate.rights.status === "allowed" || candidate.rights.status === "attribution-required") && candidate.processingManifest.fidelity.outcome === "passed" && candidate.hardGates.length === PVOX_NON_OVERRIDABLE_GATE_KINDS.length && candidate.hardGates.every(({ outcome }) => outcome === "passed") && candidate.processingManifest.requiredCapabilities.every((name) => candidate.processingManifest.capabilities.find((
|
|
4571
|
+
return candidate.admissionStatus === "confirmable" && candidate.confirmationRequired && candidate.match.hardConstraintPass && candidate.match.assurance !== "none" && (candidate.rights.status === "allowed" || candidate.rights.status === "attribution-required") && candidate.processingManifest.fidelity.outcome === "passed" && candidate.hardGates.length === PVOX_NON_OVERRIDABLE_GATE_KINDS.length && candidate.hardGates.every(({ outcome }) => outcome === "passed") && candidate.processingManifest.requiredCapabilities.every((name) => candidate.processingManifest.capabilities.find((entry2) => entry2.capability === name)?.status === "supported");
|
|
4572
4572
|
}
|
|
4573
4573
|
function createModelCandidateConfirmationV2(input, expectedCandidate, expectedResolutionId) {
|
|
4574
4574
|
const record2 = asRecord(input, "ModelCandidateConfirmationV2");
|
|
@@ -4665,7 +4665,7 @@ function createModelPromotionReceiptV2(input, resolutionId, candidate, confirmat
|
|
|
4665
4665
|
const candidateAssetRef = createCandidateAssetRef2(record2.candidateAssetRef);
|
|
4666
4666
|
if (JSON.stringify(candidateAssetRef) !== JSON.stringify(candidate.assetRef)) throw new Error("ModelPromotionReceiptV2 must bind the exact proposed candidate identity.");
|
|
4667
4667
|
const childClosureEntries = asDenseArray(record2.assemblyChildClosureHashes, "ModelPromotionReceiptV2.assemblyChildClosureHashes", 0, PVOX_DEFAULT_LIMITS.maximumAssemblyChildren);
|
|
4668
|
-
const assemblyChildClosureHashes = childClosureEntries.map((
|
|
4668
|
+
const assemblyChildClosureHashes = childClosureEntries.map((entry2, index) => requireSha256(entry2, `ModelPromotionReceiptV2.assemblyChildClosureHashes[${index}]`));
|
|
4669
4669
|
const expectedChildClosureHashes = candidate.processingManifest.children.map(({ assetRef }) => assetRef.binaryClosureHash);
|
|
4670
4670
|
if (JSON.stringify(assemblyChildClosureHashes) !== JSON.stringify(expectedChildClosureHashes)) throw new Error("ModelPromotionReceiptV2 child closure sequence must match the canonical assembly order.");
|
|
4671
4671
|
assertDataOnlyTree(record2.finalAssetRef, "ModelPromotionReceiptV2.finalAssetRef");
|
|
@@ -4926,12 +4926,12 @@ function copyPvoxByteInput(value, fieldName, maximumByteLength) {
|
|
|
4926
4926
|
if (!(value instanceof Uint8Array)) throw new Error(`${fieldName} must be a Uint8Array.`);
|
|
4927
4927
|
let byteLength;
|
|
4928
4928
|
try {
|
|
4929
|
-
const
|
|
4930
|
-
const
|
|
4931
|
-
const
|
|
4932
|
-
if (
|
|
4933
|
-
byteLength =
|
|
4934
|
-
const buffer =
|
|
4929
|
+
const typedArrayPrototype3 = Object.getPrototypeOf(Uint8Array.prototype);
|
|
4930
|
+
const byteLengthGetter3 = Object.getOwnPropertyDescriptor(typedArrayPrototype3, "byteLength")?.get;
|
|
4931
|
+
const bufferGetter3 = Object.getOwnPropertyDescriptor(typedArrayPrototype3, "buffer")?.get;
|
|
4932
|
+
if (byteLengthGetter3 === void 0 || bufferGetter3 === void 0) throw new Error("Typed-array intrinsics are unavailable.");
|
|
4933
|
+
byteLength = byteLengthGetter3.call(value);
|
|
4934
|
+
const buffer = bufferGetter3.call(value);
|
|
4935
4935
|
if (typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer) {
|
|
4936
4936
|
throw new Error(`${fieldName} must not use shared mutable storage.`);
|
|
4937
4937
|
}
|
|
@@ -6701,9 +6701,9 @@ function snapshot(value, budget, depth = 0) {
|
|
|
6701
6701
|
return value;
|
|
6702
6702
|
}
|
|
6703
6703
|
if (Array.isArray(value)) return Object.freeze(list(value).map((item) => snapshot(item, budget, depth + 1)));
|
|
6704
|
-
const
|
|
6705
|
-
for (const key of Object.keys(
|
|
6706
|
-
return Object.freeze(
|
|
6704
|
+
const fields4 = record(value);
|
|
6705
|
+
for (const key of Object.keys(fields4)) fields4[key] = snapshot(fields4[key], budget, depth + 1);
|
|
6706
|
+
return Object.freeze(fields4);
|
|
6707
6707
|
}
|
|
6708
6708
|
function path(value) {
|
|
6709
6709
|
if (typeof value !== "string" || value.length > 1024 || !value.length) fail();
|
|
@@ -6775,14 +6775,14 @@ async function qualify(input) {
|
|
|
6775
6775
|
contentType(file.contentType);
|
|
6776
6776
|
}
|
|
6777
6777
|
const entries = list(inventory.entries).map(record);
|
|
6778
|
-
uniquePaths(entries.map((
|
|
6778
|
+
uniquePaths(entries.map((entry2) => path(entry2.path)));
|
|
6779
6779
|
const ids = /* @__PURE__ */ new Set();
|
|
6780
|
-
for (const
|
|
6781
|
-
if (Object.keys(
|
|
6782
|
-
ids.add(
|
|
6783
|
-
const descriptor = descriptors.get(
|
|
6780
|
+
for (const entry2 of entries) {
|
|
6781
|
+
if (Object.keys(entry2).some((key) => !["id", "path", "role", "contentType"].includes(key)) || typeof entry2.id !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(entry2.id) || ids.has(entry2.id) || !["mesh", "material", "texture", "animation", "auxiliary"].includes(entry2.role)) fail();
|
|
6782
|
+
ids.add(entry2.id);
|
|
6783
|
+
const descriptor = descriptors.get(entry2.path);
|
|
6784
6784
|
if (!descriptor) fail("inventory-mismatch");
|
|
6785
|
-
if (
|
|
6785
|
+
if (entry2.contentType !== void 0 && contentType(entry2.contentType) !== descriptor.contentType) fail("content-mismatch");
|
|
6786
6786
|
}
|
|
6787
6787
|
if (entries.length !== descriptors.size) fail("inventory-mismatch");
|
|
6788
6788
|
const payloads = list(root.files).map((value) => {
|
|
@@ -6812,6 +6812,179 @@ async function qualify(input) {
|
|
|
6812
6812
|
return Object.freeze({ ...manifest, files: Object.freeze([...manifest.files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) });
|
|
6813
6813
|
}
|
|
6814
6814
|
|
|
6815
|
+
// src/model-texture-resources.ts
|
|
6816
|
+
import { isGpuModelResourceGraph } from "@plasius/gpu-model-core";
|
|
6817
|
+
var MODEL_TEXTURE_RESOURCE_LIMITS = Object.freeze({ resources: 16384, derived: 4096, nodes: 65536, values: 262144, characters: 2097152, depth: 16, timeoutMs: 6e4 });
|
|
6818
|
+
var ModelTextureResourceError = class extends TypeError {
|
|
6819
|
+
constructor(code) {
|
|
6820
|
+
super(`Invalid model texture resources: ${code}`);
|
|
6821
|
+
this.code = code;
|
|
6822
|
+
this.name = "ModelTextureResourceError";
|
|
6823
|
+
}
|
|
6824
|
+
code;
|
|
6825
|
+
};
|
|
6826
|
+
function fail2(code = "invalid-input") {
|
|
6827
|
+
throw new ModelTextureResourceError(code);
|
|
6828
|
+
}
|
|
6829
|
+
function fields(value, allowed) {
|
|
6830
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail2();
|
|
6831
|
+
const keys = Reflect.ownKeys(value);
|
|
6832
|
+
if (keys.length > 1024) fail2("limit-exceeded");
|
|
6833
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
6834
|
+
for (const key of keys) {
|
|
6835
|
+
if (typeof key !== "string" || key.length > 128 || ["__proto__", "prototype", "constructor"].includes(key) || allowed && !allowed.includes(key)) fail2();
|
|
6836
|
+
const property = Object.getOwnPropertyDescriptor(value, key);
|
|
6837
|
+
if (!("value" in property) || !property.enumerable) fail2();
|
|
6838
|
+
result[key] = property.value;
|
|
6839
|
+
}
|
|
6840
|
+
return result;
|
|
6841
|
+
}
|
|
6842
|
+
function array(value, maximum) {
|
|
6843
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) fail2();
|
|
6844
|
+
if (value.length > maximum) fail2("limit-exceeded");
|
|
6845
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail2();
|
|
6846
|
+
const result = [];
|
|
6847
|
+
for (let i = 0; i < value.length; i++) {
|
|
6848
|
+
const property = Object.getOwnPropertyDescriptor(value, String(i));
|
|
6849
|
+
if (!property || !("value" in property) || !property.enumerable) fail2();
|
|
6850
|
+
result.push(property.value);
|
|
6851
|
+
}
|
|
6852
|
+
return result;
|
|
6853
|
+
}
|
|
6854
|
+
function snapshot2(value, budget, depth = 0) {
|
|
6855
|
+
if (--budget.values < 0 || depth > MODEL_TEXTURE_RESOURCE_LIMITS.depth) fail2("limit-exceeded");
|
|
6856
|
+
if (value === null || value === void 0 || typeof value === "boolean") return value;
|
|
6857
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
6858
|
+
if (typeof value === "string") {
|
|
6859
|
+
budget.characters -= value.length;
|
|
6860
|
+
if (value.length > 2048 || budget.characters < 0) fail2("limit-exceeded");
|
|
6861
|
+
return value;
|
|
6862
|
+
}
|
|
6863
|
+
if (Array.isArray(value)) return Object.freeze(array(value, MODEL_TEXTURE_RESOURCE_LIMITS.resources).map((v) => snapshot2(v, budget, depth + 1)));
|
|
6864
|
+
const result = fields(value);
|
|
6865
|
+
for (const key of Object.keys(result)) {
|
|
6866
|
+
budget.characters -= key.length;
|
|
6867
|
+
if (budget.characters < 0) fail2("limit-exceeded");
|
|
6868
|
+
result[key] = snapshot2(result[key], budget, depth + 1);
|
|
6869
|
+
}
|
|
6870
|
+
return Object.freeze(result);
|
|
6871
|
+
}
|
|
6872
|
+
function token2(value) {
|
|
6873
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value)) fail2();
|
|
6874
|
+
return value;
|
|
6875
|
+
}
|
|
6876
|
+
var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
6877
|
+
var imageMime = (value) => ["image/png", "image/jpeg", "image/webp", "image/ktx2"].includes(value);
|
|
6878
|
+
var bufferMime = (value) => ["application/octet-stream", "application/vnd.plasius.gpu-buffer"].includes(value);
|
|
6879
|
+
function storageRole(role, file) {
|
|
6880
|
+
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");
|
|
6881
|
+
if (!valid) fail2("role-mismatch");
|
|
6882
|
+
}
|
|
6883
|
+
async function qualifyModelTextureResources(input, options = {}) {
|
|
6884
|
+
let timer;
|
|
6885
|
+
let signal;
|
|
6886
|
+
let onAbort;
|
|
6887
|
+
try {
|
|
6888
|
+
const started = performance.now();
|
|
6889
|
+
const controls = fields(options, ["signal", "timeoutMs"]);
|
|
6890
|
+
const timeoutMs = controls.timeoutMs === void 0 ? 1e4 : controls.timeoutMs;
|
|
6891
|
+
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MODEL_TEXTURE_RESOURCE_LIMITS.timeoutMs) fail2();
|
|
6892
|
+
signal = controls.signal;
|
|
6893
|
+
const aborted = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted").get;
|
|
6894
|
+
const check = () => {
|
|
6895
|
+
if (signal !== void 0 && aborted.call(signal)) fail2("aborted");
|
|
6896
|
+
if (performance.now() - started >= timeoutMs) fail2("deadline-exceeded");
|
|
6897
|
+
};
|
|
6898
|
+
check();
|
|
6899
|
+
const root = fields(input, ["manifest", "packageManifest", "files", "graph", "resources", "derived"]);
|
|
6900
|
+
const graph = root.graph;
|
|
6901
|
+
if (!isGpuModelResourceGraph(graph)) fail2("unverified-graph");
|
|
6902
|
+
if (graph.nodes.length > MODEL_TEXTURE_RESOURCE_LIMITS.nodes) fail2("limit-exceeded");
|
|
6903
|
+
const budget = { values: MODEL_TEXTURE_RESOURCE_LIMITS.values, characters: MODEL_TEXTURE_RESOURCE_LIMITS.characters };
|
|
6904
|
+
const inventory = snapshot2(root.packageManifest, budget);
|
|
6905
|
+
const bindings = array(snapshot2(root.resources, budget), MODEL_TEXTURE_RESOURCE_LIMITS.resources).map((value) => {
|
|
6906
|
+
const binding = fields(value, ["resourceId", "filePath"]);
|
|
6907
|
+
return Object.freeze({ resourceId: token2(binding.resourceId), filePath: tokenPath(binding.filePath) });
|
|
6908
|
+
});
|
|
6909
|
+
const derived = array(snapshot2(root.derived, budget), MODEL_TEXTURE_RESOURCE_LIMITS.derived).map((value) => {
|
|
6910
|
+
const record2 = fields(value, ["role", "filePath", "sourceResourceIds"]);
|
|
6911
|
+
if (record2.role !== "gpu-upload" && record2.role !== "texture-atlas") fail2("role-mismatch");
|
|
6912
|
+
const ids = array(record2.sourceResourceIds, MODEL_TEXTURE_RESOURCE_LIMITS.resources).map(token2);
|
|
6913
|
+
if (!ids.length || new Set(ids).size !== ids.length) fail2("resource-mismatch");
|
|
6914
|
+
return Object.freeze({ role: record2.role, filePath: tokenPath(record2.filePath), sourceResourceIds: Object.freeze(ids.sort(compare)) });
|
|
6915
|
+
});
|
|
6916
|
+
check();
|
|
6917
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
6918
|
+
timer = setTimeout(() => reject(new ModelTextureResourceError("deadline-exceeded")), Math.max(1, timeoutMs - (performance.now() - started)));
|
|
6919
|
+
onAbort = () => reject(new ModelTextureResourceError("aborted"));
|
|
6920
|
+
if (signal) AbortSignal.prototype.addEventListener.call(signal, "abort", onAbort, { once: true });
|
|
6921
|
+
});
|
|
6922
|
+
const manifest = await Promise.race([
|
|
6923
|
+
qualifyModelResourceManifest({ manifest: root.manifest, files: root.files, packageManifest: inventory, signal, timeoutMs }),
|
|
6924
|
+
interrupted
|
|
6925
|
+
]);
|
|
6926
|
+
check();
|
|
6927
|
+
const files = new Map(manifest.files.map((file) => [file.path, file]));
|
|
6928
|
+
const roles = new Map(inventory.entries.map((entry2) => [entry2.path, entry2.role]));
|
|
6929
|
+
for (const entry2 of inventory.entries) storageRole(entry2.role, files.get(entry2.path));
|
|
6930
|
+
const resources = new Map(graph.nodes.filter((node) => node.kind === "resource").map((node) => [node.id, node.value]));
|
|
6931
|
+
if (bindings.length !== resources.size || new Set(bindings.map((b) => b.resourceId)).size !== bindings.length) fail2("resource-mismatch");
|
|
6932
|
+
const resourceFiles = /* @__PURE__ */ new Set();
|
|
6933
|
+
const qualified2 = bindings.map((binding) => {
|
|
6934
|
+
const value = resources.get(binding.resourceId);
|
|
6935
|
+
const file = files.get(binding.filePath);
|
|
6936
|
+
if (!value || !file) fail2("resource-mismatch");
|
|
6937
|
+
const { resource, location, exportEmbeddingPolicy } = value;
|
|
6938
|
+
if (location.kind === "external") {
|
|
6939
|
+
if (binding.filePath !== location.relativePath || file.sha256 !== resource.contentHash || file.byteLength !== resource.byteLength || file.contentType !== resource.mimeType) fail2("resource-mismatch");
|
|
6940
|
+
if (resource.kind === "image" ? roles.get(file.path) !== "texture" : file.role !== "binary") fail2("role-mismatch");
|
|
6941
|
+
} else if (binding.filePath !== manifest.entrypoint || file.role !== "model") fail2("resource-mismatch");
|
|
6942
|
+
resourceFiles.add(binding.filePath);
|
|
6943
|
+
return Object.freeze({ ...binding, kind: resource.kind, contentHash: resource.contentHash, byteLength: resource.byteLength, mimeType: resource.mimeType, location, exportEmbeddingPolicy });
|
|
6944
|
+
});
|
|
6945
|
+
const outputFiles = /* @__PURE__ */ new Set();
|
|
6946
|
+
for (const output of derived) {
|
|
6947
|
+
const file = files.get(output.filePath);
|
|
6948
|
+
if (!file || outputFiles.has(output.filePath) || resourceFiles.has(output.filePath)) fail2("resource-mismatch");
|
|
6949
|
+
outputFiles.add(output.filePath);
|
|
6950
|
+
for (const id of output.sourceResourceIds) {
|
|
6951
|
+
const source2 = resources.get(id);
|
|
6952
|
+
if (!source2 || output.role === "texture-atlas" && source2.resource.kind !== "image") fail2("resource-mismatch");
|
|
6953
|
+
}
|
|
6954
|
+
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");
|
|
6955
|
+
}
|
|
6956
|
+
const materials = graph.nodes.filter((node) => node.kind === "material").map((node) => node.value);
|
|
6957
|
+
const textures = graph.nodes.filter((node) => node.kind === "texture").map((node) => node.value);
|
|
6958
|
+
const source = graph.nodes.find((node) => node.kind === "provenance");
|
|
6959
|
+
if (source.kind !== "provenance") fail2();
|
|
6960
|
+
snapshot2({ materials, textures, source: source.value }, budget);
|
|
6961
|
+
check();
|
|
6962
|
+
return Object.freeze({
|
|
6963
|
+
contractVersion: 1,
|
|
6964
|
+
manifest,
|
|
6965
|
+
source: source.value,
|
|
6966
|
+
resources: Object.freeze(qualified2.sort((a, b) => compare(a.resourceId, b.resourceId))),
|
|
6967
|
+
materials: Object.freeze(materials),
|
|
6968
|
+
textures: Object.freeze(textures),
|
|
6969
|
+
derived: Object.freeze(derived.sort((a, b) => compare(a.filePath, b.filePath)))
|
|
6970
|
+
});
|
|
6971
|
+
} catch (error) {
|
|
6972
|
+
if (error instanceof ModelTextureResourceError) throw error;
|
|
6973
|
+
if (error instanceof ModelResourceManifestError) return fail2(error.code === "aborted" || error.code === "deadline-exceeded" || error.code === "limit-exceeded" ? error.code : "package-mismatch");
|
|
6974
|
+
return fail2();
|
|
6975
|
+
} finally {
|
|
6976
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
6977
|
+
if (signal && onAbort) AbortSignal.prototype.removeEventListener.call(signal, "abort", onAbort);
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6980
|
+
function tokenPath(value) {
|
|
6981
|
+
if (typeof value !== "string" || !value.length || value.length > 1024) fail2();
|
|
6982
|
+
return value;
|
|
6983
|
+
}
|
|
6984
|
+
|
|
6985
|
+
// src/model-resource-promotion.ts
|
|
6986
|
+
import { canonicalizeGpuContract as canonicalizeGpuContract3, computeSha256 as computeSha2562 } from "@plasius/gpu-shader";
|
|
6987
|
+
|
|
6815
6988
|
// src/model-conversion-diagnostics.ts
|
|
6816
6989
|
var MODEL_CONVERSION_DIAGNOSTIC_LIMITS = Object.freeze({ issues: 100, resources: 100, values: 16384, depth: 12, string: 2048, characters: 262144, timeoutMs: 6e4 });
|
|
6817
6990
|
var ModelConversionDiagnosticsError = class extends TypeError {
|
|
@@ -6822,50 +6995,50 @@ var ModelConversionDiagnosticsError = class extends TypeError {
|
|
|
6822
6995
|
}
|
|
6823
6996
|
code;
|
|
6824
6997
|
};
|
|
6825
|
-
function
|
|
6998
|
+
function fail3(code = "invalid-input") {
|
|
6826
6999
|
throw new ModelConversionDiagnosticsError(code);
|
|
6827
7000
|
}
|
|
6828
|
-
function
|
|
6829
|
-
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
7001
|
+
function fields2(value) {
|
|
7002
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
6830
7003
|
const result = /* @__PURE__ */ Object.create(null);
|
|
6831
7004
|
const keys = Reflect.ownKeys(value);
|
|
6832
|
-
if (keys.length > 1024)
|
|
7005
|
+
if (keys.length > 1024) fail3("limit-exceeded");
|
|
6833
7006
|
for (const key of keys) {
|
|
6834
|
-
if (typeof key !== "string" || key.length > 128 || ["__proto__", "prototype", "constructor"].includes(key))
|
|
7007
|
+
if (typeof key !== "string" || key.length > 128 || ["__proto__", "prototype", "constructor"].includes(key)) fail3();
|
|
6835
7008
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
6836
|
-
if (!("value" in descriptor) || !descriptor.enumerable)
|
|
7009
|
+
if (!("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
6837
7010
|
result[key] = descriptor.value;
|
|
6838
7011
|
}
|
|
6839
7012
|
return result;
|
|
6840
7013
|
}
|
|
6841
7014
|
function only(record2, keys) {
|
|
6842
|
-
if (Object.keys(record2).some((key) => !keys.includes(key)))
|
|
7015
|
+
if (Object.keys(record2).some((key) => !keys.includes(key))) fail3();
|
|
6843
7016
|
}
|
|
6844
|
-
function
|
|
6845
|
-
if (--budget.values < 0 || depth > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.depth)
|
|
7017
|
+
function snapshot3(value, budget, depth = 0) {
|
|
7018
|
+
if (--budget.values < 0 || depth > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.depth) fail3("limit-exceeded");
|
|
6846
7019
|
if (value === null || typeof value === "boolean") return value;
|
|
6847
7020
|
if (typeof value === "string") {
|
|
6848
7021
|
budget.characters -= value.length;
|
|
6849
|
-
if (value.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.string || budget.characters < 0)
|
|
7022
|
+
if (value.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.string || budget.characters < 0) fail3("limit-exceeded");
|
|
6850
7023
|
return value;
|
|
6851
7024
|
}
|
|
6852
7025
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
6853
7026
|
if (Array.isArray(value)) {
|
|
6854
|
-
if (Object.getPrototypeOf(value) !== Array.prototype || value.length > 1024)
|
|
6855
|
-
if (Reflect.ownKeys(value).length !== value.length + 1)
|
|
7027
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || value.length > 1024) fail3("limit-exceeded");
|
|
7028
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail3();
|
|
6856
7029
|
const result = [];
|
|
6857
7030
|
for (let i = 0; i < value.length; i++) {
|
|
6858
7031
|
const descriptor = Object.getOwnPropertyDescriptor(value, String(i));
|
|
6859
|
-
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable)
|
|
6860
|
-
result.push(
|
|
7032
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
7033
|
+
result.push(snapshot3(descriptor.value, budget, depth + 1));
|
|
6861
7034
|
}
|
|
6862
7035
|
return Object.freeze(result);
|
|
6863
7036
|
}
|
|
6864
|
-
const record2 =
|
|
7037
|
+
const record2 = fields2(value);
|
|
6865
7038
|
for (const key of Object.keys(record2)) {
|
|
6866
7039
|
budget.characters -= key.length;
|
|
6867
|
-
if (budget.characters < 0)
|
|
6868
|
-
record2[key] =
|
|
7040
|
+
if (budget.characters < 0) fail3("limit-exceeded");
|
|
7041
|
+
record2[key] = snapshot3(record2[key], budget, depth + 1);
|
|
6869
7042
|
}
|
|
6870
7043
|
return Object.freeze(record2);
|
|
6871
7044
|
}
|
|
@@ -6878,35 +7051,35 @@ async function qualifyModelConversionDiagnostics(input, options = {}) {
|
|
|
6878
7051
|
let onAbort;
|
|
6879
7052
|
try {
|
|
6880
7053
|
const start = performance.now();
|
|
6881
|
-
const controls =
|
|
7054
|
+
const controls = fields2(options);
|
|
6882
7055
|
only(controls, ["signal", "timeoutMs"]);
|
|
6883
7056
|
const timeoutMs = controls.timeoutMs === void 0 ? 1e4 : controls.timeoutMs;
|
|
6884
|
-
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.timeoutMs)
|
|
7057
|
+
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.timeoutMs) fail3();
|
|
6885
7058
|
signal = controls.signal;
|
|
6886
7059
|
const aborted = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted").get;
|
|
6887
7060
|
const check = () => {
|
|
6888
|
-
if (signal !== void 0 && aborted.call(signal))
|
|
6889
|
-
if (performance.now() - start >= timeoutMs)
|
|
7061
|
+
if (signal !== void 0 && aborted.call(signal)) fail3("aborted");
|
|
7062
|
+
if (performance.now() - start >= timeoutMs) fail3("deadline-exceeded");
|
|
6890
7063
|
};
|
|
6891
7064
|
check();
|
|
6892
|
-
const args =
|
|
7065
|
+
const args = fields2(snapshot3(input, { values: MODEL_CONVERSION_DIAGNOSTIC_LIMITS.values, characters: MODEL_CONVERSION_DIAGNOSTIC_LIMITS.characters }));
|
|
6893
7066
|
only(args, ["outcome", "source", "report", "resources"]);
|
|
6894
|
-
if (!["completed", "unsupported", "failed"].includes(args.outcome))
|
|
7067
|
+
if (!["completed", "unsupported", "failed"].includes(args.outcome)) fail3();
|
|
6895
7068
|
const source = createModelProvenance(args.source);
|
|
6896
|
-
const reportInput =
|
|
7069
|
+
const reportInput = fields2(args.report);
|
|
6897
7070
|
const maximumIssues = MODEL_CONVERSION_DIAGNOSTIC_LIMITS.issues - (args.outcome === "completed" ? 0 : 1);
|
|
6898
|
-
if (!Array.isArray(reportInput.issues) || reportInput.issues.length > maximumIssues)
|
|
6899
|
-
if (!Array.isArray(args.resources) || args.resources.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.resources)
|
|
7071
|
+
if (!Array.isArray(reportInput.issues) || reportInput.issues.length > maximumIssues) fail3("limit-exceeded");
|
|
7072
|
+
if (!Array.isArray(args.resources) || args.resources.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.resources) fail3("limit-exceeded");
|
|
6900
7073
|
const resources = /* @__PURE__ */ new Map();
|
|
6901
7074
|
const identities = /* @__PURE__ */ new Map();
|
|
6902
7075
|
for (const value of args.resources) {
|
|
6903
|
-
const binding =
|
|
7076
|
+
const binding = fields2(value);
|
|
6904
7077
|
only(binding, ["path", "resource"]);
|
|
6905
|
-
if (typeof binding.path !== "string" || !binding.path.startsWith("/") || binding.path === "/" || resources.has(binding.path))
|
|
7078
|
+
if (typeof binding.path !== "string" || !binding.path.startsWith("/") || binding.path === "/" || resources.has(binding.path)) fail3();
|
|
6906
7079
|
const resource = createModelResourceRef(binding.resource);
|
|
6907
|
-
const
|
|
6908
|
-
if (identities.has(resource.uri) && identities.get(resource.uri) !==
|
|
6909
|
-
identities.set(resource.uri,
|
|
7080
|
+
const identity2 = JSON.stringify(resource);
|
|
7081
|
+
if (identities.has(resource.uri) && identities.get(resource.uri) !== identity2) fail3();
|
|
7082
|
+
identities.set(resource.uri, identity2);
|
|
6910
7083
|
resources.set(binding.path, resource);
|
|
6911
7084
|
}
|
|
6912
7085
|
check();
|
|
@@ -6920,7 +7093,7 @@ async function qualifyModelConversionDiagnostics(input, options = {}) {
|
|
|
6920
7093
|
const report = core.evaluateGpuModelDiagnostics(reportInput);
|
|
6921
7094
|
const diagnostics = report.diagnostics.map((diagnostic) => {
|
|
6922
7095
|
const resource = resources.get(diagnostic.path);
|
|
6923
|
-
if (diagnostic.path !== "/" && !resource)
|
|
7096
|
+
if (diagnostic.path !== "/" && !resource) fail3("unbound-resource");
|
|
6924
7097
|
const base = resource ? { resource } : {};
|
|
6925
7098
|
switch (diagnostic.kind) {
|
|
6926
7099
|
case "warning":
|
|
@@ -6952,7 +7125,194 @@ async function qualifyModelConversionDiagnostics(input, options = {}) {
|
|
|
6952
7125
|
});
|
|
6953
7126
|
} catch (error) {
|
|
6954
7127
|
if (error instanceof ModelConversionDiagnosticsError) throw error;
|
|
6955
|
-
return
|
|
7128
|
+
return fail3();
|
|
7129
|
+
} finally {
|
|
7130
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
7131
|
+
if (signal && onAbort) AbortSignal.prototype.removeEventListener.call(signal, "abort", onAbort);
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
|
|
7135
|
+
// src/model-resource-promotion.ts
|
|
7136
|
+
var MODEL_RESOURCE_PROMOTION_LIMITS = Object.freeze({ bindings: 1024, values: 262144, characters: 2097152, depth: 24, timeoutMs: 6e4 });
|
|
7137
|
+
var ModelResourcePromotionError = class extends TypeError {
|
|
7138
|
+
constructor(code) {
|
|
7139
|
+
super(`Invalid model resource promotion: ${code}`);
|
|
7140
|
+
this.code = code;
|
|
7141
|
+
this.name = "ModelResourcePromotionError";
|
|
7142
|
+
}
|
|
7143
|
+
code;
|
|
7144
|
+
};
|
|
7145
|
+
var qualified = /* @__PURE__ */ new WeakSet();
|
|
7146
|
+
function fail4(code = "invalid-input") {
|
|
7147
|
+
throw new ModelResourcePromotionError(code);
|
|
7148
|
+
}
|
|
7149
|
+
function fields3(value, allowed) {
|
|
7150
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail4();
|
|
7151
|
+
const keys = Reflect.ownKeys(value);
|
|
7152
|
+
if (keys.length > 1024) fail4("limit-exceeded");
|
|
7153
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
7154
|
+
for (const key of keys) {
|
|
7155
|
+
if (typeof key !== "string" || key.length > 128 || ["__proto__", "constructor", "prototype"].includes(key) || allowed && !allowed.includes(key)) fail4();
|
|
7156
|
+
const property = Object.getOwnPropertyDescriptor(value, key);
|
|
7157
|
+
if (!("value" in property) || !property.enumerable) fail4();
|
|
7158
|
+
result[key] = property.value;
|
|
7159
|
+
}
|
|
7160
|
+
return result;
|
|
7161
|
+
}
|
|
7162
|
+
function array2(value, maximum) {
|
|
7163
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) fail4();
|
|
7164
|
+
if (value.length > maximum) fail4("limit-exceeded");
|
|
7165
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail4();
|
|
7166
|
+
return Array.from({ length: value.length }, (_, i) => {
|
|
7167
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(i));
|
|
7168
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail4();
|
|
7169
|
+
return descriptor.value;
|
|
7170
|
+
});
|
|
7171
|
+
}
|
|
7172
|
+
function snapshot4(value, budget, depth = 0) {
|
|
7173
|
+
if (--budget.values < 0 || depth > MODEL_RESOURCE_PROMOTION_LIMITS.depth) fail4("limit-exceeded");
|
|
7174
|
+
if (value === null || value === void 0 || typeof value === "boolean") return value;
|
|
7175
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
7176
|
+
if (typeof value === "string") {
|
|
7177
|
+
budget.characters -= value.length;
|
|
7178
|
+
if (value.length > 2048 || budget.characters < 0) fail4("limit-exceeded");
|
|
7179
|
+
return value;
|
|
7180
|
+
}
|
|
7181
|
+
if (Array.isArray(value)) return Object.freeze(array2(value, MODEL_RESOURCE_MANIFEST_LIMITS.files).map((v) => snapshot4(v, budget, depth + 1)));
|
|
7182
|
+
const result = fields3(value);
|
|
7183
|
+
for (const key of Object.keys(result)) {
|
|
7184
|
+
budget.characters -= key.length;
|
|
7185
|
+
if (budget.characters < 0) fail4("limit-exceeded");
|
|
7186
|
+
result[key] = snapshot4(result[key], budget, depth + 1);
|
|
7187
|
+
}
|
|
7188
|
+
return Object.freeze(result);
|
|
7189
|
+
}
|
|
7190
|
+
var typedArrayPrototype2 = Object.getPrototypeOf(Uint8Array.prototype);
|
|
7191
|
+
var byteLengthGetter2 = Object.getOwnPropertyDescriptor(typedArrayPrototype2, "byteLength").get;
|
|
7192
|
+
var bufferGetter2 = Object.getOwnPropertyDescriptor(typedArrayPrototype2, "buffer").get;
|
|
7193
|
+
function copyFiles(value) {
|
|
7194
|
+
let total = 0;
|
|
7195
|
+
const files = array2(value, MODEL_RESOURCE_MANIFEST_LIMITS.files).map((v) => {
|
|
7196
|
+
const file = fields3(v, ["path", "bytes", "contentType"]);
|
|
7197
|
+
const bytes = file.bytes;
|
|
7198
|
+
if (typeof file.path !== "string" || typeof file.contentType !== "string" || !(bytes instanceof Uint8Array) || Object.getPrototypeOf(bytes) !== Uint8Array.prototype || !(bufferGetter2.call(bytes) instanceof ArrayBuffer)) fail4();
|
|
7199
|
+
const length = byteLengthGetter2.call(bytes);
|
|
7200
|
+
total += length;
|
|
7201
|
+
if (length > MODEL_RESOURCE_MANIFEST_LIMITS.fileBytes || total > MODEL_RESOURCE_MANIFEST_LIMITS.totalBytes) fail4("limit-exceeded");
|
|
7202
|
+
return { path: file.path, bytes, contentType: file.contentType };
|
|
7203
|
+
});
|
|
7204
|
+
return files.map((f) => ({ ...f, bytes: new Uint8Array(f.bytes) }));
|
|
7205
|
+
}
|
|
7206
|
+
var same = (a, b) => canonicalizeGpuContract3(a) === canonicalizeGpuContract3(b);
|
|
7207
|
+
var entry = (m) => m.files.find((f) => f.path === m.entrypoint);
|
|
7208
|
+
var identity = (m) => `${m.assetId}/${m.version}`;
|
|
7209
|
+
async function createModelResourcePromotionRecord(input, options = {}) {
|
|
7210
|
+
let timer;
|
|
7211
|
+
let signal;
|
|
7212
|
+
let onAbort;
|
|
7213
|
+
try {
|
|
7214
|
+
const started = performance.now();
|
|
7215
|
+
const controls = fields3(options, ["signal", "timeoutMs"]);
|
|
7216
|
+
const timeoutMs = controls.timeoutMs === void 0 ? 1e4 : controls.timeoutMs;
|
|
7217
|
+
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MODEL_RESOURCE_PROMOTION_LIMITS.timeoutMs) fail4();
|
|
7218
|
+
signal = controls.signal;
|
|
7219
|
+
const aborted = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted").get;
|
|
7220
|
+
const check = () => {
|
|
7221
|
+
if (signal !== void 0 && aborted.call(signal)) fail4("aborted");
|
|
7222
|
+
if (performance.now() - started >= timeoutMs) fail4("deadline-exceeded");
|
|
7223
|
+
};
|
|
7224
|
+
check();
|
|
7225
|
+
const root = fields3(input, ["promotion", "source", "output", "diagnostics", "resourceBindings", "candidate", "confirmation", "rollback"]);
|
|
7226
|
+
const budget = { values: MODEL_RESOURCE_PROMOTION_LIMITS.values, characters: MODEL_RESOURCE_PROMOTION_LIMITS.characters };
|
|
7227
|
+
const snap = (v) => snapshot4(v, budget);
|
|
7228
|
+
const promotionInput = snap(root.promotion);
|
|
7229
|
+
const sourceInput = fields3(root.source, ["manifest", "packageManifest", "files"]);
|
|
7230
|
+
const outputInput = fields3(root.output, ["manifest", "packageManifest", "files", "graph", "resources", "derived"]);
|
|
7231
|
+
const sourceMetadata = { manifest: snap(sourceInput.manifest), packageManifest: snap(sourceInput.packageManifest) };
|
|
7232
|
+
const outputMetadata = {
|
|
7233
|
+
manifest: snap(outputInput.manifest),
|
|
7234
|
+
packageManifest: snap(outputInput.packageManifest),
|
|
7235
|
+
graph: outputInput.graph,
|
|
7236
|
+
resources: snap(outputInput.resources),
|
|
7237
|
+
derived: snap(outputInput.derived)
|
|
7238
|
+
};
|
|
7239
|
+
const rawDiagnostics = snap(root.diagnostics);
|
|
7240
|
+
const candidate = createModelCandidate(snap(root.candidate));
|
|
7241
|
+
const confirmation = createModelCandidateConfirmation(snap(root.confirmation), candidate, candidate.resolutionId);
|
|
7242
|
+
const bindings = array2(snap(root.resourceBindings), MODEL_RESOURCE_PROMOTION_LIMITS.bindings).map((v) => {
|
|
7243
|
+
const b = fields3(v, ["resource", "package", "filePath"]);
|
|
7244
|
+
if (b.package !== "source" && b.package !== "output" || typeof b.filePath !== "string") fail4();
|
|
7245
|
+
return Object.freeze({ resource: createModelResourceRef(b.resource), package: b.package, filePath: b.filePath });
|
|
7246
|
+
}).sort((a, b) => a.resource.uri < b.resource.uri ? -1 : a.resource.uri > b.resource.uri ? 1 : 0);
|
|
7247
|
+
const history = root.rollback === void 0 ? void 0 : fields3(root.rollback, ["prior", "current"]);
|
|
7248
|
+
if (history && (!qualified.has(history.prior) || !qualified.has(history.current))) fail4("rollback-mismatch");
|
|
7249
|
+
const prior = history?.prior;
|
|
7250
|
+
const current = history?.current;
|
|
7251
|
+
const sourceFiles = copyFiles(sourceInput.files);
|
|
7252
|
+
const outputFiles = copyFiles(outputInput.files);
|
|
7253
|
+
check();
|
|
7254
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
7255
|
+
timer = setTimeout(() => reject(new ModelResourcePromotionError("deadline-exceeded")), Math.max(1, timeoutMs - (performance.now() - started)));
|
|
7256
|
+
onAbort = () => reject(new ModelResourcePromotionError("aborted"));
|
|
7257
|
+
if (signal) AbortSignal.prototype.addEventListener.call(signal, "abort", onAbort, { once: true });
|
|
7258
|
+
});
|
|
7259
|
+
const work = async () => {
|
|
7260
|
+
const source = await qualifyModelResourceManifest({ ...sourceMetadata, files: sourceFiles, signal, timeoutMs });
|
|
7261
|
+
check();
|
|
7262
|
+
const output = await qualifyModelTextureResources({ ...outputMetadata, files: outputFiles }, { signal, timeoutMs });
|
|
7263
|
+
check();
|
|
7264
|
+
const diagnostics = await qualifyModelConversionDiagnostics(rawDiagnostics, { signal, timeoutMs });
|
|
7265
|
+
check();
|
|
7266
|
+
assertImmutableAssetVersion(source.version);
|
|
7267
|
+
assertImmutableAssetVersion(output.manifest.version);
|
|
7268
|
+
if (identity(source) === identity(output.manifest) || !same(output.manifest, createModelAssetManifest({ ...promotionInput.manifest, files: [...promotionInput.manifest.files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0) })) || output.manifest.assetKind !== "model") fail4("package-mismatch");
|
|
7269
|
+
const converter = candidate.processingManifest.converter;
|
|
7270
|
+
if (!diagnostics.technicalAccepted || !same(diagnostics.source, candidate.provenance) || diagnostics.source.contentHash !== entry(source).sha256 || output.source.sourceContentHash !== entry(source).sha256 || converter.sourceContentHash !== entry(source).sha256 || converter.outputContentHash !== entry(output.manifest).sha256 || converter.id !== output.source.converterId || converter.version !== output.source.converterVersion || converter.sourceFormat !== output.source.sourceFormat || !same(diagnostics.converter.diagnostics, converter.diagnostics) || !same(diagnostics.converter.losses, converter.losses)) fail4("evidence-mismatch");
|
|
7271
|
+
const byUri = /* @__PURE__ */ new Map();
|
|
7272
|
+
for (const binding of bindings) {
|
|
7273
|
+
const file = (binding.package === "source" ? source : output.manifest).files.find((f) => f.path === binding.filePath);
|
|
7274
|
+
if (!file || byUri.has(binding.resource.uri) || file.sha256 !== binding.resource.sha256 || file.byteLength !== binding.resource.byteLength || file.contentType !== binding.resource.contentType) fail4("evidence-mismatch");
|
|
7275
|
+
byUri.set(binding.resource.uri, binding);
|
|
7276
|
+
}
|
|
7277
|
+
const requireResource = (resource, outputOnly = false) => {
|
|
7278
|
+
const binding = byUri.get(resource.uri);
|
|
7279
|
+
if (!binding || !same(binding.resource, resource) || outputOnly && binding.package !== "output") fail4("evidence-mismatch");
|
|
7280
|
+
return binding;
|
|
7281
|
+
};
|
|
7282
|
+
for (const d of diagnostics.diagnostics) if (d.resource) requireResource(d.resource);
|
|
7283
|
+
for (const lod of candidate.processingManifest.lods) requireResource(lod.resource, true);
|
|
7284
|
+
if (candidate.processingManifest.collision.resource) requireResource(candidate.processingManifest.collision.resource, true);
|
|
7285
|
+
for (const f of candidate.processingManifest.fidelityEvidence) if (f.evidenceResource) requireResource(f.evidenceResource, true);
|
|
7286
|
+
if (requireResource(candidate.processingManifest.lods[0].resource, true).filePath !== output.manifest.entrypoint) fail4("evidence-mismatch");
|
|
7287
|
+
if (candidate.assetRef.disposition === "existing" && (candidate.assetRef.asset.assetId !== output.manifest.assetId || candidate.assetRef.asset.version !== output.manifest.version)) fail4("evidence-mismatch");
|
|
7288
|
+
if (promotionInput.sourceAdapter !== output.manifest.sourceAdapter || promotionInput.reviewReport.passed !== true || promotionInput.reviewReport.findings.some((f) => f.severity === "blocking")) fail4("gate-blocked");
|
|
7289
|
+
const expectedSuffix = `/${output.manifest.assetId}/${output.manifest.version}/${output.manifest.entrypoint}`;
|
|
7290
|
+
const runtimeUri = new URL(promotionInput.runtimeManifestUri);
|
|
7291
|
+
if (!runtimeUri.pathname.endsWith(expectedSuffix)) fail4("evidence-mismatch");
|
|
7292
|
+
const promotion = await createGpuAssetPromotionRecord(promotionInput, new Map(outputFiles.map((f) => [f.path, f.bytes])));
|
|
7293
|
+
check();
|
|
7294
|
+
const closure = { contractVersion: 1, source, output, diagnostics, resourceBindings: Object.freeze(bindings), candidate, confirmation };
|
|
7295
|
+
const closureHash = await computeSha2562(new TextEncoder().encode(canonicalizeGpuContract3({ contractVersion: 1, source, output, diagnostics, resourceBindings: bindings })));
|
|
7296
|
+
check();
|
|
7297
|
+
let rollback;
|
|
7298
|
+
if (promotion.outcome === "rolled-back") {
|
|
7299
|
+
if (!prior || !current || prior.promotion.assetId !== promotion.assetId || current.promotion.assetId !== promotion.assetId || prior.promotion.runtimeChannel !== promotion.runtimeChannel || current.promotion.runtimeChannel !== promotion.runtimeChannel || promotion.version !== prior.promotion.version || promotion.version === current.promotion.version || promotion.rollbackOfVersion !== current.promotion.version || closureHash !== prior.closureHash || !same(promotion.manifest, prior.promotion.manifest) || promotion.runtimeManifestUri !== prior.promotion.runtimeManifestUri) fail4("rollback-mismatch");
|
|
7300
|
+
rollback = Object.freeze({ fromVersion: current.promotion.version, fromClosureHash: current.closureHash, toVersion: prior.promotion.version, toClosureHash: prior.closureHash });
|
|
7301
|
+
} else if (history || promotion.rollbackOfVersion !== void 0) fail4("rollback-mismatch");
|
|
7302
|
+
const result = Object.freeze({ ...closure, promotion, closureHash, ...rollback ? { rollback } : {} });
|
|
7303
|
+
qualified.add(result);
|
|
7304
|
+
return result;
|
|
7305
|
+
};
|
|
7306
|
+
return await Promise.race([work(), interrupted]);
|
|
7307
|
+
} catch (error) {
|
|
7308
|
+
let code;
|
|
7309
|
+
try {
|
|
7310
|
+
code = Object.getOwnPropertyDescriptor(error, "code")?.value;
|
|
7311
|
+
} catch {
|
|
7312
|
+
return fail4();
|
|
7313
|
+
}
|
|
7314
|
+
if (["invalid-input", "limit-exceeded", "package-mismatch", "evidence-mismatch", "gate-blocked", "rollback-mismatch", "aborted", "deadline-exceeded"].includes(code)) return fail4(code);
|
|
7315
|
+
return fail4();
|
|
6956
7316
|
} finally {
|
|
6957
7317
|
if (timer !== void 0) clearTimeout(timer);
|
|
6958
7318
|
if (signal && onAbort) AbortSignal.prototype.removeEventListener.call(signal, "abort", onAbort);
|
|
@@ -7360,7 +7720,7 @@ async function validateGpuAssetFiles(input) {
|
|
|
7360
7720
|
if (bytes.byteLength !== file.byteLength) {
|
|
7361
7721
|
throw new Error(`GPU asset file ${file.path} byte length differs from its descriptor.`);
|
|
7362
7722
|
}
|
|
7363
|
-
if (await
|
|
7723
|
+
if (await computeSha2563(bytes) !== file.sha256) {
|
|
7364
7724
|
throw new Error(`GPU asset file ${file.path} digest differs from its descriptor.`);
|
|
7365
7725
|
}
|
|
7366
7726
|
if (file.contentType === ASSET_JSON_CONTENT_TYPE) {
|
|
@@ -7380,7 +7740,7 @@ async function validateGpuAssetFiles(input) {
|
|
|
7380
7740
|
const expectedDomainManifest = domainManifestForAsset(manifest);
|
|
7381
7741
|
if (expectedDomainManifest !== null) {
|
|
7382
7742
|
const parsedEntrypoint = parseJsonBytes(entrypointBytes, "GPU asset manifest entrypoint");
|
|
7383
|
-
if (
|
|
7743
|
+
if (canonicalizeGpuContract4(parsedEntrypoint) !== canonicalizeGpuContract4(expectedDomainManifest)) {
|
|
7384
7744
|
throw new Error("GPU asset entrypoint bytes differ from the validated domain manifest.");
|
|
7385
7745
|
}
|
|
7386
7746
|
} else if (manifest.assetKind === "shader-validation-evidence") {
|
|
@@ -7627,7 +7987,7 @@ function assertEvidenceEntrypointIdentity(value, ref2) {
|
|
|
7627
7987
|
function assertCanonicalJsonBytes(bytes, fieldName) {
|
|
7628
7988
|
const value = parseJsonBytes(bytes, fieldName);
|
|
7629
7989
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
7630
|
-
if (
|
|
7990
|
+
if (canonicalizeGpuContract4(value) !== text) {
|
|
7631
7991
|
throw new Error(`${fieldName} must use canonical JSON serialization.`);
|
|
7632
7992
|
}
|
|
7633
7993
|
}
|
|
@@ -7637,10 +7997,10 @@ async function bindDomainManifestBytes(bytes, manifest, fieldName) {
|
|
|
7637
7997
|
}
|
|
7638
7998
|
assertCanonicalJsonBytes(bytes, `${fieldName} manifest`);
|
|
7639
7999
|
const parsed = parseJsonBytes(bytes, `${fieldName} manifest`);
|
|
7640
|
-
if (
|
|
8000
|
+
if (canonicalizeGpuContract4(parsed) !== canonicalizeGpuContract4(manifest)) {
|
|
7641
8001
|
throw new Error(`${fieldName} manifest bytes differ from the supplied manifest.`);
|
|
7642
8002
|
}
|
|
7643
|
-
return
|
|
8003
|
+
return computeSha2563(bytes);
|
|
7644
8004
|
}
|
|
7645
8005
|
function assertRoleCount(manifest, role, expectedCount) {
|
|
7646
8006
|
if (manifest.files.filter((file) => file.role === role).length !== expectedCount) {
|
|
@@ -7761,10 +8121,14 @@ export {
|
|
|
7761
8121
|
MODEL_RESOLUTION_V2_JSON_SCHEMAS,
|
|
7762
8122
|
MODEL_RESOLUTION_V2_STATES,
|
|
7763
8123
|
MODEL_RESOURCE_MANIFEST_LIMITS,
|
|
8124
|
+
MODEL_RESOURCE_PROMOTION_LIMITS,
|
|
7764
8125
|
MODEL_RIGHTS_STATUSES,
|
|
8126
|
+
MODEL_TEXTURE_RESOURCE_LIMITS,
|
|
7765
8127
|
MODEL_TEXT_ONLY_ASSURANCE_CEILING_REASON_CODE,
|
|
7766
8128
|
ModelConversionDiagnosticsError,
|
|
7767
8129
|
ModelResourceManifestError,
|
|
8130
|
+
ModelResourcePromotionError,
|
|
8131
|
+
ModelTextureResourceError,
|
|
7768
8132
|
PVOX_ASSEMBLY_CLOSURE_HASH_DOMAIN,
|
|
7769
8133
|
PVOX_ASSET_MANIFEST_VERSION,
|
|
7770
8134
|
PVOX_BINARY_CLOSURE_HASH_DOMAIN,
|
|
@@ -7899,6 +8263,7 @@ export {
|
|
|
7899
8263
|
createModelRequestSpecV2,
|
|
7900
8264
|
createModelResolution,
|
|
7901
8265
|
createModelResolutionV2,
|
|
8266
|
+
createModelResourcePromotionRecord,
|
|
7902
8267
|
createModelResourceRef,
|
|
7903
8268
|
createModelRightsAssessment,
|
|
7904
8269
|
createModelTechnicalProfile,
|
|
@@ -7957,6 +8322,7 @@ export {
|
|
|
7957
8322
|
projectPvoxNamedJsonClosureV1,
|
|
7958
8323
|
qualifyModelConversionDiagnostics,
|
|
7959
8324
|
qualifyModelResourceManifest,
|
|
8325
|
+
qualifyModelTextureResources,
|
|
7960
8326
|
toLegacyModelResolutionState,
|
|
7961
8327
|
validateGpuAssetFiles
|
|
7962
8328
|
};
|