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
I1409
Derived data stops hashing inputs it cannot control
Where it stands
devtask.issue- Readiness
- complete completion is recorded and every required evidence token is present
- Status
- DONE
- Can start
- not now
- Milestone
- Milestone status
- VERIFY
- Wave
0- Waiting on
- none
- Holds back
- none
- Behind it in all
0- Evidence
3of3
Moves
plan.transitionWhat it is
- Objective
- Make a committed derivation describe something it can be a projection of, and put the staleness refusal where merges actually happen.
- Why
- Four separate reds on 2026-09-10 shared one root, and the publication was down for hours because of it. A committed artifact that hashes its whole input tree is invalidated by the next merge — not because it is wrong, but because the tree moved; and one that hashes HEAD can never describe the commit it is in. apps/majordomus-cli/src/release/changelog.rs:75 already carries that sentence in its own comment, and the gate reaches past it anyway: a comment is not a constraint. The second half is structural rather than a design flaw: the derived merge driver resolves to ours on purpose and leans on scripts/pages current in .githooks/pre-commit to refuse one command later, and a pre-commit hook cannot run for a merge the server creates. Three of the last five master merges were committed by GitHub. So the guard's coverage is inverse to its usage — present on the route we tell people not to take, absent from the one we recommend — which is why nobody noticed it was missing. A note on why this is filed under a new milestone rather than an existing one: majordomus plan validate refuses an issue that names no milestone, so an area of standing work without one cannot be recorded in the plan at all - which is how a repository lands a great deal of change against a plan that never moves. Attaching this to a least-wrong parent would put a false edge in a model whose value is that status is derived from the graph.
- Now
- Re-measured 2026-10-14 on master c0e3585dc8. (1) The site data is now a projection of a declared input list (scripts/generate-site-data --inputs, 1495 paths) fingerprinted by content, so a derivation committed at one commit describes that commit after the trunk moves on: case 3048 merges a side branch and a trunk commit that touch no canonical input and the fingerprint and `scripts/pages current` (site and registry halves) are unchanged, while a commit to one canonical input reads as stale (exit 10). (2) Branch protection now requires the `ci` check (required_status_checks.contexts = [ci], strict: false), and a pull request's ci runs on the server-side merge ref, so the staleness refusal does run on the path a GitHub merge takes; strict is false, so a PR judged at an older base can still merge after master moved, and nothing measured here refuses that at merge time. (3) Re-measured 2026-10-15 (I1409-b): a fixture exported with `git archive` into a fresh repository, whose object database holds none of the release commits, used to read as stale (exit 10: changelog.* and artifacts.* differ) because the changelog composed every release section empty. `majordomus generate` now asks git which release windows it cannot read (a release commit, or the previous one, absent, or a shallow repository) and keeps, for those only, the groups and decisions the committed changelog already carries for the same commit, saying so (`scripts/pages current` prints the notice); everything a record carries is still composed and judged. Case 3055 holds it on an export of the commit under test: both halves current, `generate` rewrites nothing, and a moved registry source exits 10. Case 56 still clones and may return to an export. (4) Decided 2026-10-15 (I1409-c, ADR 0101 amendment): the server-merge guard is the ci-time freshness gate, not strict required checks. It acts at three points: the required ci on refs/pull/<n>/merge (site: generate-site-data --check, rust: generate --check); the forge's own text merge, which runs no merge driver and so conflicts on site/data/generated/source.json source_hash whenever both sides changed a canonical input (a driverless git merge-tree of master c0e3585dc8 with #1159 and #1149, both CONFLICTING on GitHub, lists that file); and the merged commit on push, where validate 38635619021 ran derived site data in sync on GitHub's merge c0e3585dc8 and pages 38635619039 ran build (scripts/pages build, exit 10 when stale) at 21:07:39Z before publish at 21:09:30Z. docs/CI.md no longer says nothing server-side compensates; it names these checks and says the pre-commit hook guards only a merge a person commits. Residual, stated in the ADR: derived artifacts outside the site inputs (AGENTS.md regions, .claude/skills) can text-merge cleanly; generate --check on the push run is the first refusal, and the site publishes none of them. No slice remains.
- Wanted
- A committed derived artifact is a projection of inputs the commit owns, so landing another change does not invalidate it and a fixture without history can still evaluate it. The staleness refusal exists on the path merges actually take, so the first thing that says no is a check rather than the publication.
- Done when
- unknown: the record declares no `completion`
- Risk
- Medium. The hashing change touches what every gate compares, so a wrong narrowing makes staleness undetectable rather than merely noisy - the failure would be silent in the direction that matters. The findings behind this record were measured by four sessions on 2026-09-10 and cross-checked between them; the two timing figures those sessions took for scripts/pages current disagreed sixfold on one machine twenty minutes apart, which is why no number here is offered as a benchmark.
Acceptance
- A derived artifact committed at one commit is still current at that commit after an unrelated change lands on the trunk
- test/cases/56_derived_current_gate.sh passes in a fixture whose object database does not contain the commit under test
- The staleness check runs on the path a server-side merge takes, or the record says which decision was taken instead and why
- No document claims a guard that a merge created by GitHub cannot reach
- Validation
scripts/derive-checkscripts/pages currenttest/run.sh 56_derived_current_gate- Evidence required
derivation_survives_an_unrelated_mergefixture_without_history_evaluatesserver_merge_guard_decided- Evidence present
derivation_survives_an_unrelated_mergefixture_without_history_evaluatesserver_merge_guard_decided
Scope
- Touches
scriptsapps/majordomus-cli/src/release.ai/repo/citest/cases- Not
Making the Pages build re-derive instead of refusing. Generation here is not deterministic on a given tree - machine-dependent output, %h abbreviating against the local object store, a depth-1 checkout writing a degraded changelog - so a build that re-derived could produce a third answer matching neither side. This exit will be proposed again; it is recorded here so the reason it fails is not rediscovered.Turning on branch protection. Which check is required, and what it costs a merge, is a repository setting and the operator's decision.Weakening the derived merge driver. Resolving to ours is correct; what is missing is the refusal downstream of it on the server-side path.- Depends on
- unknown: the record declares no `depends_on`
- Priority
- p1
- Profile
- implementation
- Parallel safe
- false
- Owner
- unknown: the record declares no `owner`
The work so far
- Branches
- none
- Merged
- none
- Commits
0- Trunk
- master
- Sessions
- none
- Created
- 2026-09-10
- Updated
- 2026-10-15T07:54:48Z
- Started
- 2026-10-15T07:54:37Z
- Verified
- 2026-10-15T07:54:41Z
- Completed
- 2026-10-15T07:54:48Z
Synchronisation
- Adapter
scripts/github-sync- Repository
- unknown: the derived plan's header carries no repository
- External issue
- unknown: a live GitHub state; this executable makes no network call, so only `scripts/github-sync --check` can answer it
- State there
- unknown: a live GitHub state; this executable makes no network call, so only `scripts/github-sync --check` can answer it
Findings
Nothing is wrong with these records.