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 route to the installed public SDK or to thin Arcane OS command wrappers:
| 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 |
| Materialize the exact installed SDK distribution runtime | npm run sdk:materialize |
node tools/materialize-installed-sdk-runtime.mjs |
| Develop one app against an explicit live canonical SDK source | npm run app:dev:sdk-source -- <canonical-sdk-root> --app <app> |
npm exec -- arcane dev --workspace . --sdk-runtime-source <canonical-sdk-root> --app <app> |
| 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 development lifecycle does not run package verification, tests, checks, or regeneration merely because source changed or a push began. Select npm run demo:check -- <app-id> only when the user explicitly requests that package or demonstration output. The other test, check, integration, and release commands remain explicit tools and do not run through the Git pre-push hook.
npm run sdk:materialize is the distribution-source boundary for the checked-in arcane/ tree. The thin wrapper calls the installed package's public materializeInstalledSdkRuntime({workspaceRoot}) export; the SDK resolves the declared npm alias, atomically refreshes the whole projection, and removes files that are no longer present in the installed runtime. Its summary reports the materialization status, selected package identity, and workspace-runtime result kind. Do not copy SDK source or hard-code inventory counts. Run the command after intentionally changing the exact SDK dependency and before packaging from the refreshed distribution files.
Development package workflow
- Change the smallest source-owned surface without automatically running tests, checks, builds, or packages.
- When the user explicitly selects an internal demonstration package, run
npm run demo:check -- <app-id>for that app only. - Review selected 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 runs no tests, checks, builds, or package verification. A selected demo command packages its one explicit target. When the user selects an application release, regenerate only that application and review its source and generated diffs together; any behavior suite or package check remains separately selected work. A separate Arcane OS release consumes the checked-in dist/<app-id>/ARCANE_APP_RELEASE.json contract without reading app source, importing an app adapter or source verifier, rebuilding the app, or running its content or business-behavior suites.
Live SDK source development
npm run app:dev:sdk-source -- <canonical-sdk-root> --app <app-id> is the explicit development-only live-source route. The installed SDK CLI validates that the supplied root is the canonical arcane-os SDK package at the exact executing SDK version before it starts listening. It then serves the live SDK runtime at /arcane, strong-type at /arcane/dependencies/strong-type, and the browser SDK runtime at /arcane/sdk through the SDK-owned source-mount contract.
The returned descriptor is ephemeral process state: mode is sdk-source, protocol is arcane-sdk-runtime-source/1, mutable is true, and distributionAuthority is false. It is not a distribution projection and must not be persisted as one. The command selects one app and one source root; it performs no all-app or target loop. If the canonical SDK source advances before Arcane OS installs the same published version, the exact-version check intentionally stops this route until those authorities match again.
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 keeps only its launcher style in Arcane OS and serves SDK assets from the installed SDK runtime's dynamic positive arcane/** inventory.
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 application discovery plus 25 small policy classifications, reads only the available browser release manifests, authenticates the installed SDK runtime once, and creates at most 17 app listeners plus one catalog listener sequentially. It performs zero package builds, signatures, model pulls, or Arcane OS work. Application files stream only when requested; each requested SDK catalog path is authenticated once through the SDK reader and retained for later requests in the same process. Ctrl+C closes every owned listener, and a startup failure closes any partially opened set. The command is a local test transport, not a replacement application-package 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", "dependencies", "entities", "img", "modules", "sdk", "security"],
"exclude": []
},
{
"source": "node_modules/arcane-sdk",
"destination": "licenses/arcane-os",
"include": ["LICENSE", "COMMERCIAL-LICENSE.md", "NOTICE"],
"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. The exact second route binds the collision-safe arcane-sdk dependency alias; no separate profile field or private SDK path is accepted.
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 frommachine_bundles/arcane-os-machine-bundle/src/core/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. A release that declares a portable or native target must have a native registry entry with "ownership": "external" that exactly projects the schema-2 descriptor authorized in arcane-external-apps.lock.json. A descriptor whose complete target list is exactly ["browser"] stays outside that native registry and is admitted only to the authenticated browser catalog; adding any other target restores the exact native-registry requirement. 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 and pass the source-free browser check from the exact admitted tree. For any portable or native target, also mark the native registry entry external and pass every supported target check; an exact browser-only descriptor must remain absent from that registry. 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 published Arcane SDK's public arcane-sdk/packager export. Arcane OS keeps only its command wrapper and consumes that SDK contract like any other application. A future native Arcane administration UI can call the public 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.