@plasius/asset-contracts 0.7.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 +18 -0
- package/README.md +49 -3
- package/dist/index.cjs +269 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -2
- package/dist/index.d.ts +68 -2
- package/dist/index.js +251 -58
- package/dist/index.js.map +1 -1
- package/docs/adrs/adr-0012-model-resource-promotion-closure.md +92 -0
- package/docs/adrs/index.md +1 -0
- package/docs/tdrs/index.md +1 -0
- package/docs/tdrs/tdr-0007-model-resource-promotion-closure.md +63 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/model-resource-promotion.ts +219 -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,
|
|
@@ -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) => {
|
|
@@ -6925,12 +6925,12 @@ async function qualifyModelTextureResources(input, options = {}) {
|
|
|
6925
6925
|
]);
|
|
6926
6926
|
check();
|
|
6927
6927
|
const files = new Map(manifest.files.map((file) => [file.path, file]));
|
|
6928
|
-
const roles = new Map(inventory.entries.map((
|
|
6929
|
-
for (const
|
|
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
6930
|
const resources = new Map(graph.nodes.filter((node) => node.kind === "resource").map((node) => [node.id, node.value]));
|
|
6931
6931
|
if (bindings.length !== resources.size || new Set(bindings.map((b) => b.resourceId)).size !== bindings.length) fail2("resource-mismatch");
|
|
6932
6932
|
const resourceFiles = /* @__PURE__ */ new Set();
|
|
6933
|
-
const
|
|
6933
|
+
const qualified2 = bindings.map((binding) => {
|
|
6934
6934
|
const value = resources.get(binding.resourceId);
|
|
6935
6935
|
const file = files.get(binding.filePath);
|
|
6936
6936
|
if (!value || !file) fail2("resource-mismatch");
|
|
@@ -6963,7 +6963,7 @@ async function qualifyModelTextureResources(input, options = {}) {
|
|
|
6963
6963
|
contractVersion: 1,
|
|
6964
6964
|
manifest,
|
|
6965
6965
|
source: source.value,
|
|
6966
|
-
resources: Object.freeze(
|
|
6966
|
+
resources: Object.freeze(qualified2.sort((a, b) => compare(a.resourceId, b.resourceId))),
|
|
6967
6967
|
materials: Object.freeze(materials),
|
|
6968
6968
|
textures: Object.freeze(textures),
|
|
6969
6969
|
derived: Object.freeze(derived.sort((a, b) => compare(a.filePath, b.filePath)))
|
|
@@ -6982,6 +6982,9 @@ function tokenPath(value) {
|
|
|
6982
6982
|
return value;
|
|
6983
6983
|
}
|
|
6984
6984
|
|
|
6985
|
+
// src/model-resource-promotion.ts
|
|
6986
|
+
import { canonicalizeGpuContract as canonicalizeGpuContract3, computeSha256 as computeSha2562 } from "@plasius/gpu-shader";
|
|
6987
|
+
|
|
6985
6988
|
// src/model-conversion-diagnostics.ts
|
|
6986
6989
|
var MODEL_CONVERSION_DIAGNOSTIC_LIMITS = Object.freeze({ issues: 100, resources: 100, values: 16384, depth: 12, string: 2048, characters: 262144, timeoutMs: 6e4 });
|
|
6987
6990
|
var ModelConversionDiagnosticsError = class extends TypeError {
|
|
@@ -7074,9 +7077,9 @@ async function qualifyModelConversionDiagnostics(input, options = {}) {
|
|
|
7074
7077
|
only(binding, ["path", "resource"]);
|
|
7075
7078
|
if (typeof binding.path !== "string" || !binding.path.startsWith("/") || binding.path === "/" || resources.has(binding.path)) fail3();
|
|
7076
7079
|
const resource = createModelResourceRef(binding.resource);
|
|
7077
|
-
const
|
|
7078
|
-
if (identities.has(resource.uri) && identities.get(resource.uri) !==
|
|
7079
|
-
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);
|
|
7080
7083
|
resources.set(binding.path, resource);
|
|
7081
7084
|
}
|
|
7082
7085
|
check();
|
|
@@ -7129,6 +7132,193 @@ async function qualifyModelConversionDiagnostics(input, options = {}) {
|
|
|
7129
7132
|
}
|
|
7130
7133
|
}
|
|
7131
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();
|
|
7316
|
+
} finally {
|
|
7317
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
7318
|
+
if (signal && onAbort) AbortSignal.prototype.removeEventListener.call(signal, "abort", onAbort);
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
|
|
7132
7322
|
// src/index.ts
|
|
7133
7323
|
var ASSET_CONTRACTS_PACKAGE = "@plasius/asset-contracts";
|
|
7134
7324
|
var UNIFIED_ASSET_PIPELINE_FEATURE_FLAG_ID = "asset.pipeline.unified-ai-assets.enabled";
|
|
@@ -7530,7 +7720,7 @@ async function validateGpuAssetFiles(input) {
|
|
|
7530
7720
|
if (bytes.byteLength !== file.byteLength) {
|
|
7531
7721
|
throw new Error(`GPU asset file ${file.path} byte length differs from its descriptor.`);
|
|
7532
7722
|
}
|
|
7533
|
-
if (await
|
|
7723
|
+
if (await computeSha2563(bytes) !== file.sha256) {
|
|
7534
7724
|
throw new Error(`GPU asset file ${file.path} digest differs from its descriptor.`);
|
|
7535
7725
|
}
|
|
7536
7726
|
if (file.contentType === ASSET_JSON_CONTENT_TYPE) {
|
|
@@ -7550,7 +7740,7 @@ async function validateGpuAssetFiles(input) {
|
|
|
7550
7740
|
const expectedDomainManifest = domainManifestForAsset(manifest);
|
|
7551
7741
|
if (expectedDomainManifest !== null) {
|
|
7552
7742
|
const parsedEntrypoint = parseJsonBytes(entrypointBytes, "GPU asset manifest entrypoint");
|
|
7553
|
-
if (
|
|
7743
|
+
if (canonicalizeGpuContract4(parsedEntrypoint) !== canonicalizeGpuContract4(expectedDomainManifest)) {
|
|
7554
7744
|
throw new Error("GPU asset entrypoint bytes differ from the validated domain manifest.");
|
|
7555
7745
|
}
|
|
7556
7746
|
} else if (manifest.assetKind === "shader-validation-evidence") {
|
|
@@ -7797,7 +7987,7 @@ function assertEvidenceEntrypointIdentity(value, ref2) {
|
|
|
7797
7987
|
function assertCanonicalJsonBytes(bytes, fieldName) {
|
|
7798
7988
|
const value = parseJsonBytes(bytes, fieldName);
|
|
7799
7989
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
7800
|
-
if (
|
|
7990
|
+
if (canonicalizeGpuContract4(value) !== text) {
|
|
7801
7991
|
throw new Error(`${fieldName} must use canonical JSON serialization.`);
|
|
7802
7992
|
}
|
|
7803
7993
|
}
|
|
@@ -7807,10 +7997,10 @@ async function bindDomainManifestBytes(bytes, manifest, fieldName) {
|
|
|
7807
7997
|
}
|
|
7808
7998
|
assertCanonicalJsonBytes(bytes, `${fieldName} manifest`);
|
|
7809
7999
|
const parsed = parseJsonBytes(bytes, `${fieldName} manifest`);
|
|
7810
|
-
if (
|
|
8000
|
+
if (canonicalizeGpuContract4(parsed) !== canonicalizeGpuContract4(manifest)) {
|
|
7811
8001
|
throw new Error(`${fieldName} manifest bytes differ from the supplied manifest.`);
|
|
7812
8002
|
}
|
|
7813
|
-
return
|
|
8003
|
+
return computeSha2563(bytes);
|
|
7814
8004
|
}
|
|
7815
8005
|
function assertRoleCount(manifest, role, expectedCount) {
|
|
7816
8006
|
if (manifest.files.filter((file) => file.role === role).length !== expectedCount) {
|
|
@@ -7931,11 +8121,13 @@ export {
|
|
|
7931
8121
|
MODEL_RESOLUTION_V2_JSON_SCHEMAS,
|
|
7932
8122
|
MODEL_RESOLUTION_V2_STATES,
|
|
7933
8123
|
MODEL_RESOURCE_MANIFEST_LIMITS,
|
|
8124
|
+
MODEL_RESOURCE_PROMOTION_LIMITS,
|
|
7934
8125
|
MODEL_RIGHTS_STATUSES,
|
|
7935
8126
|
MODEL_TEXTURE_RESOURCE_LIMITS,
|
|
7936
8127
|
MODEL_TEXT_ONLY_ASSURANCE_CEILING_REASON_CODE,
|
|
7937
8128
|
ModelConversionDiagnosticsError,
|
|
7938
8129
|
ModelResourceManifestError,
|
|
8130
|
+
ModelResourcePromotionError,
|
|
7939
8131
|
ModelTextureResourceError,
|
|
7940
8132
|
PVOX_ASSEMBLY_CLOSURE_HASH_DOMAIN,
|
|
7941
8133
|
PVOX_ASSET_MANIFEST_VERSION,
|
|
@@ -8071,6 +8263,7 @@ export {
|
|
|
8071
8263
|
createModelRequestSpecV2,
|
|
8072
8264
|
createModelResolution,
|
|
8073
8265
|
createModelResolutionV2,
|
|
8266
|
+
createModelResourcePromotionRecord,
|
|
8074
8267
|
createModelResourceRef,
|
|
8075
8268
|
createModelRightsAssessment,
|
|
8076
8269
|
createModelTechnicalProfile,
|