Landing precondition: judge public/ as COMMITTED, and point stale consumers at the build owner

- Check 2 read public/ from the filesystem, so a gitignored, untracked leftover (a pre-rename public/assets/
  in an older worktree) REFUSED repo's clean README-only landing. It now reads the commit being landed
  (git ls-tree HEAD public/). Tested: an ignored leftover passes; a committed extra folder is still refused.
- The stale-consumer refusal now states README rule 5 (dx-shepherd, 2026-09-25): one agent owns the full
  build. A change that touches library/ or anything compiled into other plugins is handed to studio to land
  with the consumer rebuild; a change stale only in the author's own plugin rebuilds just that bundle.
This commit is contained in:
doxa-maps-88655c70 2026-09-25 20:40:15 +00:00
parent c53367f920
commit db92d79d0a

View file

@ -22,7 +22,6 @@
* DOXA_SKIP_FRESHNESS=1 skips (4) — for a DRY check only; main-capture scan never sets it.
*/
import { spawnSync } from 'node:child_process'
import { readdirSync, existsSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { TREE_NAME } from './_tree-name.mjs'
@ -42,8 +41,11 @@ if (dirty.stdout.trim()) refuse('the working tree differs from the commit being
console.log('LANDING-PRECONDITIONS: 1 clean — the working tree is the commit being landed')
// 2. ONE FOLDER, and it is the configured one
const pub = resolve(ROOT, 'public')
const entries = existsSync(pub) ? readdirSync(pub).sort() : []
// What LANDS is the commit, so read public/ from HEAD, not from the disk: a gitignored, untracked leftover
// (a pre-rename public/assets/ in an older worktree) refused a clean README-only landing (repo, 2026-09-25).
const tree = run('git', ['ls-tree', '--name-only', 'HEAD', 'public/'])
if (tree.status !== 0) refuse('could not read public/ from HEAD')
const entries = tree.stdout.split('\n').filter(Boolean).map((l) => l.replace(/^public\//, '')).sort()
const want = ['README.md', TREE_NAME].sort()
if (JSON.stringify(entries) !== JSON.stringify(want)) {
const extra = entries.filter((e) => !want.includes(e)), missing = want.filter((e) => !entries.includes(e))
@ -62,7 +64,13 @@ if (process.env.DOXA_SKIP_FRESHNESS === '1') {
console.log('LANDING-PRECONDITIONS: 4 fresh — SKIPPED (DOXA_SKIP_FRESHNESS=1; dry check only, never set by main-capture scan)')
} else {
const fresh = run(process.execPath, ['internal/check-composite-freshness.mjs'])
if (fresh.status !== 0) refuse('a committed composite is not what its source builds — rebuild it (BUNDLE=<name> bun x vite build, bun run tree, bun run fix:sri) and commit', (fresh.stdout + fresh.stderr).trim().split('\n').filter((l) => /✗|STALE|FAIL/.test(l)).slice(0, 8).join('\n'))
if (fresh.status !== 0) refuse(
'a committed composite is not what its source builds (stale consumers).\n' +
'README rule 5: ONE agent owns the full build. If your change touches library/ or anything compiled into\n' +
'other plugins, do not rebuild everyone yourself: commit it on your branch and hand the landing to studio\n' +
'(msg-agent studio "please land <branch>@<sha>: library change, consumers need rebuilding"). If only YOUR\n' +
'own plugin is stale, rebuild just it: BUNDLE=<name> bun x vite build && bun run tree && bun run fix:sri.',
(fresh.stdout + fresh.stderr).trim().split('\n').filter((l) => /✗|STALE|FAIL/.test(l)).slice(0, 8).join('\n'))
console.log('LANDING-PRECONDITIONS: 4 fresh — every composite rebuilds to its committed bytes')
}
console.log('LANDING-PRECONDITIONS: PASS — this commit may land.')