Problem
A source can be correct while the system around it is false. A paper changes but its reader context does not. A route changes but its examples still teach the old path. A canon rule changes but a shipped skill still carries the previous behaviour. The edit looks finished because the source file is finished; the break survives in its outputs.
The Author must never be the reminder system for those relationships.
Invariant
A substantive edit is incomplete until every materially affected output is either updated, explicitly confirmed current, or prepared as exact bytes behind the one consent gate that controls it.
Start with the link check: python3 ~/.local/share/alexandria/scripts/change_closure.py check <each file you changed> lists every output already known to follow those files that nobody has read since they changed (§ Receipts hold present state only), and the task is not done while one stays listed.
This applies to any artifact whose meaning, behaviour, audience, interface, or claims changed. A typo or formatting-only edit with no downstream effect does not earn a ceremony.
Find the affected surface
Use two passes. Neither is sufficient alone.
1. Explicit relationships
Start with relationships the system already declares:
- imports, callers, schemas, routes, manifests, tests, and generated-file headers
- source/derivative rules and build scripts
- links and named references in canon or documentation
- the links receipts already hold for the same source (§ Receipts hold present state only):
change_closure.py check <source>lists every one whose output has not been read since the source changed - an enabled publication's exact audience, context, metadata, and reader surfaces
These are the load-bearing edges. Follow them even when the changed wording looks small.
2. Semantic impact scan
Then search the relevant local corpus for the changed claim, concept, name, behaviour, and its close paraphrases. Judge each match by meaning, not string equality. This catches what no import graph can express: a new argument that changes a summary, a renamed concept that survives in onboarding, a product rule that changes what a reader should be able to ask.
Do not ask the Author to maintain a dependency graph. The Engine (the Author's own model) discovers relationships from the files and the current task, then keeps only the small amount of state needed to verify them later.
Close the change
Run this after the source edit, not as parallel authorship of source and derivative:
- Name the change set. List the current source files whose combined meaning changed.
- Discover outputs. Run both passes over the relevant local corpus.
- Resolve every material output to exactly one present-state result:
- updated — regenerated or edited from the current source, then verified;
- confirmed-current — read against the changed source and still semantically correct, with the reason recorded;
- prepared — the exact proposed output exists locally, but an outward write or visibility change waits behind one informed consent gate.
- Verify behaviour. Test the assembled result at the closest available surface. A matching hash proves which bytes were checked, not that they express the source correctly.
- Write the receipt. Give each material output its link in the unit's receipt, and stamp it with
confirmonly after the semantic check passes.
If an output cannot be closed, leave the receipt open and name the exact blocker. Never report the edit complete while an affected output is merely remembered in chat.
Receipts hold present state only
Receipts are sovereign local files under:
~/alexandria/system/change-closure/
One small receipt per stable change unit, not one per routine batch. A change unit is a source and the outputs that will move with it again: a paper and its reader context, a rule and the skills that carry it. Name the receipt for the unit, not the date, and rewrite it in place whenever that unit changes.
Each output that will need checking again is one link, a line anywhere in the receipt:
- <output> ← <source> | <how it follows> | <result> | reviewed <fingerprint>
It says the output is made from the source, or has to agree with it. Paths are relative to the alexandria folder or start with ~/ or /; an output that is not a file (a live page) is written as its address, and then only its source is checked. § <heading> after a markdown path narrows it to the part under that heading, so an edit elsewhere in a long file does not flag it. A heading the file holds more than once is named with its parent, § <parent> > <heading>, and the check lists it until it is. An output that follows two sources has two lines. The fingerprint is the source's when the output was last read against it, and only the link check writes it: python3 ~/.local/share/alexandria/scripts/change_closure.py lists every link whose source changed since then, never reviewed, or pointing at nothing (given paths, only the links whose output or source is one of them), and change_closure.py confirm <output> stamps an output's links once a model has read it against its sources and updated it, found it right, or prepared its update behind the gate that controls it (the gate itself waits in the queue, § Where each change goes). The script only flags; the judgement stays the model's. The link is the one line a script reads; for everything else write the clearest compact markdown the change needs. One useful shape:
# <change unit>
status: closed | prepared | open
## affected outputs
- <output> ← <source> | explicit:<relationship> | updated | reviewed <fingerprint>
- <output> § <heading> ← <source> § <heading> | semantic:<reason> | confirmed-current | reviewed <fingerprint>
- <outward target> ← <source> | explicit:<relationship> | prepared | reviewed <fingerprint>
## verification
- <the present-state behaviour that was checked>
## gate
- none
The headings, order, and result words are an example, not a contract. What matters is plain text from which the next capable model can recover the affected outputs, what each follows and why, what was verified, and what, if anything, still needs consent. Better models should improve the receipt without a migration.
For prepared, replace gate: none with the one exact action the Author can approve. For open, record the blocker and the unresolved output. A receipt is current only while the link check lists none of its links and its stated invariant still holds. Dates and modification times are never freshness proof: an old receipt can be true, and one written a minute ago can already be false.
A receipt is not an exhaustive dependency database. Record only relationships that were material to this closure or are load-bearing enough to check again.
Closed history leaves the folder. The folder holds only open or prepared closures and current receipts, never a log. A closed receipt whose inputs have moved on is history, which Git keeps: once the newer state is closed, in that unit's rewritten receipt or another one, delete the old receipt. If the newer state is not closed, that is unfinished closure: close it, then rewrite or retire the receipt. A folder of one-per-batch receipts from an older habit folds the same way: carry what a still-current unit needs into that unit's one receipt and delete the rest.
The workspace index
Keep one Engine-owned _index.md in the same folder. It is present state too, never a log: one entry per workspace the Engine edits, rewritten in place, holding the current workspace fingerprint and the current receipts that close it.
In a Git workspace, fingerprint the current HEAD, the staged and unstaged tracked diff, and non-ignored untracked files; Git's ignore rules keep build junk out. If the receipt folder sits inside that workspace, exclude it and use the remaining tracked-content state instead of a self-referential HEAD. Exclude only declared runtime churn such as logs, hook markers, and raw inbox material; canon, product, and system source stay watched. This detects a new source file as well as edits to existing ones, without reopening closure merely because a receipt was written. Outside Git, fingerprint the explicit source set the Engine touched. A workspace mismatch is a signal to inspect, never proof that an output is stale.
Session continuity
At the next session start, and before closing a session that made substantive edits:
- compare each touched workspace with
_index.md; a changed workspace with no matching current receipt is unfinished closure, so inspect its actual diff before doing related work; - run the link check and close every link it lists: read the output against its source, update it, find it right, or prepare it behind its gate, then
confirmit; a receipt that still records fingerprints another way is recomputed by hand and given links as it is rewritten; - for a receipt whose fingerprints no longer match or whose invariant no longer holds, close the newer state and rewrite or retire the receipt as above;
- finish
openwork before claiming the related edit done; - keep a
preparedgate within the current flow without inventing additional prompts; - when a prepared gate fires, find every receipt that names the released bytes or behaviour and refresh it from
preparedto the actual live state; a later receipt cannot silently close an older prepared one; - update
_index.mdonly after every substantive change in that workspace is closed or prepared.
This is a present-state check, not a scheduled freshness check.
The link check runs wherever a stale output would otherwise pass, so no model has to remember a link. Before calling an edit done, and at every close, run it on the files the task changed (change_closure.py check <each file>) and close what it lists; the alexandria skill's background pass runs it whole. Anything the Author builds or publishes with a script runs it for what it touched and prints what it lists. A build may confirm an output it has just regenerated from the current source, because those bytes came from it; everything that needs reading waits for the model. A publish never reports success while a link touching a published file is listed.
Where each change goes
Improving something other people use includes the people who use it: its public copy, what a new person installs, and the update existing installs are offered. Source-only, local-only, merged-only and signed-but-unavailable are steps on the way, never done.
Every change is one of three kinds, sorted the moment it is made.
- How the Author works belongs in a module. Update the module it improves, or save a new one in
system/skills/without asking. When a change reshapes a module (it now does two jobs, or belongs with another), split or move it in the same turn. Ask one plain question only when it is genuinely unclear where something belongs. - What the Author thinks belongs in their constitution. Where they publish, its public face is their Library pages.
- The Author's data stays where it is.
Where a module goes is read from the module, never from a registry. A skill with a shared: line has a public copy under the Author's name. A file whose header names a public module is the Author's own copy of it. A method the Author maintains for other people inside a product ships through that product's release. Everything else stays theirs, and a from: line only credits where a module came from. A module holds the general method; what is only the Author's (their settings, paths, examples and dated lessons) sits under a final ## Add-on heading in their own copy, wins where it differs, and never leaves their machine. Everything above the heading matches the public copy, so an improvement is written once, in general words, and goes out as written; sorting a new line is asking whether a stranger's system would want it with the names, dates and paths removed.
The reply says it. Whatever changed is a wrote on the reply's footer (wrote to *capture.md*), and whatever actually left is a sent (sent to *users*, sent to *marketplace*, sent to *library*). The line never says where a change will go later or that something waits: where it goes is read from the file, a change bound for other people goes out by the rules below and is named as sent when it does (in that reply, or in the sweep's one-line report), and what waits for the Author's yes is counted on the next chat's live line and offered in the alexandria skill. Nothing about modules or the Library happens silently.
One queue. Each change bound for other people is one line in system/change-closure/_queue.md, beside _index.md: what it is, where it goes, the source file and commit, and one plain sentence a stranger would understand in a changelog. The file holds only what has not gone out; a line leaves once its change is live, and Git keeps the history. Session start shows how many lines wait and when the last sweep ran, so carrying them out never depends on anyone remembering. A sweep is any pass that carries lines out (the alexandria skill's background pass, below, or a product's own release); each one rewrites a last sweep: <date> line at the top of the queue when it ends.
When it goes out.
- What the Author already published waits for their yes, in the alexandria skill. Its background pass prepares each waiting line: the new public copy of a shared module, with nothing private in it (names, positions not made public, paths that exist only on this machine), and the refreshed text of any approved Library page whose sources have moved, through that page's filter. It also checks every file that feeds a shared module or a published page for a change no reply queued. The menu's marketplace and mirror lines offer what is ready by name; choosing one shows the exact result, and a yes publishes exactly that version by its own route (the share step, or the Library approval). Anything still waiting at the session close is written to the notepad and offered again when the next session opens, after a check that it is still live; no close ever asks, and neither does ordinary work. An Author may give a standing yes for updates to modules they already share, kept as one line in their guide so every later session reads it; those then go out in the same background pass without asking, and a first share still waits for its one yes. Several changes to one module become one version. Anything new (a module never shared, a page never published, a wider audience) is not upkeep and keeps its own offer and its own yes.
- A product's own modules go out with the product. Whoever maintains a product carries their queued lines into it once a day under that product's release rules. New people get the change at setup; an existing install is told an update is waiting and takes it only when its owner chooses. Nothing ever writes into someone else's system.
Once a line goes out, verify what other people actually receive, clear it, and refresh every receipt that called it prepared. A no, or no answer, leaves it for the next session.
Consent and optional capabilities
Change closure never creates authority. Local derivatives may be regenerated automatically. Any publication, message, wider audience, account action, or other outward write keeps its existing consent boundary.
Optional capabilities degrade independently. If no Library, PLM, cloud bridge, native hook, or other adapter is enabled, omit that branch and close the local relationships that do exist. A missing optional branch never breaks the sovereign local loop.
For an already enabled named publication, closure stays inside the approved artifact, purpose, and audience. A new artifact, new destination, or broader audience is not maintenance; it needs a new direct request and its own gate.
Present state, not deltas
Under all of this is one test: read present ground truth and compare it to a named invariant, never only a record of what changed. A complete changelog can still mislead, and present state can show a break no recent change mentions. Deltas reconstruct history; state decides what to do next. Change closure applies the test to a source and all of its affected outputs, with fingerprints and a durable handoff to the next session, and the same test checks any other loop that can drift silently: installed files against the release, derived files against their source, a deploy against its readiness.
Failure classes
- Edit-trigger only. The Engine meant to update outputs in the same turn but missed one, or missed the receipt itself. The workspace fingerprint and the link check must still detect the false state later.
- Remembered links. A link kept only in a model's memory, or in prose such as a list of follow-up steps in a source's header, holds until the next model misses it. Write it as a link so the check carries it.
- A lagging source. A link to a copy nobody keeps current (a repository checkout behind its releases, an old export) watches the wrong bytes and stays wrong. Link to the file the release or build keeps current, or to the nearest copy that is.
- String search only. Renamed wording passes while a semantically stale summary survives. Always run the semantic pass.
- Graph theatre. A large hand-maintained dependency registry becomes another stale artifact. Discover from current files; retain only verified material edges.
- Receipt log. One dated receipt per batch piles up until every session re-checks history. Keep one receipt per unit and retire what Git already keeps.
- Hash theatre. Matching fingerprints prove identity, not correctness. Pair them with a stated semantic or behavioural verification.
- Consent collapse. Treating an existing relationship as permission to publish new bytes or widen an audience. Prepare behind the existing gate instead.
- Optional-core coupling. A missing external adapter blocks local work. Omit the unavailable branch and close the rest.
- Protected-source blind spot. A generated installer surface changes while a protected Author-owned source remains authoritative and therefore untouched. Treat protection as a relationship, not an exemption: update or confirm the authoritative source separately, then test the behaviour in a genuinely fresh host session.
- Gate-fired receipt drift. Exact bytes ship later, but the receipt that called them
preparedis never reopened, leaving two official states. Firing a gate must refresh every receipt that depended on it before the release can be called closed. - Silent divergence. A shared module improves in the Author's own copy and never reaches its public copy, until an update overwrites the improvement or other people run the older version. The reply queues it at the edit; the alexandria skill's check of every feeding file catches what a reply missed.
When Not To Use
Skip a receipt for edits that provably change no meaning or behaviour. Use one whenever a missed downstream effect could make the system confidently present two different truths.