/** * Building a module's published payload — the one computation both gates run. * * `telo publish` pushes it and `telo release` digests it, and they have to be * the same bytes or the ledger's number and the registry's number are answers to * different questions. So there is one builder, and the publish command consumes * it rather than carrying its own copy of the transform. * * ## What makes the bytes a pure function of the commit * * Three inputs used to leak in, and each is closed here: * * 1. **Pins were discovered, not authored.** Publish fetched each remote * dependency's published `telo.yaml` to derive its hash, best-effort — so one * commit produced different manifest bytes depending on network reachability. * An author's pin is now the input: `telo install` / `telo upgrade` write it, * and an unpinned remote import is refused here rather than resolved. * 2. **Re-serialization was conditional** on at least one pin having been * written, so the same manifest serialized two ways depending on its own * content. It is unconditional now. * 3. **A sibling's pin came from the registry.** An in-repo sibling is on disk, * so its published manifest is *derived* — recursively, through this same * builder — which is what lets a whole release batch be planned offline, with * post-bump versions the registry has never seen. * 4. **The derived manifest was not the published one.** The `layers:` index was * injected during the push, so what this builder returned was a document no * registry ever holds — and a sibling pin, being a hash of it, named bytes * that do not exist. The index is written here now, which is what makes * `publishedManifest()` true to its name and requires layer framing to be a * pure function of the files it covers. * * The publish destination stays an input, because canonicalization writes it * into the manifest. It is not derivable, so the ledger records which base its * digests were taken against. */ import { type ArtifactSelector, type LayerRole } from "@telorun/analyzer"; import { type PayloadFile } from "@telorun/kernel"; import { type Partition } from "./partition-layers.js"; /** A layer built from a working copy: what publish pushes and what the release * ledger digests. Mutable-shaped because the transport's own `PayloadLayer` is, * and the two are handed straight across. */ export interface BuiltLayer { role: LayerRole; selector?: ArtifactSelector; files: PayloadFile[]; } export interface ModulePayload { /** The `telo.yaml` that ships, byte for byte: canonicalized, pinned, includes * inlined, `layers:` index written. A dependent's import pin is a hash of * exactly this. */ readonly manifest: string; readonly layers: BuiltLayer[]; readonly partition: Partition; /** * Absolute paths of every file the controller builds read — the module's own * sources, the shared libraries they inline, their dependency tree. The * release edge graph is built from these; nothing else can see what a bundle * actually contains. */ readonly buildInputs: readonly string[]; /** * In-repo siblings this module imports relatively: the sibling's absolute * manifest path (the release edge) and the ref the import canonicalized to * (which publish checks resolves at its published location before pushing). */ readonly relativeImports: readonly { readonly manifestPath: string; readonly ref: string; }[]; /** * Remote imports carrying an author-written pin, already split into the ref * and the hash it claims. * * Split HERE because this is the only place both spellings are in hand: a * scalar-form pin carries its hash as a `#sha256-…` fragment on the source, * and the object form carries it as an `integrity:` sibling. A consumer handed * only the ref can recover the first by re-splitting and the second not at all * — which is exactly how the verification below came to be a silent no-op for * every pin in this repo. * * Publish verifies these against the registry — fetch, compare, hard-fail on * mismatch. A sibling-derived pin is deliberately not here: it was computed * from local bytes the registry has not seen yet, and the batch pushes * dependencies first precisely so it does not have to have. */ readonly authoredPins: readonly { readonly alias: string; /** The ref with no integrity fragment — what the registry is asked for. */ readonly ref: string; /** The hash the author committed. */ readonly integrity: string; }[]; } export interface PayloadBuilderOptions { /** The read-only registry origin `registry://` refs resolve against. Not the * publish destination, which is per module and passed to `payload()`. */ readonly registryOrigin?: string; /** Where the controller build cache lives. */ readonly cacheRoot: string; } /** * One module's payload, memoized across a batch. * * Memoized because a release digests every module in a workspace and the * standard library's import graph is dense — `modules/sql`'s published manifest * is needed by five dependents to derive their pins, and re-deriving it per * dependent would rebuild its controller each time. One memo, not two: a * module's published manifest carries its `layers:` index, so producing the * text and producing the payload are the same computation. */ export declare class ModulePayloadBuilder { private readonly options; private readonly payloads; /** Manifest paths whose payload is in flight, so an import cycle is reported * rather than deadlocking on a memo entry that is awaiting itself. */ private readonly deriving; /** * Where each manifest publishes. * * A ROOT's destination is an input — which repo a module publishes to is a * policy no graph can answer, so the caller states it (the release derives it * as `/`, the rule the OCI mirror has always used). * * A SIBLING's is *derived*: the parent's destination with the relative path * applied, which is the transport's own rule and exactly what canonicalization * writes into the manifest. That one is not a free choice — deriving it * independently, from the directory name say, would work for this repo's * layout and quietly disagree with the ref the artifact actually carries the * moment a layout differs. * * When a module is reached both ways and the two answers differ, * `claimDestination` refuses rather than picking one, because the manifest can * only carry a single ref. */ private readonly destinations; constructor(options: PayloadBuilderOptions); payload(manifestPath: string, destination: string): Promise; /** * The in-repo siblings this module imports relatively, without building a * single controller. * * Publish ORDER needs only this — a dependency must be pushed before its * dependents, because canonicalization writes the sibling's ref into the * manifest and publish then hard-fails when it does not resolve — and running * esbuild across the whole standard library to answer it would be minutes of * work for a question the manifests already contain. */ relativeImportsOf(manifestPath: string, destination: string): Promise; /** * The published `telo.yaml` text — byte for byte what the transport pushes, * which is what a dependent hashes to derive its pin. * * It builds the module's layers, because the `layers:` index is inside that * text and each entry's `blob` covers framed bytes. Deriving the manifest * alone was cheaper and wrong: the number it produced was of a document that * is never published. */ publishedManifest(manifestPath: string): Promise; private assertNotDeriving; private claimDestination; private destinationOf; /** The transport that owns where this manifest publishes. It decides both the * ref a sibling import canonicalizes to and the framing a layer's `blob` * digest covers, so both halves of the published manifest come from one. */ private transportFor; /** * Canonicalize, pin and inline — every rewrite that stands between the * author's `telo.yaml` and the published one. */ private transformManifest; private siblingHash; private buildPayload; } //# sourceMappingURL=module-payload.d.ts.map