ADR-012 — Minimal on-disk pack_dir layout (resolves OQ2’s common core)

Status: proposed (2026-08-30).
Context: ctg_open(pack_dir, …) takes a directory, but nothing defines what is inside one — OQ2 leaves packaging "likely per-platform (AAR, .so+assets, KMP artifact), with a common descriptor+manifest". Every consumption mode (hub, source build, internal distribution) already "resolves to the identical on-disk shape: descriptor + artifacts + manifest", yet no packager, verifier, or staging plugin can be written against a shape that is never pinned. The first packager (packageCartridge for the functiongemma-270m family) needs the answer now.
Decision: Pin only the common core a pack_dir MUST have, leaving per-platform carriers (AAR, zip, OCI layer) as transport around it: descriptor.json (the capability descriptor, byte-identical to the copy whose digest the manifest’s descriptor artifact entry pins), manifest.json (the signed manifest; the one file never listed in its own artifacts), and artifacts/ — every manifest artifact’s path resolves relative to pack_dir root, and by convention producers place payloads under artifacts/ grouped by role (artifacts/runtime/libwhisper-ctg.so, artifacts/weights/model.gguf, artifacts/model/module.vmfb, artifacts/tokenizer/tokenizer.json). The runtime library the host dlopen`s is the artifact with `role: runtime — the descriptor does not repeat its path. Nothing else at root; a producer MAY add dot-prefixed files (.provenance/, .cache/), which verifiers MUST ignore and MUST NOT sign. A carrier format (AAR, zip) is compliant iff extracting it yields exactly this shape.
Consequences: ctg-conform and the Phase-2 staging plugin get a target shape to verify and stage; digests keep working unchanged (they were always path-relative); per-platform packaging debate (OQ2) narrows to "which carrier", no longer "which layout"; the Forge’s older specVersion: 0.1 manifest lineage is explicitly out of scope of this ADR.