@plasius/gpu-model-core 0.3.0 → 0.4.1

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/README.md CHANGED
@@ -314,3 +314,151 @@ repository auto-merge is unavailable. Version `0.1.0` may use the explicit,
314
314
  time-limited `bootstrap_first_publish` production gate only while the package is absent;
315
315
  that credential is removed after the npm trusted publisher binding is active.
316
316
  <!-- END PLASIUS RELEASE INTEGRITY -->
317
+
318
+ ## Adapter contracts and canonical conversion
319
+
320
+ `ModelAdapter` exposes `metadata`, `inspect`, `load`, `validate` and `export`.
321
+ Register one explicit lowercase format ID per adapter using
322
+ `createModelConversionRegistry`. Core has no concrete format parsers, IO
323
+ resolvers, renderer dependencies, format sniffing, discovery or caches. Those
324
+ belong to the dedicated format/runtime packages; inject adapters after a runtime
325
+ has checked the remote `gpu.model.conversion.enabled` flag. Disable that flag
326
+ and pin the prior public version to roll back adoption.
327
+
328
+ ```ts
329
+ import {
330
+ createModelConversionRegistry, type ModelAdapter,
331
+ type ModelSource, type ModelTarget,
332
+ } from "@plasius/gpu-model-core";
333
+
334
+ // Applications supply installed, conformance-tested adapters.
335
+ async function convert(sourceAdapter: ModelAdapter, targetAdapter: ModelAdapter) {
336
+ const registry = createModelConversionRegistry([sourceAdapter, targetAdapter]);
337
+ const source: ModelSource = {
338
+ kind: "uint8-array", bytes: new Uint8Array([1, 2, 3]),
339
+ fileNameHint: "synthetic.input",
340
+ };
341
+ const target: ModelTarget = { kind: "array-buffer" };
342
+ const result = await registry.convert({
343
+ sourceFormat: sourceAdapter.metadata.formatId,
344
+ targetFormat: targetAdapter.metadata.formatId,
345
+ source, target,
346
+ }, { mode: "strict", allowLoss: false, timeoutMs: 30_000 });
347
+ if (!result.accepted) return result.diagnostics;
348
+ return result.exported;
349
+ }
350
+ ```
351
+
352
+ Conversion calls source `load`, checks the privately verified canonical document,
353
+ records target capability losses, calls target `validate(document)`, and finally
354
+ calls target `export`. Same-format conversion follows the same route. It never
355
+ calls `inspect` automatically, so a single-use stream is not consumed twice.
356
+ `registry.inspect`, `load`, `validate` and `export` also enforce the boundary
357
+ individually; direct export includes canonical capability/validation preflight.
358
+ Unsupported formats or IO kinds are rejected before adapter work. There is no
359
+ public direct-format converter hook, and extra registration keys are rejected.
360
+ Use plain adapter objects with the four operations as own methods.
361
+
362
+ The following are the supported declarative forms. IO hints remain optional;
363
+ paths, URLs and stream handles confer no authorization from core. The injected
364
+ runtime must enforce filesystem roots, network allowlists, size limits while
365
+ streaming, credentials, output overwrite policy, and atomic staging/commit.
366
+ Never log headers or signed URLs; scrub adapter diagnostic/evidence text before
367
+ calling the result factory. Core produces fixed error codes with no input or
368
+ exception causes, but it cannot identify secrets embedded in arbitrary adapter
369
+ messages.
370
+
371
+ ```ts
372
+ import type { ModelSource, ModelTarget } from "@plasius/gpu-model-core";
373
+
374
+ const sources: ModelSource[] = [
375
+ { kind: "file-path", path: "/models/synthetic.input" },
376
+ { kind: "url", url: "https://example.invalid/model", headers: { Accept: "application/octet-stream" } },
377
+ { kind: "blob-storage-url", url: "https://example.invalid/model", credentialMode: "runtime-resolved" },
378
+ { kind: "blob", blob: new Blob(["synthetic"]), fileNameHint: "synthetic.input" },
379
+ { kind: "array-buffer", bytes: new ArrayBuffer(4) },
380
+ { kind: "uint8-array", bytes: new Uint8Array(4), mimeTypeHint: "application/octet-stream" },
381
+ { kind: "stream", stream: new ReadableStream<Uint8Array>(), byteLengthHint: 4 },
382
+ ];
383
+ const targets: ModelTarget[] = [
384
+ { kind: "file-path", path: "/exports/synthetic.output", overwrite: false },
385
+ { kind: "blob-storage-url", url: "https://example.invalid/output", contentType: "application/octet-stream", metadata: { purpose: "synthetic" } },
386
+ { kind: "blob", mimeType: "application/octet-stream", fileName: "synthetic.output" },
387
+ { kind: "array-buffer" },
388
+ { kind: "uint8-array" },
389
+ { kind: "stream", stream: new WritableStream<Uint8Array>() },
390
+ { kind: "package", packageFormat: "zip", destinationHint: "synthetic.zip", includeSourcePayload: false },
391
+ ];
392
+ ```
393
+
394
+ HTTP(S) URLs with inline username/password are rejected. Storage descriptors
395
+ support anonymous, signed and runtime-resolved credential modes; values are
396
+ passed only to the injected adapter. Browser Web streams and Node
397
+ `AsyncIterable<Uint8Array>` readable / structural `write`+`end` writable streams
398
+ are supported without importing Node types into the public declarations.
399
+ Memory input bytes are copied; Blob values are immutable, and streams remain
400
+ opaque capabilities. Direct memory descriptors are capped at 64 MiB. Larger
401
+ models should use streams with runtime-enforced budgets and worker isolation.
402
+
403
+ Adapters construct their shared envelope with `createModelResultBase`:
404
+
405
+ ```ts
406
+ import { createModelResultBase, type ModelOperationOptions } from "@plasius/gpu-model-core";
407
+
408
+ function completedEvidence(options: ModelOperationOptions = {}) {
409
+ const mode = options.mode ?? "strict";
410
+ return createModelResultBase({
411
+ standard: "gltf-glb", // Use the adapter's actual diagnostic profile.
412
+ mode, allowLoss: options.allowLoss ?? false, issues: [],
413
+ ...(mode === "forensic" ? { rawSource: { synthetic: true } } : {}),
414
+ });
415
+ }
416
+ // load: { ...completedEvidence(options), document: verifiedDocument }
417
+ // inspect: { ...completedEvidence(options), capabilities: metadata, detectedFormat: metadata.formatId }
418
+ // validate: { ...completedEvidence(options), valid: true }
419
+ // export: { ...completedEvidence(options), target: target.kind, output: optionalMemoryPayload }
420
+ ```
421
+
422
+ The factory reuses `evaluateGpuModelDiagnostics`; its immutable report is privately
423
+ recorded. Spread the returned base unchanged into an operation result. Structural
424
+ clones and invented report/projection arrays are rejected. `warnings` projects
425
+ warning-severity diagnostics, `repairs` retains completed repair evidence, and
426
+ `lossReports` retains dropped/approximated fidelity. The registry checks the
427
+ caller's mode and loss consent independently, so an adapter cannot grant itself
428
+ permission. Unknown/unsupported semantics remain blocking in all modes.
429
+
430
+ `ModelLoadResult.document` is optional only for refused operations; accepted
431
+ loads require a privately verified document from this core module instance.
432
+ `ModelExportResult.output` optionally carries Blob, ArrayBuffer or Uint8Array
433
+ matching the selected memory target. A `ResourcePackageManifest` describes
434
+ portable relative paths, roles and content types; duplicate identities/paths and
435
+ unsafe paths are rejected. It is not proof of output bytes or promotion.
436
+ Canonical resource identity remains in `GpuModelDocument` and the separately
437
+ available resource graph. Cross-worker/module clones must be reverified and
438
+ reports rebuilt before registry use.
439
+
440
+ Registry results retain per-stage diagnostic reports, preserving different source
441
+ and target profiles, plus aggregate diagnostic/repair/loss arrays. The synthetic
442
+ `capabilities` stage uses the core `gltf-glb` report profile solely as a container
443
+ for format-neutral loss records; it applies no format-specific repair rules.
444
+ It reports animation, rig/skin and analytic-geometry downgrade when the target's
445
+ capability flags cannot preserve those values. Other prospective losses must be
446
+ reported by target validation. Adapters must stage writes and reject unconsented
447
+ loss **before** committing; core cannot undo an injected adapter's side effects.
448
+ Always check `accepted`, including after export, and do not equate a manifest or
449
+ adapter result with independently verified publication.
450
+
451
+ Each registry allows at most 128 adapters and eight concurrent operations
452
+ (configurable downward with `maxConcurrent`). Each operation or whole conversion
453
+ has a 30-second default deadline, configurable up to five minutes, and supports
454
+ `AbortSignal`. No retries occur. Cancellation and timeouts stop subsequent stages;
455
+ an unresponsive operation keeps its capacity slot until the underlying promise
456
+ settles. Synchronous parser code cannot be preempted, so isolate untrusted code
457
+ in runtime workers. Descriptor/report/manifest limits bound core work; raw
458
+ JavaScript Proxies and injected adapter/stream implementations are trusted code,
459
+ not a sandbox boundary.
460
+
461
+ The IO examples are covered by `tests/adapter-registry.test.ts`; all TypeScript
462
+ blocks in this section are additionally compiled against the built public
463
+ package during release verification. See [ADR-0010](docs/adrs/adr-0010-canonical-adapter-registry.md)
464
+ for compatibility with the initial site design and package ownership boundaries.