template publish init | add | build | rotate | revoke | verify creates keys, signs packages, builds a verified static tree, and
manages key rotation and revocation. It does not upload: publishing the built tree to
HTTPS hosting stays with your existing deploy tooling.
Everything below runs offline. Private keys never leave your workspace, and the protocol
appendix at the end remains the interoperability contract for anyone implementing it
independently.
What you publish
A tap is a static HTTPS directory containing one signed index and immutable, content-addressed package envelopes:index.json is at /community/index.json, llmwiki derives each package URL
as /community/packages/sha256/<hex>.json. The package must stay on the exact
scheme, host, and port used by the index. Redirects to another origin are
refused.
Every release has a coordinate:
1. Create a workspace and keys
0600 under my-tap/keys/, and records a workspace at sequence 0. Private keys are never
printed and are never overwritten. The command prints each public key’s SHA-256
fingerprint — publish those fingerprints through the channel your users trust.
2. Sign a package
add validates the package with the same validator a consumer runs, derives its
coordinate and payload digest, signs the package claim, and records the coordinate as
immutable: the same coordinate may never resolve to different bytes.
3. Build a distribution
build advances the sequence, signs the index, writes packages before the index, and
verifies the complete tree as a consumer would before publishing it. Only then does it
swap the tree into --out and commit the new sequence. A failed build leaves the
workspace at its old sequence, so a retry is a clean re-run.
To renew a distribution whose lifetime is expiring without changing its contents, rebuild
with --refresh. It republishes the same tree under a fresh --expires-in window and
advances the sequence. Without it, a build that finds nothing changed is refused, so an
accidental rebuild never burns a sequence.
Serve ./dist from ordinary HTTPS hosting.
4. Rotate and revoke
build. This is not an
implementation detail: a rotation claim carries the sequence of the index that publishes
it, and that sequence is only known at build time.
A publisher-key rotation re-signs every package with the successor key. It must:
a package signed by a retired key stops verifying the moment the index announces its
successor, because a verifier resolves the publisher key by id from the current index.
The payload never changes, so digests and filenames are stable.
Revoking the active publisher key requires rotating to a successor in the same build —
an index that announces a revoked key is one no client will accept.
5. Verify before you publish
publish verify checks one self-contained snapshot and refuses an index carrying
rotations, because a latest-snapshot-only directory holds no pinned keys to walk a chain
from. That is a scope limit, not a defect: build already verified the rotation against
your workspace’s own key history, and a real client verifies it against the keys it pinned
from your previous release.
Protocol appendix
The rest of this guide is the wire contract. You do not need it to publish with the CLI above; it exists so anyone can implement the protocol independently, and so you can audit exactly what the CLI signs.Create separate signing keys by hand
Use separate Ed25519 keys for the tap and each publisher. The tap key signs the catalog snapshot. A publisher key signs that publisher’s package claims. This Node.js example creates SPKI public bytes and PKCS8 private bytes in the base64 representation used by the protocol:acme-publisher-2026-01 and community-tap-2026-01.
The tap root key must reach users through a trusted channel independent of the
tap server. Users provide it explicitly when adding the tap; llmwiki does not
use trust on first use.
2. Build the template payload
The signed payload is an ordinaryProfileTemplatePackage with
sourceType: "remote". The template id and profile id must match.
sourceType: "local" through llmwiki template init --file. Restore sourceType: "remote" before signing the release
payload. Remote installation repeats package validation, profile validation,
connector-binding checks, and minimum-version checks, so a signature cannot make
an invalid package installable.
3. Canonicalize, digest, and sign the package
All digests and signatures use RFC 8785 canonical JSON bytes. Do not sign pretty-printed JSON or rely on object insertion order. The implementation uses thecanonicalize npm package.
{ coordinate, payloadDigest }, not
the full envelope:
4. Build and sign the tap index
The index lists publisher public keys and every release currently available through the tap:signature, sign that complete unsigned object
with the tap private key using the same canonicalBytes and signature
helpers, and then add the resulting signature field.
For every new snapshot:
- Increase
sequence; never reuse or decrease it. - Set valid UTC
generatedAtandexpiresAtvalues. - Keep every coordinate bound to its original digest forever, even if it is temporarily absent from a later index.
- Publish package envelopes before replacing
index.json. - Replace
index.jsonatomically at the hosting layer.
5. Verify the distribution offline before publishing
Before uploading anything, verify the built tree exactly as a client would verify the bytes it downloads. This runs offline, reads only, and writes nothing.--json for the same result as a stable object. Failures exit non-zero with a
bounded reason and never echo file contents or local paths.
Snapshot scope. This command verifies one self-contained snapshot. It refuses any
index carrying rotations or a tapKeyRotation, because a rotation chain is only
meaningful against a client’s previously pinned key, and a fresh offline check has no
pins to walk from. That is why the output says not_applicable_no_rotations. Verify a
rotation-bearing index the way a user experiences it: from a clean client that already
pinned the old key, as in the next section. See Rotate or revoke deliberately.
6. Verify from a clean client
Distribute the tap public key, its fingerprint, and the index URL through an independent trusted channel. Then test exactly what users will run:7. Publish an update
Never replace an existing release. Create a new payload with a higher semantic version, sign a new envelope, upload it at its new digest path, and publish a higher-sequence index containing the new coordinate. Users can then preview and apply the update:8. Rotate or revoke deliberately
Publisher key rotation is a signed chain. Each rotation record identifies the publisher, old key id, new public key, and effective sequence. The canonical rotation claim is signed by both the old and new private keys. Retain the rotation history in later indexes so clients that skipped snapshots can walk from their pinned key to the current key. Tap-root rotation uses the same dual-signature principle throughtapKeyRotation. Users who cannot verify a cooperative rotation must explicitly
forget and re-add the tap, which resets its trust history.
An index carrying
rotations or a tapKeyRotation is refused by
llmwiki template publish verify, which checks one self-contained snapshot and
holds no pinned keys to walk a chain from. This is a scope limit, not a defect in
your index. Verify rotation-bearing snapshots from a clean client that already
pinned the previous key, exactly as your users will experience the rotation.revocations record with kind package and the
payload digest, or kind publisher-key and the key id. Include a reason and UTC
revokedAt timestamp. Revocations accumulate monotonically in client state;
removing one from a later index does not restore trust.
Release checklist
- The payload is declarative and validates with the target llmwiki version.
templateId,profileId, publisher, and coordinate agree.- The package claim and index use RFC 8785 canonical bytes.
- Package files are uploaded before the new index becomes visible.
- The sequence increased and the expiry window is intentional.
- The tap root key is distributed outside the tap server.
- A clean client can refresh, verify, install, and report clean status.
- Previous coordinates still resolve to their original immutable bytes.