The output of OapiCodemode.Ingest.decompose/1: one document taken apart
into the pieces a SpecStore persists, with nothing dereferenced inline.
The split is by provenance, because the parts have different lifetimes:
canonical/content_hash— the evidence. Minified JSON with keys sorted at every level, and its sha256. Every other field is an interpretation computed from it, so two uploads that differ only in formatting, key order or YAML comments are the same document and interpretations can be dropped and rebuilt from it.spec— what the document says about itself:%{title, version, openapi, servers, security}, scalar-coerced (a non-stringtitle,versionoropenapiis nil; a server without a stringurlis dropped).projection— what this decomposer derived:%{security_schemes, tags, operation_count, decomposer_version, index_bytes}.decomposer_versionnames the interpretation, so a resolver fix that bumps it produces a second projection of the same evidence rather than mutating the first.index_bytesis the encoded size of the slim search index, computed here so the registry's cap is enforced at ingest rather than at loop start.operations—[%OapiCodemode.Operation{}]. Payload refs (schemas, request bodies, responses, headers) are preserved verbatim; only the refs that decide which operations exist and how they are addressed are resolved during decomposition.components—%{kind => %{name => json}}for the standard component kinds exceptexamples;x-*extensions undercomponentsare dropped.
Summary
Types
@type t() :: %OapiCodemode.Decomposed{ canonical: binary(), components: %{required(String.t()) => %{required(String.t()) => term()}}, content_hash: String.t(), operations: [OapiCodemode.Operation.t()], projection: %{ security_schemes: map(), tags: [String.t()], operation_count: non_neg_integer(), decomposer_version: pos_integer(), index_bytes: non_neg_integer() }, spec: %{ title: String.t() | nil, version: String.t() | nil, openapi: String.t() | nil, servers: [map()], security: list() | nil } }