From 46d26e868bae4aae38230cddc7604950e5af61dc Mon Sep 17 00:00:00 2001 From: "K. Isom" Date: Thu, 17 Sep 2026 10:14:22 -0700 Subject: [PATCH] Make gate scripts handle table-form dependencies and fail closed Implemented-By: Laguna S 2.1 (OpenCode) --- docs/implementer-log.md | 1 + scripts/check-crate-deps.sh | 44 ++++++++++------ scripts/check-dep-docs.sh | 97 ++++++++++++++++++++++++------------ scripts/check-lines.sh | 22 +++++--- scripts/test-gate-scripts.sh | 21 ++++++++ 5 files changed, 130 insertions(+), 55 deletions(-) mode change 100644 => 100755 scripts/check-crate-deps.sh mode change 100644 => 100755 scripts/check-dep-docs.sh mode change 100644 => 100755 scripts/check-lines.sh diff --git a/docs/implementer-log.md b/docs/implementer-log.md index b964491..de39fd9 100644 --- a/docs/implementer-log.md +++ b/docs/implementer-log.md @@ -13,6 +13,7 @@ reviewer adds findings under "Reviews" once per milestone. | M1/06-proto-records | 2026-09-17 | done | 2 | fail | none | Added crates/proto/src/audit.rs (DecisionRecord, AuditRecord) and crates/proto/src/log.rs (ToolCall, LogRecord) re-exported from lib.rs; 3 fixture tests pass, 40 total across the five proto test files. cargo-fmt reordered the lib.rs re-exports before the gate. | | M1/07-brokerd-decision | 2026-09-17 | done | 1 | pass | none | Added Decision (Debug only, private fields), decide (Err(NoGrant) until M3) and the run stub (ToolResponse::Failed) in crates/brokerd; Decision::new carries expect(dead_code). 2 unit + 3 doctests (2 compile_fail) pass; verified the compile_fail guards by temporarily making new pub. `make gate` prints `gate: ok`. | | M1/08-proto-strictness | 2026-09-17 | done | 1 | pass | none | Added deny_unknown_fields to AuditRecord and ToolCall in crates/proto; bounded Timestamp (MAX const, from_unix_millis -> Result, parse bounds via from_unix_millis, now clamps to MAX) in ids.rs. 45 proto tests pass; `cargo fmt --all` and `make gate` print `gate: ok`. | +| M1/09-gate-scripts-table-form | 2026-09-17 | done | 1 | pass | none | Rewrote check-lines, check-crate-deps and check-dep-docs to parse table-form (`[dependencies.x]`) and dotted (`x.path`) dependencies and to fail closed (exit 1 when crates/Cargo.toml/docs/dependencies.md is missing); check-lines now prints `file has N lines (limit 500)`. Self-test passes with 0 failures, all three scripts pass on the real tree, and `make gate` prints `gate: ok`. | ## Reviews diff --git a/scripts/check-crate-deps.sh b/scripts/check-crate-deps.sh old mode 100644 new mode 100755 index c38ee8c..a995de4 --- a/scripts/check-crate-deps.sh +++ b/scripts/check-crate-deps.sh @@ -1,12 +1,20 @@ #!/bin/sh # Fails if `proto` depends on any workspace crate, or if any other crate depends on # a workspace crate other than `proto`. Every manifest section whose name contains -# "dependencies" (including [dev-dependencies]) is inspected. A dependency is the -# key in such a section, however it is written: `x.workspace = true`, -# `x = { path = ".." }` or `x = "1"`. +# "dependencies" (including [dev-dependencies] and [build-dependencies]) is +# inspected. A dependency name can appear as a plain key (`x = "1"`), a dotted key +# (`x.workspace = true` or `x.path = ".."`) or a table header (`[dependencies.x]`); +# the lines inside a table-form dependency are settings of that dependency, not +# further dependencies. ROOT="${1:-.}" status=0 +# Fail closed: there is nothing to check without a crates directory. +if [ ! -d "$ROOT/crates" ]; then + echo "check-crate-deps: $ROOT/crates is not a directory" >&2 + exit 1 +fi + # Workspace crate names: directories under ROOT/crates that contain a Cargo.toml. workspace="" for d in "$ROOT"/crates/*/; do @@ -19,25 +27,31 @@ for crate in "$ROOT"/crates/*/; do [ -d "$crate" ] || continue crate_name=$(basename "$crate") [ -f "$crate/Cargo.toml" ] || continue + # Emit one dependency name per line, from plain/dotted keys and table headers. keys=$(awk ' - BEGIN { in_deps = 0; depth = 0 } - /^\[/ { - sec = $0 - sub(/^\[[ \t]*/, "", sec) - sub(/[ \t]*\][ \t]*.*$/, "", sec) - in_deps = (sec ~ /dependencies/) + BEGIN { in_deps = 0; in_table = 0; depth = 0 } + /^[ \t]*\[/ { + hdr = $0 + sub(/^[ \t]*\[/, "", hdr) + sub(/[ \t]*\][ \t]*.*$/, "", hdr) + if (hdr ~ /dependencies/) { + n = split(hdr, parts, ".") + if (parts[n] ~ /dependencies$/) { in_deps = 1; in_table = 0 } + else { in_deps = 1; in_table = 1; print parts[n] } + } else { in_deps = 0; in_table = 0 } depth = 0 next } + in_table { next } in_deps { if (depth == 0) { t = $0 sub(/^[ \t]+/, "", t) - if (t ~ /^[A-Za-z0-9][A-Za-z0-9_-]*(\.workspace)?[ \t]*=/) { - if (match(t, /[A-Za-z0-9][A-Za-z0-9_-]*/)) { - k = substr(t, RSTART, RLENGTH) - if (RSTART == 1) print k - } + if (match(t, /^[A-Za-z0-9][A-Za-z0-9_-]*(\.[A-Za-z0-9_-]+)*[ \t]*=/)) { + name = t + sub(/[ \t]*=.*/, "", name) + sub(/\..*/, "", name) + print name } } s = $0 @@ -48,7 +62,7 @@ for crate in "$ROOT"/crates/*/; do s = substr(s, RSTART + 1) } } - ' "$crate/Cargo.toml" 2>/dev/null) + ' "$crate/Cargo.toml") for key in $keys; do hit=0 for w in $workspace; do diff --git a/scripts/check-dep-docs.sh b/scripts/check-dep-docs.sh old mode 100644 new mode 100755 index 503362c..f43ab70 --- a/scripts/check-dep-docs.sh +++ b/scripts/check-dep-docs.sh @@ -1,11 +1,27 @@ #!/bin/sh -# Fails if a key in [workspace.dependencies] of ROOT/Cargo.toml that is not a -# workspace crate has no table row starting `| `name` |` in ROOT/docs/dependencies.md. -# Also fails if any dependency line in any ROOT/crates/*/Cargo.toml lacks -# `workspace = true`. +# Fails if a workspace dependency declared in ROOT/Cargo.toml has no table row in +# ROOT/docs/dependencies.md, or if any dependency in a crate manifest lacks +# `workspace = true` (on its own line for the plain/dotted form, or as a +# `workspace = true` line inside a table-form dependency). A table header such as +# `[dependencies.x]` introduces dependency x; the lines that follow it until the +# next section header are settings of x, never dependencies themselves. ROOT="${1:-.}" status=0 +# Fail closed. +if [ ! -d "$ROOT/crates" ]; then + echo "check-dep-docs: $ROOT/crates is not a directory" >&2 + exit 1 +fi +if [ ! -f "$ROOT/Cargo.toml" ]; then + echo "check-dep-docs: $ROOT/Cargo.toml is not a file" >&2 + exit 1 +fi +if [ ! -f "$ROOT/docs/dependencies.md" ]; then + echo "check-dep-docs: $ROOT/docs/dependencies.md is not a file" >&2 + exit 1 +fi + # Workspace crate names: directories under ROOT/crates that contain a Cargo.toml. workspace="" for d in "$ROOT"/crates/*/; do @@ -17,26 +33,31 @@ done root_toml="$ROOT/Cargo.toml" docs="$ROOT/docs/dependencies.md" -# Extract dependency keys from every [dependencies*]-ish section. +# Part 1: non-workspace-crate workspace dependencies in ROOT/Cargo.toml must have a docs row. dep_keys=$(awk ' - BEGIN { in_deps = 0; depth = 0 } - /^\[/ { - sec = $0 - sub(/^\[[ \t]*/, "", sec) - sub(/[ \t]*\][ \t]*.*$/, "", sec) - in_deps = (sec ~ /dependencies/) + BEGIN { in_deps = 0; in_table = 0; depth = 0 } + /^[ \t]*\[/ { + hdr = $0 + sub(/^[ \t]*\[/, "", hdr) + sub(/[ \t]*\][ \t]*.*$/, "", hdr) + if (hdr ~ /dependencies/) { + n = split(hdr, parts, ".") + if (parts[n] ~ /dependencies$/) { in_deps = 1; in_table = 0 } + else { in_deps = 1; in_table = 1; print parts[n] } + } else { in_deps = 0; in_table = 0 } depth = 0 next } + in_table { next } in_deps { if (depth == 0) { t = $0 sub(/^[ \t]+/, "", t) - if (t ~ /^[A-Za-z0-9][A-Za-z0-9_-]*(\.workspace)?[ \t]*=/) { - if (match(t, /[A-Za-z0-9][A-Za-z0-9_-]*/)) { - k = substr(t, RSTART, RLENGTH) - if (RSTART == 1) print k - } + if (match(t, /^[A-Za-z0-9][A-Za-z0-9_-]*(\.[A-Za-z0-9_-]+)*[ \t]*=/)) { + name = t + sub(/[ \t]*=.*/, "", name) + sub(/\..*/, "", name) + print name } } s = $0 @@ -47,43 +68,53 @@ dep_keys=$(awk ' s = substr(s, RSTART + 1) } } -' "$root_toml" 2>/dev/null) +' "$root_toml") -# Part 1: non-workspace-crate workspace dependencies must have a docs table row. for key in $dep_keys; do hit=0 for w in $workspace; do if [ "$key" = "$w" ]; then hit=1; break; fi done if [ "$hit" -eq 0 ]; then - if ! awk -v k="$key" 'index($0, "| `" k "` |") == 1 { found = 1; exit } END { exit !found }' "$docs" 2>/dev/null; then + if ! awk -v k="$key" 'index($0, "| `" k "` |") == 1 { found = 1; exit } END { exit !found }' "$docs"; then echo "check-dep-docs: $key has no row in $docs" >&2 status=1 fi fi done -# Part 2: every dependency line in a crate manifest must contain `workspace = true`. +# Part 2: every dependency in a crate manifest must have `workspace = true`. for crate in "$ROOT"/crates/*/; do [ -d "$crate" ] || continue crate_name=$(basename "$crate") [ -f "$crate/Cargo.toml" ] || continue - bad=$(awk ' - BEGIN { in_deps = 0; depth = 0 } - /^\[/ { - sec = $0 - sub(/^\[[ \t]*/, "", sec) - sub(/[ \t]*\][ \t]*.*$/, "", sec) - in_deps = (sec ~ /dependencies/) + bad=$(awk -v cn="$crate_name" ' + BEGIN { in_deps = 0; in_table = 0; cur = ""; has_ws = 0; depth = 0 } + /^[ \t]*\[/ { + if (in_table && !has_ws) printf "check-dep-docs: %s table-form dependency %s has no `workspace = true` line\n", cn, cur + in_table = 0 + hdr = $0 + sub(/^[ \t]*\[/, "", hdr) + sub(/[ \t]*\][ \t]*.*$/, "", hdr) + if (hdr ~ /dependencies/) { + n = split(hdr, parts, ".") + if (parts[n] ~ /dependencies$/) { in_deps = 1 } + else { in_deps = 1; in_table = 1; cur = parts[n]; has_ws = 0 } + } else { in_deps = 0 } depth = 0 next } + in_table { + if ($0 ~ /workspace[ \t]*=[ \t]*true/) has_ws = 1 + next + } in_deps { if (depth == 0) { t = $0 sub(/^[ \t]+/, "", t) - if (t ~ /^[A-Za-z0-9][A-Za-z0-9_-]*(\.workspace)?[ \t]*=/) { - print $0 + if (match(t, /^[A-Za-z0-9][A-Za-z0-9_-]*(\.[A-Za-z0-9_-]+)*[ \t]*=/)) { + if ($0 !~ /workspace[ \t]*=[ \t]*true/) + printf "check-dep-docs: %s declares a dependency without `workspace = true`: %s\n", cn, $0 } } s = $0 @@ -94,12 +125,12 @@ for crate in "$ROOT"/crates/*/; do s = substr(s, RSTART + 1) } } - ' "$crate/Cargo.toml" 2>/dev/null | grep -v 'workspace = true') + END { if (in_table && !has_ws) printf "check-dep-docs: %s table-form dependency %s has no `workspace = true` line\n", cn, cur } + ' "$crate/Cargo.toml") if [ -n "$bad" ]; then - echo "$bad" | while read -r line; do - [ -n "$line" ] && echo "check-dep-docs: $crate_name declares a dependency without workspace = true: $line" >&2 - done + printf '%s\n' "$bad" >&2 status=1 fi done + exit $status diff --git a/scripts/check-lines.sh b/scripts/check-lines.sh old mode 100644 new mode 100755 index 9187321..32e52d7 --- a/scripts/check-lines.sh +++ b/scripts/check-lines.sh @@ -2,13 +2,21 @@ # Fails if any *.rs file under ROOT/crates has more than 500 lines. # Files under any target/ directory are ignored. Test files count. ROOT="${1:-.}" +max=500 -bad=$(find "$ROOT/crates" -type f -name '*.rs' ! -path '*/target/*' \ - -exec awk 'END { exit !(NR > 500) }' {} \; -print 2>/dev/null) -if [ -n "$bad" ]; then - echo "$bad" | while read -r f; do - [ -n "$f" ] && echo "check-lines: $f" >&2 - done +# Fail closed: there is nothing to check without a crates directory. +if [ ! -d "$ROOT/crates" ]; then + echo "check-lines: $ROOT/crates is not a directory" >&2 exit 1 fi -exit 0 + +# -prune stops find descending into target/ trees. +find "$ROOT/crates" -type d -name target -prune -o -type f -name '*.rs' -print \ +| while read -r f; do + n=$(awk 'END { print NR }' "$f") + if [ "$n" -gt "$max" ]; then + echo "check-lines: $f has $n lines (limit $max)" >&2 + exit 1 + fi +done +exit $? diff --git a/scripts/test-gate-scripts.sh b/scripts/test-gate-scripts.sh index f51d127..9e26746 100644 --- a/scripts/test-gate-scripts.sh +++ b/scripts/test-gate-scripts.sh @@ -63,6 +63,15 @@ tree "$tmp/c-dev"; printf '\n[dev-dependencies]\nbrokerd.workspace = true\n' >> expect fail "role dev-depends on another role" check-crate-deps.sh "$tmp/c-dev" tree "$tmp/c-proto"; manifest "$tmp/c-proto/crates/proto" proto 'loopd.workspace = true' expect fail "proto depends on a role" check-crate-deps.sh "$tmp/c-proto" +# A dependency can also be written as its own table: [dependencies.NAME] +tree "$tmp/c-sect"; printf '\n[dependencies.brokerd]\npath = "../brokerd"\n' >> "$tmp/c-sect/crates/loopd/Cargo.toml" +expect fail "role depends on another role, written as a table" check-crate-deps.sh "$tmp/c-sect" +tree "$tmp/c-sectw"; printf '\n[dev-dependencies.brokerd]\nworkspace = true\n' >> "$tmp/c-sectw/crates/loopd/Cargo.toml" +expect fail "role dev-depends on another role, written as a table" check-crate-deps.sh "$tmp/c-sectw" +tree "$tmp/c-dot"; manifest "$tmp/c-dot/crates/loopd" loopd 'proto.workspace = true' 'brokerd.path = "../brokerd"' +expect fail "role depends on another role, written with a dotted key" check-crate-deps.sh "$tmp/c-dot" +tree "$tmp/c-sectok"; manifest "$tmp/c-sectok/crates/loopd" loopd; printf '\n[dependencies.proto]\nworkspace = true\n' >> "$tmp/c-sectok/crates/loopd/Cargo.toml" +expect pass "role depends on proto, written as a table" check-crate-deps.sh "$tmp/c-sectok" # check-dep-docs.sh tree "$tmp/d-ok" @@ -73,6 +82,18 @@ tree "$tmp/d-prose"; printf 'rand = "0.9"\n' >> "$tmp/d-prose/Cargo.toml"; print expect fail "a mention in prose is not a table row" check-dep-docs.sh "$tmp/d-prose" tree "$tmp/d-loose"; manifest "$tmp/d-loose/crates/loopd" loopd 'proto.workspace = true' 'rand = "0.9"' expect fail "crate declares a dependency outside the workspace table" check-dep-docs.sh "$tmp/d-loose" +tree "$tmp/d-sect"; printf '\n[dependencies.rand]\nversion = "0.9"\n' >> "$tmp/d-sect/crates/loopd/Cargo.toml" +expect fail "table-form dependency without workspace = true" check-dep-docs.sh "$tmp/d-sect" +tree "$tmp/d-sectok"; printf '\n[dependencies.serde]\nworkspace = true\nfeatures = ["derive"]\n' >> "$tmp/d-sectok/crates/loopd/Cargo.toml" +expect pass "table-form dependency with workspace = true" check-dep-docs.sh "$tmp/d-sectok" + +# A check that cannot find what it checks must fail, not pass. +mkdir -p "$tmp/empty" +expect fail "check-lines without a crates directory" check-lines.sh "$tmp/empty" +expect fail "check-crate-deps without a crates directory" check-crate-deps.sh "$tmp/empty" +expect fail "check-dep-docs without a crates directory" check-dep-docs.sh "$tmp/empty" +tree "$tmp/d-nodoc"; rm "$tmp/d-nodoc/docs/dependencies.md" +expect fail "check-dep-docs without docs/dependencies.md" check-dep-docs.sh "$tmp/d-nodoc" if [ "$fails" -ne 0 ]; then echo "test-gate-scripts: $fails failure(s)" >&2