# Packaging and release output

Packaged native and sandboxed-page plugins contain exactly `manifest.json` plus
the declared entry, styles, and assets. The manifest integrity map covers every
non-manifest byte and is written last as the generation marker. Native entries
are self-contained ESM with no runtime imports.

Native plugins declaring `ui.editor.extension` also contain the manifest's
`editorExtensionBuild.path`. The generated esbuild script emits this normalized
metafile proof, binds it to the native entry digest, externalizes the exact
CodeMirror/Yjs/y-codemirror/lib0 set, and rejects any remaining runtime import
or bundled copy. Use public editor-extension declarations only through
`import type`; construct extensions from the modules supplied to the
registered factory.

Run the project build, public validator, contract tests, and package command.
The CLI creates a deterministic `.thortiq-plugin` archive and never runs the
plugin or a package-manager hook. Source, `node_modules`, VCS state, environment
files, credentials, private keys, detached checksums, and undeclared files are
invalid installed payload.

Distribution signatures and provenance are detached metadata. The developer-kit
release signature uses the `thortiq-developer-kit-release-v1` trust store;
plugin-publisher signatures use a distinct publisher trust store.

## Detached publisher signing

Package publisher metadata uses
`schemas/signed-package-v1.schema.json`. Version 1 binds the publisher and
fixed Ed25519 key ID to the package ID/version, manifest/API/runtime,
deterministic archive and installed-package SHA-256 digests, update policy,
build identity, public provenance, and canonical UTC timestamps. The signature
is unpadded base64url. `expiresAt` may be `null`; when present it must be later
than `issuedAt` and no more than 366 days later.

Keep the metadata beside the `.thortiq-plugin` archive, never inside it.
Start from a schema-shaped JSON draft whose `signature` has `algorithm:
"Ed25519"`, `encoding: "base64url"`, and `keyId`, but no `value`. Then run:

```sh
thortiq-plugin signing-payload ./example.thortiq-plugin \
  --metadata ./example.signing-draft.json \
  --out ./example.signing-payload.json
```

The command revalidates the closed archive, checks both digests and every
identity field, and creates the one canonical byte payload with exclusive
file creation. Transfer those public bytes to the publisher's external signing
system, sign them with Ed25519, and add the resulting 64-byte unpadded-base64url
signature as `signature.value` in a detached metadata copy. The CLI deliberately
accepts no private-key argument or signing-key environment variable.

Verify a release without network access using a bounded local publisher-key
document:

```sh
thortiq-plugin verify-signature ./example.thortiq-plugin \
  --metadata ./example.signed-package.json \
  --trust-store ./trusted-publisher-keys.json
```

The key document is `{ "schemaVersion": 1, "keys": [...] }`. Each key record
contains `publisherId`, `publisherName`, `keyId`, fixed `algorithm:
"Ed25519"`, a 32-byte unpadded-base64url `publicKey`, canonical `validFrom`,
nullable `validUntil`/`revokedAt`, and nullable `replacedByKeyId`. Verification
does no network lookup, X.509 processing, or algorithm negotiation. A valid
signature proves package identity; it does not grant native execution trust,
permissions, connections, or contribution authority.

Install the validated archive externally with
`thortiq-plugin install <archive> --vault <vault-root>`. The CLI commits to
`.thortiq/plugins/<plugin-id>/` through same-filesystem staging with the
manifest last; `--replace` preserves the previous package until commit.
Package removal retains settings, storage, checkpoints, and secrets; exact
namespace deletion is a separate in-app irreversible action. See
[development-lifecycle.md](development-lifecycle.md).
