@plasius/asset-contracts 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +130 -1
- package/dist/index.cjs +345 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -2
- package/dist/index.d.ts +127 -2
- package/dist/index.js +329 -6
- package/dist/index.js.map +1 -1
- package/docs/adrs/adr-0010-public-conversion-diagnostics.md +61 -0
- package/docs/adrs/adr-0011-reloadable-texture-resource-roles.md +92 -0
- package/docs/adrs/index.md +2 -0
- package/docs/tdrs/index.md +2 -0
- package/docs/tdrs/tdr-0005-public-conversion-diagnostics.md +51 -0
- package/docs/tdrs/tdr-0006-reloadable-texture-resource-roles.md +91 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/model-conversion-diagnostics.ts +185 -0
- package/src/model-texture-resources.ts +215 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# ADR 0010: Public Conversion Diagnostics
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-10-24
|
|
5
|
+
- Tracking: asset-contracts Task49; site Feature1153 / Epic1147
|
|
6
|
+
|
|
7
|
+
## Context and decision
|
|
8
|
+
|
|
9
|
+
Existing ModelConverterDiagnostic/ModelConversionLoss and signed processing,
|
|
10
|
+
fidelity, rights, human review and promotion contracts already cover their
|
|
11
|
+
boundaries. Model-core0.7.0 owns canonical strict/tolerant/forensic policy,
|
|
12
|
+
applied repairs, unsupported data and loss consent. Its private diagnostics may
|
|
13
|
+
contain original values, rawSource, private paths and free-form adapter text.
|
|
14
|
+
They cannot be forwarded as public contract evidence.
|
|
15
|
+
|
|
16
|
+
Add qualifyModelConversionDiagnostics as an opt-in async boundary. Lazy import
|
|
17
|
+
the existing exact published core dependency and reuse its evaluator; do not
|
|
18
|
+
implement another acceptance policy or format parser. Inert bounded snapshots
|
|
19
|
+
precede the first await. Project fixed category codes/messages, exact public
|
|
20
|
+
resource bindings and existing ModelProvenance. Root diagnostics use source
|
|
21
|
+
identity; unmatched non-root paths and contradictory URI identities reject.
|
|
22
|
+
Only recognized completed repairs expose core-owned rule IDs. Discard adapter
|
|
23
|
+
codes/text/private paths, original values and forensic snapshots. Unknown
|
|
24
|
+
adapter codes become fixed category codes; unknown repair rules still block.
|
|
25
|
+
|
|
26
|
+
Explicit failure takes precedence, then unsupported, then canonical rejection,
|
|
27
|
+
then warning/clean success. Denied loss remains blocking in the legacy converter
|
|
28
|
+
loss array. Detailed output and existing-shape converter arrays are frozen.
|
|
29
|
+
Technical acceptance is not approval. No existing gate or factory is weakened.
|
|
30
|
+
|
|
31
|
+
## Alternatives, costs and limits
|
|
32
|
+
|
|
33
|
+
Regex redaction cannot reliably scrub arbitrary provider text. Tightening
|
|
34
|
+
legacy factories would break staged consumers. Copying the canonical evaluator
|
|
35
|
+
would create policy drift. Requiring eager core loading would add avoidable
|
|
36
|
+
first-load work. Lazy import is asynchronous and may load a bundler chunk; module
|
|
37
|
+
loading cannot be cancelled, but the wait is bounded. Bounded synchronous work
|
|
38
|
+
checks deadlines before/after and cannot be preempted. Resource/provenance
|
|
39
|
+
binding is a trusted-caller assertion; byte evidence and promotion remain
|
|
40
|
+
separate. No dependency versions change. Tasks12/23/50/51 retain animation,
|
|
41
|
+
shader ABI, role and promotion-closure scope respectively.
|
|
42
|
+
|
|
43
|
+
## Rollout, NFR and release
|
|
44
|
+
|
|
45
|
+
Backend evaluator gpu.model.conversion.enabled controls consumer adoption;
|
|
46
|
+
disabled/rollback callers retain legacy contracts. No service lookup or flag
|
|
47
|
+
mutation in this library, no UI entitlement capability. Test both branches and
|
|
48
|
+
rollback, malformed metadata, stable codes/serialization, privacy canaries,
|
|
49
|
+
resource identity, mutation, limits, deadline/cancellation and real processing
|
|
50
|
+
manifest composition. Existing independent gate tests remain active.
|
|
51
|
+
|
|
52
|
+
Security/privacy use allowlisted output and value-free errors. Fixed work and
|
|
53
|
+
input budgets, no retries/I/O/global state address reliability/performance and
|
|
54
|
+
scalability; immutable typed JSON and reused factories address maintenance and
|
|
55
|
+
testability. Fixed codes support safe observability without logging. UI/SEO and
|
|
56
|
+
durable storage controls are N/A. Require Node24 frozen install, full tests and
|
|
57
|
+
changed-source LCOV>=80%, types/lint/build, full/runtime audits, tree/privacy/pack
|
|
58
|
+
and packed/public ESM/CJS/types/browser consumers. Update README/Unreleased/TDR.
|
|
59
|
+
Release additive minor through PR, exact-main push CI, existing hosted
|
|
60
|
+
cd.yml/main/production OIDC, registry bytes/provenance and consumer checks.
|
|
61
|
+
No site deployment is authorized or required by this package-only Task.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# ADR 0011: Reloadable Texture and Resource Roles
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-10-24
|
|
5
|
+
- Tracking: asset-contracts Task 50; site Feature 1153 / Epic 1147
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Released AssetFileRole already defines model, binary, texture and metadata.
|
|
10
|
+
Task 48 qualifies exact file inventory/bytes, and model-core 0.7.0 owns verified
|
|
11
|
+
resources, material-to-texture references and authored interpretation metadata.
|
|
12
|
+
Neither boundary maps the canonical inventory to storage roles or records
|
|
13
|
+
separate GPU upload/atlas exports while retaining original resource identities.
|
|
14
|
+
Legacy descriptor construction must remain compatible.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
Add opt-in async qualifyModelTextureResources. Compose the existing
|
|
19
|
+
qualifyModelResourceManifest and accept only a privately branded model-core
|
|
20
|
+
GpuModelResourceGraph. Reuse canonical materials/textures, including samplers,
|
|
21
|
+
color spaces, usages, transforms, embedding policy and authored sourceMetadata.
|
|
22
|
+
Never reimplement the canonical schemas or parse a model format here.
|
|
23
|
+
|
|
24
|
+
Use the matching module-format core guard through a normal named import. A
|
|
25
|
+
new graph is already immutable/verified. Dynamically importing ESM from the
|
|
26
|
+
CJS build would select a different private WeakSet and reject legitimate CJS
|
|
27
|
+
require-created graphs. This correctness requirement makes lazy loading this
|
|
28
|
+
guard inappropriate. Core remains external; bundlers can remove unused exports
|
|
29
|
+
with sideEffects=false. Existing diagnostic evaluation retains its dynamic
|
|
30
|
+
import. Consumers must produce graphs using the same installed core instance
|
|
31
|
+
and module format as this package (ESM import with ESM, require with CJS).
|
|
32
|
+
|
|
33
|
+
Map canonical mesh entries to model/binary, material/animation to JSON metadata,
|
|
34
|
+
texture to supported image texture, and auxiliary to binary or JSON metadata.
|
|
35
|
+
Existing role values are unchanged. Explicit source bindings match every graph
|
|
36
|
+
resource exactly once. External files match graph path/hash/length/MIME;
|
|
37
|
+
embedded resources retain independent identity and name their owning model
|
|
38
|
+
entrypoint. Do not invent separate embedded archive entries or replace source
|
|
39
|
+
hashes with entrypoint hashes. Preserve original conversion provenance.
|
|
40
|
+
|
|
41
|
+
A derived record has gpu-upload or texture-atlas semantics, a distinct exported
|
|
42
|
+
file path and unique original sourceResourceIds. Uploads use auxiliary/binary
|
|
43
|
+
buffer files; atlases use texture/image files and image sources. Reject missing
|
|
44
|
+
sources, output collisions, source overwrite, unknown roles and incompatible
|
|
45
|
+
MIME declarations. Output is frozen deterministic version-1 JSON with no Blob
|
|
46
|
+
or renderer allocation. Canonical material/texture arrays retain their order.
|
|
47
|
+
|
|
48
|
+
## Limits and trust
|
|
49
|
+
|
|
50
|
+
Snapshot caller metadata and exact bytes before awaiting. Reuse Task 48 path,
|
|
51
|
+
collision, byte/hash and inventory gates. Apply fixed resource/derived/graph,
|
|
52
|
+
metadata value/character/depth and deadline limits. Honor native cancellation;
|
|
53
|
+
use no I/O, retries, mutable registry, arbitrary callbacks or logging. Hashing
|
|
54
|
+
cannot be interrupted inside Web Crypto; checks prevent successful late return.
|
|
55
|
+
Errors expose fixed codes only. Synchronous bounded work is checked before and
|
|
56
|
+
after; it cannot be preempted by a JavaScript timer.
|
|
57
|
+
|
|
58
|
+
Authored sourceMetadata, material names/extensions and original texture paths
|
|
59
|
+
are deliberately preserved trusted package content. The output is NOT a
|
|
60
|
+
public-safe diagnostics response. Adapters must minimize/sanitize authored
|
|
61
|
+
metadata before public distribution; use Task 49's separate diagnostic
|
|
62
|
+
projection for public technical messages. Core-brand validation cannot be
|
|
63
|
+
replaced by shape checks or serialization; reloaded canonical payloads must be
|
|
64
|
+
verified again by core before a new graph is created.
|
|
65
|
+
|
|
66
|
+
Embedded ownership, internal model references and derivation algorithms remain
|
|
67
|
+
trusted adapter assertions. Exact byte qualification does not prove semantic
|
|
68
|
+
format validity, inspect derived image signatures, verify a GPU upload layout,
|
|
69
|
+
attest an atlas algorithm, or authorize promotion. Tasks 12/23/51 retain
|
|
70
|
+
animation metadata, shader ABI and promotion closure. No existing approval gate
|
|
71
|
+
changes. No dependency/version/peer/override changes are required.
|
|
72
|
+
|
|
73
|
+
## Alternatives, rollout and delivery
|
|
74
|
+
|
|
75
|
+
Adding storage role enums would duplicate existing roles and break closed
|
|
76
|
+
consumers. Tightening legacy factories would break staged construction.
|
|
77
|
+
Flattening derived output over authored textures would lose reload fidelity.
|
|
78
|
+
Copying core schemas or accepting a structural graph would discard verification.
|
|
79
|
+
|
|
80
|
+
The backend evaluator for gpu.model.conversion.enabled controls consumer
|
|
81
|
+
adoption. Enabled callers opt into this qualifier; disabled/rollback callers
|
|
82
|
+
retain existing factories. The library performs no flag lookup/mutation. No UI
|
|
83
|
+
or entitlement capability applies. Test both branches and rollback.
|
|
84
|
+
|
|
85
|
+
Require requirements-first round-trip/contract tests, malformed input/role/MIME
|
|
86
|
+
and missing/duplicate identity cases, deterministic/frozen output, cancellation,
|
|
87
|
+
deadline and bounded metadata tests. Run Node 24 frozen install, full tests and
|
|
88
|
+
privacy, coverage >=80% and changed-source LCOV, lint/types/build, audits/tree,
|
|
89
|
+
privacy/pack and packed/public ESM/CJS/types/browser consumers. Release additive
|
|
90
|
+
minor through source PR, exact-main push CI and existing two-phase
|
|
91
|
+
cd.yml/main/production hosted OIDC governance (ADR 0007), then verify registry
|
|
92
|
+
integrity/provenance. Site adoption/deployment remains separate.
|
package/docs/adrs/index.md
CHANGED
|
@@ -9,3 +9,5 @@
|
|
|
9
9
|
- [ADR 0007: Hosted OIDC Package Publication](./adr-0007-hosted-oidc-package-publication.md)
|
|
10
10
|
- [ADR 0008: Package-Owned Issue Tracking](./adr-0008-package-owned-issue-tracking.md)
|
|
11
11
|
- [ADR 0009: Additive Model Resource Manifest Qualification](./adr-0009-model-resource-manifest-qualification.md)
|
|
12
|
+
- [ADR 0010: Public Conversion Diagnostics](./adr-0010-public-conversion-diagnostics.md)
|
|
13
|
+
- [ADR 0011: Reloadable Texture and Resource Roles](./adr-0011-reloadable-texture-resource-roles.md)
|
package/docs/tdrs/index.md
CHANGED
|
@@ -4,3 +4,5 @@
|
|
|
4
4
|
- [TDR 0002: Exact-Version Validation Routing](./tdr-0002-exact-version-validation-routing.md)
|
|
5
5
|
- [TDR 0003: PVOX Validation and Confirmation Binding](./tdr-0003-pvox-validation-and-confirmation-binding.md)
|
|
6
6
|
- [TDR 0004: Model Resource Manifest Qualification](./tdr-0004-model-resource-manifest-qualification.md)
|
|
7
|
+
- [TDR 0005: Public Conversion Diagnostics](./tdr-0005-public-conversion-diagnostics.md)
|
|
8
|
+
- [TDR 0006: Reloadable Texture and Resource Qualification](./tdr-0006-reloadable-texture-resource-roles.md)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# TDR 0005: Public Conversion Diagnostics
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-10-24
|
|
5
|
+
- Tracking: asset-contracts Task49 / site Feature1153
|
|
6
|
+
- Decision: ADR0010
|
|
7
|
+
|
|
8
|
+
## Input and evaluation
|
|
9
|
+
|
|
10
|
+
Accept outcome completed/unsupported/failed, existing public source provenance,
|
|
11
|
+
canonical GpuModelDiagnosticInput and exact private path/public resource bindings.
|
|
12
|
+
Snapshot inert JSON (no getters, symbols, sparse arrays, exotic prototypes or
|
|
13
|
+
cycles) with depth12/16,384values/2,048-characterstrings/262,144aggregatecharacters;
|
|
14
|
+
records/arrays have hard1024member limits. Limit issues/resources to100. Reserve
|
|
15
|
+
one issue slot for non-completed outcomes. Reject duplicate paths and conflicting
|
|
16
|
+
resource declarations for a URI. Every non-root diagnostic needs a binding;
|
|
17
|
+
root uses source. Reuse existing provenance/resource factories and fixed errors.
|
|
18
|
+
|
|
19
|
+
After snapshot, lazy-import published exact model-core0.7.0 and evaluate canonical
|
|
20
|
+
policy. Do not trust a caller-supplied accepted flag or re-evaluate repairs here.
|
|
21
|
+
Project core categories into fixed public codes/text; only accepted repairs expose
|
|
22
|
+
core rule IDs. Drop arbitrary source code/message/path, originalValue/rawSource.
|
|
23
|
+
Explicit failed outcome wins, then unsupported kind/outcome, rejected policy,
|
|
24
|
+
warning/clean success. Canonical consent controls loss severity; denied losses
|
|
25
|
+
remain blocking for existing fidelity derivation. Return frozen version1 JSON
|
|
26
|
+
with technicalAccepted, source, standard/mode, detailed diagnostics and exact
|
|
27
|
+
legacy converter diagnostic/loss arrays. No approval/receipt is synthesized.
|
|
28
|
+
|
|
29
|
+
Native AbortSignal and deadline1–60,000ms(default10,000) cover the operation.
|
|
30
|
+
Race lazy import against bounded timer/abort; clean timer/listener in finally.
|
|
31
|
+
Check deadlines around bounded synchronous snapshot/evaluation; neither loader
|
|
32
|
+
nor synchronous JS can be cancelled mid-execution. No network/filesystem/logging,
|
|
33
|
+
retry or callback dependency is added. Input references are caller assertions,
|
|
34
|
+
not byte verification; Task48/51 retain manifest/promotion responsibilities.
|
|
35
|
+
|
|
36
|
+
## Requirements-based validation and delivery
|
|
37
|
+
|
|
38
|
+
Tests first cover status precedence, strict/tolerant/applied and unknown repairs,
|
|
39
|
+
allowed/denied dropped+approximated loss, fixed messages/codes, forensic redaction,
|
|
40
|
+
source/resource binding, JSON serialization and mutation isolation. Test invalid
|
|
41
|
+
codes/kinds/shapes, accessors/prototypes/symbols/sparse/cyclic inputs, limits and
|
|
42
|
+
options, cancellation/deadline and legacy array ceilings. Compose actual
|
|
43
|
+
ModelProcessingManifest evidence and verify independent fidelity/hard/rights
|
|
44
|
+
gates still reject missing or contradictory evidence. Preserve existing tests.
|
|
45
|
+
|
|
46
|
+
Backend gpu.model.conversion.enabled adoption enabled/disabled/rollback branches
|
|
47
|
+
are tested without live changes; no capability. No dependency versions change.
|
|
48
|
+
Run Node24 frozen full gates, >=80%changed-source LCOV, pack/consumer/audit,
|
|
49
|
+
then PR CI, merge, exact-main CI and approved hosted cd.yml/main/production OIDC.
|
|
50
|
+
Verify registry immutable bytes/provenance/signatures and actual public consumers
|
|
51
|
+
before Done. README/Unreleased and ADR/TDR indices ship with additive minor.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# TDR 0006: Reloadable Texture and Resource Qualification
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-10-24
|
|
5
|
+
- Tracking: asset-contracts Task 50; site Feature 1153
|
|
6
|
+
- Decision: ADR 0011
|
|
7
|
+
|
|
8
|
+
## Sequence and role policy
|
|
9
|
+
|
|
10
|
+
1. Read inert root/options records without executing getters. Verify the graph
|
|
11
|
+
with the matching ESM/CJS core instance's private brand, not a shape cast.
|
|
12
|
+
2. Snapshot inventory, resource bindings and derived records with shared fixed
|
|
13
|
+
budgets before awaiting. Reject unknown fields, accessors, symbols, prototype
|
|
14
|
+
keys, sparse/exotic arrays, non-finite values and malformed IDs.
|
|
15
|
+
3. Invoke existing qualifyModelResourceManifest, which synchronously snapshots
|
|
16
|
+
manifest metadata and all bounded exact byte payloads before its first hash.
|
|
17
|
+
Race the operation against native cancellation/deadline and clean listeners.
|
|
18
|
+
4. Map every inventory entry to an existing storage role and MIME policy:
|
|
19
|
+
|
|
20
|
+
| Canonical role | Storage role | MIME policy |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| mesh | model | model/*, application/json, application/octet-stream, text/plain |
|
|
23
|
+
| mesh | binary | application/octet-stream, application/vnd.plasius.gpu-buffer |
|
|
24
|
+
| material or animation | metadata | application/json |
|
|
25
|
+
| texture | texture | image/png, image/jpeg, image/webp, image/ktx2 |
|
|
26
|
+
| auxiliary | binary or metadata | buffer MIME for binary; application/json for metadata |
|
|
27
|
+
|
|
28
|
+
5. Require exactly one binding per graph resource. For external resources,
|
|
29
|
+
compare exact graph path, verified content hash/length/MIME and appropriate
|
|
30
|
+
image/buffer role. Embedded resources bind only the model entrypoint and
|
|
31
|
+
retain their independent hash/length/MIME rather than the container's hash.
|
|
32
|
+
6. Validate each derived output's distinct existing file and original source
|
|
33
|
+
IDs. gpu-upload requires auxiliary/binary with buffer MIME. texture-atlas
|
|
34
|
+
requires texture with image MIME and image sources. Never overwrite a bound
|
|
35
|
+
source file or reassign a source identity to derived bytes. Multiple authored
|
|
36
|
+
texture interpretations may share one original image resource.
|
|
37
|
+
7. Bound canonical authored material/texture/provenance metadata, retain its
|
|
38
|
+
already immutable values and emit JSON without Blob payloads. Sort resource
|
|
39
|
+
bindings by resourceId, derivatives by path and source-ID sets by UTF-16
|
|
40
|
+
code units. Preserve canonical array order and original provenance.
|
|
41
|
+
|
|
42
|
+
This is declarative role qualification, not a parser, MIME sniffer, GPU-layout
|
|
43
|
+
validator or promotion attestation. Derived records are optional (empty array);
|
|
44
|
+
caller-declared derivation/embedding is not cryptographic algorithm evidence.
|
|
45
|
+
|
|
46
|
+
## Fixed limits and errors
|
|
47
|
+
|
|
48
|
+
Resource bindings: 16,384; derived records: 4,096; core graph nodes: 65,536.
|
|
49
|
+
Shared metadata budgets: 262,144 values, 2,097,152 UTF-16 characters, depth 16,
|
|
50
|
+
2,048 characters/string and 128/key; arrays at most 16,384. IDs use 128-character
|
|
51
|
+
canonical tokens. The existing byte qualifier also applies its own limits:
|
|
52
|
+
16,384 files, 64 MiB/file, 128 MiB total and portable-path rules. Default deadline
|
|
53
|
+
10 seconds, configurable integer 1–60,000 ms. Cancellation/timer cover the
|
|
54
|
+
async boundary and checks surround bounded synchronous phases. Web Crypto work
|
|
55
|
+
cannot be interrupted but never yields a successful result after cancellation
|
|
56
|
+
or expiry. Work is sequential and introduces no retry or storage/network I/O.
|
|
57
|
+
|
|
58
|
+
ModelTextureResourceError exposes invalid-input, limit-exceeded,
|
|
59
|
+
unverified-graph, package-mismatch, resource-mismatch, role-mismatch, aborted or
|
|
60
|
+
deadline-exceeded. Upstream errors are normalized without caller values.
|
|
61
|
+
|
|
62
|
+
## Verification, rollout and limitations
|
|
63
|
+
|
|
64
|
+
Synthetic fixtures independently verify core image/buffer bytes; canonical
|
|
65
|
+
material references require a real UV accessor. Compare external hashes and
|
|
66
|
+
reloaded material JSON with core metadata; serialize and reconstruct canonical
|
|
67
|
+
materials/textures, then verify core accepts the reloaded document. An embedded
|
|
68
|
+
image fixture uses an actual data URI without a fictitious exported source
|
|
69
|
+
image. Derivative hashes differ from the source identity. Test duplicate/missing
|
|
70
|
+
resources, role/MIME drift, unknown roles, graph clone rejection, multiple
|
|
71
|
+
interpretations, getters/proxies, mutation isolation, limits, deadline/abort and
|
|
72
|
+
legacy enabled/disabled/rollback composition. Qualify both typed model and
|
|
73
|
+
legacy envelopes; do not modify shader ABI or animation schemas.
|
|
74
|
+
|
|
75
|
+
Trusted authored sourceMetadata is intentionally preserved; callers own privacy
|
|
76
|
+
minimization before distribution. Public diagnostics use the separate Task 49
|
|
77
|
+
projection. UI accessibility/SEO/browser UI and durable storage controls are N/A
|
|
78
|
+
here. Security/privacy: value-free failures, bounded inert input and exact bytes.
|
|
79
|
+
Performance/reliability: fixed budgets, sequential hashing, no GPU allocation,
|
|
80
|
+
I/O or retry. Maintainability/testability: existing schemas/qualifier reuse and
|
|
81
|
+
immutable deterministic JSON. Compatibility: additive API, unchanged legacy
|
|
82
|
+
factories, dependency versions and existing approval gates.
|
|
83
|
+
|
|
84
|
+
The source-of-truth backend evaluator gpu.model.conversion.enabled controls
|
|
85
|
+
adoption. Disable/rollback retains legacy factories; no service call or mutation
|
|
86
|
+
inside this package and capability N/A. Normal module-format core guard import
|
|
87
|
+
preserves private brand identity across ESM/CJS. Verify packed/public ESM/CJS
|
|
88
|
+
consumers with their matching core entry and browser bundling/execution. Release
|
|
89
|
+
through existing PR/exact-main CI/cd.yml/production hosted OIDC route, verify
|
|
90
|
+
public registry integrity/provenance and dependency signatures. No site release
|
|
91
|
+
or backend secretref mutation is authorized by this Task.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasius/asset-contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Canonical contracts for Plasius asset jobs, model resolution, immutable WGSL shader assets, reviews, and promotion records.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
package/src/index.ts
CHANGED
|
@@ -39,6 +39,8 @@ export * from "./pvox-hash-preimage.js";
|
|
|
39
39
|
export * from "./pvox-model-resolution.js";
|
|
40
40
|
export * from "./pvox-model-resolution-schema.js";
|
|
41
41
|
export * from "./model-resource-manifest.js";
|
|
42
|
+
export * from "./model-texture-resources.js";
|
|
43
|
+
export * from "./model-conversion-diagnostics.js";
|
|
42
44
|
export {
|
|
43
45
|
GPU_SHADER_STORE_FEATURE_FLAG,
|
|
44
46
|
GPU_SHADER_STYLE_SELECTION_CAPABILITY,
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { GpuModelDiagnosticInput, GpuModelDiagnosticMode, GpuModelDiagnosticStandard } from "@plasius/gpu-model-core";
|
|
2
|
+
import {
|
|
3
|
+
createModelProvenance, createModelResourceRef,
|
|
4
|
+
type ModelProvenance, type ModelResourceRef, type ModelConverterDiagnostic, type ModelConversionLoss,
|
|
5
|
+
} from "./model-resolution.js";
|
|
6
|
+
|
|
7
|
+
/** Hard ceilings for public conversion evidence, including private input metadata. */
|
|
8
|
+
export const MODEL_CONVERSION_DIAGNOSTIC_LIMITS = Object.freeze({ issues: 100, resources: 100, values: 16_384, depth: 12, string: 2048, characters: 262_144, timeoutMs: 60_000 });
|
|
9
|
+
|
|
10
|
+
/** Exact private canonical path to an opaque, credential-free public resource. */
|
|
11
|
+
export interface ModelConversionResourceBinding {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly resource: ModelResourceRef;
|
|
14
|
+
}
|
|
15
|
+
/** Canonical evaluator input; arbitrary adapter text is never projected. */
|
|
16
|
+
export interface ModelConversionDiagnosticsInput {
|
|
17
|
+
readonly outcome: "completed" | "unsupported" | "failed";
|
|
18
|
+
readonly source: ModelProvenance;
|
|
19
|
+
readonly report: GpuModelDiagnosticInput;
|
|
20
|
+
readonly resources: readonly ModelConversionResourceBinding[];
|
|
21
|
+
}
|
|
22
|
+
/** Cancellation and deadline cover snapshotting, lazy import and evaluation. */
|
|
23
|
+
export interface ModelConversionDiagnosticsOptions {
|
|
24
|
+
readonly signal?: AbortSignal;
|
|
25
|
+
readonly timeoutMs?: number;
|
|
26
|
+
}
|
|
27
|
+
/** Stable public codes; adapter-supplied codes are never treated as this vocabulary. */
|
|
28
|
+
export type ModelConversionDiagnosticCode = "conversion.warning" | "conversion.repair" | "conversion.loss.dropped" | "conversion.loss.approximated" | "conversion.unsupported" | "conversion.blocked" | "conversion.failed";
|
|
29
|
+
/** Technical evidence only; this is never human, rights, security or promotion approval. */
|
|
30
|
+
export interface QualifiedModelConversionDiagnostic extends ModelConverterDiagnostic {
|
|
31
|
+
readonly code: ModelConversionDiagnosticCode;
|
|
32
|
+
readonly resource?: ModelResourceRef;
|
|
33
|
+
readonly effect?: "dropped" | "approximated";
|
|
34
|
+
readonly fallback?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Deterministic JSON projection plus legacy-compatible converter evidence fields. */
|
|
37
|
+
export interface QualifiedModelConversionDiagnostics {
|
|
38
|
+
readonly contractVersion: 1;
|
|
39
|
+
readonly status: "succeeded" | "warning" | "unsupported" | "failed";
|
|
40
|
+
readonly technicalAccepted: boolean;
|
|
41
|
+
readonly standard: GpuModelDiagnosticStandard;
|
|
42
|
+
readonly mode: GpuModelDiagnosticMode;
|
|
43
|
+
readonly source: ModelProvenance;
|
|
44
|
+
readonly diagnostics: readonly QualifiedModelConversionDiagnostic[];
|
|
45
|
+
readonly converter: {
|
|
46
|
+
readonly diagnostics: readonly ModelConverterDiagnostic[];
|
|
47
|
+
readonly losses: readonly ModelConversionLoss[];
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Fixed, value-free error; never includes adapter/provider exception text. */
|
|
51
|
+
export class ModelConversionDiagnosticsError extends TypeError {
|
|
52
|
+
constructor(public readonly code: "invalid-input" | "limit-exceeded" | "unbound-resource" | "aborted" | "deadline-exceeded") {
|
|
53
|
+
super(`Invalid model conversion diagnostics: ${code}`);
|
|
54
|
+
this.name = "ModelConversionDiagnosticsError";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function fail(code: ModelConversionDiagnosticsError["code"] = "invalid-input"): never { throw new ModelConversionDiagnosticsError(code); }
|
|
58
|
+
|
|
59
|
+
function fields(value: unknown): Record<string, unknown> {
|
|
60
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail();
|
|
61
|
+
const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
62
|
+
const keys = Reflect.ownKeys(value);
|
|
63
|
+
if (keys.length > 1024) fail("limit-exceeded");
|
|
64
|
+
for (const key of keys) {
|
|
65
|
+
if (typeof key !== "string" || key.length > 128 || ["__proto__", "prototype", "constructor"].includes(key)) fail();
|
|
66
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key)!;
|
|
67
|
+
if (!("value" in descriptor) || !descriptor.enumerable) fail();
|
|
68
|
+
result[key] = descriptor.value;
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
function only(record: Record<string, unknown>, keys: readonly string[]): void {
|
|
73
|
+
if (Object.keys(record).some((key) => !keys.includes(key))) fail();
|
|
74
|
+
}
|
|
75
|
+
function snapshot(value: unknown, budget: { values: number; characters: number }, depth = 0): unknown {
|
|
76
|
+
if (--budget.values < 0 || depth > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.depth) fail("limit-exceeded");
|
|
77
|
+
if (value === null || typeof value === "boolean") return value;
|
|
78
|
+
if (typeof value === "string") {
|
|
79
|
+
budget.characters -= value.length;
|
|
80
|
+
if (value.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.string || budget.characters < 0) fail("limit-exceeded");
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || value.length > 1024) fail("limit-exceeded");
|
|
86
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail();
|
|
87
|
+
const result = [];
|
|
88
|
+
for (let i = 0; i < value.length; i++) {
|
|
89
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(i));
|
|
90
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail();
|
|
91
|
+
result.push(snapshot(descriptor.value, budget, depth + 1));
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze(result);
|
|
94
|
+
}
|
|
95
|
+
const record = fields(value);
|
|
96
|
+
for (const key of Object.keys(record)) {
|
|
97
|
+
budget.characters -= key.length;
|
|
98
|
+
if (budget.characters < 0) fail("limit-exceeded");
|
|
99
|
+
record[key] = snapshot(record[key], budget, depth + 1);
|
|
100
|
+
}
|
|
101
|
+
return Object.freeze(record);
|
|
102
|
+
}
|
|
103
|
+
function legacy(value: QualifiedModelConversionDiagnostic): ModelConverterDiagnostic {
|
|
104
|
+
return Object.freeze({ severity: value.severity, code: value.code, message: value.message });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Evaluate canonical diagnostics and project fixed public text. Unknown adapter codes
|
|
109
|
+
* are intentionally discarded; unknown repair rules remain blocking in model-core.
|
|
110
|
+
* Exact path bindings are assertions by the trusted caller, not byte attestations.
|
|
111
|
+
*/
|
|
112
|
+
export async function qualifyModelConversionDiagnostics(input: unknown, options: ModelConversionDiagnosticsOptions = {}): Promise<QualifiedModelConversionDiagnostics> {
|
|
113
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
114
|
+
let signal: AbortSignal | undefined;
|
|
115
|
+
let onAbort: (() => void) | undefined;
|
|
116
|
+
try {
|
|
117
|
+
const start = performance.now();
|
|
118
|
+
const controls = fields(options);
|
|
119
|
+
only(controls, ["signal", "timeoutMs"]);
|
|
120
|
+
const timeoutMs = controls.timeoutMs === undefined ? 10_000 : controls.timeoutMs;
|
|
121
|
+
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.timeoutMs) fail();
|
|
122
|
+
signal = controls.signal as AbortSignal | undefined;
|
|
123
|
+
const aborted = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted")!.get!;
|
|
124
|
+
const check = () => {
|
|
125
|
+
if (signal !== undefined && aborted.call(signal)) fail("aborted");
|
|
126
|
+
if (performance.now() - start >= timeoutMs) fail("deadline-exceeded");
|
|
127
|
+
};
|
|
128
|
+
check();
|
|
129
|
+
const args = fields(snapshot(input, { values: MODEL_CONVERSION_DIAGNOSTIC_LIMITS.values, characters: MODEL_CONVERSION_DIAGNOSTIC_LIMITS.characters }));
|
|
130
|
+
only(args, ["outcome", "source", "report", "resources"]);
|
|
131
|
+
if (!["completed", "unsupported", "failed"].includes(args.outcome as string)) fail();
|
|
132
|
+
const source = createModelProvenance(args.source);
|
|
133
|
+
const reportInput = fields(args.report);
|
|
134
|
+
// Reserve room for the outcome diagnostic in legacy arrays limited to 100.
|
|
135
|
+
const maximumIssues = MODEL_CONVERSION_DIAGNOSTIC_LIMITS.issues - (args.outcome === "completed" ? 0 : 1);
|
|
136
|
+
if (!Array.isArray(reportInput.issues) || reportInput.issues.length > maximumIssues) fail("limit-exceeded");
|
|
137
|
+
if (!Array.isArray(args.resources) || args.resources.length > MODEL_CONVERSION_DIAGNOSTIC_LIMITS.resources) fail("limit-exceeded");
|
|
138
|
+
const resources = new Map<string, ModelResourceRef>();
|
|
139
|
+
const identities = new Map<string, string>();
|
|
140
|
+
for (const value of args.resources) {
|
|
141
|
+
const binding = fields(value);
|
|
142
|
+
only(binding, ["path", "resource"]);
|
|
143
|
+
if (typeof binding.path !== "string" || !binding.path.startsWith("/") || binding.path === "/" || resources.has(binding.path)) fail();
|
|
144
|
+
const resource = createModelResourceRef(binding.resource);
|
|
145
|
+
const identity = JSON.stringify(resource);
|
|
146
|
+
if (identities.has(resource.uri) && identities.get(resource.uri) !== identity) fail();
|
|
147
|
+
identities.set(resource.uri, identity);
|
|
148
|
+
resources.set(binding.path, resource);
|
|
149
|
+
}
|
|
150
|
+
check();
|
|
151
|
+
const interrupted = new Promise<never>((_resolve, reject) => {
|
|
152
|
+
timer = setTimeout(() => reject(new ModelConversionDiagnosticsError("deadline-exceeded")), Math.max(1, timeoutMs - (performance.now() - start)));
|
|
153
|
+
onAbort = () => reject(new ModelConversionDiagnosticsError("aborted"));
|
|
154
|
+
if (signal) AbortSignal.prototype.addEventListener.call(signal, "abort", onAbort, { once: true });
|
|
155
|
+
});
|
|
156
|
+
const core = await Promise.race([import("@plasius/gpu-model-core"), interrupted]);
|
|
157
|
+
check();
|
|
158
|
+
const report = core.evaluateGpuModelDiagnostics(reportInput);
|
|
159
|
+
const diagnostics: QualifiedModelConversionDiagnostic[] = report.diagnostics.map((diagnostic) => {
|
|
160
|
+
const resource = resources.get(diagnostic.path);
|
|
161
|
+
if (diagnostic.path !== "/" && !resource) fail("unbound-resource");
|
|
162
|
+
const base = resource ? { resource } : {};
|
|
163
|
+
switch (diagnostic.kind) {
|
|
164
|
+
case "warning": return Object.freeze({ ...base, severity: "warning", code: "conversion.warning", message: "Conversion completed with an advisory." });
|
|
165
|
+
case "repair": return Object.freeze({ ...base, severity: "warning", code: "conversion.repair", message: "Conversion applied a canonical repair fallback.", fallback: diagnostic.ruleId });
|
|
166
|
+
case "loss": return Object.freeze({ ...base, severity: reportInput.allowLoss === true ? "warning" : "blocking", code: `conversion.loss.${diagnostic.effect}`, message: diagnostic.effect === "dropped" ? "Conversion dropped source data." : "Conversion approximated source data.", effect: diagnostic.effect });
|
|
167
|
+
case "unsupported": return Object.freeze({ ...base, severity: "blocking", code: "conversion.unsupported", message: "Conversion encountered unsupported data." });
|
|
168
|
+
case "blocking-error": return Object.freeze({ ...base, severity: "blocking", code: "conversion.blocked", message: "Conversion did not satisfy canonical validation policy." });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
if (args.outcome !== "completed") diagnostics.push(Object.freeze({ severity: "blocking", code: args.outcome === "failed" ? "conversion.failed" : "conversion.unsupported", message: args.outcome === "failed" ? "Conversion failed." : "Conversion encountered unsupported data." }));
|
|
172
|
+
const technicalAccepted = args.outcome === "completed" && report.accepted;
|
|
173
|
+
const status = args.outcome === "failed" ? "failed" : args.outcome === "unsupported" || report.diagnostics.some((item) => item.kind === "unsupported") ? "unsupported" : !technicalAccepted ? "failed" : diagnostics.length ? "warning" : "succeeded";
|
|
174
|
+
const losses = diagnostics.filter((item) => item.effect !== undefined).map(legacy);
|
|
175
|
+
check();
|
|
176
|
+
return Object.freeze({ contractVersion: 1, status, technicalAccepted, standard: report.standard, mode: report.mode, source,
|
|
177
|
+
diagnostics: Object.freeze(diagnostics), converter: Object.freeze({ diagnostics: Object.freeze(diagnostics.filter((item) => item.effect === undefined).map(legacy)), losses: Object.freeze(losses) }) });
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error instanceof ModelConversionDiagnosticsError) throw error;
|
|
180
|
+
return fail();
|
|
181
|
+
} finally {
|
|
182
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
183
|
+
if (signal && onAbort) AbortSignal.prototype.removeEventListener.call(signal, "abort", onAbort);
|
|
184
|
+
}
|
|
185
|
+
}
|