Skip to content

First run in a foreign repository

forensic finding: what `doctor` reports in a repository that is not this one, why 109 of its findings cannot be satisfied there, and the decision that needs making

Rendered from docs/ADOPTION_FIRST_RUN.md — the same Markdown GitHub shows.

Majordomus reads healthy from inside its own checkout and broken from outside it. This document records the measurement, because the difference is 109 failures and nothing in the repository said so.

Everything below was verified against the published release v0.3.1 and the tree at c2629a26, by installing the tool the way a stranger does: curl | sh from the published site into a throwaway HOME, then a fresh git init repository. It was found twice, by two sessions working independently within minutes of each other, and the two decompositions agree exactly. Nothing here proposes a change; the reading is a decision, and section 6 states what has to be decided rather than deciding it.

0. What this was measured against

A forensic document that cannot be re-run decays into an assertion, so the conditions are pinned rather than described:

tool versionv0.3.1, the published release
how it was installedcurl -fsSL .../install.sh | sh into a throwaway HOME, from the public site — not a local build, not this checkout
repository under testa fresh git init with one empty commit and nothing else
repository the finding is aboutthis one, at c2629a26
rules the package ships50, all under .ai/repo/rules/vendor/majordomus/
rules that produce a finding41

A later run that differs from this is either drift or a fix, and the numbers above are what tells them apart.

1. The contract this falsifies

docs/ADOPTION.md does not merely suggest a first run. It prescribes four commands and then states an outcome:

Add the two hook lines init printed. Run doctor again; it should report zero failures.

That is a promise with a number in it, which is what makes it testable.

2. What actually happens

The documented path, followed to the letter and with nothing else done to the repository:

curl -fsSL https://majordomus.dev/install.sh | sh
cd <fresh repository>
majordomus init
majordomus update
# the two hook lines init prints, pasted into .git/hooks/
majordomus doctor

doctor reports 109 failures and exits 10. The two wiring checks flip to OK, so the hooks really are satisfied; the 109 are what remains after the document's own instructions have been carried out.

countfailure
68names claim <id>, which is not in docs/CLAIMS.yaml
39test test/cases/NN_*.sh does not exist
1test/run.sh does not glob test/cases/; a new case would not run
1no .github/workflows/validate.yml; nothing runs the doctrine tests on integration

Before the hooks are pasted the count is 111: the same 109 plus the two hook lines the document tells you to add. 109 is the number a person who reads our adoption document and does what it says will see.

3. Why every one of them is unfixable by the adopter

Each of the 109 names something that exists only in this repository: our claims ledger, our test cases, our test runner, our CI workflow. An adopting repository has none of them and is not supposed to.

108 of them are evidence paths: a document or a test file that a rule cites as its own proof. The 109th is a different kind of thing and section 5 takes it separately, so "rotted evidence path" describes 108 of these findings rather than all of them.

The references come from the rule package the tool installs. For example, ai-layout-integrity.v1.md, as init writes it into a foreign repository:

x-majordomus:
  claims: [init-refuses, ai-layer-manifest, local-state-ignored, legacy-migration, tool-location-independent]
  tests: [test/cases/01_init.sh]

Those two fields describe how we verify our own rule. The validator checks them in whatever repository it runs in, so away from home every one of them resolves to nothing.

This is the whole baseline rather than a noisy corner of it. The 109 come from 41 distinct doctrinesai-layout-integrity 6, projection-integrity 5, then state-consistency, rule-package-integrity, doctrine-wiring-integrity and context-integrity at 4 each, and a long tail — out of the 50 rules the package ships. In the fresh repository .ai/repo/rules/project/ holds only its README.md, so no project rule is involved and nothing is confounded. The finding is exactly that the shipped package cannot satisfy itself anywhere but at home.

4. Why nobody saw it

Every session that has ever run doctor ran it inside this checkout, where test/cases/ and docs/CLAIMS.yaml exist and every reference resolves. The tool is not silent about the problem; it has simply never been asked the question from outside.

The same shape produced the release defect found the same night: a push made with the default token starts no workflow, so a release could be published and the site would still serve a 404 for its metadata. Both are checks that pass in the one environment we look at and fail in the one the user is in. That is one lesson, not two.

5. The one that is wrong twice

Three of the four categories are rotted evidence paths — a document or a test that exists here and not there. The fourth is not:

FAIL doctrine    ci — no .github/workflows/validate.yml; nothing runs the doctrine tests on integration

That asserts the adopting repository must carry Majordomus's own workflow file. Fixing the evidence-path problem would silence it, but it would still be a rule about the vendor's CI enforced against the consumer's repository, which is a separate defect in the same line. It should be decided separately rather than swept up.

6. What has to be decided

The reading both sessions arrived at independently, stated so that it can be accepted or rejected rather than assumed:

On a vendored rule, x-majordomus.tests and x-majordomus.claims are the vendor's evidence for its own rule, not an obligation on the repository that adopts it. They should be verified in the package's home repository, where the paths resolve, and skipped elsewhere.

The caution that belongs with it. "Skip when the path does not resolve" is one line away from a check that cannot fail. Implemented lazily it skips the evidence everywhere, including here, and a package could then ship rules whose tests and claims had rotted with nothing to say so. For an adopter the two implementations behave identically, and in a diff they look identical. What distinguishes them is a test in this repository proving the evidence is still checked here. Without that test the fix is indistinguishable from the defect it replaces.

Not decided here: whether the vendored package should carry those fields at all, or carry them under a name that says whose evidence they are; whether the CI rule in section 5 should be removed, narrowed to the vendor, or restated as something an adopter can satisfy; and whether the two hook findings should read as pending rather than failed, which is a wording question with no design content and should not ride on the larger decision.

7. Reproduction

T=$(mktemp -d); export HOME="$T"
curl -fsSL https://majordomus.dev/install.sh | sh
mkdir "$T/r" && cd "$T/r" && git init -q . && git commit -q --allow-empty -m init
"$T/.local/bin/majordomus" init
"$T/.local/bin/majordomus" update
printf '#!/bin/sh\n%s doctor || exit $?\n' "$T/.local/bin/majordomus" > .git/hooks/pre-commit
printf '#!/bin/sh\n%s finish --check || exit $?\n' "$T/.local/bin/majordomus" > .git/hooks/pre-push
chmod +x .git/hooks/pre-commit .git/hooks/pre-push
"$T/.local/bin/majordomus" doctor; echo "exit $?"     # 109 failures, exit 10
rm -rf "$T"