Build
Application packaging
Positive app inventories, shared payloads, adapters, deterministic output, and verification.
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
- Run focused source tests while iterating.
- Run
npm run checkbefore ordinary push or handoff. - When an internal demonstration needs a real package, run
npm run demo:check -- <app-id>. - 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. - Do not run
--allmerely 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, orprereleaseselects the revision.--preid betaselects a prerelease identifier.--set 2.0.0packages an exact new version.bump <app> 2.0.0changes 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:
- authenticates the published release identity and exact file inventory;
- validates policy/capability parity:
verified_only:falserequiresai.models.unverified.inference, while that capability is forbidden for averified_only:truepolicy; - requires the complete set of app-owned
Modelfileand*.Modelfilefiles inapps/<id>/to match the declared definitions exactly; - preserves each owning app's definitions under native
app/<id>/and keeps platform-owned definitions sourced only fromarcane/models/under nativeapp/shared/; and - 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.