Add versioned v1 backfill artifact contracts - #3681
Conversation
Defines the six document families the changelog backfill pipeline passes between its stages (inventory, overrides, semantic model, plan, provenance, ledger), each wrapped in a small envelope carrying the document kind and schema version so readers fail fast and explicitly on documents they do not understand. Adds canonical JSON serialization and deterministic SHA-256 hashing so plans are content-addressed, plus first-class identity types (canonical PR/issue URLs, synthetic file identities). Contracts and serialization only: no CLI surface changes, no behavior changes to existing changelog code. Part of elastic/docs-eng-team#672
Mpdreamz
left a comment
There was a problem hiding this comment.
Request changes: this overcomplicates a one-off migration
The goal is narrow: get historical release-note bundles into S3 so the CDN has what already exists on elastic.co/docs/release-notes. This PR lands a six-family, versioned, content-addressed artifact platform (inventory / overrides / semantic-model / plan / provenance / ledger) before anything reads published notes or writes S3.
That is a lot of permanent machinery for what should be a temporary one-off CLI, removed once the upload is done — something like docs-builder changelog migrate-from-web.
What migrate-from-web needs to do (functionally)
- Know which products/versions are in scope (flags + a small config/cutoff list is enough).
- Fetch the published release-note content for those scopes (the live site, or the Markdown that backs it).
- Turn that into the existing bundle YAML shape we already upload/scrub/serve (
Bundle/ inline entries) — not a parallel “semantic model.” - Upload to
bundle/{product}/with create-only semantics (never overwrite). - Print what was created / skipped / failed. Done. Delete the command afterward.
What we do not need from this PR
- Enveloped, schema-versioned, hashed cross-stage “documents” (and please avoid that overloaded term next to Elasticsearch).
- Overrides / plan / provenance / ledger families for a throwaway tool.
- A custom CanonicalJson + content-addressing layer so stages can pin each other.
Paper trail
The durable output is the bundles on S3. If you want an audit trail, one run report (scope, source→bundle key, created/skipped/failed + reason, object ETag/hash) attached to the issue/PR is enough — not a versioned in-repo artifact platform.
Please collapse or close this foundation until a vertical slice of migrate-from-web proves any of this surface is still justified.
Summary
Adds the versioned v1 contracts for the six document families the changelog backfill pipeline passes between its stages, so components can interoperate deterministically and plans can be content-addressed. Contracts and serialization only — no CLI surface changes (
docs/cli-schema.jsonis untouched), no behavior changes to existing changelog code, and everything is new files under:src/services/Elastic.Changelog/Backfill/— the contracts (namespaceElastic.Changelog.Backfill), with aREADME.mdgiving a plain-language tourtests/Elastic.Changelog.Tests/Backfill/— the testsThe six document families
inventoryoverridessemantic-modelProductReleaseshape from the epic: category family, optional precise type / breaking-change subtype, title, description, impact/action, highlight, product references, canonical PR/issue URLs, areas, source locations) plus structured triage diagnosticsplancreate-bundle,create-amend,create-supplemental-bundle,skip-existing,manual-review,conflict), pinned to all inputs: source commits, input document hashes, current-state snapshot, optional enrichment snapshot, and the deployed scrubber allowlist identity (content SHA-256 and/or deployment commit)provenanceledgerIdentity types are first-class: canonical PR URL, canonical issue URL, and synthetic file identity (stable name + checksum, the thing amend retractions match on). PR/issue references must always be full canonical URLs — bare numbers are rejected by validation.
Envelopes and schema versioning
Every persisted document is wrapped in an envelope carrying
artifact(the family name) andschema_version.BackfillDocuments.Deserialize<T>checks both before parsing the payload and throwsBackfillDocumentExceptionwith an actionable message on: invalid JSON, a non-object top level, a missing header field, an unknown artifact name, a different document kind than requested, or a schema version other than the one this code supports (exact match per family, all currently 1). There is no silent best-effort parsing. Deserialized documents are treated as external input:requiredfields are enforced by the serializer, semantic validation runs on every read and write and reports all problems at once with their location (e.g.releases[0]: entries[2]: …), and documents over a 64M-character safety bound are refused.Canonicalization and hashing
A document's identity is SHA-256 over the UTF-8 bytes of the canonical form of its envelope, written
sha256:+ 64 lower-case hex. Canonical form (CanonicalJson):\r\n/\rinside strings normalized to\nThe pretty-printed on-disk form never affects the hash: reading a file back and re-hashing gives the same answer.
AOT
All serialization goes through the source-generated
BackfillJsonContext(snake_case properties, kebab-case string enums viaJsonStringEnumMemberName, nulls omitted). No reflection; the AOT publish of docs-builder is warning-free with these types included.Test plan
All verified locally:
dotnet formatclean ·./build.sh build --skip-dirty-checkgreen ·dotnet publish -c Release(AOT) zero trim/AOT warnings ·tests/Elastic.Changelog.Tests: 873 passed, 0 failed (67 new) ·docs/cli-schema.jsonuntouchedPart of elastic/docs-eng-team#672
Note: issue #672 also resolves where checked-in inventories, overrides, plans, provenance, and ledgers live long term; that placement discussion can follow up on this PR without changing these contracts.