Backstage catalog export implementation plan¶
Objective¶
Ship a useful catalog integration without coupling streamt's compiler to one catalog product or inventing an unverified mutating API. The first delivery is therefore a deterministic, offline Backstage catalog export built from a private, immutable, versioned neutral catalog projection.
The implementation must follow the normative
Backstage catalog export specification.
This plan orders the work into independently committable slices and assigns
non-overlapping file ownership so that focused agents can work without merging
partially compatible contracts.
Status — 2026-09-02¶
Implemented through the exact offline Backstage core-entity boundary. The normative specification is frozen and this plan is reconciled to its 2026-09-02 contract. The offline DataHub metadata-file export has since landed under its separate specification. Conduktor Console publication remains separate, unimplemented work.
| Slice | Status | Landed evidence |
|---|---|---|
| Normative contract | Complete | 35bf386, e596275, baae40e |
| 1 — schemas and validator | Complete | 7ccdd13; wheel/sdist inspection and Node validator foundation in 6fe5d91 |
| 2 — neutral projection | Complete | 4fc715b; strict adapter inputs in 9ee7439 |
| 3 — mapper and serializer | Complete | 8c26b4a |
| 4 — CLI and atomic output | Complete | 1d4404f |
| 5 — distribution and parity gates | Complete | 6fe5d91, 6057a6e |
| 6 — public documentation | Complete | Public reference, CLI reference, support matrix, release notes, and roadmap in this documentation slice |
The test, lint, type, documentation, wheel/sdist, isolated-wheel, and pinned Node parity checks are executable release gates. A release still requires its own green CI run; this status is not a substitute for that run's result.
The agreed first product boundary is:
- one Backstage
Systemfor the explicit streamt project and environment; Resourceentities for declared sources, physical topic outputs, and Gateway virtual topics;Componententities only for actual stream-processing units;- exact direct relationships derived from compiler resolution, never guessed transitive edges;
- the exact
streamt.dev/contractstate annotation on model outputs with a declared contract, not a schema body or copy of a full ODCS document; - canonical multi-document YAML for text output and the ordinary streamt JSON envelope for structured output;
- pinned, offline Backstage schema validation; and
- no catalog network client, provider mutation, deployment state access, or deployed-runtime observation.
DataHub mapping and Conduktor Console publication are outside this Backstage plan. The separately supported offline DataHub command is not enabled or implied by a successful Backstage export; Console publication remains deferred.
Normative dependency and reconciliation gate¶
docs/specs/backstage-catalog-export.md owns the frozen wire contract. If an
implementation detail below appears to conflict with it, the specification
wins and this plan must be corrected before code is merged; tests must not
establish a third contract.
The concurrently agreed values are:
- Backstage tag
v1.54.2, commit4bfa231152c6e454a2728850f63c6feb3d396191; - Node parity package
@backstage/catalog-model@1.10.0; - private projection type names
CatalogSnapshot,CatalogDataset,CatalogProcess,CatalogDependency,CatalogOwnerLabel, andCatalogContractSummary; - command shape:
streamt docs backstage \
--catalog-id <ID> \
--catalog-namespace <NAMESPACE> \
--default-owner-ref <REF> \
--lifecycle <LIFECYCLE> \
[--owner-map <PATH>] \
[--kafka-cluster-ref <REF>] \
[--gateway-cluster-ref <REF>] \
[--domain-ref <REF>] \
[--output-file <PATH>]
- command error
E507_BACKSTAGE_INVALID; - bounded warnings
W113_BACKSTAGE_SINK_OUTPUT_OMITTEDandW114_BACKSTAGE_EXPOSURE_OMITTED; - exact upstream schema closure:
Entity.schema.json,EntityEnvelope.schema.json,EntityMeta.schema.json,shared/common.schema.json, andkinds/{System,Resource,Component}.v1alpha1.schema.json.
The following private implementation-level names may be refined without changing the normative wire contract:
- the compressed package-resource filenames listed in Slice 1;
- projection version
1and the private builder namebuild_catalog_snapshot; - Backstage result type
BackstageCatalogExportand mapper namegenerate_backstage_catalog.
The owner-map grammar is already exact: strict JSON with only integer
version: 1 and an owners object, with duplicate-key detection at every
level and exact case-sensitive owner labels. The frozen inclusive limits are
1,048,576 UTF-8 bytes, nesting depth 4, 10,000 owner entries, 256 code points
per label, and 256 code points per reference. The parser must reject a byte
order mark and unpaired surrogate, boundary-test every limit, and never treat
the file as YAML.
None of these names is public Python API. Compatibility attaches to the CLI, entity documents, deterministic serialization, codes, and documented mapping.
Non-goals¶
- publishing, updating, or deleting entities through a Backstage API;
- reading an existing Backstage catalog to infer ownership or identity;
- Conduktor Console publication or use of Console topic-creation APIs as a metadata substitute;
- DataHub Metadata Change Proposal generation or emitter integration in this Backstage command; the separate offline DataHub command owns that format;
- catalog ingestion configuration, locations, refresh jobs, or authentication;
- copying generated SQL, connection configuration, endpoints, credentials, state locations, operation history, or provider observations into metadata;
- claiming runtime lineage, health, deployment, or job lifecycle visibility;
- adding Backstage, Node, DataHub, or Conduktor client dependencies to the streamt runtime package; or
- changing the streamt DSL solely to fill optional catalog fields.
Frozen invariants¶
Every slice must preserve these properties.
- Pure and offline. Parsing and one
Compiler.compile(dry_run=True)call are the only project-processing steps. No state backend, deployer, provider, DNS, HTTP, Kafka, or catalog client may be constructed. - Neutral first. The projection contains streamt catalog meaning, not
Backstage
apiVersion,kind, annotations, or entity refs. The Backstage mapper is a consumer of the projection. - Immutable and versioned. Projection containers and nested values are
deeply immutable, retain a literal projection version, and do not retain a
mutable
StreamtProject, manifest, resolved-model, or compiled-model object. - Allowlisted metadata. Only names, descriptions, declared tags and owners, absent/declared/enforced Model-contract state, materialization identity, and exact resolved direct dependencies may enter the projection. SQL and all runtime/provider configuration are forbidden even when they appear benign.
- No invention. Catalog identity, namespace, lifecycle, default owner, owner mappings, and optional domain/cluster refs are explicit inputs. Missing optional knowledge is omitted or produces one specified bounded warning; it is never synthesized from an endpoint or environment name.
- Stable identity. Backstage names and refs are produced by one documented canonical encoder. The exporter rejects blank values, invalid refs, duplicate generated identities, collisions across entity kinds, ambiguous ownership, and dangling internal relations before writing any bytes.
- Exact topology. Relationships reflect direct compiler-resolved edges. They do not claim transitive dependencies. Sources and topic outputs are resources; actual processing models are components; Gateway virtual topics are separate resources rather than aliases for physical Kafka topics.
- All-or-nothing validation. Every generated entity passes the pinned
envelope and kind validator, plus local cross-entity semantic checks, before
stdout or
--output-filechanges. The validator performs no remote$refretrieval. - Deterministic output. The same parsed project, environment, compiler result, and explicit catalog options produce byte-identical YAML. Entity, relation, owner, tag, mapping, and annotation order is stable; timestamps and random identifiers are absent; YAML aliases are disabled.
- Safe output. Text mode emits only validated canonical YAML to stdout; warnings go to stderr. JSON mode uses the normal structured formatter. File output is an atomic same-directory replace and never exposes partial output. Errors and warnings are secret-neutral and use stable locations.
- Portable packaging. The wheel and source distribution contain the exact pinned schemas. Installed-wheel export works from outside the checkout with repository imports removed and network calls disabled.
Slice 1 — pinned Backstage schemas and offline validator¶
Files owned by the schema agent¶
src/streamt/integrations/catalog/__init__.pysrc/streamt/integrations/catalog/backstage_validation.pysrc/streamt/docs/schemas/backstage-1.54.2-entity.json.gz.b64src/streamt/docs/schemas/backstage-1.54.2-entity-envelope.json.gz.b64src/streamt/docs/schemas/backstage-1.54.2-entity-meta.json.gz.b64src/streamt/docs/schemas/backstage-1.54.2-common.json.gz.b64src/streamt/docs/schemas/backstage-1.54.2-system-v1alpha1.json.gz.b64src/streamt/docs/schemas/backstage-1.54.2-resource-v1alpha1.json.gz.b64src/streamt/docs/schemas/backstage-1.54.2-component-v1alpha1.json.gz.b64src/streamt/docs/schemas/README.mdtests/unit/test_backstage_validation.py
The decoded documents must remain byte-for-byte upstream artifacts; do not
rewrite $id, $ref, formatting, or property order to make resolution easier.
Work¶
- Vendor the seven-document schema closure from the pinned Backstage commit.
Encode each as deterministic gzip (
mtime=0) plus base64, matching the existing schema-resource convention. - Record upstream tag, commit, source path, license, decoded byte size, and
SHA-256 for every document in
src/streamt/docs/schemas/README.mdand as constants next to the loader. - Decode with strict base64, decompress, verify size and SHA-256, decode UTF-8,
parse JSON, require an object root, and call
Draft7Validator.check_schemafor every schema. - Build an explicit in-memory registry for Backstage's nonstandard bare IDs
and refs such as
Entity,EntityMeta, andcommon#.... Unknown or remote resolution must fail locally; the validator must not fetch schemas. - Validate every entity first against
EntityEnvelope.schema.jsonand then against exactly one allowed kind schema. Reject other kinds and versions. Sort validation failures by JSON path and message and expose one stable, secret-neutralBackstageValidationError. - Add local checks that the schema set has exactly the expected IDs and refs, so a future upstream pin cannot silently broaden the accepted vocabulary.
Acceptance¶
- all seven resources pass checksum, Draft-07 schema, registry, and positive entity tests;
- corrupt base64, gzip, checksum, JSON, schema, missing-ref, remote-ref, envelope, wrong-kind, wrong-version, and kind-schema failures are bounded and deterministic;
- tests monkeypatch network entry points and prove validation stays offline;
- the validator accepts representative
System,Resource, andComponentdocuments that also pass the pinned Node implementation.
Gate¶
uv run pytest -q tests/unit/test_backstage_validation.py
uv run ruff check src/streamt/integrations/catalog/backstage_validation.py \
tests/unit/test_backstage_validation.py
uv run mypy src/streamt
uv run python scripts/check_mypy_baseline.py
git diff --check
Suggested commit: feat(catalog): pin Backstage schemas and offline validator.
Slice 2 — immutable provider-neutral projection¶
Slice 2 starts only after the schema/resource names and the normative mapping inputs are frozen. It does not depend on Backstage validation at runtime, but the order prevents two agents from inventing incompatible identity contracts.
Files owned by the projection agent¶
src/streamt/integrations/catalog/model.pysrc/streamt/integrations/catalog/__init__.py(projection exports only, serialized after the Slice 1 edit)tests/unit/test_catalog_projection.py
No other agent edits the package __init__.py during this slice.
Work¶
- Define frozen private value types
CatalogSnapshot,CatalogDataset,CatalogProcess,CatalogDependency,CatalogOwnerLabel, andCatalogContractSummary. Use tuples, frozen dataclasses, and immutable scalar values all the way down; a frozen outer dataclass containing a list or mutable mapping is not sufficient. - Set the exact projection version to
1. Reject unsupported versions at every mapper boundary rather than optimistically interpreting them. - Implement
build_catalog_snapshot(project, resolved_models, compiled_models, *, effective_environment)as a pure function. Inputs must be the complete post-compile views from the sameCompilerinstance. Reject partial/mismatched model keysets. Catalog ID and namespace are Backstage adapter inputs and must not enter the neutral snapshot. - Represent separately:
- each declared source dataset and its physical Kafka topic;
- each model's physical topic output when one exists;
- each compiled Gateway virtual topic output when one exists;
- each model that represents an actual streaming process; and
- every exact direct source/model dependency needed by that process.
- Preserve only the allowlisted declared description, tags, owner label, absent/declared/enforced Model-contract state, and materialization facts required by the mapping. Do not retain columns, schema bodies, counts, checksums, SQL, a complete ODCS document, or arbitrary user configuration.
- Produce stable logical keys independent of Backstage syntax. Reject missing targets, generated-key collisions, duplicate physical identities, and any compiler/project disagreement before returning a snapshot.
- Retain only exact exposure logical names for later omission warnings, preserving duplicate declarations as separate omissions. The mapper emits only the specified sink/exposure warnings with stable safe locations; they never contain other exposure fields or connector configuration values.
Acceptance¶
- full and minimal projects yield exact immutable snapshots;
- source-only, process, physical-output, and Gateway-virtual-topic cases remain distinct;
- direct dependency order and generated logical identity are deterministic;
- mutation attempts against every nested container fail;
- changes to an input object after construction cannot change a snapshot;
- partial compiler views, name collisions, ambiguous outputs, dangling edges, sinks, and exposures follow the frozen error/warning policy;
- repr, exception, and warning tests contain no SQL, endpoint, credential, connector config, or environment-secret sentinel.
Gate¶
uv run pytest -q tests/unit/test_catalog_projection.py \
tests/unit/test_compiled_models.py tests/unit/test_macros.py
uv run ruff check src/streamt/integrations/catalog/model.py \
tests/unit/test_catalog_projection.py
uv run mypy src/streamt
uv run python scripts/check_mypy_baseline.py
git diff --check
Suggested commit: feat(catalog): add immutable neutral projection.
Slice 3 — Backstage mapper, semantic checks, and serializer¶
Slice 3 consumes, but never reconstructs, the neutral snapshot. It may start only after the Slice 2 types, version, identity keys, and warning behavior are committed.
Files owned by the mapper agent¶
src/streamt/integrations/catalog/backstage.pysrc/streamt/integrations/catalog/__init__.py(Backstage exports only, serialized after Slice 2)tests/unit/test_backstage_export.py
The mapper agent does not edit validation resources, the projection builder, CLI code, or public documentation.
Work¶
- Define immutable
BackstageCatalogExportcontaining the validated entity tuple, warnings, and canonical YAML bytes/text. Define one puregenerate_backstage_catalog(snapshot, *, default_owner_ref, lifecycle, owner_map, kafka_cluster_ref, gateway_cluster_ref, domain_ref)entry point. - Map one project/environment
System, datasetResourceentities, and actual processComponententities exactly as frozen by the specification. A sink emits its proved Connect-process Component but no destination Resource. Do not emit placeholder Components for sources, exposures, or infrastructure. - Resolve owners by exact declared label through the strict owner map, then use the explicit default. Validate canonical Backstage refs. Never derive an owner from an email, endpoint, path, or current OS user.
- Add only exact direct
dependsOn/system/owner/domain relations. Use optional explicit Kafka and Gateway cluster refs only where the mapping is semantically exact. Sort and deduplicate relations without inventing missing entities. - Emit only the frozen
streamt.dev/...annotations, including the compact contract summary. Annotation values must be bounded and deterministic. No raw manifest, SQL, full contract, runtime config, or provider address may be serialized. - Check name/ref collisions and all internal relation targets across the complete entity set, validate every entity through Slice 1, and only then serialize.
- Serialize canonical multi-document YAML in the frozen entity order, using
stable mapping order, UTF-8,
\n, an explicit document separator policy, and a dumper that disables aliases. Reparse the YAML in tests and prove it is semantically identical to the validated entity tuple.
Acceptance¶
- golden tests cover one System plus source/topic/Gateway Resources and actual process Components with exact direct relations;
- owner overrides, default owner, lifecycle, optional cluster refs, and domain refs have exact positive and negative tests;
- invalid name/ref, duplicate identity, dangling relation, unsupported
projection version, unrepresentable metadata, and schema failures produce
E507_BACKSTAGE_INVALID-ready safe failures before serialization; - repeated generation and shuffled declaration inputs produce byte-identical YAML;
- the output contains no YAML anchors, Python tags, timestamps, SQL, config, endpoints, or secret sentinels;
- every golden entity passes both the vendored validator and the Node parity validator described in Slice 5.
Gate¶
uv run pytest -q tests/unit/test_backstage_validation.py \
tests/unit/test_catalog_projection.py tests/unit/test_backstage_export.py
uv run ruff check src/streamt/integrations/catalog tests/unit/test_backstage_*.py \
tests/unit/test_catalog_projection.py
uv run mypy src/streamt
uv run python scripts/check_mypy_baseline.py
git diff --check
Suggested commit: feat(catalog): map neutral metadata to Backstage.
Slice 4 — CLI and atomic output¶
Files owned by the CLI agent¶
src/streamt/cli/commands/docs.pysrc/streamt/core/errors.pytests/unit/test_cli_backstage.py
The CLI agent does not change the projection, mapper, validator, schema
resources, packaging workflow, or documentation. One owner must make all edits
to the already large docs.py command module.
Work¶
- Add the exact
streamt docs backstageoptions from the normative contract, plus the existing--project-dir/-pand--env/-eproject-selection options. Do not add a network target, token, publish flag, or implicit mode. - Require and validate catalog identity, namespace, default owner ref, and lifecycle before compilation. Parse the optional owner map strictly and provider-free. Reject duplicate keys, unknown top-level keys, blank values, non-string pairs, wildcard rules, and environment placeholders.
- Parse the project with the standard warning routing. Construct one
Compiler, callcompile(dry_run=True)exactly once, and pass that same compiler's completeresolved_modelsandcompiled_modelsto the neutral builder. No output artifact directory is written. - Convert projection and mapper failures to
E507_BACKSTAGE_INVALIDwith a stable safe location. Emit onlyW113_BACKSTAGE_SINK_OUTPUT_OMITTEDandW114_BACKSTAGE_EXPOSURE_OMITTEDfor the frozen lossy cases. Parser warnings retain their current codes and stay off the YAML stream. - In text mode, emit only canonical YAML to stdout unless
--output-fileis set. With a file, validate all entities and bytes first, write a sibling temporary file, fsync/close as required by the repository convention, and atomically replace the target. Do not print a success banner into document stdout. - In global JSON mode, use
OutputFormatter. Itsdataobject has exactlystandard,release,api_version,entities,counts, andoutput_file;countshas exact orderedSystem,Resource, andComponentkeys. Never embed YAML as an escaped second representation. Quiet behavior follows existing docs commands without skipping file creation. - Prove via construction spies that every success and failure path is free of state, deployer, provider, and network construction.
Acceptance¶
- exact help/options, missing-required-input, owner-map, parse, compile, projection, mapping, validation, and file failures use the frozen contract;
- text stdout is parseable multi-document YAML and warnings appear only on stderr; JSON stdout is one parseable formatter envelope;
- stdout and file modes contain the same canonical document bytes;
- a failed export leaves no new file and preserves an existing destination;
- compile is called once with
dry_run=True; no generated project artifact is written; - network/state/deployer/provider constructors are forbidden in tests;
STREAMT_*and configuration secret sentinels do not appear in stdout, stderr, structured error fields, output files, or exception reprs.
Gate¶
uv run pytest -q tests/unit/test_cli_backstage.py \
tests/unit/test_backstage_export.py tests/unit/test_cli_docs_odcs.py \
tests/unit/test_cli_openlineage.py
uv run ruff check src/streamt/cli/commands/docs.py src/streamt/core/errors.py \
tests/unit/test_cli_backstage.py
uv run mypy src/streamt
uv run python scripts/check_mypy_baseline.py
git diff --check
If either adjacent test filename does not exist at implementation time, use the repository's actual ODCS/OpenLineage CLI test filename; do not create an alias solely to satisfy this command.
Suggested commit: feat(cli): export validated Backstage catalog YAML.
Slice 5 — distribution and Node parity release gates¶
The Node validator is an independent release oracle, not a runtime dependency and not a second serializer.
Files owned by the distribution agent¶
tests/package/backstage_catalog_wheel_smoke.pytests/package/backstage_catalog_node_validate.cjstests/unit/test_backstage_validation.py(distribution-resource inspection section only, serialized after the schema agent).github/workflows/ci.yml(package job only)
No package-gate work changes production source, schema payloads, unit mapping
behavior, docs, or roadmap. The schema agent and distribution agent must not
edit test_backstage_validation.py concurrently; transfer ownership after
Slice 1 merges.
Work¶
- Extend distribution inspection to build one wheel and one source distribution, enumerate the expected seven resources, decode them, and verify exact size/SHA pins from outside the checkout.
- Run
backstage_catalog_wheel_smoke.pywith the isolated installed-wheel executable,PYTHONPATHremoved, Python isolated mode where applicable, working directory outside the repository, and network entry points patched to fail. Compile a representative project and assert exact canonical YAML, warnings, relations, schema validation, secret neutrality, and repeat-byte determinism. - Install the exact independent parity package without modifying repository manifests or locks:
audit_dir="$(mktemp -d)"
npm install --prefix "$audit_dir" --ignore-scripts --no-save \
--package-lock=false @backstage/catalog-model@1.10.0 yaml@2.8.1
cp tests/package/backstage_catalog_node_validate.cjs "$audit_dir/validate.cjs"
(cd "$audit_dir" && node validate.cjs < catalog.yaml)
- The CommonJS validator must parse every YAML document and call the exact
systemEntityV1alpha1Validator,resourceEntityV1alpha1Validator, orcomponentEntityV1alpha1Validatorexport according to kind, awaiting.check(entity)and failing on an unknown kind. It must not normalize or rewrite entities before checking them. - Keep
npm installas a bounded package-job setup step. The installed streamt smoke itself remains offline, and normal Python unit/package execution does not require Node or npm.
Acceptance¶
- wheel and source distribution contain exactly the expected decoded schema artifacts and pins;
- the installed executable succeeds outside the checkout without network;
- source-tree, wheel, and repeated wheel exports are byte-identical;
- every emitted entity passes both Python and Backstage Node validators;
- a deliberately invalid entity is rejected by both validators;
- the package job proves all three supported entity kinds and direct relation shapes, including a Gateway virtual-topic Resource;
- distribution output and logs contain no test secret, endpoint, SQL, raw configuration, or repository path.
Gate¶
uv run python -m build
STREAMT_TEST_DISTRIBUTIONS_DIR="$PWD/dist" \
uv run pytest -q \
tests/unit/test_backstage_validation.py::test_built_wheel_and_source_distribution_contain_backstage_resources
isolated_root="$(mktemp -d)"
cp tests/package/backstage_catalog_wheel_smoke.py "$isolated_root/"
(cd "$isolated_root" && env -u PYTHONPATH \
/tmp/streamt-wheel-smoke/bin/python -I backstage_catalog_wheel_smoke.py)
audit_dir="$(mktemp -d)"
npm install --prefix "$audit_dir" --ignore-scripts --no-save \
--package-lock=false @backstage/catalog-model@1.10.0 yaml@2.8.1
cp tests/package/backstage_catalog_node_validate.cjs "$audit_dir/validate.cjs"
(cd "$audit_dir" && node validate.cjs < catalog.yaml)
git diff --check
CI may use runner-scoped temporary paths instead of /tmp, but it must retain
the clean installed-wheel boundary. Suggested commit:
test(package): gate Backstage export distributions and parity.
Slice 6 — public documentation and roadmap truth¶
This slice lands only after Slices 1–5 and their release gates pass.
Files owned by the documentation agent¶
docs/specs/backstage-catalog-export.md(implementation status only)docs/plans/2026-09-02-backstage-catalog-export.md(status only)docs/reference/backstage-catalog.mddocs/reference/cli.mddocs/reference/support-matrix.mddocs/reference/release-notes.mdmkdocs.ymlROADMAP.mdtests/unit/test_docs_backstage_example.py
The documentation agent does not modify source, schemas, unit implementation tests, package scripts, or CI.
Work¶
- Document the exact offline command, required identity/owner/lifecycle inputs, owner-map grammar, YAML/JSON/file behavior, entity mapping, annotations, warnings, validation pin, determinism, and safe failure model.
- Add an executable minimal example and a richer example covering a process, topic output, Gateway virtual topic, owner mapping, and optional refs.
- State the limits prominently: this exports design metadata only; it neither publishes to a catalog nor observes deployment/runtime lifecycle.
- Record the wheel/source-distribution resource gate, isolated-wheel smoke, and Backstage Node parity gate in support and release documentation.
- Keep roadmap claims separated: Backstage and the later DataHub offline exports may be marked complete only under their own passed gates, while Conduktor Console publication remains unchecked.
- Link the normative specification, this execution plan, and reference page without making a future Console or live-publication design normative by implication.
Gate¶
uv run pytest -q tests/unit/test_docs_backstage_example.py
uv run mkdocs build --strict
git diff --check
Suggested commit: docs(catalog): document offline Backstage export.
Parallel execution and ownership¶
The dependency order is strict even when agents run concurrently:
normative contract
|
v
Slice 1 schemas/validator ----+
| |
v |
Slice 2 neutral projection |
| |
v v
Slice 3 mapper/serializer --> Slice 5 gate scaffolding
|
v
Slice 4 CLI/output ----------> Slice 5 executable gates
|
v
Slice 6 docs/roadmap
- The schema agent owns the schema resources, README schema section, validator, and validator tests until Slice 1 is committed.
- The projection agent owns only
model.py, model tests, and its serialized__init__.pyexport edit. - The mapper agent starts after the projection commit and owns only
backstage.py, mapper tests, and its serialized__init__.pyedit. - The CLI agent starts after mapper APIs are frozen and exclusively owns
docs.py,core/errors.py, and CLI tests. - The distribution agent may prepare isolated scripts after filenames and wire contracts freeze, but edits the package workflow and shared validation test only after prior ownership transfers.
- The documentation agent runs last and records only executable truth.
No slice may smuggle later-slice behavior into its commit. In particular, Slice 1 has no mapper, Slice 2 imports no Backstage vocabulary, Slice 3 has no Click or filesystem output, Slice 4 has no network/publisher, and Slice 5 has no production fixes hidden in a test commit.
Final acceptance matrix¶
| Concern | Unit | CLI | Installed wheel | Wheel/sdist | Node parity |
|---|---|---|---|---|---|
| Pinned schema integrity and offline refs | required | indirect | required | required | independent |
| Neutral deep immutability and exact coverage | required | indirect | smoke | n/a | n/a |
| System/Resource/Component mapping | required | required | required | n/a | required |
| Direct dependency and owner/ref semantics | required | required | required | n/a | required |
| Canonical deterministic YAML | required | required | byte repeat | n/a | parsed |
| Structured JSON envelope | n/a | required | smoke | n/a | n/a |
| Atomic file output and failure preservation | n/a | required | required | n/a | n/a |
| No network/state/deployer/provider | spies | spies | hard fail | n/a | Python smoke only |
| Secret/config/SQL neutrality | sentinels | sentinels | sentinels | resource-only | document sentinels |
| Bounded errors and warnings | required | required | smoke | corrupt artifact | invalid entity |
Release gates¶
All required release gates now have executable coverage. Each release must still run them and remain green:
- The normative spec and this plan agree on every mapping and output rule.
- All seven vendored schemas match the pinned upstream commit and pass no-network Draft-07 validation.
- The full projection, mapping, and CLI focused suites run in the Python 3.10–3.12 test matrix.
- Ruff and the repository's zero-error mypy baseline are CI gates.
- The full unit suite and strict MkDocs build are CI gates.
- Isolated-wheel export and exact wheel/source-distribution resource inspection run outside the checkout.
- Every representative emitted entity is checked by
@backstage/catalog-model@1.10.0with release-test-onlyyaml@2.8.1. - Public docs and the roadmap keep DataHub and Conduktor Console publication explicitly separate from offline export support.
Console publication requires a separate evidence-backed specification naming a supported metadata-only API, authentication/TLS/timeout behavior, external identity and ownership rules, reviewed mutation semantics, idempotency and delete policy, and recovery behavior. The later offline DataHub exporter owns its separate versioned Metadata Change Proposal/aspect contract and does not reuse the Backstage serializer. DataHub publication still requires its own server, authorization, idempotency, deletion, and recovery contract; neither offline exporter implies it merely because both consume the neutral projection.