forked from heavy-duty/ceremony
fix: refuse non-regular nodes everywhere; machine-verify the mirror README
Round 1 (PR #43): codex-bot + kimi-bot reproduced cp writing through a committed symlink and cmp/find resolving through or skipping link nodes — both modes now refuse any non-regular node (.ceremony itself, anything under it, the root AGENTS.md incl. dangling links and non-file nodes) before touching the tree. kimi-bot also found .ceremony/README.md was machine-written but never machine-verified: --check now cmps it against the generated text (missing or drifted -> red). 22 regression rows; every symlink row asserts the victim untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
77f195a6ae
commit
85d4eb3770
2 changed files with 162 additions and 1 deletions
|
|
@ -39,11 +39,24 @@ set -euo pipefail
|
|||
# vendored copy — mirror means mirror, or "extra" files accumulate as
|
||||
# unverified doctrine).
|
||||
#
|
||||
# AND THE MIRROR IS PLAIN FILES. A symlink committed anywhere this tool
|
||||
# touches — a vendored path, a subdirectory, `.ceremony/` itself, the root
|
||||
# AGENTS.md — redirects the tool outside the mirror: cp writes THROUGH the
|
||||
# link (anywhere the CI token can reach), cmp reads through it and reports
|
||||
# the target's bytes as the mirror's, and a `find -type f` scan skips link
|
||||
# nodes entirely, so the stray poses as doctrine while staying invisible
|
||||
# (PR #43's review round, both findings reproduced). So both modes refuse
|
||||
# any non-regular node before touching anything: the fix for a symlink is
|
||||
# a human deleting it, never a tool following it.
|
||||
#
|
||||
# TWO FILES ARE SPECIAL, both deliberately:
|
||||
# * `.ceremony/README.md` is GENERATED here — the machine-managed marker
|
||||
# plus where the pin lives — instead of per-file banners, so every
|
||||
# vendored file stays byte-identical to its source and the check is a
|
||||
# plain cmp, never a strip-the-banner parse.
|
||||
# plain cmp, never a strip-the-banner parse. It is verified like
|
||||
# everything else (against the generated text, not the source tree):
|
||||
# the file that says "a hand edit goes red" must itself go red when
|
||||
# hand-edited, or the marker is the one unverified spot in the mirror.
|
||||
# * the consumer's ROOT AGENTS.md is scaffolded once by --fix and never
|
||||
# overwritten. Agent harnesses auto-load root AGENTS.md (the cross-agent
|
||||
# convention), so the stub is what makes "you are a reviewer here" a
|
||||
|
|
@ -221,6 +234,44 @@ a drill means here, code conventions) live in CONTRIBUTING.md.
|
|||
EOF
|
||||
}
|
||||
|
||||
# --- the mirror is plain files (see the header; PR #43's review round) ----------
|
||||
|
||||
# Refusals, not repairs, in BOTH modes — deliberately unlike drift, where
|
||||
# --fix is the advertised cure: repairing a symlink means either deleting a
|
||||
# node that points somewhere or writing through it, and a tool must do
|
||||
# neither on its own. -L before -d/-f everywhere: the test that follows the
|
||||
# link is exactly the bug.
|
||||
guard_plain_tree() {
|
||||
local offenders
|
||||
if [ -L "$MIRROR" ]; then
|
||||
die "docs-sync: $MIRROR is a symlink, not a directory — a linked mirror" \
|
||||
" redirects every write outside the tree this tool is allowed to" \
|
||||
" touch. Refusing both modes: delete the symlink, then re-run" \
|
||||
" docs-sync --fix."
|
||||
fi
|
||||
if [ -d "$MIRROR" ]; then
|
||||
offenders="$(find "$MIRROR" -mindepth 1 ! -type f ! -type d | LC_ALL=C sort)"
|
||||
[ -z "$offenders" ] || die \
|
||||
"docs-sync: non-regular node(s) in the mirror — a symlink (or fifo," \
|
||||
" socket, …) under $MIRROR/ makes cp write and cmp read outside the" \
|
||||
" mirror, and hides from the file scan. Refusing both modes; delete" \
|
||||
" these by hand, then re-run docs-sync --fix:" \
|
||||
"$offenders"
|
||||
fi
|
||||
if [ -L AGENTS.md ]; then
|
||||
die "docs-sync: the root AGENTS.md is a symlink — the scaffold and the" \
|
||||
" existence check must never resolve through a link (a dangling one" \
|
||||
" would even make --fix write through it). Refusing both modes:" \
|
||||
" replace the symlink with a regular file (or delete it and let" \
|
||||
" docs-sync --fix scaffold the stub)."
|
||||
fi
|
||||
if [ -e AGENTS.md ] && [ ! -f AGENTS.md ]; then
|
||||
die "docs-sync: the root AGENTS.md exists but is not a regular file —" \
|
||||
" nothing this tool could do to it is right. Refusing both modes:" \
|
||||
" remove it, then re-run docs-sync --fix to scaffold the stub."
|
||||
fi
|
||||
}
|
||||
|
||||
# --- check ----------------------------------------------------------------------
|
||||
|
||||
run_check() {
|
||||
|
|
@ -253,6 +304,18 @@ run_check() {
|
|||
" $MIRROR/ must be vendored and verified, or it poses as doctrine" \
|
||||
" without being checked. Fix: run docs-sync --fix (it deletes orphans)."
|
||||
done < <(mirror_files)
|
||||
|
||||
# The README is machine-written against generated text, so it is
|
||||
# machine-verified against the same text — the marker that warns "a hand
|
||||
# edit goes red" is not itself an unverified hole (kimi-bot, PR #43).
|
||||
if [ ! -f "$MIRROR/$README_NAME" ]; then
|
||||
complain "docs-sync: $MIRROR/$README_NAME is missing — the machine-managed" \
|
||||
" marker is part of the mirror. Fix: run docs-sync --fix."
|
||||
elif ! readme_content | cmp -s - "$MIRROR/$README_NAME"; then
|
||||
complain "docs-sync: $MIRROR/$README_NAME has drifted from its generated" \
|
||||
" content — the README is machine-written, and a hand edit here is" \
|
||||
" exactly what its own text warns against. Fix: run docs-sync --fix."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Existence only, content free: the stub is per-repo the moment the repo
|
||||
|
|
@ -313,6 +376,7 @@ run_fix() {
|
|||
fi
|
||||
}
|
||||
|
||||
guard_plain_tree
|
||||
case "$mode" in
|
||||
check) run_check ;;
|
||||
fix) run_fix ;;
|
||||
|
|
|
|||
|
|
@ -176,6 +176,103 @@ check "root AGENTS.md missing → check fails, teaching --fix" 1 \
|
|||
"run docs-sync --fix" in_consumer fresh --check --source "$SRC"
|
||||
in_consumer fresh --fix --source "$SRC" >/dev/null
|
||||
|
||||
# --- the README is machine-verified, not just machine-written -------------------
|
||||
# The marker that says "a hand edit goes red" must itself go red when
|
||||
# hand-edited (kimi-bot, PR #43's review round).
|
||||
|
||||
printf 'hand edit\n' >>"$TMP/fresh/.ceremony/README.md"
|
||||
check "hand-edited README → check fails naming it" 1 ".ceremony/README.md" \
|
||||
in_consumer fresh --check --source "$SRC"
|
||||
check "--fix rewrites the drifted README" 0 "wrote .ceremony/README.md" \
|
||||
in_consumer fresh --fix --source "$SRC"
|
||||
|
||||
rm "$TMP/fresh/.ceremony/README.md"
|
||||
check "missing README → check fails naming it" 1 \
|
||||
".ceremony/README.md is missing" in_consumer fresh --check --source "$SRC"
|
||||
in_consumer fresh --fix --source "$SRC" >/dev/null
|
||||
check "README repaired → check green again" 0 "exact mirror" \
|
||||
in_consumer fresh --check --source "$SRC"
|
||||
|
||||
# --- the mirror is plain files: symlinks and friends refused ---------------------
|
||||
# PR #43's review round (codex-bot + kimi-bot, independent repros): cp
|
||||
# writes THROUGH a committed link, cmp reads through it, and a `find
|
||||
# -type f` scan cannot even see it. Both modes refuse; every row with a
|
||||
# victim asserts the victim untouched.
|
||||
|
||||
consumer sneaky 0.3.0
|
||||
in_consumer sneaky --fix --source "$SRC" >/dev/null
|
||||
printf 'victim v1\n' >"$TMP/sneaky/victim.md"
|
||||
|
||||
rm "$TMP/sneaky/.ceremony/RULES.md"
|
||||
ln -s ../victim.md "$TMP/sneaky/.ceremony/RULES.md"
|
||||
check "vendored path as symlink → check refuses naming it" 1 \
|
||||
".ceremony/RULES.md" in_consumer sneaky --check --source "$SRC"
|
||||
check "vendored path as symlink → fix refuses (never writes through)" 1 \
|
||||
"non-regular" in_consumer sneaky --fix --source "$SRC"
|
||||
check "the link's target is untouched" 0 "victim v1" cat "$TMP/sneaky/victim.md"
|
||||
rm "$TMP/sneaky/.ceremony/RULES.md"
|
||||
in_consumer sneaky --fix --source "$SRC" >/dev/null
|
||||
|
||||
# A stray link is exactly what the -type f extra-file scan was blind to:
|
||||
# unlisted doctrine, previously invisible.
|
||||
ln -s ../victim.md "$TMP/sneaky/.ceremony/STRAYLINK.md"
|
||||
check "stray symlink (invisible to -type f) → check refuses" 1 \
|
||||
"STRAYLINK.md" in_consumer sneaky --check --source "$SRC"
|
||||
check "stray symlink → fix refuses too (no silent deletion of a link)" 1 \
|
||||
"STRAYLINK.md" in_consumer sneaky --fix --source "$SRC"
|
||||
rm "$TMP/sneaky/.ceremony/STRAYLINK.md"
|
||||
|
||||
mkfifo "$TMP/sneaky/.ceremony/PIPE"
|
||||
check "a fifo in the mirror → refused, not read" 1 "non-regular" \
|
||||
in_consumer sneaky --check --source "$SRC"
|
||||
rm "$TMP/sneaky/.ceremony/PIPE"
|
||||
|
||||
rm -rf "$TMP/sneaky/.ceremony/guide"
|
||||
mkdir -p "$TMP/sneaky/elsewhere"
|
||||
ln -s ../elsewhere "$TMP/sneaky/.ceremony/guide"
|
||||
check "vendored subdirectory as symlink → fix refuses" 1 \
|
||||
".ceremony/guide" in_consumer sneaky --fix --source "$SRC"
|
||||
check "nothing was written into the linked directory's target" 1 "" \
|
||||
test -e "$TMP/sneaky/elsewhere/DEEP.md"
|
||||
rm "$TMP/sneaky/.ceremony/guide"
|
||||
in_consumer sneaky --fix --source "$SRC" >/dev/null
|
||||
check "sneaky consumer repaired → check green" 0 "exact mirror" \
|
||||
in_consumer sneaky --check --source "$SRC"
|
||||
|
||||
consumer linked-mirror 0.3.0
|
||||
mkdir -p "$TMP/linked-mirror-target"
|
||||
ln -s ../linked-mirror-target "$TMP/linked-mirror/.ceremony"
|
||||
check ".ceremony/ itself a symlink → check refuses" 1 "symlink" \
|
||||
in_consumer linked-mirror --check --source "$SRC"
|
||||
check ".ceremony/ itself a symlink → fix refuses" 1 "symlink" \
|
||||
in_consumer linked-mirror --fix --source "$SRC"
|
||||
check "the link's target directory stayed empty" 0 "" \
|
||||
test -z "$(ls -A "$TMP/linked-mirror-target")"
|
||||
|
||||
consumer linked-stub 0.3.0
|
||||
printf 'stub victim\n' >"$TMP/linked-stub/other.md"
|
||||
ln -s other.md "$TMP/linked-stub/AGENTS.md"
|
||||
check "root AGENTS.md as symlink → check refuses" 1 \
|
||||
"AGENTS.md is a symlink" in_consumer linked-stub --check --source "$SRC"
|
||||
check "root AGENTS.md as symlink → fix refuses" 1 \
|
||||
"AGENTS.md is a symlink" in_consumer linked-stub --fix --source "$SRC"
|
||||
check "the scaffold did not write through the link" 0 "stub victim" \
|
||||
cat "$TMP/linked-stub/other.md"
|
||||
|
||||
# Dangling is the sharpest case: -e is false through a dangling link, so a
|
||||
# naive `[ ! -e ] && scaffold` writes the stub through it.
|
||||
rm "$TMP/linked-stub/AGENTS.md"
|
||||
ln -s does-not-exist.md "$TMP/linked-stub/AGENTS.md"
|
||||
check "dangling AGENTS.md symlink → fix refuses (would write through)" 1 \
|
||||
"symlink" in_consumer linked-stub --fix --source "$SRC"
|
||||
check "nothing appeared at the dangling target" 1 "" \
|
||||
test -e "$TMP/linked-stub/does-not-exist.md"
|
||||
|
||||
rm "$TMP/linked-stub/AGENTS.md"
|
||||
mkdir "$TMP/linked-stub/AGENTS.md"
|
||||
check "root AGENTS.md as a directory → refused, named" 1 \
|
||||
"not a regular file" in_consumer linked-stub --fix --source "$SRC"
|
||||
|
||||
# --- the action's wiring: inputs arrive as env vars -----------------------------
|
||||
|
||||
consumer env-wired 0.3.0
|
||||
|
|
|
|||
Loading…
Reference in a new issue