aontu

Publish a package

Publish a package with aontu publish, gated on compatibility with the version before it, into a local repository that aontu pkg serve serves and aontu sync reads.

Rendered from docs/how-to/publish-a-package.md in the engine repository, where a correction belongs, and where the test suite executes every example on this page.

A package is a versioned, signed archive of a module. Publishing one sends three objects to a repository: the manifest that describes the archive file by file and pins what the module means, a proof that signs the manifest, and the archive itself. A consumer’s sync fetches the three, checks the proof, the bytes and the meaning in that order, and refuses at the first that does not hold. This guide publishes into a directory, which is a repository a laptop can serve, and gates the next version on the one before it.

The package declares its path, its version, its entry, and that it may leave the machine. Its service/pkg.aon:

pkg: { path:"corp.example/schemas/service" version:"1.4.2" main:"service.aon" }
publish: public

and its entry, service/service.aon:

name: string
port: *8080|integer

A publish is signed. The key is an Ed25519 private key in PKCS#8 PEM, and aontu pkg keygen mints one, once, printing the signer id that a consumer will name:

$ aontu pkg keygen key.pem
...

The line is signer: ed25519:…, the public half of the key. Keep the file where a CI job can read it and nobody else can; the id is not a secret.

A dry run, then the publish

Without --yes, publish runs every check and sends nothing. --to names a directory to publish into instead of the write path:

$ aontu publish --key key.pem --to repo service
verdict: dry-run
corp.example/schemas/service 1.4.2 public
archive: sha256:8a26b9f1b78620c8d452e69e9681a31027df431c833896bc98545627e6df3016 (2 files, 339 bytes)
module: corp.example/schemas/service service.aon aon1-oQs6Ng6XxP2FHQGTYescREGDrDPfLLW1Liq4OS8Gs2E
file: pkg.aon sha256:7cc6671240e0c5f327dad20a10361edb98c687430eeadc9fc3d9604e287a11f4 96
file: service.aon sha256:36851584bea3d7109995deae363852734aca001c9e494b8868d736c5c49e7275 33
...
dry run: nothing sent (add --yes)

Read it top to bottom: the package and version, the archive’s digest and what it holds, the module’s canon-hash, then the manifest’s digest, the signer, and where it would go. Then send it:

$ aontu publish --yes --key key.pem --to repo service
verdict: sent
...
sent

The directory now holds the read-path layout: pkg/<path>/@v/ with the archive, manifest and proof, the version list and @latest, and an advisory. It is a repository. Nothing in it was evaluated by the repository, and nothing in it is trusted by a consumer for more than availability: the consumer recomputes every pin.

The gate

The version after this one must admit every document this one admitted, and resolve every position this one resolved, to the same value: a consumer whose build worked keeps the same build. publish fetches the highest version the repository holds and compares. A candidate service-1.4.3/pkg.aon:

pkg: { path:"corp.example/schemas/service" version:"1.4.3" main:"service.aon" }
publish: public

whose service-1.4.3/service.aon adds an optional key:

name: string
port: *8080|integer
owner?: string

is compatible, and the report names what it was gated against:

$ aontu publish --yes --key key.pem --to repo service-1.4.3
verdict: sent
...
against: corp.example/schemas/service 1.4.2
...

A candidate service-1.5.0/pkg.aon:

pkg: { path:"corp.example/schemas/service" version:"1.5.0" main:"service.aon" }
publish: public

whose service-1.5.0/service.aon makes that key required:

name: string
port: *8080|integer
owner: string

is refused, with the key it refuses on, and nothing is written:

$ aontu publish --yes --key key.pem --to repo service-1.5.0
verdict: breaking
...
$.owner: the general value requires this key; the specific value makes it optional, so instances without it are admitted
...
$ echo $?
1

Every 1.4.3 consumer without an owner would stop evaluating, so the release is refused, and there is no major version to bump past the gate: compatibility is decided, not asserted. A breaking change ships as a new package path, and the last version of the old path declares moved: <new path>, which refuses every import of the old name with the destination.

Serve it, and read from it

aontu pkg serve serves the directory over HTTP on a loopback address, byte for byte, and runs until interrupted:

$ aontu pkg serve repo
serving /home/me/repo at http://127.0.0.1:8017

A consumer names the base and the signer it accepts in its own pkg.aon, and syncs. A base is https, or http on a loopback host, which is what makes the laptop’s registry reachable:

pkg: { path:"corp.example/checkout" main:"main.aon" }
dep: "corp.example/schemas/service": v: "1.4.2"

repo: base: ["http://127.0.0.1:8017"]
repo: trust: "corp.example/*": { signer:"ed25519:…" inclusion:none }
$ aontu sync
verdict: ok
fetched: corp.example/schemas/service 1.4.2
corp.example/schemas/service 1.4.2 aon1-oQs6Ng6XxP2FHQGTYescREGDrDPfLLW1Liq4OS8Gs2E

aontu get corp.example/schemas/service@1.4.3 raises the dependency and syncs; aontu pkg outdated asks what could move. A version is selectable by name at once, and by get without a version only after the repository has held it for three days, so a compromised publisher’s newest release does not reach every consumer the hour it lands.

Publishing to the public repository is the same command without --to: aontu publish --yes --key key.pem --token token from a CI job, where the token is the forge’s OIDC token and the write path decides admission from it. The verb’s full contract is aontu publish; the consumer side is vendor a dependency closure.

The public repository is not serving yet. Its default bases have no address, so until they do a project names its repo.base, and a publisher writes with --to <dir> and serves with aontu pkg serve.