@holoscript/holosystem 0.2.1 → 0.2.3

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
@@ -55,6 +55,553 @@ returns one highest-priority candidate, and carries authority, validation, lease
55
55
  and spend stop conditions. It selects work; it does not claim a board task,
56
56
  publish a package, spend funds, or bypass caller authority.
57
57
 
58
+ ## Substrate Closure
59
+
60
+ The substrate closure replaces an implicit operating-system dependency tower
61
+ with a deterministic, caller-owned infrastructure graph. Every component names
62
+ an exact version, portable source revision, content digest, custody owner,
63
+ dependency edges, installation-script state, and a cryptographically
64
+ authenticated rebuild. External
65
+ components can remain during migration, but they are visible sovereignty
66
+ boundaries rather than hidden transitive dependencies.
67
+
68
+ ```bash
69
+ npx holosystem substrate \
70
+ --input substrate.json \
71
+ --output substrate-lock.json \
72
+ --json
73
+ ```
74
+
75
+ ```js
76
+ import { buildSubstrateClosure, createRebuildAttestationPayload } from '@holoscript/holosystem';
77
+
78
+ const component = {
79
+ id: 'holosystem',
80
+ kind: 'runtime',
81
+ version: '1.0.0',
82
+ custody: {
83
+ mode: 'owned',
84
+ owner: 'holoscript',
85
+ trustDomain: 'holoscript-release',
86
+ },
87
+ source: { uri: 'holorepo://holoscript', revision: 'abc123' },
88
+ artifact: { digest: 'sha256:<64 lowercase hex characters>' },
89
+ execution: { installScripts: 'none' },
90
+ requires: [],
91
+ verification: {
92
+ rebuilds: [
93
+ {
94
+ verifier: 'independent-builder',
95
+ digest: 'sha256:<same 64 lowercase hex characters>',
96
+ signature: '<Ed25519 signature in canonical base64>',
97
+ },
98
+ ],
99
+ },
100
+ };
101
+
102
+ // The independent builder signs this exact UTF-8 payload with its private key.
103
+ const payload = createRebuildAttestationPayload({
104
+ verifier: 'independent-builder',
105
+ component,
106
+ });
107
+
108
+ const receipt = buildSubstrateClosure({
109
+ root: 'holosystem',
110
+ coverage: {
111
+ includedLayers: ['kernel', 'runtime', 'toolchain'],
112
+ missingLayers: [],
113
+ },
114
+ verificationPolicy: {
115
+ minimumIndependentRebuilds: 1,
116
+ trustRoots: [
117
+ {
118
+ verifier: 'independent-builder',
119
+ trustDomain: 'independent-builders',
120
+ publicKey: '<Ed25519 public key in PEM format>',
121
+ },
122
+ ],
123
+ },
124
+ components: [component],
125
+ });
126
+ ```
127
+
128
+ The command exits with code `2` and still emits the blocked receipt when it
129
+ finds a missing or unreachable dependency, a cycle, a floating version, a local
130
+ source path, an invalid digest, an untrusted verifier, a forged signature, a
131
+ same-domain rebuild, a present installation script, or too few matching
132
+ rebuilds. The receipt exposes only public-key fingerprints, not the supplied
133
+ PEM text. Installation scripts remain blocked until a later execution layer can
134
+ prove they were forcibly disabled or sandboxed.
135
+
136
+ `coverage.includedLayers` declares what the graph represents, while any known
137
+ gap remains in `coverage.missingLayers` and blocks readiness. Coverage is still
138
+ a caller assertion rather than discovery proof; importers must carry their
139
+ known blind spots into this field.
140
+
141
+ Trust domains are assertions in the caller-owned policy. The signature proves
142
+ that the named key attested to the exact component tuple; it does not prove that
143
+ the builder is organizationally independent or that the component is safe.
144
+ Production trust roots therefore belong to independently governed rebuilders,
145
+ not keys created by the component custodian for the same release.
146
+
147
+ ### Import an npm dependency graph
148
+
149
+ `substrate-import` derives the graph from an npm `package-lock.json` instead of
150
+ asking an operator to maintain the dependency edges manually. It supports lock
151
+ formats [v2 and v3](https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/),
152
+ preserves nested Node resolution, converts canonical npm SRI
153
+ integrity into content digests, and marks every registry package as external
154
+ custody.
155
+
156
+ ```json
157
+ {
158
+ "root": {
159
+ "id": "demo-app",
160
+ "custody": {
161
+ "mode": "owned",
162
+ "owner": "demo",
163
+ "trustDomain": "demo-release"
164
+ },
165
+ "source": {
166
+ "uri": "https://github.com/example/demo",
167
+ "revision": "demo-revision-1"
168
+ }
169
+ },
170
+ "verificationPolicy": {
171
+ "minimumIndependentRebuilds": 1,
172
+ "trustRoots": [
173
+ {
174
+ "verifier": "rebuild-farm",
175
+ "trustDomain": "independent-rebuild-farm",
176
+ "publicKey": "<Ed25519 public key in PEM format>"
177
+ }
178
+ ]
179
+ }
180
+ }
181
+ ```
182
+
183
+ ```bash
184
+ npx holosystem substrate-import \
185
+ --lock package-lock.json \
186
+ --config substrate-import.json \
187
+ --output substrate-input.json \
188
+ --json
189
+ ```
190
+
191
+ The command writes the generated closure input to `--output` and emits an
192
+ import receipt on stdout. A valid import has status
193
+ `coverage-and-attestation-required`, or `execution-policy-required` when a
194
+ package declares an installation script; neither means `ready`. Lockfiles prove
195
+ resolution and registry integrity, but not a rebuild, safe lifecycle execution,
196
+ or complete operating-system coverage. After independent builders attach signed
197
+ attestations, run the existing
198
+ `holosystem substrate --input substrate-input.json --json` gate.
199
+
200
+ Production dependencies are imported by default. Set `"includeDev": true` in
201
+ the import configuration to include direct development dependencies. Optional
202
+ branches present in the lock are retained as a conservative superset; missing
203
+ optional dependencies are counted but do not block import. No package or
204
+ install script is executed. `registry.npmjs.org` lockfile indirection is
205
+ preserved as `npm://configured-registry/...` instead of pretending that the
206
+ configured registry endpoint is known. The root component's artifact digest
207
+ represents the canonical lockfile, not a compiled application artifact. Native
208
+ libraries and operating-system packages are not derived yet, so those gaps are
209
+ carried into the generated input and block the closure. Local links,
210
+ missing runtime entries, malformed or weak integrity, non-portable sources, and
211
+ private keys in the trust policy block import without echoing their values.
212
+
213
+ ```js
214
+ import { importNpmPackageLock } from '@holoscript/holosystem';
215
+
216
+ const imported = importNpmPackageLock({
217
+ lockfile,
218
+ root,
219
+ verificationPolicy,
220
+ });
221
+ ```
222
+
223
+ ### Import an installed Debian package graph
224
+
225
+ `substrate-import-debian` converts offline Debian evidence into an
226
+ operating-system substrate graph. It accepts one or more independently anchored
227
+ repository indexes, parses the standard control-stanza format,
228
+ resolves `Depends`, `Pre-Depends`, alternatives, exact version relations, and
229
+ installed virtual-package providers according to the
230
+ [Debian relationship rules](https://www.debian.org/doc/debian-policy/ch-relationships.html),
231
+ and binds each installed package to the SHA-256 or SHA-512 and `Filename` in a
232
+ caller-anchored `Packages` index.
233
+
234
+ The command does not run `dpkg`, `apt`, or any package script. Supply:
235
+
236
+ - the installed `dpkg` status text;
237
+ - each uncompressed repository `Packages` index and its expected digest;
238
+ - a JSON map for every installed `package:architecture`, containing hashes for
239
+ any `preinst`, `postinst`, `prerm`, `postrm`, or `config` scripts;
240
+ - a config containing the repository base, system root, custody, and optional
241
+ rebuild policy.
242
+
243
+ ```json
244
+ {
245
+ "repository": {
246
+ "uri": "https://deb.debian.org/debian/",
247
+ "packagesIndexDigest": "sha256:<digest of the exact Packages input>"
248
+ },
249
+ "root": {
250
+ "id": "demo-linux",
251
+ "version": "12.1-1",
252
+ "custody": {
253
+ "mode": "owned",
254
+ "owner": "demo",
255
+ "trustDomain": "demo-os-release"
256
+ },
257
+ "source": {
258
+ "uri": "https://images.example.com/demo-linux",
259
+ "revision": "snapshot-20260716"
260
+ }
261
+ }
262
+ }
263
+ ```
264
+
265
+ ```json
266
+ {
267
+ "base-files:amd64": {},
268
+ "demo-app:amd64": {
269
+ "postinst": "sha256:<digest of the installed postinst file>"
270
+ }
271
+ }
272
+ ```
273
+
274
+ ```bash
275
+ npx holosystem substrate-import-debian \
276
+ --status status \
277
+ --packages Packages \
278
+ --maintainer-scripts maintainer-scripts.json \
279
+ --config debian-substrate-import.json \
280
+ --output debian-substrate-input.json \
281
+ --json
282
+ ```
283
+
284
+ Real installations usually span base, security, update, or third-party
285
+ archives. Use `--sources` instead of `--packages` for those systems. The source
286
+ manifest is a JSON array; local `packages` paths are read by the CLI but are not
287
+ copied into the receipt.
288
+
289
+ ```json
290
+ [
291
+ {
292
+ "packages": "Packages.main",
293
+ "uri": "https://deb.debian.org/debian/",
294
+ "packagesIndexDigest": "sha256:<digest>",
295
+ "authentication": {
296
+ "inReleasePath": "InRelease",
297
+ "verifier": {
298
+ "path": "/usr/bin/gpgv",
299
+ "digest": "sha256:<gpgv-binary-digest>"
300
+ },
301
+ "keyrings": [
302
+ {
303
+ "path": "/usr/share/keyrings/debian-archive-keyring.gpg",
304
+ "digest": "sha256:<keyring-digest>"
305
+ }
306
+ ],
307
+ "trustedFingerprints": ["<exact-primary-or-signing-key-fingerprint>"],
308
+ "expected": {
309
+ "suite": "stable",
310
+ "codename": "trixie",
311
+ "architecture": "amd64",
312
+ "component": "main"
313
+ },
314
+ "packagesIndexPath": "main/binary-amd64/Packages",
315
+ "maxReleaseAgeSeconds": 604800
316
+ },
317
+ "custody": {
318
+ "owner": "debian-main",
319
+ "trustDomain": "debian-main-archive"
320
+ }
321
+ },
322
+ {
323
+ "packages": "Packages.security",
324
+ "uri": "https://security.debian.org/debian-security/",
325
+ "packagesIndexDigest": "sha256:<digest>",
326
+ "custody": {
327
+ "owner": "debian-security",
328
+ "trustDomain": "debian-security-archive"
329
+ }
330
+ }
331
+ ]
332
+ ```
333
+
334
+ ```bash
335
+ npx holosystem substrate-import-debian \
336
+ --status status \
337
+ --sources debian-sources.json \
338
+ --maintainer-scripts maintainer-scripts.json \
339
+ --config debian-substrate-import.json \
340
+ --output debian-substrate-input.json \
341
+ --json
342
+ ```
343
+
344
+ Authentication paths are operational inputs resolved from the current working
345
+ directory and are not copied into receipts. Use `releasePath` plus
346
+ `releaseSignaturePath` instead of `inReleasePath` for detached `Release.gpg`
347
+ signatures. A Git-for-Windows verifier can set `verifier.pathStyle` to `msys`;
348
+ other verifiers use the default `native` path style. The package never fetches
349
+ or imports keys, so establishing the verifier digest, keyring digest, and exact
350
+ fingerprint allowlist remains a caller-owned trust-bootstrap operation.
351
+ On the first import, omit `minimumReleaseDate`; on later imports, copy the
352
+ accepted `release.date` from the last receipt to reject rollback within the
353
+ freshness window. Multi-signed archives require every signature visible to
354
+ `gpgv` to resolve through the pinned keyring and fingerprint allowlist. Treat
355
+ archive key transitions as explicit configuration updates rather than weakening
356
+ verification. Signing-key compromise, key lifecycle, and the trusted clock
357
+ remain caller-owned boundaries.
358
+
359
+ Every fully installed package becomes an external-custody component and the
360
+ system root depends on all of them, including packages no longer reachable from
361
+ another package. The importer implements Debian's version ordering, including
362
+ epochs and the special tilde order. It fails closed on an index-digest mismatch,
363
+ missing package artifact, unsatisfied or ambiguous dependency, path traversal,
364
+ incomplete maintainer-script evidence, untrusted signature, stale Release,
365
+ distribution identity change, or signed index mismatch.
366
+
367
+ A source without authentication still uses its caller digest only; it does not
368
+ claim that a Debian `Release` or `InRelease` signature was verified and retains
369
+ the blocking `repository-authentication` coverage gap. When every source passes,
370
+ the receipt includes that layer and records the signed Release-to-Packages hash
371
+ chain, pinned verifier and keyring digests, exact signer fingerprints, and replay
372
+ window. Likewise, Debian
373
+ [maintainer scripts execute during package transitions](https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html),
374
+ so any present script still blocks substrate readiness. The generated input
375
+ always marks operating-system coverage as included. Signed rebuild attestations,
376
+ native-build provenance, and the existing `substrate` gate remain required.
377
+
378
+ ```js
379
+ import { importDebianPackageSnapshot } from '@holoscript/holosystem';
380
+
381
+ const imported = importDebianPackageSnapshot({
382
+ status,
383
+ sources: [{ packagesIndex, repository, custody }],
384
+ maintainerScripts,
385
+ root,
386
+ verificationPolicy,
387
+ });
388
+ ```
389
+
390
+ For a verification-only workflow, call `verifyDebianRepositoryRelease` with the
391
+ same authentication fields plus `packagesIndex` and `packagesIndexDigest`. It
392
+ returns `holoscript.holosystem.debian-release-auth.v1`; `verified` is true only
393
+ when OpenPGP verification, signer pinning, release identity and freshness, and
394
+ the signed Packages checksum and size all pass.
395
+
396
+ ### Build a native artifact in a closed executor
397
+
398
+ `native-build` is the first executable bridge from the substrate graph to a
399
+ reproducible native artifact. The initial vocabulary deliberately supports one
400
+ small target: a C11 translation unit compiled by GCC into a Linux AMD64 GNU ELF
401
+ executable. It does not accept a shell command, arbitrary compiler executable,
402
+ free-form flags, lifecycle script, network mode, user id, or weaker isolation
403
+ policy from the plan.
404
+
405
+ Hash the caller-owned source tree first:
406
+
407
+ ```bash
408
+ npx holosystem native-build-source --source ./native-source --json
409
+ ```
410
+
411
+ Then create a plan using that source digest and a digest-pinned local Docker
412
+ binary plus an immutable OCI manifest reference:
413
+
414
+ ```json
415
+ {
416
+ "schema": "holoscript.holosystem.native-build-plan.v1",
417
+ "id": "demo-native-build",
418
+ "source": {
419
+ "digest": "sha256:<source-manifest-digest>",
420
+ "sourceDateEpoch": 1700000000
421
+ },
422
+ "target": {
423
+ "os": "linux",
424
+ "architecture": "amd64",
425
+ "abi": "gnu"
426
+ },
427
+ "executor": {
428
+ "kind": "docker",
429
+ "digest": "sha256:<docker-binary-digest>",
430
+ "image": "docker.io/library/gcc@sha256:<amd64-manifest-digest>"
431
+ },
432
+ "compiler": {
433
+ "family": "gcc",
434
+ "language": "c11",
435
+ "source": "main.c",
436
+ "optimization": "speed"
437
+ },
438
+ "output": {
439
+ "path": "demo",
440
+ "format": "elf-executable"
441
+ },
442
+ "limits": {
443
+ "timeoutSeconds": 60,
444
+ "memoryMiB": 512,
445
+ "cpus": 1,
446
+ "pids": 64,
447
+ "tmpfsMiB": 64
448
+ },
449
+ "rebuilds": 2
450
+ }
451
+ ```
452
+
453
+ ```bash
454
+ npx holosystem native-build \
455
+ --plan native-build.json \
456
+ --source ./native-source \
457
+ --executor /absolute/operational/path/to/docker \
458
+ --artifact-dir ./native-output \
459
+ --output native-build-receipt.json \
460
+ --json
461
+ ```
462
+
463
+ The first successful run is intentionally `artifact-pin-required` and exits
464
+ with code `2`. It performs two clean builds and reports their common digest but
465
+ does not claim `native-build` coverage. Copy that digest into
466
+ `expectedArtifactDigest`, use a new empty artifact directory, and rerun. Only a
467
+ matching reproducible result returns `verified`, exits `0`, and includes the
468
+ `native-build` layer.
469
+
470
+ The runner verifies an immutable source snapshot before mounting it, checks the
471
+ Docker executable digest, prohibits image pulls and networking, mounts both the
472
+ root filesystem and source read-only, drops all Linux capabilities, enables
473
+ `no-new-privileges`, runs as uid/gid 65534, bounds time, memory, CPU, PIDs and
474
+ temporary storage, and invokes `/usr/local/bin/gcc` directly with generated
475
+ deterministic arguments. It rejects undeclared files, symlink outputs,
476
+ non-AMD64 ELF results, mismatched artifact pins, and differing rebuild digests.
477
+ Compiler logs are represented only by byte counts and hashes; operational host
478
+ paths are not copied into the receipt.
479
+
480
+ ```js
481
+ import {
482
+ createNativeRebuildAttestationPayload,
483
+ inspectNativeBuildPlan,
484
+ inspectNativeBuildSource,
485
+ runNativeBuild,
486
+ } from '@holoscript/holosystem';
487
+ ```
488
+
489
+ `createNativeRebuildAttestationPayload` joins a verified native receipt to the
490
+ existing `holoscript.holosystem.rebuild-attestation.v1` payload. The independent
491
+ builder still signs that payload outside this package, and the substrate gate
492
+ still verifies the signature against caller-owned trust roots. The receipt
493
+ does not prove the Docker host, compiler-image supply chain, host kernel, CPU,
494
+ or organizational independence; those remain named boundaries.
495
+
496
+ This tracer commences the build/execution layer. It is not a claim that
497
+ HoloSystem already replaces measured boot, UEFI/device firmware, an ISA backend,
498
+ or physical hardware verification. Those require separate target contracts and
499
+ receipts rather than broader process execution in this API.
500
+ The adversary classes, attack specifications, test mapping, and residual trust
501
+ boundary are recorded in the [native-build threat model](./docs/native-build-threat-model.md).
502
+
503
+ ### Launch a measured machine VM
504
+
505
+ `vm-launch` extends the executable tracer below the container boundary. It boots
506
+ an AMD64 Linux kernel and initramfs as a full-system q35 guest with QEMU TCG.
507
+ `vm-launch-whpx` is a separately named adapter for QEMU's Windows Hypervisor
508
+ Platform backend. The adapters do not share an accelerator switch and never
509
+ fall back to one another. Both vocabularies are deliberately closed: Windows
510
+ AMD64 host, `qemu-system-x86_64.exe`, q35, 128 MiB, one CPU, and exactly two
511
+ launches. Plans cannot provide a command, argument, environment variable,
512
+ device, network, firmware, monitor, display, or fallback setting.
513
+
514
+ First measure the complete caller-owned QEMU runtime closure and both guest
515
+ artifacts:
516
+
517
+ ```bash
518
+ npx holosystem vm-executor --runtime C:/absolute/path/to/qemu-runtime --json
519
+ npx holosystem vm-asset --kind kernel --file C:/absolute/path/to/vmlinuz --json
520
+ npx holosystem vm-asset --kind initrd --file C:/absolute/path/to/initramfs --json
521
+ ```
522
+
523
+ Create a plan with those digests and the SHA-256 digest of the exact expected
524
+ serial success signal:
525
+
526
+ ```json
527
+ {
528
+ "schema": "holoscript.holosystem.vm-launch-plan.v1",
529
+ "id": "demo-vm-proof",
530
+ "host": { "os": "windows", "architecture": "amd64" },
531
+ "executor": {
532
+ "kind": "qemu-system",
533
+ "binary": "qemu-system-x86_64.exe",
534
+ "binaryDigest": "sha256:<qemu-executable-digest>",
535
+ "runtimeDigest": "sha256:<complete-runtime-manifest-digest>"
536
+ },
537
+ "target": { "architecture": "amd64", "machine": "q35", "accelerator": "tcg" },
538
+ "guest": {
539
+ "kernelDigest": "sha256:<kernel-digest>",
540
+ "initrdDigest": "sha256:<initrd-digest>",
541
+ "expectedConsoleDigest": "sha256:<exact-serial-output-digest>"
542
+ },
543
+ "resources": { "memoryMiB": 128, "cpus": 1, "timeoutSeconds": 30 },
544
+ "launches": 2
545
+ }
546
+ ```
547
+
548
+ For WHPX, use schema `holoscript.holosystem.whpx-vm-launch-plan.v1`, set the
549
+ target accelerator to `whpx`, and add `guest.expectedDiagnosticsDigest`. That
550
+ digest pins the exact WHPX diagnostic bytes after removal of only the generated
551
+ private executor-path prefix. The WHPX adapter requires successful explicit
552
+ WHPX execution; feature labels or QEMU's compiled backend list are not treated
553
+ as proof that acceleration is usable.
554
+
555
+ ```bash
556
+ npx holosystem vm-launch \
557
+ --plan vm-launch.json \
558
+ --runtime C:/absolute/path/to/qemu-runtime \
559
+ --kernel C:/absolute/path/to/vmlinuz \
560
+ --initrd C:/absolute/path/to/initramfs \
561
+ --output vm-launch-receipt.json \
562
+ --json
563
+ ```
564
+
565
+ Use `npx holosystem vm-launch-whpx` with the same four caller-owned artifact
566
+ arguments for the WHPX schema. WHPX requires the Windows Hypervisor Platform
567
+ API exposed to QEMU; see the [QEMU WHPX documentation](https://www.qemu.org/docs/master/system/whpx.html)
568
+ and [Microsoft's Windows Hypervisor Platform API overview](https://learn.microsoft.com/en-us/virtualization/api/).
569
+
570
+ The runner creates a private snapshot and remeasures the complete QEMU closure,
571
+ kernel, and initramfs before and after each launch. It then generates QEMU
572
+ arguments that disable user configuration, default devices, networking, USB,
573
+ display, monitor, and reboot;
574
+ passes only a minimal environment; bounds output and time; and requires both
575
+ launches to produce the pinned serial digest and fixed debug-exit code. TCG
576
+ requires no emulator diagnostics. WHPX requires the normalized diagnostic
577
+ digest pinned by its plan; unexpected bytes block the receipt. Receipts contain
578
+ only digests and byte counts, never raw console output or operational host
579
+ paths.
580
+
581
+ A verified TCG receipt includes `machine-vm-launch`,
582
+ `guest-artifact-measurement`, and `virtual-device-minimization`, reports
583
+ `hardwareBacked: false`, and leaves hardware acceleration missing. A verified
584
+ WHPX receipt additionally includes `hardware-hypervisor-acceleration` and sets
585
+ `hardwareBacked: true` only after two explicit WHPX launches succeed. Both
586
+ adapters leave `host-process-isolation` missing: QEMU still has the ambient
587
+ rights of the Windows process. Receipts therefore expose
588
+ `isolation.hostProcess: "ambient-windows-process"` and
589
+ `isolation.verified: false` even when WHPX acceleration is verified. Neither
590
+ receipt proves an IOMMU, measured boot, firmware authenticity, confidential
591
+ memory, or side-channel resistance. The
592
+ exact claims are in the [VM launch threat model](./docs/vm-launch-threat-model.md).
593
+
594
+ ```js
595
+ import {
596
+ inspectVmExecutor,
597
+ inspectVmLaunchAsset,
598
+ inspectVmLaunchPlan,
599
+ inspectWhpxVmLaunchPlan,
600
+ runVmLaunch,
601
+ runWhpxVmLaunch,
602
+ } from '@holoscript/holosystem';
603
+ ```
604
+
58
605
  ## What It Creates
59
606
 
60
607
  The default configuration pins public contracts for: