DR-0015: Split the platform vocabulary into SCM and CI axes; formats not services
Status: accepted
Context
platform: github | ado appeared in the export declaration, the slice
config, and manifest provenance — and in each place it silently stood in for
up to three different facts: where the repository is hosted (SCM: clone,
tags, PRs, branch policies), what runs the pipelines (CI: workflow dialect,
output conventions, pin-line shape), and where tickets live (work
tracking). "ado" was especially overloaded: Azure Repos, Azure Pipelines,
and Azure Boards are three products that need not travel together. The
mixed case — Azure Pipelines building a GitHub-hosted repository — was
unrepresentable, and runtime env-sniffing (GITHUB_ACTIONS/TF_BUILD)
picked the wrong axis for SCM-bound operations.
Decision
- Two explicit axes, distinct value vocabularies.
- SCM:
github | azure-repos— publisher coordinates (publisher.scm), manifest provenance (source.scm), and the consumer's own host (scm:in the slice config, written by init and inferred from the origin remote when not given). - CI:
github-actions | azure-pipelines | none— recorded once asci:in the slice config.noneis first-class: no scaffolded pipelines, manual orchestration, the manifest'ssource.releaseis the pin. - Work tracking has no axis: no core config names a ticket system (DR-0014).
- SCM:
- The format-vs-service rule. Core may understand vendor file formats — pipeline YAML dialects (conformance detectors), CODEOWNERS syntax, CI output conventions, clone-URL templates. Core must not call vendor services — those live in handlers. This is the line that makes "is X coupled?" decidable for every future feature.
- Upstream reads over the git protocol. Tag listing (
ls-remote, peeled) and file-at-tag reads (depth-1 fetch) are SCM-neutral, sopublisher.scmdemotes to provenance metadata plus a shorthand expansion hint; core logic never branches on it. - Dialect selection by recorded config, not env-sniffing. Conformance
parses the dialect named by
ci:, so a fleet audit or local run decides identically to CI. Env detection survives only for the CI output surface, where the ambient host is by definition the right answer. - Ownership follows the SCM axis. CODEOWNERS is GitHub-only and
opt-in at init (
--codeowners); Azure Repos consumers get a required-reviewers branch-policy checklist item backed by therequired_reviewers_policyattestation. Thecontrol-plane-ownedcore rule iswaivableaccordingly.
Alternatives considered
- One
platformvalue with per-feature overrides. Keeps the common case short but preserves the conflation; the mixed case becomes a pile of exceptions. Rejected. - Infer everything at runtime, record nothing. Fails precisely where
conformance matters most (offline/fleet evaluation) and cannot represent
ci: none. Rejected. - Full URLs everywhere, no shorthand. Considered; kept shorthand because publisher coordinates appear in human-maintained YAML, with verbatim URL/path always accepted (and used by the scenario kit).
Consequences
- A GitHub-hosted repo built by Azure Pipelines is now simply
scm: github, ci: azure-pipelines— every feature picks the right axis. ci: nonegives a supported fully-manual mode: gate/watch/sync run by hand or cron; pipeline-dependent conformance rules reportskipped, and the forfeited PR-time enforcement is stated on the init checklist rather than hidden.- Schema stays v1 (pre-release, no compatibility debt); the old
publisher.platformkey is simply gone. - CI remains the one axis with in-core knowledge (template packs, dialect parsing, output surface) — host-environment adaptation, allowed by the format-vs-service rule and irreducible for software that runs inside CI.