Skip to content

I1409 — Derived data stops hashing inputs it cannot control

Make a committed derivation describe something it can be a projection of, and put the staleness refusal where merges actually happen.

READY wave 0 · p1 · implementation profile · runs alone

Part of derived-data-integrity — A committed derivation describes something it can be a projection of.

Ready. Every dependency is done, so majordomus plan start I1409 will be accepted.

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.

Current state

master 3fb515f5c carries site/data/generated at input hash e6abaa466516 while the tree hashes to 4fe657a09b10, and three consecutive master Pages runs (3fb515f5c, 2ce8d66e5, c44c2d4c8) failed identically at the build step on that refusal; the site last answered from 5e67318db. gh api repos/korczis/prismatic-majordomus/branches/master/protection returns 404 Branch not protected, so the site gate in .ai/repo/ci/gates.yaml that does check derived-data currency is advisory and reports after the merge rather than gating it. test/cases/56_derived_current_gate.sh exports git archive HEAD into a fixture whose object database contains no commits, so the changelog composition reports fatal: Not a valid commit name and changelog.* read as differing. One branch paid three full re-derives in one evening — 185s, 1125s and 2135s — solely because master moved underneath it; the growth is machine contention, about forty concurrent suites at load 152 on 18 cores, not tree size, and no absolute timing taken on 2026-09-10 should be read as a property of any command.

Desired state

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.

Scope

  • scripts
  • apps/majordomus-cli/src/release
  • .ai/repo/ci
  • test/cases

Out of scope

  • 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.

Dependencies

None. This issue is a root of the graph.

Acceptance criteria

  • 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-check
  • scripts/pages current
  • test/run.sh 56_derived_current_gate

Evidence required

  • derivation_survives_an_unrelated_merge
  • fixture_without_history_evaluates

Evidence

None recorded. Every token above needs a command or an artifact behind it before this issue can be completed; narrative is refused.

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.

Timeline

started
verified
completed

Those three fields, the evidence above and the state of the dependencies are all the status is made of. There is no status field to disagree with them.

Canonical record: .ai/repo/project/issues/I1409.yaml. Read it back with majordomus plan show I1409.