Decision Record: A lightweight lane for small fixes to reach the concept store
Summary
This epic adds a fifth entry point into the concept store, so that a change small enough not to justify an epic can still record why it mattered. The lane creates the two files the distiller already requires for a drainable entry, directly and without committing them, instead of teaching the distiller a new kind of entry. A structural rule called the razor keeps the lane honest: a fix may only append one decision log entry to a page that already exists, and that rule is checked mechanically before the distillation pull request opens.
Chosen Approach
The knowledge machinery is gated on epics at exactly two points. The file that heads a queue entry has one producer, and that producer walks an epic issue's sub-issue graph. The close stage's preconditions are epic-shaped, because they require every sub-issue of an epic to be closed. The lane bypasses both points by creating the pair of files directly in the area the distiller already scans for entries that were never committed, and then stopping. It writes nothing to GitHub, cuts no branch, opens no pull request, and commits nothing.
Everything downstream is reuse. The drain discovers the new directory alongside the ones it already scans. The merge precondition, the derivation of which entries have been consumed, the reciprocity fan-out, the code anchor refresh, the atlas regeneration, and the reviewed distillation pull request all run unchanged. The only new mechanism in the system is a validator mode that proves a page changed in no way except gaining one decision log entry. That mode rides a contract the drain already honours, which is that a non-zero exit from the validator blocks the pull request.
A standards conformance pass was attempted and found nothing to check against. This repository documents no standards for the command definitions and tooling libraries the epic changes, and its stack document covers only the Prime application. The lead confirmed that no standards are expected for this layer. The epic's own assumptions are therefore the operative constraints: new code reaches the repository's 95% coverage bar, the trunk branch is resolved the way the drain already resolves it, and no new configuration key is added.
Key Decisions
Create the epic lane's two files directly, and keep their existing names
- Decision: The new command writes the two files a drainable entry requires, using the same two file names the epic lane uses. A new field in the first file's header records that the entry is a fix, and that field is what the razor and the drain key on.
- Why: Only two things actually force an epic today, which are the single producer of the entry's header file and the epic-shaped preconditions of the close stage. Writing both files directly bypasses both. Reusing the file names buys a one-line change to the drain's discovery rule instead of a second code path through every phase downstream of it.
- Refuted alternative: Give the fix entry honestly named files and a parallel discovery and drain path. That reads correctly and avoids a permanent naming wart, but it doubles the surface area of the drain, which is the phase most expensive to keep correct, for a lane whose entire value is being cheap. The naming wart is accepted as the price of the small change. It is worth revisiting when a second kind of non-epic entry appears, because at that point the shape the two kinds share deserves a name of its own.
The command's input is a provenance reference, and it may name an issue or a pull request
- Decision: The command takes a reference in the grammar the concept store already uses for provenance. A bare number, a number with a leading hash, and an owner and repository qualified number are all accepted. The number may identify an issue or a pull request, and the command does not require one rather than the other.
- Why: One GitHub number is the irreducible floor rather than a preference, because the store's provenance grammar admits only forms an agent can follow back to the context. A fix with no number has no legal provenance token and cannot be filed at all. Issues and pull requests share one number namespace, so distinguishing them costs effort and buys nothing. Taking the reference already in provenance grammar removes a whole class of question about canonical form, because what resolves at the input is what reaches the page.
- Refuted alternative: Invent a new literal marker to sit beside the existing bootstrap and manual markers. It reads tidily, but a bare word cannot be followed back to any context, which defeats the only job a provenance marker has. It would also break the derivation of which entries have been consumed, because that derivation matches whole provenance tokens in structured positions, and one shared marker would match every fix entry ever drained.
A bare reference given from a workspace hub is qualified before it reaches a page
- Decision: When the command runs from a workspace hub and the developer gives a bare number, the reference written to the page is qualified with the owner and repository resolved from the entry's recorded range. The number the developer named is preserved exactly, and only the qualification is added.
- Why: The concept store requires that a drain running from a hub writes only the qualified form, because in a hub the issue never lives in the drain's own repository and a bare reference there resolves to the wrong issue. The specification's rule that provenance is always what the developer named governs which artifact is referenced, meaning that a pull request is never substituted for the issue it closes and an issue is never substituted for a pull request. It does not govern the qualification, so the two rules do not actually conflict.
- Refuted alternative: Refuse a bare reference outright when the command runs from a hub. That keeps the rule that what the developer named is what gets written literally true in every respect, but it adds a refusal the developer has to recover from in order to produce a reference the command could have derived without them.
The razor: a fix may only append to a page that already exists
- Decision: A fix entry may append decision log entries to pages that already exist. It may not create a page, retire one, or change what a page asserts. A change that needs to alter what a page asserts is a design change and keeps taking the epic lane.
- Why: The reason is that the store must not lie, and not that the process should be tidy. A concept page states intent rather than implementation, so a fix that restores intended behaviour leaves nothing in the page body false, and one appended log entry is the whole correct outcome. A fix that changes intended behaviour is different, because appending a log entry to a page whose body still asserts the superseded behaviour produces a page that contradicts itself. Retrieval from the store is grep-native, so a reader loads the summary and the key invariants and may never reach the log. The cheap path would therefore leave a confidently false invariant standing on a page marked as verified, which is worse than never recording the rationale at all. A store with gaps is more useful than a store that lies. The accepted cost is real and is stated plainly: a one-line change that invalidates one invariant line pays the full epic price.
State the razor as byte identity outside the appended entry, not as a list of forbidden fields
- Decision: The check truncates the changed page at the line where its single gained log heading starts, removes the attribution line from the page frontmatter, normalises trailing whitespace, and compares the result against the page content at the base, byte for byte.
- Why: The comparison is total. Every forbidden edit lands inside the compared region and fails, whether it touches a key invariant, a summary sentence, a neighbour list, an alias, a domain, a status, an integration bullet, or an earlier log entry. Nothing has to be enumerated, so nothing can be forgotten, and the check does not develop a silent hole when a new page field is introduced.
- Refuted alternative: Enumerate the forbidden fields and check each one. That gives better diagnostics and is easier to read, but it has to be maintained in step with the page schema. Its failure mode when it falls behind is a silent pass on exactly the edit the razor exists to catch.
The razor is load-bearing at the drain; the authoring command's version is advisory
- Decision: The razor is enforced in the validator, on the computed page changes, before the distillation pull request opens. The authoring command also warns when a fix looks like epic work, and that warning fails soft and is never a gate.
- Why: The authoring command writes no pages, so a check there can only guess at what the drain will later synthesise. The weight belongs on the side that can see the page writes it constrains. Enforcing in the validator introduces no new gate semantics, because the drain already cannot proceed past a non-zero exit. That is the point: the razor cannot be argued out of by a prompt.
- Refuted alternative: Enforce only in the authoring command and leave the drain untouched. That is the smaller change and gives the developer immediate feedback, but it gates the side with no visibility into the page writes, and it leaves the drain free to write whatever a prompt talks it into.
The recorded entry kind is authoritative, and a disagreement with the directory name is a hard block
- Decision: Every drain behaviour that depends on whether an entry is a fix dispatches on the kind recorded in the entry's header, not on the entry's directory name. When the recorded kind and the directory name disagree, the drain stops that entry with a named diagnostic.
- Why: The specification has discovery key on the directory name and the razor key on the recorded kind, and describes both as guards without saying which wins. Every resolution other than a hard block silently picks one of two contradictory claims about what the entry is. Choosing the directory name would additionally allow an entry to be drained as an epic while its own header says it is a fix, which is the case the razor exists to catch.
The recorded range must have reached the trunk, and a range is never guessed
- Decision: Both ends of the recorded range are full commit identifiers, and the head is verified to be an ancestor of the trunk before anything is written. When the range cannot be resolved from the reference and no range was supplied, the command asks the developer for it.
- Why: The drain's merge precondition tests reachability from the trunk. Recording a fix against work that has not landed guarantees the entry fails that gate later, and it trains the operator to waive the gate, which is the exact failure the two-test precondition exists to prevent. A wrong range distils the wrong pages weeks later and does so invisibly, so falling back to a default such as the previous commit is worse than asking.
Reuse the existing merge-safe range derivation through a new read-only read
- Decision: The range comes from the derivation the pull-request helper already implements, exposed through a new read that returns the range without creating a worktree. No second range derivation is written.
- Why: The existing derivation already handles squash merges, merge commits, and rebases correctly, including the comparison of changed file sets that resolves the ambiguous case between a squash and a rebase. The only gap is that the existing path returns the range as a side effect of checking out a worktree, and this lane has no use for a worktree.
- Refuted alternative: Compute a simpler range inside the new command. That avoids touching the helper, but it would have to solve squash and rebase safety again, and a second implementation of a subtle rule is a second place for it to be wrong.
The developer supplies only the reason; the description of the change is derived
- Decision: The command derives what changed from the difference across the resolved range, and asks the developer only why the change mattered. It also offers the feature the fix belongs to as an optional answer.
- Why: What changed can be read from the code, so it is read from the code, which is how the close stage already works. The single prompt for the reason is the lane's forcing function and the whole justification for the lane existing. The feature is optional because no phase of the drain reads it, so requiring it would only force the command to invent a value it cannot verify.
No approval checkpoint in the authoring command
- Decision: The command reports what it wrote and stops. It presents no approval gate.
- Why: Under the forcing-function razor, generating and stopping is one gate and persisting is another. This command writes nothing durable and nothing to GitHub, so a second gate over a scratch directory would be ceremony that forces no decision.
The conformance gate is skipped by contract, and the entry records that in words
- Decision: The conformance gate does not run against a fix entry. Pointed at one, it stops and gives that reason. The fix entry records a literal not-applicable value where an epic entry records its conformance result, and no receipt is written.
- Why: The gate checks implemented code against acceptance criteria, success metrics, and a decision record's invariants. A fix entry has none of the three, so running the gate is not optional, it is undefined. Stopping is the honest outcome, and degrading into a pass would be misleading. A literal value keeps the state searchable and means it can never be read as a waiver.
Single-repo checkouts and workspace hubs only, with no migration path for member repositories
- Decision: The command refuses to run inside a member repository and names the qualified reference form to use from the hub instead.
- Why: The existing migration path for member repositories has the epic as its unit, meaning the union of an epic's uncommitted artifacts and its committed scratch. A fix has neither an epic nor scratch, so supporting it would mean a second parallel implementation of a verify-then-remove protocol for a lane whose value is being cheap. The hub path needs no new machinery, because a qualified reference resolves against the member repository and the drain already resolves each recorded repository to its sibling checkout when it derives the difference.
- Refuted alternative: Build the migration path for member repositories. It removes an awkward rule about where the command may be run, but the machinery costs more than the lane it would serve.
A fix produces no process lesson, no deferred scope, and no decision record
- Decision: The lane writes no process lesson file and creates no directory for one. The fix entry has no deferred scope section and no process lesson section, and it carries no decision record reference.
- Why: The process lesson exists to carry estimate, decomposition, and sequencing lessons that planning estimation later consumes. A fix has no estimate, no decomposition, and no sequencing. Generating a lesson for every bug fix is exactly the speculative over-generation the razor exists to cut, and the volume would bury the real lessons. The absent decision record needs no new mechanism, because the drain already accepts an entry whose reasoning comes solely from the close record.
The derivation of which entries have been consumed is left unchanged
- Decision: No widening of that derivation is made, and this is load-bearing rather than incidental.
- Why: A fix entry's provenance token is exactly the form the existing matcher already handles in structured positions. The matcher never knew whether a number belonged to an epic, and it still does not need to. The result is better than unchanged: a fix that drains successfully is guaranteed to leave a provenance token, because the razor requires exactly one appended log entry per changed page and that entry's heading is a structured provenance position. The consequence accepted for epics, where a drain producing no changes leaves no provenance and is offered again on the next run, cannot arise for a fix, because a fix that produces no changes is a hard block instead.
A rationale that maps to no existing page is a named hard block
- Decision: When a fix entry's reasoning maps to no page that already exists, the drain reports a named per-entry hard block, writes nothing for that entry, and leaves the entry directory in place for a later run.
- Why: This is the razor working, not a gap in it. A decision with no page is a decision that needs a page, and creating pages is epic work. Leaving the directory in place keeps the entry discoverable, under the existing rule that an entry is never deleted automatically.
The strike-through loosening is deliberately not built
- Decision: A fix may not strike through a false invariant in place, even though that operation would be subtractive and mechanically checkable.
- Why: It is a real loosening of the razor, and loosening a razor before there is evidence that it binds too tightly is how razors die. The option stays available and stays checkable if the evidence arrives, so deferring it costs nothing except a later revisit.
Constraints & Invariants
The authoring command
- The command writes nothing to GitHub, cuts no branch, opens no pull request, and commits nothing; it creates exactly two files in the area the distiller scans for uncommitted entries, and then stops.
- The provenance recorded is always the reference the developer named, so a pull request is never substituted for the issue it closes and an issue is never substituted for a pull request, even when the range was taken from that pull request.
- When the command runs from a workspace hub, the recorded reference is qualified with the owner and repository resolved from the entry's recorded range, and the number the developer gave is preserved unchanged.
- A recorded range carries full commit identifiers at both ends, never a branch name and never a symbolic head, and its head is verified to be an ancestor of the trunk before anything is written.
- The command refuses and writes nothing when the target carries the repository's declared epic classification, when a materialised epic already exists for the same number, when it is run inside a member repository, or when a referenced pull request has not been merged.
- The feature association is written when the developer names one and omitted when they do not, and it is never invented, because no phase of the drain reads it.
- The razor warning raised by the authoring command is advisory and fails soft, so failing to warn is never a defect and never blocks anything.
The razor and the validator
- The append-only check is total by construction: it compares the page content ahead of the single gained log entry against the base content, byte for byte, exempting only the attribution line in the page frontmatter and trailing whitespace, and it enumerates no forbidden fields.
- The check requires that each path's status against the base is a modification and that the page resolves at the base, so page creation, page deletion, and page rename all fail on one test.
- Exactly one log heading may be gained per changed page, and both zero and more than one fail.
- The check applies to concept pages only, and derived sidecars that the drain regenerates are never passed to it, because a fix drain may legitimately create or rewrite them.
- The validator's existing checks and their exit behaviour are unchanged when the new mode is not requested, and they still run in full alongside it when it is.
- The drain verifies that the validator available to it supports the append-only mode before it drains a fix entry, and refuses that entry with a named diagnostic when it does not.
The drain
- A fix entry is drainable only when it carries both required files, exactly as an epic entry is, and it is excluded from drain-health accounting and never deleted automatically, exactly as any uncommitted entry is.
- Whether an entry is a fix is determined by the kind recorded in its header rather than by its location or directory name, and a disagreement between the recorded kind and the directory name stops that entry with a named diagnostic.
- Every change a fix entry produces is an update to an existing page; none creates a page, retires one, adds or removes a neighbour, or sets a domain, which makes the reciprocity fan-out empty by construction rather than by a special case.
- A fix drain never changes a page's verification status, because any such change lands inside the compared region and blocks the pull request.
- Each entry is applied, validated, and committed as its own unit before the next entry begins, so that the base the razor compares against is that entry's own starting point; batching several entries into one staging pass would make the check compare the wrong region and must not be introduced as an optimisation.
- The drain's checkpoint before the pull request names, for each fix entry, the page it will change and the log entry it will append, so the human sees the mapping before the write is prepared.
- The step that removes committed entries has no target for a fix entry, so it removes nothing and reports no missing target, and nothing in the uncommitted area is ever staged or committed.
- Draining an epic entry is unchanged by this epic, including an epic entry discovered in the same run as a fix entry, and the append-only mode is never applied to an epic entry's pages.
Risks (BLOCKER / ADDRESS only)
-
ADDRESS — a workspace hub can run a vendored copy of the validator that predates this epic, so the razor could fail to run at all: The drain runs the validator from source in a single-repo checkout, but from a vendored bundle in a hub, and that bundle is a copy that can lag. The validator treats an argument it does not recognise as a file path, so an older bundle handed the new mode would not run the razor, and would fail, if it failed at all, with a complaint about a missing file rather than a named refusal. A gate that can quietly not run is not a gate. The decision taken is invariant 13: the drain checks that the validator supports the mode before draining a fix entry, and hard-refuses the entry when it does not. An undrained fix can be recovered; a fix merged without the razor cannot, because the log entry it wrote is append-only.
-
ADDRESS — the razor bounds what a fix may write but not which page it writes to: The hard block for a rationale with no home fires only when the conclusion is that no page fits. The cheaper conclusion is to attach the entry to a loosely related page instead, and a wrong-page append is permanent, because the log is append-only and a correction is a second entry on a page that should never have carried the first. The decision taken is invariant 19: the drain's existing checkpoint before the pull request must name the target page and the entry it will append, per fix entry. The checkpoint already exists, so this is a reporting requirement rather than new machinery.
Open Clarifications
None. Four clarifications were raised during the analysis and all four were resolved before this record was written. Their resolutions are recorded above: the hub qualification rule, the authority of the recorded entry kind over the directory name, the ratified reuse of the epic lane's file names together with its revisit trigger, and the confirmation that no standards are documented for this layer.
Decision Record: A lightweight lane for small fixes to reach the concept store
Summary
This epic adds a fifth entry point into the concept store, so that a change small enough not to justify an epic can still record why it mattered. The lane creates the two files the distiller already requires for a drainable entry, directly and without committing them, instead of teaching the distiller a new kind of entry. A structural rule called the razor keeps the lane honest: a fix may only append one decision log entry to a page that already exists, and that rule is checked mechanically before the distillation pull request opens.
Chosen Approach
The knowledge machinery is gated on epics at exactly two points. The file that heads a queue entry has one producer, and that producer walks an epic issue's sub-issue graph. The close stage's preconditions are epic-shaped, because they require every sub-issue of an epic to be closed. The lane bypasses both points by creating the pair of files directly in the area the distiller already scans for entries that were never committed, and then stopping. It writes nothing to GitHub, cuts no branch, opens no pull request, and commits nothing.
Everything downstream is reuse. The drain discovers the new directory alongside the ones it already scans. The merge precondition, the derivation of which entries have been consumed, the reciprocity fan-out, the code anchor refresh, the atlas regeneration, and the reviewed distillation pull request all run unchanged. The only new mechanism in the system is a validator mode that proves a page changed in no way except gaining one decision log entry. That mode rides a contract the drain already honours, which is that a non-zero exit from the validator blocks the pull request.
A standards conformance pass was attempted and found nothing to check against. This repository documents no standards for the command definitions and tooling libraries the epic changes, and its stack document covers only the Prime application. The lead confirmed that no standards are expected for this layer. The epic's own assumptions are therefore the operative constraints: new code reaches the repository's 95% coverage bar, the trunk branch is resolved the way the drain already resolves it, and no new configuration key is added.
Key Decisions
Create the epic lane's two files directly, and keep their existing names
The command's input is a provenance reference, and it may name an issue or a pull request
A bare reference given from a workspace hub is qualified before it reaches a page
The razor: a fix may only append to a page that already exists
State the razor as byte identity outside the appended entry, not as a list of forbidden fields
The razor is load-bearing at the drain; the authoring command's version is advisory
The recorded entry kind is authoritative, and a disagreement with the directory name is a hard block
The recorded range must have reached the trunk, and a range is never guessed
Reuse the existing merge-safe range derivation through a new read-only read
The developer supplies only the reason; the description of the change is derived
No approval checkpoint in the authoring command
The conformance gate is skipped by contract, and the entry records that in words
Single-repo checkouts and workspace hubs only, with no migration path for member repositories
A fix produces no process lesson, no deferred scope, and no decision record
The derivation of which entries have been consumed is left unchanged
A rationale that maps to no existing page is a named hard block
The strike-through loosening is deliberately not built
Constraints & Invariants
The authoring command
The razor and the validator
The drain
Risks (BLOCKER / ADDRESS only)
ADDRESS — a workspace hub can run a vendored copy of the validator that predates this epic, so the razor could fail to run at all: The drain runs the validator from source in a single-repo checkout, but from a vendored bundle in a hub, and that bundle is a copy that can lag. The validator treats an argument it does not recognise as a file path, so an older bundle handed the new mode would not run the razor, and would fail, if it failed at all, with a complaint about a missing file rather than a named refusal. A gate that can quietly not run is not a gate. The decision taken is invariant 13: the drain checks that the validator supports the mode before draining a fix entry, and hard-refuses the entry when it does not. An undrained fix can be recovered; a fix merged without the razor cannot, because the log entry it wrote is append-only.
ADDRESS — the razor bounds what a fix may write but not which page it writes to: The hard block for a rationale with no home fires only when the conclusion is that no page fits. The cheaper conclusion is to attach the entry to a loosely related page instead, and a wrong-page append is permanent, because the log is append-only and a correction is a second entry on a page that should never have carried the first. The decision taken is invariant 19: the drain's existing checkpoint before the pull request must name the target page and the entry it will append, per fix entry. The checkpoint already exists, so this is a reporting requirement rather than new machinery.
Open Clarifications
None. Four clarifications were raised during the analysis and all four were resolved before this record was written. Their resolutions are recorded above: the hub qualification rule, the authority of the recorded entry kind over the directory name, the ratified reuse of the epic lane's file names together with its revisit trigger, and the confirmation that no standards are documented for this layer.