Files
alkstore/docs/architecture/decisions/018-provenance-register-and-cherry-picks.md
T

298 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-018: Substrate provenance register and cherry-pick procedure
## Status
Accepted (2026-10-06, Phase 1 — OQ-11's resolution, items (2) and (3);
item (1) dissolved by [ADR-013](013-fold-substrate-into-sqlite.md) §4.
Fills in the register's format and the cherry-pick procedure the prior
records point at: [ADR-012](012-forked-substrate-design.md) §1/§6,
[ADR-013](013-fold-substrate-into-sqlite.md) §3/§4,
[ADR-017](017-contract-versioning.md) §2 class 4)
## Context
[ADR-011] fired the fork; [ADR-012] designed it (fidelity posture,
contract-blind boundary, port deltas, upstream-tracking stance);
[ADR-013] folded it into `alkstore-sqlite` as the `src/substrate/`
module subtree. Every one of those records pinned the *existence* of a
provenance register — its location (in-tree, beside the subtree), its
timing (fork-scaffold), and its initial contents (upstream identity +
the delta list) — and deferred two questions to OQ-11:
- **(2) The register's format and delta-list granularity.** ADR-012 §1
named a delta list but not its shape: are port deltas recorded
per-commit or per-delta-class? Does each future cherry-pick get its
own entry, and does that entry land at adoption time or in a
deferred sweep?
- **(3) The cherry-pick procedure in practice.** ADR-012 §6 said
adoption is deliberate and recorded; ADR-017 §2 class 4 said
cherry-picks are non-events because ADR-012 §2's contract-blind
boundary makes them structurally un-contract-side. Neither says
*how* a candidate is evaluated, applied, or recorded so the
deliberate-work posture stays auditable — and ADR-013's fold moved
the boundary's enforcement from the crate graph to review
discipline (its accepted negative consequence), which makes the
audit trail the record that review works from rather than an
optional artifact.
Much of the shape is already fixed by the retained records, and this
ADR composes with it rather than re-deciding it:
- **The register is a second ledger — for the substrate, not the
contract.** ADR-017 §1 rejected a parallel contract-version ledger
under the one-normative-owner rule; the substrate is the
complementary case: it is *not* a contract artifact, its changes are
class-4 non-events ([ADR-017] §2.4), and its semver carrier (the
engine crate's changelog) has no *versioning* reason to mention
them — the semver does not move for them. Upstream lineage is a
fact the contract machinery cannot carry — the register is its
single normative home.
- **The alksocks precedent supplies the in-tree shape.** alksocks'
ADR-013 there (the targeted fork) folded the forked core into
`src/rfc1928/` with a bundled module-level `LICENSE` notice, module
docs carrying the fork-point statement, and git history as the
fork-point record — and that shape shipped on crates.io without
provenance problems ([ADR-013]'s context cites it twice). The
distinction this ADR must add: alksocks' fold *retired* the vendor
framing — upstream is quiet, and a future fix "arrives as a
hand-port" with no register apparatus. The honker fork is the other
case: its fix stream is *alive* (the #80/#133 train is what fired
the fork), and ADR-012 §6 commits to deliberate cherry-picks.
Recurrent adoption needs a durable ledger; a one-shot extraction
does not. Both are the same posture — the record apparatus scales
with the upstream's activity.
- **The known defect class is named in the evidence base already.**
`quality-read-honker-core.md` §3 (D-1..D-7) is the re-derivation
work's rationale-of-record — its entries feed the re-derivation
delta entries' Why fields; the fix train the fork point *inherits*
(`4881f27`, `3ab43aa`, `claimed_at` et al. at `f4e53c6`) is
upstream lineage, recorded under upstream identity, not a delta of
ours. The scaffold work is formatting, not gathering.
## Decision
### 1. The provenance register: `alkstore-sqlite/src/substrate/PROVENANCE.md`
The provenance register is a **single Markdown file inside the
substrate subtree** — `alkstore-sqlite/src/substrate/PROVENANCE.md` —
per the ADR-017 §1 altitude: it is substrate lineage, so it lives with
the substrate; the fold's "in-tree, beside the substrate subtree"
placement ([ADR-013] §3) names the directory, this ADR names the
file. (ADR-012 §1's `provenance.md`-at-the-crate-root named the
pre-fold crate-root location; superseded with that §1. The uppercase
filename follows the family precedent for standing-in-tree notices —
the alksocks bundled `LICENSE` inside its forked module.)
The register **carries two sections**:
1. **Upstream identity** (ADR-012 §1's initial contents): the fork
source — upstream project, `/workspace/honker` reference checkout @
`f4e53c6` (package `node-v0.5.1-10-gf4e53c6`), published-release
lineage (0.5.0, 2026-08-23), license **MIT OR Apache-2.0** — and
the fork-point statement: the scaffold commits are the provenance
line in git history, exactly alksocks' extraction-commit-window
shape. This section is also where inherited lineage facts live
(the fix train the fork point carries — `4881f27`, `3ab43aa`,
`claimed_at` et al. — is upstream code we inherited, not a delta
of ours).
2. **The delta register** — §2's format; every divergence from
lineage lands here *including cherry-picks* (their category is
`cherry-pick`; §3's procedure writes to this section — there is
no separate cherry-pick ledger). The scaffold's port deltas
([ADR-012] §4, ADR-011's scope register) are its initial entries;
cherry-picks are empty at scaffold, append-only forever.
The register is **not** itself a guard — the contract-blind
boundary's guards remain the diff fence, review, and the contract
suite ([ADR-013] §2's re-sited enforcement); the register is the
audit trail review works from. It records: what lineage this is,
what we have already changed, and how each change arrived. Its duty
is completeness, not authority — the ADRs remain the decisions of
record.
### 2. Delta register format: one entry per delta, tagged by category — never per commit
**Per-delta, category-tagged** — one entry per distinct delta, each
carrying its category tag — never one per commit. Granularity the
evidence dictates: the fork's divergence arrives in bursts (the
scaffold adds the port deltas at once; a future upstream fix train
lands as several commits, one category), and a
per-commit register would grow into a duplicate of the git log — a
second ledger for the same fact, the drift ADR-017 §1 rejects. The
git history of this repository *is* the per-commit record; the
register is the semantic index over it.
Each entry pins **five fields**:
- **Category** — one of the standing classes (kept-half port delta /
re-derivation / drop / hygiene / cherry-pick), so the entry says at
a glance which slice of the scope register the change touches.
- **What** — the delta in one or two sentences (the read's W-numbers
and D-numbers are admissible identifiers — e.g. "W-1 reconnect
backoff applied").
- **Where** — the module path(s) in the subtree the delta touches.
- **Why** — the deciding record (ADR + section, quality-read finding,
or the cherry-picked upstream issue/commit hash pair — see §3).
- **Lineage** — for cherry-picks: the upstream commit hash(es) the
delta carries (the `4881f27`/`3ab43aa` pattern the evidence uses);
for born deltas (port deltas, re-derivations): "ours" — no upstream
counterpart exists.
The **initial entries at scaffold** are fixed by the retained
records, not re-litigated: the ADR-011 scope register's keep /
re-derive / drop tripartition as three class entries, the three
watcher deltas of ADR-012 §4 (W-1, W-2, the dead-man's-switch
re-death; W-3's not-actionable and W-4's keep-RW recorded where the
read put them), the `__alkstore_*` rename, and the port discipline
deltas (sync substrate, no-panics/no-unwrap hygiene). Nothing here
makes new decisions — the register's section 2 simply carries what
ADR-011 §3 and ADR-012 §3–§5 already decided, in the format this ADR
pins, and the scaffold task inherits the list as its checklist.
Subsequent entries append only. Amending an existing entry is
permitted solely to *correct the record* (a wrong path, a mis-cited
hash) — never to change the register's story; a superseded delta is a
new entry that cites the one it supersedes.
### 3. The cherry-pick procedure: five steps, one record
When upstream (honker-core proper — the reference checkout's lineage)
ships a fix relevant to the kept half, adoption runs this procedure,
whose steps make the ADR-012 §6 deliberate-work posture auditable —
each step leaves a checkable artifact:
1. **Candidate identification (notice duty).** Since no ambient
tracking exists ([ADR-012] §6), a candidate arrives by deliberate
review of the reference checkout / upstream history when a reason
exists (a defect observed in our fork — first stop, upstream's fix
train; a fresh upstream release announcement; a routine periodic
sweep at whatever cadence future maintenance wants, or none).
The procedure does *not* create a sweep duty — it says that if a
candidate arrives by any route, the following steps apply.
2. **Evaluation against the port-cost logic** ([ADR-012] §6): does
the upstream fix touch kept-half machinery that still tracks its
lineage? Does it beat our owned, tested, contract-ahead fork on
maintenance rather than novelty (quality-read §7's re-adoption
bar's spirit, applied per-fix)? A fix for a defect class in the
re-derived machinery (the queue ops — D-1–D-3, D-5–D-7's fix
classes; D-4's `lock_acquire` sits in the *kept* half and stays
cherry-pick-eligible) is *not adoptable* — it
evaluates against machinery that no longer tracks upstream, and
the correct response to an observed defect in re-derived code is
an owned fix under the fork's own test discipline.
3. **Application** — hand-application of the upstream change against
the kept half, preserving ADR-012 §3's fidelity posture (module
structure and internal names survive the application where the
surrounding code does; mechanical token renames stay banned), plus
the family deltas the substrate always carries (no panics, no
`unwrap()` outside tests — the port applies exactly this
discipline to the inherited fixes). The application is verified
against the inherited suites and the contract suite's substrate
rows — the fork's version of alksocks' differential-suite guard,
which is what makes "the cherry-pick preserved the inherited
semantics" a testable claim rather than an assertion.
4. **Record** — one delta-register entry (fields §2 pins;
category `cherry-pick`, lineage = the upstream commit hashes,
why = the evaluation's reasoning and outcome) **plus one line in
the engine crate's changelog** — not a versioning event
([ADR-017] §2 class 4: the engine's semver does not move for it),
but a *changelog-visible* entry citing the register, so a reader
auditing the crate's history between versions finds the substrate
change without needing to know the register exists. This is the
fold's answer to the crate-graph signal the standalone
`alkstore-substrate` crate's version bumps would have carried.
5. **Commit** — the cherry-pick commit message names the upstream
commit hash(es); the register entry cites the commit. Git history
and the register cross-link, and the fork point stays linear: every
lineage-relative change from scaffold onward is
register-entry-addressable by commit.
**Recording timing: at adoption, not swept.** The register entry and
changelog line land in the same commit as the applied change; there
is no deferred-registration window in which the delta exists but the
record does not. Keeping it that way is what makes the audit trail
trustworthy without enforcement tooling.
**When a cherry-pick would touch the re-derived half or cross the
contract boundary**: it is *not* a cherry-pick, it is a fork change —
an owned edit, evaluated by the engine's own review discipline, and
(if it moves contract-visible semantics) classified under ADR-017's
taxonomy like any engine change. The register records it as a born
delta / maintenance entry, never as lineage adoption. The boundary
between the two record types *is* ADR-012 §2's contract-blind
boundary showing up in the maintenance posture.
### 4. What this does not decide
- **Sweep cadence** — deliberately unset; ADR-012 §6's no-ambient
posture governs (adoption is triggered, never scheduled).
- **Re-adoption of upstream as a dependency** — quality-read §7's
bar, unchanged and untouched here.
- **The scaffold task itself** — the register's initial contents and
the scaffold work items are the fork-scaffold task's body, now
without open questions; per the OQ's own scoping, this resolution
gates nothing and precedes no task.
## Consequences
**Positive**
- OQ-11's residue resolves without new machinery: the register is one
Markdown file whose two sections mirror the decided facts (identity,
deltas — cherry-picks riding the delta register as tagged entries),
the procedure is five steps of
discipline already implied by retained records, and the class-4
non-event status of substrate changes now has a paper trail.
- The audit posture scales: scaffold-time deltas and future
cherry-picks land in the same section in the same format, so a
reader of the subtree in
year three finds one register answering "what is this code, what
differs from its lineage, and why."
- The engine crate's changelog gains substrate visibility without
gaining versioning noise — the one-line entries carry no semver
movement and cannot be mistaken for contract events.
**Negative (accepted)**
- The register is maintained by discipline, not tooling — nothing
enforces that a substrate edit added a register entry beyond the
diff fence and review (the same accepted-enforcement weakness
ADR-013 §2 stated for contract-blindness; its answer here is the
same: low-risk because the boundary is structural and the file is
adjacent).
- The changelog-line duty adds one small standing obligation to every
cherry-pick; skipping it is a process defect, not a caught one.
## References
- OQ-11 (`docs/architecture/open-questions.md`) — this ADR's
resolution; items (2)/§2 and (3)/§3, item (1) dissolved by
[ADR-013] §4.
- [ADR-011](011-sqlite-substrate-fork.md) — the fork decision and its
scope register (the initial delta entries' content).
- [ADR-012](012-forked-substrate-design.md) — §1/§6 (the register's
naming and the upstream-tracking stance), §2 (the boundary that
splits cherry-picks from fork changes), §3 (the fidelity posture
the application step preserves), §4 (the port deltas).
- [ADR-013](013-fold-substrate-into-sqlite.md) — the fold (§3's
in-tree placement; §4's reshaping of this OQ); its re-sited
enforcement is the audit gap this register fills.
- [ADR-017](017-contract-versioning.md) — §1 (the one-normative-
owner rule this register's scope respects), §2 class 4 (substrate
cherry-picks as non-events — the classification this procedure
gives a paper trail), §4.2 (the version-stamped suite the
application step verifies against).
- `docs/research/quality-read-honker-core.md` — the fork calculus,
the defect register (D-1..D-7), the W-1..W-4 notes, §7's
re-adoption bar; the hash-citation pattern (`4881f27`, `3ab43aa`)
§2's lineage field adopts.
- alksocks `docs/architecture/decisions/011-inline-vendor.md` and
`013-targeted-fork.md` (@ `/workspace/@alkdev/alksocks`) — the
in-tree LICENSE/module-docs/git-history provenance shape, and the
quiet-upstream contrast (no register apparatus needed there) that
scopes why this ADR adds one.
[ADR-011]: 011-sqlite-substrate-fork.md
[ADR-012]: 012-forked-substrate-design.md
[ADR-013]: 013-fold-substrate-into-sqlite.md
[ADR-017]: 017-contract-versioning.md