Development record
A private advisory's record never enters the public ledger, its store is never committed, and nothing public carries an advisory id or hint
Context
A private security advisory is the one piece of work abcd's records cannot hold
in the open. The issue ledger is committed and public, so an advisory copied
into it discloses the hole before its fix exists; and every outward artefact a
release makes (commit messages, pull-request bodies, the changelog, the release
page, records made public) is a place an advisory can be named by accident.
The product thinker ruled on 2026-10-09
(iss-2610090703514921)
that an advisory's record stays out of the public ledger until its fix lands,
and on 2026-10-12 adopted abcd embargo
(itd-2610112239022766,
refining itd-149)
as the first rung that does it in code, including closing an advisory on GitHub
on the person's approval.
The hand-runs that preceded the intent kept the private records in an
uncommitted worktree and searched for every advisory id by hand before each
push. Those are trust rules, not features: they bind every verb that writes a
record or an outward text, not only embargo. Under the itd-84 routing
(capability to intent, trust rule to ADR and brief invariant, as adr-44 did for
remote writes) they are recorded here.
Already locked: worktrees live in the machine-scoped store (adr-2610031751065746); a remote write happens only through a dedicated, confirmed verb and the caller's own identity (adr-44).
Decision
- A private advisory's record never enters the public ledger while its
advisory is unpublished. abcd writes it only into the embargo store, a
worktree at
~/.abcd.noindex/worktrees/<root-sha>/embargo/, and never under the checkout's own.abcd/work/issues/. The link from an advisory to its record lives in the store's state, not in the record, and a record minted from an advisory takes a neutral slug, never one derived from its title. - The store is never committed. It is created detached, so nothing in it has a ref to push, and every verb that reads or writes it first refuses, writing nothing, when its HEAD is on a branch, when its index tracks a record the state names, when it resolves inside the current checkout, or when its state file is not owned by this account and closed to group and other writes.
- Nothing public carries an advisory id or a hint of one. Every outward
artefact is checked before it is made public, and abcd refuses one that
names an unpublished advisory's id, a CVE id reserved for it, its title, or
the location of the private records. A refusal names where the match was
found and never repeats it. abcd's own listings that would show a private
record's title (such as
peers) skip the store. What a check cannot recognise, a paraphrase or a count of unfixed reports, is stated as its limit and stays the author's to avoid. - Publishing and closing an advisory on GitHub each need one explicit approval per act, bound to exactly what was shown. A publication plan and a closing text are each shown first with a digest; the act runs only with an approval naming that digest, and an approval for any other plan or text sends nothing. A host passes an approval only after the person, in that session, has seen what it covers and said yes; an agent never supplies one on its own. This refines adr-44's first rule: adr-44 requires a dedicated, confirmed verb for a remote write; this rule makes the confirmation one per act and binds it to the content confirmed, because an advisory published or closed with the wrong text cannot be taken back into private.
Alternatives Considered
- Keep the record in the public ledger with its text redacted. Rejected: the record's file name, its date, its category and its severity are each a hint, and the 2026-10-09 ruling keeps the record out, not merely its words.
- Hold the work in GitHub's temporary private forks. Rejected: they skip CI, branch protection and the merge queue, so a fix made there lands unchecked.
- A private remote or a separate private repository for the records. Rejected for this rung: it adds a second remote to keep in step and a second place to leak from, for a need (sharing between machines) the intent's scope conditions exclude.
- Encrypted records committed to the ledger. Rejected: the ciphertext's presence and its file history are themselves a hint, and abcd would take on key management.
- A detached, never-committed store in the machine-scoped worktree store, with an outbound check and per-act approvals. Chosen: it is what the hand-runs proved, it holds records where the existing capture and resolve code already reads them, and each failure mode has a refusal a test can hold.
Consequences
internal/core/embargocarries the refusals in decisions 1 and 2 as its store check, and each is held by a named test in the intent's spec.- Every outward text abcd composes during an embargo passes the outbound check of decision 3 before it is posted or committed; a later verb that writes outward text inherits the obligation.
- The records live on one machine only. Handling one release's advisories from two machines is out of reach until a later decision lifts it.
- A record that lands with its fix enters the public ledger only after the advisory is published, or redacted; redacting and restoring are a later rung and inherit decision 3.
- The brief carries the rule as invariant 22 in
02-constraints/03-invariants.md, in force once this record is accepted.
Frontmatter
| id | adr-2610120558328615 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-10-12 |
.abcd/development/decisions/adrs/2610120558328615-a-private-advisory-s-record-never-enters.md
open on GitHub ↗ · commit history ↗
References4
- adr-44abcd never mutates a remote uninvited, and identity derives from caller-local factsrelated
- adr-2610031751065746The worktree store lives under the renamed home, `~/.abcd.noindex/worktrees/<root-sha>/<name>/`, and the rest of its rule is unchangedrelated
- itd-149abcd handles inbound security advisories and issues, and cuts the release, for every managed repo. The 2026-08-27 security-advisory pilot proved the loop by hand across two independent operators: advisory and issue intake, recall-dedup triage against the ledger, record-first fixes in isolated worktrees, one integration branch, an independent adversarial gate over the assembled diff, auto-merge armed only after APPROVE, and a derived release cut through the semantic-receipt gates. This intent automates that loop as abcd capability for any managed repo, with findings F-A through F-W of the pilot note as the acceptance-criteria source and F-U (re-run every required pass against the final content commit; never transfer a verdict) and F-Q (the two release gates are human-by-design) load-bearing.related
- itd-2610112239022766Private security reports stay private until their fix shipsrelated
mentions4
- iss-2610090703514921A drain reads only the issue ledger, so security advisories that others submit through the forge's private vulnerability reporting never reach it. Unpublished advisories are not named by any record, so nothing in abcd shows whether they were triaged. They cannot simply be copied into the ledger as issues, because the ledger is public and an advisory stays private until it is fixed; and even a ledger issue in the security category is handed back by the drain under its default rule.
- itd-84An Intent Is Decomposed Before It Is Filed, And A Reversal Is Only Ever Flagged
- itd-2609151838312703Sessions under any account, on any machine or in the cloud announce what they are doing through short fixed-form announcements on the shared remote, a person reads everyone's status with one command, and what is really a hold stays a reservation
- spc-2610120557432880when-someone-reports-a-security-hole-in