mirror of
https://github.com/larksuite/cli.git
synced 2026-08-03 08:32:46 +08:00
Compare commits
12 Commits
feat/plugi
...
refactor/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
217f4e5567 | ||
|
|
820305536c | ||
|
|
d48c218d0d | ||
|
|
abe0d09d4b | ||
|
|
7c2ca4e465 | ||
|
|
cbe0fb12df | ||
|
|
59b6393250 | ||
|
|
f1ce88b48e | ||
|
|
c09b0d5dd3 | ||
|
|
1772afe22d | ||
|
|
acd50f25fa | ||
|
|
e488cf4cd3 |
13
.github/workflows/arch-audit.yml
vendored
13
.github/workflows/arch-audit.yml
vendored
@@ -62,19 +62,6 @@ jobs:
|
||||
go list -m -u all 2>/dev/null | grep '\[' >> report.md || echo "All dependencies up to date" >> report.md
|
||||
echo '```' >> report.md
|
||||
|
||||
- name: Circular dependency check
|
||||
run: |
|
||||
echo "## Circular Dependencies" >> report.md
|
||||
go list -f '{{.ImportPath}} {{join .Imports " "}}' ./... | \
|
||||
go run golang.org/x/tools/cmd/digraph@v0.31.0 scc 2>&1 | tee cycles.txt
|
||||
if [ -s cycles.txt ]; then
|
||||
echo '```' >> report.md
|
||||
cat cycles.txt >> report.md
|
||||
echo '```' >> report.md
|
||||
else
|
||||
echo "No circular dependencies detected." >> report.md
|
||||
fi
|
||||
|
||||
- name: E2E coverage gaps
|
||||
run: |
|
||||
echo "## E2E Coverage Gaps" >> report.md
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -119,6 +119,8 @@ jobs:
|
||||
env:
|
||||
QUALITY_GATE_CHANGED_FROM: ${{ github.event.pull_request.base.sha || github.event.before || 'origin/main' }}
|
||||
run: echo "QUALITY_GATE_CHANGED_FROM=$(bash scripts/resolve-changed-from.sh)" >> "$GITHUB_ENV"
|
||||
- name: Enforce layering ratchet
|
||||
run: bash scripts/check-layering-ratchet.sh "$QUALITY_GATE_CHANGED_FROM"
|
||||
- name: Run golangci-lint
|
||||
run: go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 run --new-from-rev="$QUALITY_GATE_CHANGED_FROM"
|
||||
- name: Run source-contract lint guards (lintcheck)
|
||||
|
||||
1
Makefile
1
Makefile
@@ -49,6 +49,7 @@ fmt-check:
|
||||
|
||||
script-test:
|
||||
bash scripts/resolve-changed-from.test.sh
|
||||
bash scripts/check-layering-ratchet.test.sh
|
||||
bash scripts/ci-workflow.test.sh
|
||||
bash scripts/semantic-review-workflow.test.sh
|
||||
$(NODE) --test scripts/e2e_domains.test.js scripts/fetch_e2e_tat.test.js scripts/install.test.js scripts/release-preflight.test.js scripts/semantic-review-verify-artifact.test.js scripts/pr-quality-summary.test.js scripts/semantic-review-publish.test.js scripts/ci-quality-summary-publish.test.js
|
||||
|
||||
40
internal/qualitygate/deptest/layering-edges.txt
Normal file
40
internal/qualitygate/deptest/layering-edges.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
# from denied owner reason added_at
|
||||
github.com/larksuite/cli/extension/credential/env github.com/larksuite/cli/internal/charcheck arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/env github.com/larksuite/cli/internal/core arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/env github.com/larksuite/cli/internal/envvars arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/env github.com/larksuite/cli/internal/i18n arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/env github.com/larksuite/cli/internal/keychain arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/env github.com/larksuite/cli/internal/validate arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/env github.com/larksuite/cli/internal/vfs arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/env github.com/larksuite/cli/internal/vfs/localfileio arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/sidecar github.com/larksuite/cli/internal/charcheck arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/sidecar github.com/larksuite/cli/internal/core arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/sidecar github.com/larksuite/cli/internal/envvars arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/sidecar github.com/larksuite/cli/internal/i18n arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/sidecar github.com/larksuite/cli/internal/keychain arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/sidecar github.com/larksuite/cli/internal/validate arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/sidecar github.com/larksuite/cli/internal/vfs arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/credential/sidecar github.com/larksuite/cli/internal/vfs/localfileio arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/extension/transport/sidecar github.com/larksuite/cli/internal/envvars arch-migration extension pre-module-split (via core/envvars) 2026-07-24
|
||||
github.com/larksuite/cli/events github.com/larksuite/cli/shortcuts/im/convert_lib arch-migration events root aggregates the existing im conversion dependency 2026-07-24
|
||||
github.com/larksuite/cli/events github.com/larksuite/cli/shortcuts/common arch-migration events root aggregates the existing im conversion dependency 2026-07-24
|
||||
github.com/larksuite/cli/events/im github.com/larksuite/cli/shortcuts/im/convert_lib arch-migration events->shortcuts inversion via convert_lib 2026-07-24
|
||||
github.com/larksuite/cli/events/im github.com/larksuite/cli/shortcuts/common arch-migration events->shortcuts inversion via convert_lib 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/im github.com/larksuite/cli/internal/auth arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/mail github.com/larksuite/cli/internal/auth arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/minutes github.com/larksuite/cli/internal/auth arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/vc github.com/larksuite/cli/internal/auth arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/apps github.com/larksuite/cli/internal/keychain arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/drive github.com/larksuite/cli/internal/credential arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/im github.com/larksuite/cli/internal/credential arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/minutes github.com/larksuite/cli/internal/credential arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/vc github.com/larksuite/cli/internal/credential arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/apps github.com/larksuite/cli/internal/client arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/drive github.com/larksuite/cli/internal/client arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/im github.com/larksuite/cli/internal/client arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/mail github.com/larksuite/cli/internal/client arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/markdown github.com/larksuite/cli/internal/client arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/task github.com/larksuite/cli/internal/client arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/apps github.com/larksuite/cli/internal/vfs arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/event github.com/larksuite/cli/internal/vfs arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
github.com/larksuite/cli/shortcuts/mail github.com/larksuite/cli/internal/vfs arch-migration shortcut bypasses RuntimeContext gate 2026-07-24
|
||||
1745
internal/qualitygate/deptest/layering_test.go
Normal file
1745
internal/qualitygate/deptest/layering_test.go
Normal file
File diff suppressed because it is too large
Load Diff
140
scripts/check-layering-ratchet.sh
Normal file
140
scripts/check-layering-ratchet.sh
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
layering_ratchet_extract_keys() {
|
||||
local source_file="$1"
|
||||
local output_file="$2"
|
||||
awk -F '\t' '
|
||||
function trim(value) {
|
||||
sub(/^[[:space:]]+/, "", value)
|
||||
sub(/[[:space:]]+$/, "", value)
|
||||
return value
|
||||
}
|
||||
{
|
||||
content = trim($0)
|
||||
if (content == "" || substr(content, 1, 1) == "#") {
|
||||
next
|
||||
}
|
||||
if (NF != 5) {
|
||||
printf "Malformed layering ratchet row at %s:%d: expected five tab-separated fields.\n", FILENAME, FNR > "/dev/stderr"
|
||||
exit 2
|
||||
}
|
||||
from = $1
|
||||
denied = $2
|
||||
owner = $3
|
||||
reason = $4
|
||||
added_at = $5
|
||||
sub(/\r+$/, "", added_at)
|
||||
if (from != trim(from) || denied != trim(denied) || owner != trim(owner) || reason != trim(reason) || added_at != trim(added_at)) {
|
||||
printf "Malformed layering ratchet row at %s:%d: fields must not have surrounding whitespace.\n", FILENAME, FNR > "/dev/stderr"
|
||||
exit 2
|
||||
}
|
||||
if (from == "" || denied == "" || owner == "" || reason == "" || added_at !~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) {
|
||||
printf "Malformed layering ratchet row at %s:%d: fields must be non-empty and added_at must use YYYY-MM-DD.\n", FILENAME, FNR > "/dev/stderr"
|
||||
exit 2
|
||||
}
|
||||
print from "\t" denied
|
||||
}
|
||||
' "$source_file" | LC_ALL=C sort >"$output_file" || return
|
||||
|
||||
local duplicates
|
||||
duplicates="$(uniq -d "$output_file")" || return
|
||||
if [[ -n "$duplicates" ]]; then
|
||||
echo "Layering ratchet contains duplicate (from, denied) keys: $source_file" >&2
|
||||
printf '%s\n' "$duplicates" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
layering_ratchet_hash_keys() {
|
||||
local source_file="$1"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$source_file" | awk '{ print $1 }' || return
|
||||
return
|
||||
fi
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$source_file" | awk '{ print $1 }' || return
|
||||
return
|
||||
fi
|
||||
echo "Layering ratchet requires sha256sum or shasum." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
layering_ratchet_validate_bootstrap_snapshot() {
|
||||
local current_keys="$1"
|
||||
local expected_count="$2"
|
||||
local expected_hash="$3"
|
||||
local current_count
|
||||
local current_hash
|
||||
current_count="$(wc -l <"$current_keys" | tr -d '[:space:]')" || return
|
||||
current_hash="$(layering_ratchet_hash_keys "$current_keys")" || return
|
||||
if [[ "$current_count" != "$expected_count" || "$current_hash" != "$expected_hash" ]]; then
|
||||
echo "::error::Layering ratchet bootstrap differs from the approved $expected_count-edge snapshot." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
layering_ratchet_main() (
|
||||
set -euo pipefail
|
||||
|
||||
local ratchet_file="internal/qualitygate/deptest/layering-edges.txt"
|
||||
if root="$(git rev-parse --show-toplevel 2>/dev/null)"; then
|
||||
cd "$root" || return
|
||||
else
|
||||
echo "Layering ratchet must run inside a Git worktree." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local base_revision="${1:-${QUALITY_GATE_CHANGED_FROM:-}}"
|
||||
local approved_initial_count="${2:-39}"
|
||||
local approved_initial_hash="${3:-5636d50d10b9de1e08dc9f06cd66671b3f438650fa5b7f28b95aa7d5a69a1c21}"
|
||||
if [[ -z "$base_revision" ]]; then
|
||||
echo "Layering ratchet requires a base revision." >&2
|
||||
return 1
|
||||
fi
|
||||
if ! git cat-file -e "$base_revision^{commit}" 2>/dev/null; then
|
||||
echo "Layering ratchet base revision does not exist: $base_revision" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -f "$ratchet_file" ]]; then
|
||||
echo "Layering ratchet file is missing: $ratchet_file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tmp_dir
|
||||
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/layering-ratchet.XXXXXX")" || return
|
||||
local base_file="$tmp_dir/base.txt"
|
||||
local base_keys="$tmp_dir/base.keys"
|
||||
local current_keys="$tmp_dir/current.keys"
|
||||
local additions="$tmp_dir/additions.keys"
|
||||
|
||||
layering_ratchet_cleanup_current_run() {
|
||||
rm -f "$base_file" "$base_keys" "$current_keys" "$additions"
|
||||
rmdir "$tmp_dir"
|
||||
}
|
||||
trap layering_ratchet_cleanup_current_run EXIT
|
||||
|
||||
layering_ratchet_extract_keys "$ratchet_file" "$current_keys" || return
|
||||
|
||||
if ! git cat-file -e "$base_revision:$ratchet_file" 2>/dev/null; then
|
||||
layering_ratchet_validate_bootstrap_snapshot "$current_keys" "$approved_initial_count" "$approved_initial_hash" || return
|
||||
return
|
||||
fi
|
||||
|
||||
git show "$base_revision:$ratchet_file" >"$base_file" || return
|
||||
layering_ratchet_extract_keys "$base_file" "$base_keys" || return
|
||||
LC_ALL=C comm -13 "$base_keys" "$current_keys" >"$additions" || return
|
||||
|
||||
if [[ -s "$additions" ]]; then
|
||||
echo "::error::Layering ratchet contains new (from, denied) keys. Fix the dependency instead of adding rows." >&2
|
||||
while IFS=$'\t' read -r from denied; do
|
||||
printf 'from=%s denied=%s\n' "$from" "$denied" >&2
|
||||
done <"$additions"
|
||||
return 1
|
||||
fi
|
||||
)
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
layering_ratchet_main "${1:-${QUALITY_GATE_CHANGED_FROM:-}}"
|
||||
fi
|
||||
278
scripts/check-layering-ratchet.test.sh
Normal file
278
scripts/check-layering-ratchet.test.sh
Normal file
@@ -0,0 +1,278 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
script="$repo_root/scripts/check-layering-ratchet.sh"
|
||||
ratchet_file="internal/qualitygate/deptest/layering-edges.txt"
|
||||
tmp="$(mktemp -d "${TMPDIR:-/tmp}/check-layering-ratchet-test.XXXXXX")"
|
||||
source "$script"
|
||||
|
||||
cleanup_tmp() {
|
||||
rm -rf "$tmp"
|
||||
}
|
||||
trap cleanup_tmp EXIT
|
||||
|
||||
row() {
|
||||
printf '%s\t%s\towner\treason\t2026-07-24\n' "$1" "$2"
|
||||
}
|
||||
|
||||
git_init() {
|
||||
local dir="$1"
|
||||
git init -q -b main "$dir"
|
||||
git -C "$dir" config user.name test
|
||||
git -C "$dir" config user.email test@example.com
|
||||
mkdir -p "$dir/$(dirname "$ratchet_file")"
|
||||
}
|
||||
|
||||
write_rows() {
|
||||
local dir="$1"
|
||||
shift
|
||||
{
|
||||
printf '# from\tdenied\towner\treason\tadded_at\n'
|
||||
while (( $# > 0 )); do
|
||||
row "$1" "$2"
|
||||
shift 2
|
||||
done
|
||||
} >"$dir/$ratchet_file"
|
||||
}
|
||||
|
||||
commit_ratchet() {
|
||||
local dir="$1"
|
||||
git -C "$dir" add "$ratchet_file"
|
||||
git -C "$dir" commit -q -m "ratchet"
|
||||
}
|
||||
|
||||
expect_pass() {
|
||||
local dir="$1"
|
||||
local base="$2"
|
||||
if ! (cd "$dir" && bash "$script" "$base"); then
|
||||
echo "Expected layering ratchet check to pass in $dir." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
expect_fail() {
|
||||
local dir="$1"
|
||||
local base="$2"
|
||||
local expected="$3"
|
||||
local output
|
||||
if output="$(cd "$dir" && bash "$script" "$base" 2>&1)"; then
|
||||
echo "Expected layering ratchet check to fail in $dir." >&2
|
||||
return 1
|
||||
fi
|
||||
if ! grep -Fq "$expected" <<<"$output"; then
|
||||
printf 'Layering ratchet failure did not include %q:\n%s\n' "$expected" "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
hash_file() {
|
||||
local source_file="$1"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$source_file" | awk '{ print $1 }'
|
||||
else
|
||||
shasum -a 256 "$source_file" | awk '{ print $1 }'
|
||||
fi
|
||||
}
|
||||
|
||||
bootstrap_keys() {
|
||||
local source_file="$1"
|
||||
awk -F '\t' 'NF == 5 && $1 !~ /^[[:space:]]*#/ { print $1 "\t" $2 }' "$source_file" | LC_ALL=C sort
|
||||
}
|
||||
|
||||
expect_bootstrap_pass() {
|
||||
local dir="$1"
|
||||
local base="$2"
|
||||
local count="$3"
|
||||
local hash="$4"
|
||||
if ! (cd "$dir" && layering_ratchet_main "$base" "$count" "$hash"); then
|
||||
echo "Expected layering ratchet bootstrap check to pass in $dir." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
expect_bootstrap_fail() {
|
||||
local dir="$1"
|
||||
local base="$2"
|
||||
local count="$3"
|
||||
local hash="$4"
|
||||
local output
|
||||
if output="$(cd "$dir" && layering_ratchet_main "$base" "$count" "$hash" 2>&1)"; then
|
||||
echo "Expected layering ratchet bootstrap check to fail in $dir." >&2
|
||||
return 1
|
||||
fi
|
||||
if ! grep -Fq "bootstrap differs from the approved" <<<"$output"; then
|
||||
printf 'Unexpected layering ratchet bootstrap failure:\n%s\n' "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
expect_sourced_main_fail() {
|
||||
local dir="$1"
|
||||
local base="$2"
|
||||
local expected="$3"
|
||||
local output
|
||||
if output="$(cd "$dir" && layering_ratchet_main "$base" 2>&1)"; then
|
||||
echo "Expected sourced layering ratchet main to fail in $dir." >&2
|
||||
return 1
|
||||
fi
|
||||
if ! grep -Fq "$expected" <<<"$output"; then
|
||||
printf 'Sourced layering ratchet failure did not include %q:\n%s\n' "$expected" "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_unchanged_and_metadata_changes_pass() {
|
||||
local dir="$tmp/unchanged"
|
||||
git_init "$dir"
|
||||
write_rows "$dir" from/a denied/a from/b denied/b
|
||||
commit_ratchet "$dir"
|
||||
local base
|
||||
base="$(git -C "$dir" rev-parse HEAD)"
|
||||
|
||||
expect_pass "$dir" "$base"
|
||||
sed -i.bak 's/\towner\treason\t/\tnew-owner\tnew-reason\t/' "$dir/$ratchet_file"
|
||||
rm -f "$dir/$ratchet_file.bak"
|
||||
expect_pass "$dir" "$base"
|
||||
}
|
||||
|
||||
test_deletion_passes() {
|
||||
local dir="$tmp/deletion"
|
||||
git_init "$dir"
|
||||
write_rows "$dir" from/a denied/a from/b denied/b
|
||||
commit_ratchet "$dir"
|
||||
local base
|
||||
base="$(git -C "$dir" rev-parse HEAD)"
|
||||
|
||||
write_rows "$dir" from/a denied/a
|
||||
expect_pass "$dir" "$base"
|
||||
}
|
||||
|
||||
test_addition_fails() {
|
||||
local dir="$tmp/addition"
|
||||
git_init "$dir"
|
||||
write_rows "$dir" from/a denied/a
|
||||
commit_ratchet "$dir"
|
||||
local base
|
||||
base="$(git -C "$dir" rev-parse HEAD)"
|
||||
|
||||
write_rows "$dir" from/a denied/a from/b denied/b
|
||||
expect_fail "$dir" "$base" "from=from/b denied=denied/b"
|
||||
}
|
||||
|
||||
test_equal_count_replacement_fails() {
|
||||
local dir="$tmp/replacement"
|
||||
git_init "$dir"
|
||||
write_rows "$dir" from/a denied/a from/b denied/b
|
||||
commit_ratchet "$dir"
|
||||
local base
|
||||
base="$(git -C "$dir" rev-parse HEAD)"
|
||||
|
||||
write_rows "$dir" from/a denied/a from/c denied/c
|
||||
expect_fail "$dir" "$base" "from=from/c denied=denied/c"
|
||||
}
|
||||
|
||||
test_malformed_and_missing_current_file_fail() {
|
||||
local dir="$tmp/malformed"
|
||||
git_init "$dir"
|
||||
write_rows "$dir" from/a denied/a
|
||||
commit_ratchet "$dir"
|
||||
local base
|
||||
base="$(git -C "$dir" rev-parse HEAD)"
|
||||
|
||||
printf 'from/a\tdenied/a\towner\treason\n' >"$dir/$ratchet_file"
|
||||
expect_fail "$dir" "$base" "expected five tab-separated fields"
|
||||
expect_sourced_main_fail "$dir" "$base" "expected five tab-separated fields"
|
||||
rm -f "$dir/$ratchet_file"
|
||||
expect_fail "$dir" "$base" "Layering ratchet file is missing"
|
||||
}
|
||||
|
||||
test_surrounding_whitespace_fails() {
|
||||
local dir="$tmp/surrounding-whitespace"
|
||||
git_init "$dir"
|
||||
write_rows "$dir" from/a denied/a
|
||||
commit_ratchet "$dir"
|
||||
local base
|
||||
base="$(git -C "$dir" rev-parse HEAD)"
|
||||
|
||||
printf '# from\tdenied\towner\treason\tadded_at\nfrom/a\t denied/a \towner\treason\t2026-07-24\n' >"$dir/$ratchet_file"
|
||||
expect_fail "$dir" "$base" "fields must not have surrounding whitespace"
|
||||
expect_sourced_main_fail "$dir" "$base" "fields must not have surrounding whitespace"
|
||||
}
|
||||
|
||||
test_crlf_rows_pass() {
|
||||
local dir="$tmp/crlf"
|
||||
git_init "$dir"
|
||||
write_rows "$dir" from/a denied/a
|
||||
commit_ratchet "$dir"
|
||||
local base
|
||||
base="$(git -C "$dir" rev-parse HEAD)"
|
||||
|
||||
printf '# from\tdenied\towner\treason\tadded_at\r\nfrom/a\tdenied/a\towner\treason\t2026-07-24\r\n' >"$dir/$ratchet_file"
|
||||
expect_pass "$dir" "$base"
|
||||
}
|
||||
|
||||
test_duplicate_key_fails() {
|
||||
local dir="$tmp/duplicate"
|
||||
git_init "$dir"
|
||||
write_rows "$dir" from/a denied/a
|
||||
commit_ratchet "$dir"
|
||||
local base
|
||||
base="$(git -C "$dir" rev-parse HEAD)"
|
||||
|
||||
write_rows "$dir" from/a denied/a from/a denied/a
|
||||
expect_fail "$dir" "$base" "duplicate (from, denied) keys"
|
||||
}
|
||||
|
||||
test_bootstrap_requires_the_approved_snapshot() {
|
||||
local dir="$tmp/bootstrap"
|
||||
git_init "$dir"
|
||||
printf 'base\n' >"$dir/base.txt"
|
||||
git -C "$dir" add base.txt
|
||||
git -C "$dir" commit -q -m "base"
|
||||
local base
|
||||
base="$(git -C "$dir" rev-parse HEAD)"
|
||||
|
||||
local args=()
|
||||
local index
|
||||
for index in $(seq 1 39); do
|
||||
args+=("from/$index" "denied/$index")
|
||||
done
|
||||
write_rows "$dir" "${args[@]}"
|
||||
local keys_file="$dir/initial.keys"
|
||||
bootstrap_keys "$dir/$ratchet_file" >"$keys_file"
|
||||
local expected_hash
|
||||
expected_hash="$(hash_file "$keys_file")"
|
||||
expect_bootstrap_pass "$dir" "$base" 39 "$expected_hash"
|
||||
|
||||
args[76]="from/replacement"
|
||||
write_rows "$dir" "${args[@]}"
|
||||
expect_bootstrap_fail "$dir" "$base" 39 "$expected_hash"
|
||||
|
||||
args[76]="from/39"
|
||||
args+=("from/40" "denied/40")
|
||||
write_rows "$dir" "${args[@]}"
|
||||
expect_bootstrap_fail "$dir" "$base" 39 "$expected_hash"
|
||||
}
|
||||
|
||||
test_invalid_base_fails() {
|
||||
local dir="$tmp/invalid-base"
|
||||
git_init "$dir"
|
||||
write_rows "$dir" from/a denied/a
|
||||
commit_ratchet "$dir"
|
||||
expect_fail "$dir" missing-revision "base revision does not exist"
|
||||
}
|
||||
|
||||
test_unchanged_and_metadata_changes_pass
|
||||
test_deletion_passes
|
||||
test_addition_fails
|
||||
test_equal_count_replacement_fails
|
||||
test_malformed_and_missing_current_file_fail
|
||||
test_surrounding_whitespace_fails
|
||||
test_crlf_rows_pass
|
||||
test_duplicate_key_fails
|
||||
test_bootstrap_requires_the_approved_snapshot
|
||||
test_invalid_base_fails
|
||||
@@ -170,6 +170,21 @@ if grep -Fq '${{ secrets.' <<<"$script_test_section"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -Fq 'bash scripts/check-layering-ratchet.sh "$QUALITY_GATE_CHANGED_FROM"' <<<"$lint_section"; then
|
||||
echo "lint should enforce the layering ratchet with the tested key-set checker"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -Fq "grep -vcE" <<<"$lint_section"; then
|
||||
echo "lint should not enforce the layering ratchet by row count alone"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -Fq "LAYERING_RATCHET_INITIAL_" "$workflow"; then
|
||||
echo "CI must use the immutable checked-in layering bootstrap snapshot"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -Fq "metadata-gate:" "$workflow"; then
|
||||
echo "metadata-gate should not run alongside deterministic-gate because both would upload the same facts artifact"
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user