169. What an adoption leaves behind is measured, not remembered
Context
The milestone two-folder-integration promises that a repository adopting Majordomus gains
.ai/, .majordomus/ and a few small recorded bridges, and can give them back. Every phase
after the first decides something about paths: the namespace contract, the ownership ledger,
the reconciler, uninstall, and the migration of this repository's own footprint. Those
decisions need a footprint someone has actually measured.
The first reading (docs/TWO_FOLDER_CONSOLIDATION.md at 948dbd93, 2026-09-06) was
accurate on the day it was written, and five weeks later it no longer was. Since then
capture install started writing two Claude Code mods under .claude/skills/. generate
started writing editor skills, a .zed/tasks.json and, in a foreign repository,
Majordomus's own installer page. Worktree creation started turning on
extensions.worktreeConfig. completion install started writing a block into the shell's
startup file. None of these writes was decided as part of a footprint; each was the sum of
what one subsystem needed.
Decision
-
The footprint is an inventory with its writer on every row. Section by section,
docs/TWO_FOLDER_CONSOLIDATION.mdlists every path written or mutated outside.ai/and.majordomus/: the default adoption, the lifecycle after it, commands run by choice, the git directory, config and refs, and the machine. Each row gives the file and line that writes the path, its write mode, what records it, and its class. The classes arecanonical,derived,generated,cache,runtimeandaccidental, each defined by the reason the path exists. -
The default adoption is held by a test. Case 3113 adopts a new repository with
init,updateandcapture install. It fails when the set of paths outside the two namespaces differs from the inventory's list, or when the inventory does not name one of them. It also fails when a personal adoption is visible togit status, or when the adoption's exclude block omits a path the adoption wrote. A writer cannot add to the default footprint without the inventory changing in the same commit. -
Each row has a fate. A path is a bridge when a consumer outside Majordomus reads it there. Examples:
AGENTS.md, the provider root files,.claude/settings.json, the mods Claude Code loads only from.claude/skills/, git's own configuration, and the personal exclude block. A path moves into a namespace when nothing outside reads it where it is. Examples: the.gitignoreboundary lines, which become.ai/.gitignore; the.claude/hooks/shims;docs/generated/in an adopter; thetmp/andtarget/outputs; andrefs/offload/*, which goes torefs/majordomus/. A path stops being written in an adopter when it isaccidental, as Majordomus's owndocs/INSTALL.md,site/static/install.shand site registry are.refs/majordomus/is recognised as a third namespace, in git's ref space. -
Later phases extend what exists. The inventory names, for each later phase, the mechanism that already does part of the work:
- The ownership ledger (I1109) grows out of the personal exclude block, which already records every path a personal layer wrote.
- Uninstall (I1111) refuses an edited bridge using the comparison the provider stamp
already makes. Its pattern for a managed block is
completion install --remove. - The reconciler (I1108, I1110) takes the shape the editors plan already has: desired state, actual state, conflict and orphan.
- Keeping source-tree projections out of an adopter (I1112) reuses the
share_is_hereandcrate_is_herepredicates.
A phase that builds a second mechanism beside one of these has to say why the first could not be extended.
-
This decision moves no path. It records the footprint and its fates. The defects the inventory found are each assigned to the phase that changes them. Among them: the Rust provider renderer overwrites a hand edit;
updatewrites a target without a path guard; a stale.zed/tasks.jsonis never removed; and the SessionEnd commit bypasses the repository's hooks.
Alternatives rejected
- Keep the first reading and patch it when someone notices. That is how it drifted. A footprint is a claim about every writer at once, and only a measurement can keep up with every writer.
- Put the inventory in
docs/INTEGRATION.md, as the issue record first said. That document became the pull-request integration manual after the issue was written. A footprint table inside it would serve neither topic. The issue's scope is corrected to the programme's own forensic document instead. - Test every row, optional commands included. The optional commands need a forge, a mesh, a declared offload host or a Zola site before they write anything. A test that fakes all of those proves the fakes, not the writers. The default adoption is the one every adopter performs, so that is the part the test holds. The rest is held by this reading until the ledger (I1109) records each mutation as it happens.
- Fix the defects in the same change. The issue's own risk is that "a forensic phase can become a rewrite the moment it finds something ugly". The defects are listed and each is assigned to a phase.
Consequences
- The phases of the milestone start from a footprint that a test measured on 2026-10-16, not from memory. A change to the default adoption's footprint now fails case 3113 until the inventory and its classification are updated with it.
- The inventory shows that two implementations write the root provider files and that only the shell one refuses a hand edit. This is now a stated constraint on I1114 rather than something to rediscover.
- The largest footprint that is not a file is now named: with the skeleton's defaults, every SessionEnd commits and tries to push. The ledger has to record history mutations, not only files and config.
- The sections that cover optional commands carry no automated guard until I1109. They are
dated to
d1162668036, and they are to be read as that day's measurement.