Skip to content
Cockpit0.29.0
A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live

test/cases/03_update.sh

An object of kind test

What it is

URI
majordomus://test/test/cases/03_update.sh
Kind
test
Identity
test/cases/03_update.sh
Path
test/cases/03_update.sh
Provenance
declared
Media type
text/plain
As a capability
test.test/cases/03_update.sh

Front matter

{}

Content

# majordomus-covers: update
# majordomus-negative: update doctor
# claims: bootstrap-chain, context-budget, exit-code-contract, no-counts-in-context, no-silent-overwrite, pointer-integrity, projection-fingerprint, projection-generation, wiring-reconciliation
. "$ROOT/test/lib.sh"
"$MJ" init >/dev/null
expect_exit 0 "$MJ" update
expect_grep '^create CLAUDE.md'
[ -f CLAUDE.md ] && [ -f AGENTS.md ] && [ -f GEMINI.md ] && [ ! -e .ai/repo/generated ]
# the target describes itself: the stamp names the policy hash and the content hash
expect_grep '^<!-- generated by `majordomus update` from .* \(policy [0-9a-f]{12}, content [0-9a-f]{16}\) ' CLAUDE.md
expect_grep 'generated by `majordomus update`' CLAUDE.md
# the generated files are bootstraps: they point at the layer and carry no rule of their own
for f in CLAUDE.md AGENTS.md GEMINI.md; do
  expect_grep '\.ai/README\.md' "$f"
  expect_grep 'README\.md' "$f"
  expect_grep 'majordomus context' "$f"
  expect_no_grep '\{\{' "$f"
  expect_no_grep '^\| `debugging` \|' "$f"
  expect_no_grep '^- \*\*' "$f"
done
# the policy reaches them: the default profile is named, and changing it changes the file
expect_grep '`implementation`' AGENTS.md
expect_grep '"event":"projections.updated"' .ai/local/state/ledger.jsonl
# deterministic: second run changes nothing
sha1=$(sha256_of_file CLAUDE.md)
expect_exit 0 "$MJ" update
expect_grep '^unchanged CLAUDE.md'
[ "$(sha256_of_file CLAUDE.md)" = "$sha1" ]
# doctor now only fails on wiring (10), not missing (12)
expect_exit 10 "$MJ" doctor
expect_grep 'OK +projection +CLAUDE.md — content matches its stamp'
expect_grep 'OK +budget'
# wire the hooks -> doctor healthy
cat > .git/hooks/pre-commit <<H
#!/bin/sh
$MJ doctor || exit \$?
H
cat > .git/hooks/pre-push <<H
#!/bin/sh
$MJ finish --check || exit \$?
H
chmod +x .git/hooks/pre-commit .git/hooks/pre-push
expect_exit 0 "$MJ" doctor
expect_grep 'doctor: 0 failure'
# majordomus.bootstrap-integrity: a person reaches the layer through README.md and AGENTS.md
printf '# Fixture\n\nSee AGENTS.md for how AI workers operate here.\n' > README.md
expect_exit 0 "$MJ" doctor
expect_grep 'OK +bootstrap +README.md — names AGENTS.md'
expect_grep 'OK +bootstrap +[0-9]+ projection\(s\) — each points at .ai/README.md'
printf '# Fixture\n\nNo pointer.\n' > README.md
expect_exit 10 "$MJ" doctor
expect_grep 'FAIL bootstrap +README.md — does not name AGENTS.md'
printf '# Fixture\n\nSee AGENTS.md for how AI workers operate here.\n' > README.md
# a generated file that grows a rule corpus is the two-rulebooks failure, and is refused
printf '\n### Rules\n\n- **English only** in code.\n' >> AGENTS.md
expect_exit 10 "$MJ" doctor
expect_grep 'FAIL bootstrap +AGENTS.md — carries a rule corpus of its own'
"$MJ" update --force >/dev/null
expect_exit 0 "$MJ" doctor
# a hook that swallows the exit code is not wired
printf '#!/bin/sh\n%s doctor || true\n' "$MJ" > .git/hooks/pre-commit
expect_exit 10 "$MJ" doctor
expect_grep 'swallows the exit code'
printf '#!/bin/sh\n%s doctor || exit $?\n' "$MJ" > .git/hooks/pre-commit
# hand edit: doctor flags it, update refuses, --diff shows it, --force restores
echo "my own rule" >> CLAUDE.md
expect_exit 10 "$MJ" doctor
expect_grep 'content differs from its own stamp'
expect_exit 15 "$MJ" update
expect_grep 'hand-edited'
expect_exit 0 "$MJ" update --diff CLAUDE.md
expect_grep '^-my own rule'
expect_exit 0 "$MJ" update --force
expect_no_grep 'my own rule' CLAUDE.md
expect_exit 0 "$MJ" doctor
expect_grep 'OK +links +AGENTS.md — all references resolve'
expect_grep 'OK +counts +AGENTS.md — no hardcoded counts'
# pointer-integrity and no-counts-in-context: a reference that does not resolve and a
# hardcoded count in the always-loaded file are each a failure named by doctor
printf '\nSee [the guide](docs/no-such-guide.md).\nThis repository has 42 agents.\n' >> AGENTS.md
expect_exit 10 "$MJ" doctor
expect_grep 'FAIL links +AGENTS.md — reference docs/no-such-guide.md does not resolve'
expect_grep 'FAIL counts +AGENTS.md — hardcoded count in always-loaded context'
expect_exit 0 "$MJ" update --force
expect_exit 0 "$MJ" doctor
# a fresh clone carries the stamps inside the targets, so doctor judges it exactly as here:
# no provenance file, nothing to regenerate, and a hand edit there is caught the same way
git add -A >/dev/null; git commit -qm "projections" >/dev/null
clone="$(mktemp -d "${TMPDIR:-/tmp}/mj-clone.XXXXXX")"; rm -rf "$clone"
git clone -q "$T" "$clone"
( cd "$clone" && git config user.email t@example.com && git config user.name t )
cat > "$clone/.git/hooks/pre-commit" <<H
#!/bin/sh
$MJ doctor || exit \$?
H
cat > "$clone/.git/hooks/pre-push" <<H
#!/bin/sh
$MJ finish --check || exit \$?
H
chmod +x "$clone/.git/hooks/pre-commit" "$clone/.git/hooks/pre-push"
expect_exit 0 "$MJ" --repo "$clone" doctor
expect_grep 'OK +projection +CLAUDE.md — content matches its stamp'
[ ! -e "$clone/.ai/local" ] || { echo "    a fresh clone carried local state"; exit 1; }
# every reading command answers on a clone with no local state at all, as CI runs them
for c in "context --budget-lines 400" "history --validate" "handover --resolve" "checkpoint --list" "decision list" "question list --all" "prompt list" "rules list" "doctrine status"; do
  # shellcheck disable=SC2086
  expect_exit 0 "$MJ" --repo "$clone" $c || { echo "    $c failed on a fresh clone"; exit 1; }
done
[ ! -e "$clone/.ai/local" ] || { echo "    a reading command created local state on the clone"; exit 1; }
echo "clone edit" >> "$clone/CLAUDE.md"
expect_exit 10 "$MJ" --repo "$clone" doctor
expect_grep 'content differs from its own stamp'
rm -rf "$clone"
# dry run writes nothing
sed -i.bak 's/checkpoint_interval_default: 15m/checkpoint_interval_default: 20m/' .ai/repo/policy.yaml; rm -f .ai/repo/policy.yaml.bak
expect_exit 0 "$MJ" update --dry-run
expect_grep '^update CLAUDE.md'
expect_no_grep '20m' CLAUDE.md
# over budget: nothing written, exit 10
sed -i.bak 's/always_loaded_budget_lines: 150/always_loaded_budget_lines: 10/' .ai/repo/policy.yaml; rm -f .ai/repo/policy.yaml.bak
expect_exit 10 "$MJ" update
expect_grep 'nothing written'
expect_no_grep '20m' CLAUDE.md
# unknown provider template
sed -i.bak 's/provider: gemini/provider: nosuch/' .ai/repo/policy.yaml; rm -f .ai/repo/policy.yaml.bak
sed -i.bak 's/always_loaded_budget_lines: 10/always_loaded_budget_lines: 150/' .ai/repo/policy.yaml; rm -f .ai/repo/policy.yaml.bak
expect_exit 12 "$MJ" update
expect_grep 'no template for provider'