adopter-upgrades — An adopting repository survives a Majordomus upgrade, and what the tool vendors into it is adopter-neutral
An adopter can upgrade the tool, or vendor its rules, and be told exactly what to migrate before anything refuses its commits. The migration is a command, not a hand replay. What the tool writes into an adopter (rules, skeleton, scope, candidates) is neutral to this repository and keeps a PR-only trunk clean.
ACTIVE 14 of 20 issues done
- ready
- 4
- blocked
- 2
- active
- 0
- verify
- 0
- done
- 14
- cancelled
- 0
Problem
A downstream repository, OSCILLA, audited on 2026-10-04 against v0.13.0 (e08aec8651), shows the tool breaking its adopters in ways no test in this repository sees, because every test here runs this repository's own layout. Installing 0.12.0 machine-wide made doctor report 433 failures in OSCILLA: unsealed plan stamps and use-case steps reclassified as state-mutating. Its pre-commit hook runs doctor, so every commit was refused until the layer was migrated by hand, and the migrated layer is then rejected by 0.10. Nothing records which tool version a layer was written for, and migrate handles only .majordomus to .ai. Live use-case scenarios refuse read-only knowledge subcommands while admitting check --checkpoint, which writes. Upgrading the vendored rules leaves the policy and source keys they depend on behind. The knowledge deriver writes untracked candidates onto a trunk that only pull requests change. The vendored rules and the init skeleton carry this repository's narrative, test paths and stack.
Outcome
An adopter can upgrade the tool, or vendor its rules, and be told exactly what to migrate before anything refuses its commits. The migration is a command, not a hand replay. What the tool writes into an adopter (rules, skeleton, scope, candidates) is neutral to this repository and keeps a PR-only trunk clean.
Current state
At e08aec8651 the manifest and policy name no tool or layer version. SEALED_FROM is one global constant (lib/project.awk:45, plan.rs:122). share/commands.yaml classes each top-level command once. lib/migrate.sh moves only .majordomus/. docs/INSTALL.md says only to re-run the installer to upgrade. The knowledge deriver writes .ai/repo/knowledge/candidates/ in whichever checkout closes an episode. The rule package manifest says source_revision 0.6.0.
Desired state
doctor separates version-skew findings from defects and names the migration that clears them. migrate carries a layer from one version to the next, including re-sealing old plan stamps with their original times. The effect class is judged per subcommand and flag. rules vendor update reports or applies the skeleton keys the new rules need. Candidates never dirty a trunk. Vendored rules and the init skeleton say nothing that is only true of this repository.
Scope
- .ai/repo/project
- lib
- share
- apps/majordomus-cli/src/plan.rs
- docs
- test/cases
Out of scope
- OSCILLA itself: it is the evidence, read only
- Fixes already in flight: #780-#784, fix/context-resolves-a-path-not-yet-created, #746 and 9030dc4795
- Accepting any ADR these issues propose; acceptance is the owner's act
Acceptance criteria
- Upgrading the tool over a layer written by the previous minor version yields a doctor report that names version skew and the migration command, not defects; proven by the case I1980 adds
- A live use-case scenario may run a read-only subcommand of a command that also writes, and may not run a writing flag of a read-only command; proven by the case I1981 adds
- Every issue of this milestone is DONE or CANCELLED with its reason
Validation
- bin/majordomus plan validate
- bin/majordomus usecase validate
- scripts/derive-check
Evidence required
- version_skew_is_named
- effect_class_per_subcommand
Issues
Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.
| issue | status | wave | depends on | title |
|---|---|---|---|---|
| I1980 | READY | 0 | — | A layer names the tool version it was written for, and doctor tells version skew from defects |
| I1981 | DONE | 0 | — | A command's effect class is declared per subcommand and flag, and live scenarios judge the whole argv |
| I1982 | BLOCKED | 1 | I1980 | Plan stamps written before seals existed are re-sealed by a command that keeps their original times |
| I1983 | BLOCKED | 1 | I1980 | Upgrading the vendored rules reports or applies the policy and source keys those rules depend on |
| I1984 | DONE | 0 | — | Knowledge candidates never leave untracked files on a trunk that only pull requests change |
| I1985 | DONE | 1 | I1984 | A knowledge candidate relates to its own decision and commit, not to the whole episode |
| I1986 | DONE | 0 | — | The vendored rule package says nothing that is only true of this repository |
| I1987 | DONE | 0 | — | A knowledge record's file provenance survives the file's deletion |
| I1988 | READY | 1 | I1981 | A use case's setup scripts live where the adopter declares, and its scenarios may run the adopter's own commands |
| I1989 | DONE | 0 | — | init seeds a scope for the stack it finds, not for this repository's |
| I1990 | READY | 0 | — | The pre-push check judges the refs being pushed, or says why the checkout's task binds them |
| I1991 | DONE | 0 | — | A comment-only edit of the policy regenerates no bootstrap |
| I1996 | DONE | 0 | — | doctor does not fail an adopter on a check written for this repository |
| I1998 | DONE | 0 | — | A health command in an unsupervised repository does not exit 0 |
| I1999 | READY | 0 | — | A tracked file a hook regenerates does not refuse a task that never touched it |
| I2464 | DONE | 0 | — | A path that holds a space is one path to the scope doctrine |
| I2491 | DONE | 0 | — | A personal layer changes nothing shared: init --personal and install.sh --personal keep the layer one person's own, verify it, and --shared hands it to the team |
| I2849 | DONE | 0 | — | release next raises from the trunk the repository names, never from a branch name written in the tool |
| I2941 | DONE | 0 | — | A fresh init leaves doctor clean: a README that does not name AGENTS.md gains one paragraph that does |
| I2942 | DONE | 0 | — | An installer dry run changes nothing, and the install guide says only what is true |
The graph
flowchart LR
I1980["I1980<br/>A layer names the tool version it was written for, and doctor tells version skew from defects"]:::ready
I1981["I1981<br/>A command's effect class is declared per subcommand and flag, and live scenarios judge the whole argv"]:::done
I1982["I1982<br/>Plan stamps written before seals existed are re-sealed by a command that keeps their original times"]:::blocked
I1983["I1983<br/>Upgrading the vendored rules reports or applies the policy and source keys those rules depend on"]:::blocked
I1984["I1984<br/>Knowledge candidates never leave untracked files on a trunk that only pull requests change"]:::done
I1985["I1985<br/>A knowledge candidate relates to its own decision and commit, not to the whole episode"]:::done
I1986["I1986<br/>The vendored rule package says nothing that is only true of this repository"]:::done
I1987["I1987<br/>A knowledge record's file provenance survives the file's deletion"]:::done
I1988["I1988<br/>A use case's setup scripts live where the adopter declares, and its scenarios may run the adopter's own commands"]:::ready
I1989["I1989<br/>init seeds a scope for the stack it finds, not for this repository's"]:::done
I1990["I1990<br/>The pre-push check judges the refs being pushed, or says why the checkout's task binds them"]:::ready
I1991["I1991<br/>A comment-only edit of the policy regenerates no bootstrap"]:::done
I1996["I1996<br/>doctor does not fail an adopter on a check written for this repository"]:::done
I1998["I1998<br/>A health command in an unsupervised repository does not exit 0"]:::done
I1999["I1999<br/>A tracked file a hook regenerates does not refuse a task that never touched it"]:::ready
I2464["I2464<br/>A path that holds a space is one path to the scope doctrine"]:::done
I2491["I2491<br/>A personal layer changes nothing shared: init --personal and install.sh --personal keep the layer one person's own, verify it, and --shared hands it to the team"]:::done
I2849["I2849<br/>release next raises from the trunk the repository names, never from a branch name written in the tool"]:::done
I2941["I2941<br/>A fresh init leaves doctor clean: a README that does not name AGENTS.md gains one paragraph that does"]:::done
I2942["I2942<br/>An installer dry run changes nothing, and the install guide says only what is true"]:::done
I1980 --> I1982
I1980 --> I1983
I1981 --> I1988
I1984 --> I1985
classDef done stroke:#16a34a,fill:#dcfce7,color:#14532d,stroke-width:2px
classDef active stroke:#2563eb,fill:#eff6ff,color:#1e3a8a,stroke-width:2px
classDef verify stroke:#7c3aed,fill:#f5f3ff,color:#4c1d95,stroke-width:2px
classDef ready stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef planned stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef blocked stroke:#b45309,fill:#fffbeb,color:#78350f,stroke-width:2px
classDef cancelled stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2px
classDef superseded stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2pxLinked from this diagram (20)
- I1980 A layer names the tool version it was written for, and doctor tells version skew from defects
- I1981 A command's effect class is declared per subcommand and flag, and live scenarios judge the whole argv
- I1982 Plan stamps written before seals existed are re-sealed by a command that keeps their original times
- I1983 Upgrading the vendored rules reports or applies the policy and source keys those rules depend on
- I1984 Knowledge candidates never leave untracked files on a trunk that only pull requests change
- I1985 A knowledge candidate relates to its own decision and commit, not to the whole episode
- I1986 The vendored rule package says nothing that is only true of this repository
- I1987 A knowledge record's file provenance survives the file's deletion
- I1988 A use case's setup scripts live where the adopter declares, and its scenarios may run the adopter's own commands
- I1989 init seeds a scope for the stack it finds, not for this repository's
- I1990 The pre-push check judges the refs being pushed, or says why the checkout's task binds them
- I1991 A comment-only edit of the policy regenerates no bootstrap
- I1996 doctor does not fail an adopter on a check written for this repository
- I1998 A health command in an unsupervised repository does not exit 0
- I1999 A tracked file a hook regenerates does not refuse a task that never touched it
- I2464 A path that holds a space is one path to the scope doctrine
- I2491 A personal layer changes nothing shared: init --personal and install.sh --personal keep the layer one person's own, verify it, and --shared hands it to the team
- I2849 release next raises from the trunk the repository names, never from a branch name written in the tool
- I2941 A fresh init leaves doctor clean: a README that does not name AGENTS.md gains one paragraph that does
- I2942 An installer dry run changes nothing, and the install guide says only what is true
Canonical record: .ai/repo/project/milestones/adopter-upgrades.yaml. Read it back with majordomus plan show adopter-upgrades.