ADR-0021 — The ALM is a mirror; inbound work enters through a change#
Status#
Accepted — 2026-08-20
Context#
csda alm sync reconciles the traceability matrix with Jira or Azure Boards,
and ADR-0015's change lifecycle governs how a spec that already shipped is
modified. Nothing until now said how the two relate, and the question is not
academic: the first thing any team asks for after seeing alm sync is the
other direction — "the product owner opens the ticket in Jira, make it appear
in the repo".
That request is reasonable and answering it carelessly ends the product. A
connector that can write requirements into the matrix makes the board a second
source of truth, and two sources of truth means neither is one. The value of
this tool is that validate can fail the build when a requirement has no
scenario and no test; a requirement that arrived from a ticket has neither, so
the first inbound sync would either break the gate or teach people to weaken
it.
There is also a shape problem that no amount of engineering removes. A Jira ticket does not contain an executable acceptance criterion. It has a title, a description and a status. Deriving Gherkin from it would be inventing the one thing the spec exists to pin down — and §12.13 of the closure plan already settled the equivalent question for brownfield code: documenting a system produces prose nobody re-reads, whereas linking a requirement to code produces a row the build breaks when it stops being true.
The port extracted in E0-02 raises the stakes, because adding a provider is now a row in a registry. Whatever rule governs Jira governs YouTrack, Linear, GitHub Issues and every provider after them, and it has to be written down before the fifth one arrives rather than rediscovered by each.
The precedent already exists in this repository. specops diff --as-change
takes something from outside — a pack version bump — and lands it as a
reviewable proposal rather than as an accomplished fact. That is the shape
an inbound ALM flow has to take.
Decision#
The ALM is a mirror. The spec tree is the contract.
Nothing under
scripts/alm/writes to the spec tree.spec.md,docs/specs/**andfeatures/**are read-only to every provider and to the sync core. The single file the ALM subsystem writes is.specops/alm-map.json, the REQ ↔ issue mapping, which is a record of correspondence and not a statement about the system. This was already true; the ADR makes it a rule andtests/unit/alm-conformance.test.tsmakes it a check, so a future provider cannot quietly acquire a write path.Drift is reported, never resolved. An issue closed while its requirement is still open is a finding with an exit code, not something
syncfixes. Reopening a requirement because a board says so would be the mirror deciding what the contract means.Status flows one way: matrix → board. A requirement reaching a done status closes its issue. A closed issue never advances a requirement, since the only evidence that a requirement is done is its scenario passing, and the board has no opinion about that.
Inbound work enters as a change, never as a matrix row. When
alm pullis built (E2-03), an ALM issue becomes achangewith a proposal and a delta seeded from its title and description, and with the scenarios left deliberately empty. That gap is not an unfinished feature: it is the exact place where a human — or thespec-authorrole of the multi-agent harness — does the only work that cannot be automated. The change then goes throughchange validateandchange archivelike any other, so a ticket cannot become a requirement without passing the same gate as one written by hand.A provider may not widen this. The
AlmProviderport exposes three operations — create, read status, close. A provider needing to write into the spec tree to be useful is a provider this tool does not accept.
Consequences#
The honest cost: the tool will not, and will never, let a team drive delivery from Jira alone. Someone has to write the acceptance criterion. Teams looking for a board-to-code robot are not the buyer.
The gain is the one thing this tool sells. Because the board can never write a
requirement, validate remains able to say something true about the repository
regardless of how many systems are wired into it, and the number of connectors
becomes a distribution question instead of an architectural risk.
It also settles P1 (multi-repository orchestration, §12.12) without pretending to solve it. The ALM issue is a usable identifier above the repository — that is what a mirror is for — while remaining unable to define anything, which is why using it that way is cheap rather than dangerous.
Alternatives considered#
Bidirectional status sync. Let the board reopen a requirement and let the matrix close an issue. Symmetrical, familiar from ALM integrations generally, and it produces two writers with no merge rule. The first disagreement is unresolvable and the tool has no basis for choosing, because "done" in the matrix means a scenario passed and "done" on a board means a person clicked.
The ALM as the source of truth, the repo as its projection. This is what most enterprise integrations do, and it is coherent — it is simply a different product, one where the gate cannot exist because requirements live somewhere CI cannot verify.
Import tickets straight into the matrix with a placeholder scenario.
Fastest to demo and the worst of the options: it manufactures rows that satisfy
validate without describing behaviour, which converts the gate from a check
into a formality. ADR-0018 rejected phase gates for the same reason — a rule
that does not survive contact with real work gets satisfied with fiction.
A webhook service so the board pushes in real time. Requires a hosted process with its own credentials and its own security surface. The CLI is stateless and synchronisation belongs in CI, on a schedule, which is where it already runs — the same reasoning that sent plugin distribution to v2 in D12.