Arcane OS Docs Development only

Build

Application packaging

Positive app inventories, shared payloads, adapters, deterministic output, and verification.

  • Build

The Arcane OS application packager creates verified static distribution roots from configured Arcane applications under apps/. These packages carry the application interface and shared runtime; they do not redefine the application as a website or limit its native Arcane capabilities. Sources come from the current workspace and completed packages go to dist/<app-id>. Each package keeps the repository-shaped apps/, arcane/, and allowlisted node_modules/ paths expected by Arcane application interfaces.

Routes

The npm scripts are convenient wrappers around node tools/package-app.mjs:

Task npm route Direct CLI
List app directories and versions npm run apps:list node tools/package-app.mjs list
Serve browser-approved packages on isolated loopback origins npm run apps:serve node tools/serve-browser-apps.mjs
Inspect effective rules and size npm run app:inspect -- <app> node tools/package-app.mjs inspect <app>
Package the current version npm run app:package -- <app> node tools/package-app.mjs package <app>
Package every configured app npm run app:package -- --all node tools/package-app.mjs package --all
Package a selected app release with a patch bump, after its app-owned full suite passes npm run app:release -- <app> node tools/package-app.mjs release <app>
Verify an existing package npm run app:check -- <app> node tools/package-app.mjs check <app>
Verify every public package plus its app-owned publication policy npm run check:public-apps Configured aggregate public-package gate
Change only the source version npm run app:bump -- <app> <level-or-version> node tools/package-app.mjs bump <app> <level-or-version>

list, inspect, package, check, and bump accept --json for machine-readable output where applicable. Package and version operations accept --dry-run. build aliases package, and verify aliases check.

The current development lifecycle does not verify or regenerate every public package on every push. npm test, npm run check:dev, unified developer setup, ordinary continuous integration, and the installed Git hook run only the fast development invariants. Root npm run check adds the cataloged portable OS unit profile; it still does not run application behavior or package suites. Use npm run demo:check -- <app-id> when one package is actually needed. npm run check:integration and npm run release:check:arcane-os test Arcane OS boundaries only; they do not run application behavior suites.

Development package workflow

  1. Run focused source tests while iterating.
  2. Run npm run check before ordinary push or handoff.
  3. When an internal demonstration needs a real package, run npm run demo:check -- <app-id>.
  4. Review any generated output before intentionally committing it. Do not hand-edit document-catalog.json, ARCANE_APP_RELEASE.json, PUBLIC_RELEASE.json, or another generated manifest.
  5. Do not run --all merely because shared source changed. A full application suite and release package belong only to the explicitly selected application's release.

The pre-push hook does not regenerate or verify all packages. A selected demo command packages and verifies its target. Before releasing an application, run that application's own complete behavior suite, regenerate only that application, review its source and generated diffs together, and run npm run app:check -- <app-id>. app:release performs versioned packaging and package verification; it is not a substitute for the app-owned behavior suite. A separate Arcane OS release consumes the checked-in dist/<app-id>/ARCANE_APP_RELEASE.json contract and may validate its identity, capabilities, entry point, exact inventory, sizes, hashes, safe topology, and catalog admission. The OS does not read app source, import an app adapter or source verifier, rebuild the app, or run its content or business-behavior suites.

Local browser testing

After npm run demo:check -- <app-id> verifies the selected package, run npm run apps:serve and open the printed catalog URL. The command binds only to 127.0.0.1, gives every runnable app a separate ephemeral port/origin, redirects that origin to ARCANE_APP_RELEASE.app.start, and streams only declared package inventory. App origins never serve working source or ARCANE_APP_RELEASE.json; the catalog origin has an exact allowlist limited to its launcher style and the shared Arcane theme bootstrap dependency graph.

Browser admission is explicit and exhaustive in tools/local-app-test-policy.json; it is not inferred from an HTTP 200 response, localAIModelPolicy, or native capability grants because those do not distinguish required from optional behavior. Applications classified as Arcane-required or data-required receive no browser listener. New, invalid, or unclassified apps fail closed until reviewed. Browser-classified apps run without a fabricated window.Arcane; documented browser fallbacks and unavailable-native states remain visible.

Startup performs one authoritative application discovery plus 25 small policy classifications, reads only the available browser release manifests, and creates at most 17 app listeners plus one catalog listener sequentially. It performs zero package builds, full-payload hashes, signatures, model pulls, or Arcane OS work. Files stream only when requested, Ctrl+C closes every owned listener, and a startup failure closes any partially opened set. The command is a local test transport, not a verifier: rerun the selected demo:check after source changes, then restart the server.

Semantic versions

For an application whose source is present in this repository, its arcane-package.json is the authoritative version source. After an external application is independently authorized and its source is removed, Arcane derives that app's version from the normalized descriptor in arcane-external-apps.lock.json; the admitted ARCANE_APP_RELEASE.json must match it. PWA manifest.json files and asset query strings are not treated as versions.

Machine-bundle packaging does not replace application identity with the Arcane OS version. The OS version comes only from machine_bundles/arcane-os-machine-bundle/arcane-bundle.json; source-owned app versions come from apps/<id>/arcane-package.json, while source-free external app versions come from their independently authorized locked descriptors. The native app registry intentionally contains no copied version field. Generated package, content-binding, catalog, and platform records carry both identities as bundleVersion and app.version, and verification rejects either value when it disagrees with its applicable authority.

  • package <app> rebuilds the current version without changing source files.
  • release <app> defaults to --bump patch.
  • --bump major, minor, patch, or prerelease selects the revision.
  • --preid beta selects a prerelease identifier.
  • --set 2.0.0 packages an exact new version.
  • bump <app> 2.0.0 changes only the config version.

When packaging and bumping together, the new version is written to the app config only after the staged package and app-specific verifier pass. A failed build leaves the previous package and source version in place.

Root configuration

The repository-only arcane-packager.json fixes the source and destination roots and defines named shared payloads. Each shared payload contains one or more explicit routes. Public readers can review the repository-access boundary:

{
  "schemaVersion": 1,
  "appsRoot": "apps",
  "distRoot": "dist",
  "sharedPayloads": {
    "browser-runtime": [
      {
        "source": "arcane",
        "destination": "arcane",
        "include": ["components", "css", "entities", "img", "modules"],
        "exclude": []
      }
    ]
  }
}

Routes are allowlist-first. source and destination are workspace-relative roots; include and exclude are literal paths relative to that source. A directory includes or excludes all descendants. Globs and arbitrary output paths are intentionally unsupported.

App configuration

Every publishable app owns apps/<id>/arcane-package.json:

{
  "schemaVersion": 1,
  "id": "example",
  "displayName": "Example App",
  "version": "0.1.0",
  "entry": "index.html",
  "strategy": "static",
  "localAIModelPolicy": {
    "verified_only": true,
    "models": [
      {
        "name": "EXAMPLE:8b",
        "definition": "Example-8B.Modelfile"
      }
    ]
  },
  "include": ["Example-8B.Modelfile", "index.html", "components", "img", "modules"],
  "exclude": ["img/source-material", "test"],
  "shared": ["browser-runtime"]
}

The ID must match the immediate apps/<id> directory. The entry must be a regular included file. static copies only the configured base payload. adapter invokes a trusted app-local scripts/*.mjs module for content-aware generation and verification; it still receives the same configured static base.

An application uses an adapter when publication requires per-record authorization, content regeneration, source-document consistency checks, or private-token leak detection. Such an adapter cannot be replaced with an exclude-only copy without weakening the release boundary.

Local-AI native admission metadata

localAIModelPolicy is the sole authored application model policy. Its exact shape is:

{
  "verified_only": true,
  "models": [
    {
      "name": "EXAMPLE:8b",
      "definition": "Example-8B.Modelfile"
    }
  ]
}

If the field is omitted, the effective policy is {"verified_only":true,"models":[]}. No additional fields are accepted. Model names must be bounded canonical Ollama identifiers other than OPENAI; definition values must be unique, safe app-relative Modelfile or *.Modelfile basenames. Every declared definition must be a nonempty regular file no larger than 512 KiB, covered by the positive app include rules, and preserved in the release inventory. Names and definition basenames are also unique under case-insensitive canonical comparison.

The application packager copies the normalized value to ARCANE_APP_RELEASE.json at app.localAIModelPolicy. That file is native admission metadata, not an application-facing manifest. The native OS packager then:

  1. authenticates the published release identity and exact file inventory;
  2. validates policy/capability parity: verified_only:false requires ai.models.unverified.inference, while that capability is forbidden for a verified_only:true policy;
  3. requires the complete set of app-owned Modelfile and *.Modelfile files in apps/<id>/ to match the declared definitions exactly;
  4. preserves each owning app's definitions under native app/<id>/ and keeps platform-owned definitions sourced only from arcane/models/ under native app/shared/; and
  5. projects the normalized policy only into the compiled Core descriptor as APP_DESCRIPTOR.aiModelPolicy.

The projected browser arcane-bundle.json, arcane-app-package.json, document catalogs, browser application files, and window.Arcane results omit localAIModelPolicy, aiModelPolicy, and the alias mapping. The native target also omits ARCANE_APP_RELEASE.json and the authored arcane-package.json from its browser web tree. App-owned Modelfiles may remain packaged content when an app uses their text for a canonical prompt, but that does not expose or delegate the policy: only compiled Core receives the alias-to-definition authority. Bundling another app's web interface likewise does not inherit that app's model policy.

The checked-in dist/<app-id>/ARCANE_APP_RELEASE.json itself remains physically present at the application release root and contains the generated policy. An app-owned public inventory can exclude both release-inventory records, and the native renderer projection drops ARCANE_APP_RELEASE.json, but those facts do not by themselves make a directly hosted dist/<app-id> record unfetchable. Browser runtime code, manifests, catalogs, and generic metadata APIs must not consume or project it. A static deployment that claims the record is unavailable must configure and verify an explicit server/CDN exclusion. Fetching declaration bytes would still convey no native Core capability or model authority; this is an authority boundary, not an unsupported secrecy claim.

With verified_only:true, Core evaluates only declared managed candidates. An installed alias must exactly match the packaged definition and current native resource evidence. A missing alias may proceed through the managed lifecycle only after the installed base, or a pull with complete integrity-bound pre-download evidence, passes admission; Core verifies the resulting alias before inference. With verified_only:false, Core may additionally expose any already-installed model that passes the same resource admission, but only for inference and only with ai.models.unverified.inference; unverified models are never pulled, created, repaired, copied, pushed, or deleted. Applications render the authoritative Core catalog and do not reconstruct this policy in the browser.

Admitting an app-published release into Arcane OS

The app owns every source, adapter, generator, content rule, behavior suite, and release decision needed to publish dist/<app-id>. Its successful app release writes ARCANE_APP_RELEASE.json, which binds the app identity, entry point, local-AI native admission policy, and exact published inventory, byte sizes, and SHA-256 hashes. A document-catalog app therefore owns its reviewed-document policy, public-audience and sensitivity checks, native authorization, content generation, content lock, source/provenance mapping, exact counts, extension allowlist, and leak detection. Those rules run during the app release, not during an Arcane OS build.

Arcane OS treats the checked-in dist/<app-id> tree as an opaque app-published payload. Its generic admission boundary validates ARCANE_APP_RELEASE.json, proves the declared files are the exact physical set with matching sizes and hashes, rejects unsafe topology and undeclared bytes, checks OS capability/catalog policy, and projects only that admitted payload into isolated native staging. It must not import apps/<app-id>/scripts, read an app's canonical source or working corpus, invoke an app adapter or source verifier, regenerate app content, or rerun app-owned publication or business-behavior checks. A missing, stale, linked, extra, or byte-mismatched published release fails immediately at this generic boundary and returns ownership to the app release process.

An independently maintained application uses the same final dist/<app-id> contract without keeping its source in the Arcane repository. Its native registry entry sets "ownership": "external" and must exactly project the schema-2 descriptor authorized in arcane-external-apps.lock.json. The lock records immutable repository, commit, numeric release, artifact, proof, descriptor, permission, method, security, target, Core requirement, document-count, and release-consistency identities. The consistency hashes prove that the descriptor, release manifest, and inventory agree with one another; they do not authorize the artifact. Authorization comes separately from either a pinned GitHub artifact attestation or an independent Ed25519 signature over the deterministic Arcane authorization projection. Arcane also queries GitHub by the locked immutable repository and release IDs and requires the exact numeric tag/title plus artifact/proof asset IDs, names, uploaded state, byte sizes, and any API-reported SHA-256 digests for either authority kind. Arcane never updates this lock from publisher-controlled data automatically.

The external repository creates one deterministic .arcane-app.tar.gz with the supported SDK. Its exact USTAR order is ARCANE_APP_BUNDLE.json, arcane-app.json, payload/ARCANE_APP_RELEASE.json, then the sorted release inventory beneath payload/. The SDK emits only canonical regular-file headers, two terminal zero blocks, and one exact gzip member. The app repository owns its source validation, adapter execution, behavior tests, licensing, release bytes, and publication. The Arcane admission process executes none of that application code.

Use npm run app:external:inspect -- <app-id> --artifact <bundle> --proof <proof> to authenticate and fully verify a candidate without installing it. Use npm run app:external:admit -- <app-id> --artifact <bundle> --proof <proof> only after the reviewed lock authorizes those exact files. Both commands acknowledge before disk, hash, network, or child-process work; copy each input through one stable retained handle while hashing it once; authenticate and verify the GitHub release record before decompression; enforce compressed, expanded, entry, per-file, path, and ratio ceilings; recompute the deterministic gzip identity while streaming; and hash each payload file as it is written to a private stage. The extraction root is created only after caller callbacks and authority checks finish; preseeded topology fails closed, no public callback runs between verified extraction and placement, and the complete extracted tree is re-enumerated against captured file and root identities before dist/<app-id> can be replaced atomically. Pre-commit cancellation or failure removes the private stage and preserves the prior release. The prior-release backup remains available for rollback until the new target is committed. After commit, backup/stage/runtime-lock cleanup or event-delivery degradation is returned as an explicit operational warning instead of falsely reporting that the installed release failed. An inspect receipt is intentionally non-retained; an admission receipt is process-local, so a later OS build verifies the installed release once in its own process boundary and independently enforces Core protocol/version/features, supported methods, required browser/portable targets, capabilities, and any locked document count.

Do not delete apps/<app-id> merely because an external repository or release exists. First admit the independently authorized artifact, mark the native registry entry external, and pass the source-free browser and every supported native-target check from the exact admitted tree. Then remove only that application's redundant Arcane source in a separately reviewable change. Git history preserves the former mirror; it is not an active fallback checkout.

For a document-catalog app, the already-published manifest may describe public records, generated content, and matching inert originals. The OS can verify and project those declared bytes; it cannot decide whether a source record was appropriately classified, regenerate the catalog, or substitute another source asset. This separation prevents an Arcane OS patch from becoming an implicit application release.

empty-unpublished remains the zero-record option for applications that declare a document-catalog destination without publication authorization. Classification metadata by itself is never permission to convert that policy into a nonempty package.

Verification and boundaries

The packager rejects unsafe relative paths, absolute paths, backslashes, traversal, overlapping includes, case-insensitive destination collisions, missing files, symlinks and junctions, and non-file entries. Repository metadata, .env files, local, and nested app node_modules are never published. Shared dependencies must be explicitly routed; the whole root node_modules directory cannot be selected.

The staged package receives ARCANE_APP_RELEASE.json, containing app identity, semantic version, entry point, normalized local-AI native admission policy, a digest of the effective include/exclude/shared/adapter policy, byte counts, and a sorted SHA-256 inventory. Verification fails if packaging policy drifts or a file is modified, added, deleted, linked, or replaced by a special filesystem entry. Only a verified stage replaces dist/<app>.

The importable implementation is the repository-only tools/app-packager/core.mjs; its access boundary is explained in Repository and download access. A future native Arcane administration UI can call this library through narrowly scoped host operations such as package list, build, and check. Browser pages and DBOPFS do not have authority to enumerate repository sources or write dist/, so packaging is not exposed as an unbounded browser filesystem or process API.

Repository and reviewed source access