diff --git a/README.md b/README.md index a428b39..d1cd4e7 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,17 @@ fixed limits, and disposable directories, but this test does not provide or clai mechanical network or host-filesystem isolation. Real-adapter sandbox qualification and an external-target smoke remain required. +## Inactive control policy-set validator + +`control/v1/` defines a canonical identity bundle for six later Control foundation +policies: duty separation, sandbox, credentials, risk gates, kill switch, and +immutable evidence. Its validator checks exact immutable policy and decision refs; +it does not contain or evaluate those policies. + +The package stays inactive and fail-closed. It grants no authority, activates no +profile, reads no credential, launches no adapter, and performs no external write. +Later bounded units own each policy body and its enforcement. + ## The current default team You talk **only** to yshifu, in a Claude Code session. yshifu orchestrates the other roles diff --git a/RESTORE.md b/RESTORE.md index 2066160..7bbcf2e 100644 --- a/RESTORE.md +++ b/RESTORE.md @@ -319,6 +319,17 @@ each cell, and checks the accepted 2×2 matrix plus closed negative protocol cas It uses no real adapter or credential and makes no network/host isolation, qualification, activation, or external-target-smoke claim. +Restore the three paths in the manifest's inactive control policy-set block, then +run: + +```sh +bash scripts/test/control-policy-set.test.sh +``` + +The proof validates only the canonical six-section identity bundle. It does not +evaluate a policy, grant authority, activate a profile, or enforce sandbox, +credential, risk, kill-switch, or evidence behavior. + --- ## 5. Smoke test — prove the rebuilt team is alive diff --git a/ci/required-files.txt b/ci/required-files.txt index 8411409..c099a43 100644 --- a/ci/required-files.txt +++ b/ci/required-files.txt @@ -166,3 +166,8 @@ adapter-tests/v1/fakes/forge-b.sh adapter-tests/v1/fakes/protocol-fault.sh adapter-tests/v1/fixture/source.txt scripts/test/portable-adapter-contracts.test.sh + +# Inactive control policy-set identity seam +control/v1/policy-set.jq +control/v1/validate.sh +scripts/test/control-policy-set.test.sh diff --git a/control/v1/policy-set.jq b/control/v1/policy-set.jq new file mode 100644 index 0000000..3650576 --- /dev/null +++ b/control/v1/policy-set.jq @@ -0,0 +1,67 @@ +def exact($required): + type == "object" and (keys | sort) == ($required | sort); + +def id_ok: + type == "string" and test("\\A[a-z0-9][a-z0-9._:-]{0,127}\\z"); + +def content_id_ok: + id_ok and (contains(":") | not) and (contains("/") | not); + +def sha256_ok: + type == "string" and test("\\A[0-9a-f]{64}\\z"); + +def content_ref_ok($media_type): + exact(["content_id","media_type","sha256"]) and + (.content_id | content_id_ok) and .media_type == $media_type and + (.sha256 | sha256_ok); + +def section_shape_ok: + exact(["decision_ref","policy_ref","section_id"]) and + (.section_id | id_ok) and + (.policy_ref | content_ref_ok("application/vnd.ystack.control-policy+json")) and + (.decision_ref | content_ref_ok("application/vnd.ystack.control-decision+json")); + +def core_contract_shape_ok: + exact(["generation_id","package_ref","semantic_identity"]) and + (.semantic_identity | id_ok) and + (.generation_id | + if type == "string" then test("\\Ag-[0-9a-f]{64}\\z") else false end) and + (.package_ref | content_ref_ok("application/vnd.ystack.core-contract+json")); + +def shape_ok: + exact(["body","id","kind","schema_version"]) and + .schema_version == 1 and .kind == "control_policy_set" and + (.id | id_ok) and + (.body | + exact(["activation_state","core_contract","fail_mode","policy_version","sections"]) and + (.activation_state | type == "string") and + (.fail_mode | type == "string") and + (.policy_version | type == "string") and + (.core_contract | core_contract_shape_ok) and + (.sections | type == "array" and length >= 1 and length <= 16 and + all(.[];section_shape_ok))); + +def expected_sections: + ["credential-policy","duty-separation","evidence-integrity", + "kill-switch","risk-gates","sandbox"]; + +def relations_ok: + .body as $body | + $body.policy_version == "v1" and + $body.activation_state == "inactive" and + $body.fail_mode == "closed" and + ($body.core_contract.semantic_identity | + test("\\Acore\\.contracts\\.v[1-9][0-9]*\\z")) and + ($body.sections | map(.section_id)) == expected_sections and + ($body.sections | all(.[]; + .policy_ref.content_id == ("control-policy." + .section_id))) and + ($body.sections | all(.[]; + .decision_ref.content_id == ("control-decision." + .section_id))) and + ($body.sections | map(.policy_ref.sha256) | unique | length) == 6 and + ($body.sections | map(.decision_ref.content_id) | unique | length) == 6 and + ($body.sections | map(.decision_ref.sha256) | unique | length) == 6; + +if (shape_ok | not) then "E_SHAPE" +elif (relations_ok | not) then "E_RELATION" +else empty +end diff --git a/control/v1/validate.sh b/control/v1/validate.sh new file mode 100755 index 0000000..42047c6 --- /dev/null +++ b/control/v1/validate.sh @@ -0,0 +1,84 @@ +#!/bin/bash +set -uo pipefail +export LC_ALL=C +umask 077 + +emit_error() { + case "${1:-}" in + E_USAGE|E_RUNTIME|E_LIMIT|E_PARSE|E_CANONICAL|E_SHAPE|E_RELATION) + /usr/bin/printf '%s\n' "$1" >&2 + ;; + *) /usr/bin/printf '%s\n' E_RUNTIME >&2 ;; + esac + exit 1 +} + +[ "$#" -eq 2 ] && [ "$1" = validate ] || emit_error E_USAGE +input=$2 +source_path=${BASH_SOURCE[0]} +case "$source_path" in + /*) ;; + *) source_path="$(pwd -P)/$source_path" ;; +esac +[ -f "$source_path" ] && [ ! -L "$source_path" ] || emit_error E_RUNTIME +source_dir=$(CDPATH='' cd -P -- "${source_path%/*}" 2>/dev/null && pwd -P) || + emit_error E_RUNTIME +source_path="$source_dir/${source_path##*/}" +[ "$source_path" = "$source_dir/validate.sh" ] || emit_error E_RUNTIME +policy_program="$source_dir/policy-set.jq" +[ -f "$policy_program" ] && [ ! -L "$policy_program" ] || emit_error E_RUNTIME +[ -f "$input" ] && [ ! -L "$input" ] || emit_error E_RUNTIME +jq_bin=$(command -v jq 2>/dev/null) || emit_error E_RUNTIME +case "$jq_bin" in /*) ;; *) emit_error E_RUNTIME ;; esac +[ -f "$jq_bin" ] && [ -x "$jq_bin" ] && [ ! -L "$jq_bin" ] && + [ "$($jq_bin --version 2>/dev/null)" = jq-1.6 ] || emit_error E_RUNTIME + +scratch=$(/usr/bin/mktemp -d "${TMPDIR:-/tmp}/ystack-control-policy.XXXXXX" 2>/dev/null) || + emit_error E_RUNTIME +cleanup() { /bin/rm -rf -- "$scratch" >/dev/null 2>&1 || :; } +signal_exit() { trap - EXIT HUP INT TERM; cleanup; exit 1; } +trap cleanup EXIT +trap signal_exit HUP INT TERM + +raw="$scratch/raw.json" +/bin/dd if="$input" of="$raw" bs=1048577 count=1 2>/dev/null || emit_error E_RUNTIME +raw_size=$(/usr/bin/wc -c < "$raw" | /usr/bin/tr -d ' ') || emit_error E_RUNTIME +[ "$raw_size" -le 1048576 ] || emit_error E_LIMIT +bom=$(/usr/bin/od -An -tx1 -N3 "$raw" 2>/dev/null | /usr/bin/tr -d ' \n') || + emit_error E_RUNTIME +[ "$bom" != efbbbf ] || emit_error E_PARSE +"$jq_bin" . "$raw" >/dev/null 2>&1 || emit_error E_PARSE +root_count=$("$jq_bin" -s 'length' "$raw" 2>/dev/null) || emit_error E_PARSE +[ "$root_count" -eq 1 ] || emit_error E_PARSE +canonical="$scratch/canonical.json" +"$jq_bin" -S -c . "$raw" > "$canonical" 2>/dev/null || emit_error E_PARSE +/usr/bin/cmp -s "$raw" "$canonical" || emit_error E_CANONICAL + +"$jq_bin" -e ' + def depth: + if type == "array" then + if length == 0 then 1 else 1 + ([.[] | depth] | max) end + elif type == "object" then + if length == 0 then 1 else 1 + ([.[] | depth] | max) end + else 1 end; + def members: + if type == "array" then length + ([.[] | members] | add // 0) + elif type == "object" then (keys_unsorted | length) + ([.[] | members] | add // 0) + else 0 end; + def strings_ok: + if type == "array" then all(.[];strings_ok) + elif type == "object" then + all(keys_unsorted[];utf8bytelength <= 8192) and all(.[];strings_ok) + elif type == "string" then utf8bytelength <= 8192 + else true end; + (depth <= 32) and (members <= 1024) and strings_ok +' "$raw" >/dev/null 2>&1 || emit_error E_LIMIT + +result=$("$jq_bin" -r -f "$policy_program" "$raw" 2>/dev/null) || emit_error E_RUNTIME +case "$result" in + '') ;; + E_SHAPE|E_RELATION) emit_error "$result" ;; + *) emit_error E_RUNTIME ;; +esac +trap - EXIT HUP INT TERM +cleanup diff --git a/scripts/test/control-policy-set.test.sh b/scripts/test/control-policy-set.test.sh new file mode 100755 index 0000000..6632d97 --- /dev/null +++ b/scripts/test/control-policy-set.test.sh @@ -0,0 +1,257 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2016 +set -euo pipefail +export LC_ALL=C +umask 077 + +root=$(CDPATH='' cd -P -- "${BASH_SOURCE[0]%/*}/../.." && pwd -P) +validator="$root/control/v1/validate.sh" +tmp=$(/usr/bin/mktemp -d "${TMPDIR:-/tmp}/ystack-control-policy-test.XXXXXX") +tmp=$(CDPATH='' cd -P -- "$tmp" && pwd -P) +download='' + +cleanup() { + if [ -n "$download" ] && [ -f "$download" ]; then + /bin/rm -f -- "$download" + fi + /bin/rm -rf -- "$tmp" +} +trap cleanup EXIT +fail() { /usr/bin/printf 'FAIL: %s\n' "$1" >&2; exit 1; } +passes=0 +pass() { passes=$((passes + 1)); /usr/bin/printf 'ok %s - %s\n' "$passes" "$1"; } +sha_file() { /usr/bin/shasum -a 256 "$1" | /usr/bin/awk '{print $1}'; } + +platform=$(/usr/bin/uname -s):$(/usr/bin/uname -m) +case "$platform" in + Darwin:*) jq_asset=jq-osx-amd64; jq_sha=5c0a0a3ea600f302ee458b30317425dd9632d1ad8882259fcaf4e9b868b2b1ef ;; + Linux:x86_64) jq_asset=jq-linux64; jq_sha=af986793a515d500ab2d35f8d2aecd656e764504b789b66d7e1a0b727a124c44 ;; + *) fail "unsupported host $platform" ;; +esac +jq_cache_dir="${TMPDIR:-/tmp}/ystack-portable-core-jq16" +/bin/mkdir -p "$jq_cache_dir" +jq_cache="$jq_cache_dir/$jq_asset" +if [ ! -f "$jq_cache" ] || [ "$(sha_file "$jq_cache")" != "$jq_sha" ]; then + download=$(/usr/bin/mktemp "$jq_cache_dir/.jq-1.6.XXXXXX") + /usr/bin/curl --proto '=https' --tlsv1.2 -fsSL \ + "https://github.com/jqlang/jq/releases/download/jq-1.6/$jq_asset" \ + -o "$download" + [ "$(sha_file "$download")" = "$jq_sha" ] || fail 'jq release digest' + /bin/chmod 0555 "$download" + /bin/mv "$download" "$jq_cache" + download='' +fi +[ "$(sha_file "$jq_cache")" = "$jq_sha" ] || fail 'jq digest' +bin="$tmp/bin" +/bin/mkdir -m 700 "$bin" +/bin/cp "$jq_cache" "$bin/jq" +/bin/chmod 0555 "$bin/jq" +jq_bin="$bin/jq" +[ "$("$jq_bin" --version)" = jq-1.6 ] || fail 'jq identity' + +valid="$tmp/valid.json" +"$jq_bin" -S -c -n ' + def ref($id;$media;$character): + {content_id:$id,media_type:$media,sha256:($character*64)}; + def decision_character($character): + if $character=="1" then "a" elif $character=="2" then "b" + elif $character=="3" then "c" elif $character=="4" then "d" + elif $character=="5" then "e" else "f" end; + def section($id;$character): + {section_id:$id, + policy_ref:ref("control-policy."+$id;"application/vnd.ystack.control-policy+json";$character), + decision_ref:ref("control-decision."+$id;"application/vnd.ystack.control-decision+json"; + decision_character($character))}; + {schema_version:1,kind:"control_policy_set",id:"control-policy-set.example", + body:{policy_version:"v1",activation_state:"inactive",fail_mode:"closed", + core_contract:{semantic_identity:"core.contracts.v2",generation_id:("g-"+("7"*64)), + package_ref:ref("core-contract-package.v2";"application/vnd.ystack.core-contract+json";"9")}, + sections:[section("credential-policy";"1"),section("duty-separation";"2"), + section("evidence-integrity";"3"),section("kill-switch";"4"), + section("risk-gates";"5"),section("sandbox";"6")]}} +' > "$valid" + +run_validator() { + local input=$1 out=$2 err=$3 status=0 + PATH="$bin:/usr/bin:/bin" "$validator" validate "$input" > "$out" 2> "$err" || status=$? + RUN_STATUS=$status +} +expect_pass() { + local name=$1 input=$2 + local out="$tmp/$name.out" err="$tmp/$name.err" + run_validator "$input" "$out" "$err" + [ "$RUN_STATUS" -eq 0 ] && [ ! -s "$out" ] && [ ! -s "$err" ] || fail "$name" + pass "$name" +} +expect_error() { + local name=$1 expected=$2 input=$3 + local out="$tmp/$name.out" err="$tmp/$name.err" + run_validator "$input" "$out" "$err" + if [ "$RUN_STATUS" -eq 0 ] || [ -s "$out" ] || + [ "$(/bin/cat "$err")" != "$expected" ] || + /usr/bin/grep -Fq "$tmp" "$err"; then + fail "$name" + fi + pass "$name" +} +mutate() { + local name=$1 filter=$2 + "$jq_bin" -S -c "$filter" "$valid" > "$tmp/$name.json" + /usr/bin/printf '%s\n' "$tmp/$name.json" +} + +expect_pass canonical-valid "$valid" +core_wrapper="$root/scripts/core-contract.sh" +core_fixture_dir="$root/scripts/test" +core_request="$tmp/core-request.json" +[ -x "$core_wrapper" ] || fail 'core public seam' +"$jq_bin" -L "$core_fixture_dir" -S -c -n ' + import "portable-core-stage-request-fixtures" as fixture; + fixture::request_doc("producer";("0"*64)) | + .schema_version=2 | + .body.resolved_profile_ref.schema_version=2 +' > "$core_request" +core_ref_count=0 +for section_index in 0 1 2 3 4 5; do + for ref_field in policy_ref decision_ref; do + core_ref_count=$((core_ref_count + 1)) + core_case="$tmp/core-ref-$section_index-$ref_field.json" + "$jq_bin" -S -c --argjson index "$section_index" --arg field "$ref_field" \ + --slurpfile policy "$valid" \ + '.body.selection_ref.decision_record_ref = + $policy[0].body.sections[$index][$field]' \ + "$core_request" > "$core_case" + core_out="$tmp/core-ref-$core_ref_count.out" + core_err="$tmp/core-ref-$core_ref_count.err" + PATH="$bin:/usr/bin:/bin" /bin/bash "$core_wrapper" \ + validate-document "$core_case" > "$core_out" 2> "$core_err" || + fail "core content ref $core_ref_count" + [ ! -s "$core_out" ] && [ ! -s "$core_err" ] || + fail "core content ref output $core_ref_count" + done +done +core_ref_count=$((core_ref_count + 1)) +core_case="$tmp/core-ref-$core_ref_count-package.json" +"$jq_bin" -S -c --slurpfile policy "$valid" \ + '.body.selection_ref.decision_record_ref = + $policy[0].body.core_contract.package_ref' \ + "$core_request" > "$core_case" +core_out="$tmp/core-ref-$core_ref_count.out" +core_err="$tmp/core-ref-$core_ref_count.err" +PATH="$bin:/usr/bin:/bin" /bin/bash "$core_wrapper" \ + validate-document "$core_case" > "$core_out" 2> "$core_err" || + fail 'core package content ref' +[ ! -s "$core_out" ] && [ ! -s "$core_err" ] || + fail 'core package content ref output' +[ "$core_ref_count" -eq 13 ] || fail 'core content ref coverage' +pass 'all content refs pass the public core seam' +for field in schema_version kind id body; do + expect_error "missing-envelope-$field" E_SHAPE "$(mutate "missing-envelope-$field" "del(.$field)")" +done +for field in policy_version activation_state fail_mode core_contract sections; do + expect_error "missing-body-$field" E_SHAPE "$(mutate "missing-body-$field" "del(.body.$field)")" +done +for field in semantic_identity generation_id package_ref; do + expect_error "missing-core-$field" E_SHAPE "$(mutate "missing-core-$field" "del(.body.core_contract.$field)")" +done +for field in section_id policy_ref decision_ref; do + expect_error "missing-section-$field" E_SHAPE \ + "$(mutate "missing-section-$field" "del(.body.sections[0].$field)")" +done + +expect_error section-missing E_RELATION "$(mutate section-missing '.body.sections |= .[:-1]')" +expect_error section-extra E_RELATION "$(mutate section-extra '.body.sections += [(.body.sections[0] | .section_id="other" | .policy_ref.content_id="control-policy.other" | .policy_ref.sha256=("8"*64) | .decision_ref.content_id="control-decision.other" | .decision_ref.sha256=("8"*64))]')" +expect_error section-duplicate E_RELATION "$(mutate section-duplicate '.body.sections[1]=.body.sections[0]')" +expect_error section-reordered E_RELATION "$(mutate section-reordered '.body.sections[0:2] |= reverse')" +expect_error section-renamed E_RELATION "$(mutate section-renamed '.body.sections[0].section_id="renamed"')" +expect_error policy-media E_SHAPE "$(mutate policy-media '.body.sections[0].policy_ref.media_type="application/json"')" +expect_error decision-media E_SHAPE "$(mutate decision-media '.body.sections[0].decision_ref.media_type="application/json"')" +expect_error package-media E_SHAPE "$(mutate package-media '.body.core_contract.package_ref.media_type="application/json"')" +expect_error package-id-colon E_SHAPE "$(mutate package-id-colon '.body.core_contract.package_ref.content_id="core:package"')" +expect_error package-id-path E_SHAPE "$(mutate package-id-path '.body.core_contract.package_ref.content_id="core/package"')" +expect_error bad-digest E_SHAPE "$(mutate bad-digest '.body.sections[0].policy_ref.sha256="bad"')" +expect_error bad-id E_SHAPE "$(mutate bad-id '.id="Bad ID"')" +expect_error policy-id-link E_RELATION "$(mutate policy-id-link '.body.sections[0].policy_ref.content_id="control-policy.other"')" +expect_error decision-id-link E_RELATION "$(mutate decision-id-link '.body.sections[0].decision_ref.content_id="control-decision.other"')" +expect_error core-identity E_RELATION "$(mutate core-identity '.body.core_contract.semantic_identity="other.contract"')" +expect_error core-generation E_SHAPE "$(mutate core-generation '.body.core_contract.generation_id="g-bad"')" +expect_error core-generation-number E_SHAPE "$(mutate core-generation-number '.body.core_contract.generation_id=1')" +expect_error core-generation-object E_SHAPE "$(mutate core-generation-object '.body.core_contract.generation_id={}')" +expect_error core-generation-array E_SHAPE "$(mutate core-generation-array '.body.core_contract.generation_id=[]')" +expect_error core-generation-null E_SHAPE "$(mutate core-generation-null '.body.core_contract.generation_id=null')" +expect_error active-state E_RELATION "$(mutate active-state '.body.activation_state="active"')" +expect_error mutable-state E_RELATION "$(mutate mutable-state '.body.activation_state="mutable"')" +expect_error fail-open E_RELATION "$(mutate fail-open '.body.fail_mode="open"')" +for field in command url credential vendor executable grant evaluation activation; do + expect_error "injected-$field" E_SHAPE \ + "$(mutate "injected-$field" ".body.$field=\"forbidden\"")" +done + +/usr/bin/printf '{\n' > "$tmp/parse.json" +expect_error parse E_PARSE "$tmp/parse.json" +"$jq_bin" . "$valid" > "$tmp/noncanonical.json" +expect_error noncanonical E_CANONICAL "$tmp/noncanonical.json" +/bin/cat "$valid" "$valid" > "$tmp/multi-root.json" +expect_error multi-root E_PARSE "$tmp/multi-root.json" +/usr/bin/printf '\357\273\277' > "$tmp/bom.json" +/bin/cat "$valid" >> "$tmp/bom.json" +expect_error bom E_PARSE "$tmp/bom.json" +/usr/bin/awk 'BEGIN { for (i=0;i<1048577;i++) printf "x" }' > "$tmp/raw-limit.json" +expect_error raw-limit E_LIMIT "$tmp/raw-limit.json" +"$jq_bin" -S -c -n 'reduce range(0;33) as $i (0;{x:.})' > "$tmp/depth-limit.json" +expect_error depth-limit E_LIMIT "$tmp/depth-limit.json" +"$jq_bin" -S -c -n 'reduce range(0;1025) as $i ({};. + {($i|tostring):0})' > "$tmp/member-limit.json" +expect_error member-limit E_LIMIT "$tmp/member-limit.json" +"$jq_bin" -S -c '.body.extra=("x"*8193)' "$valid" > "$tmp/string-limit.json" +expect_error string-limit E_LIMIT "$tmp/string-limit.json" + +/bin/ln -s "$valid" "$tmp/symlink.json" +expect_error symlink E_RUNTIME "$tmp/symlink.json" +/usr/bin/mkfifo "$tmp/input.fifo" +expect_error nonregular E_RUNTIME "$tmp/input.fifo" +fake_home="$tmp/fake-home" +fake_cwd="$tmp/fake-cwd" +/bin/mkdir -m 700 "$fake_home" "$fake_cwd" +ambient_out="$tmp/ambient.out" +ambient_err="$tmp/ambient.err" +(cd "$fake_cwd" && HOME="$fake_home" JQ_LIBRARY_PATH="$fake_home" \ + PATH="$bin:/usr/bin:/bin" "$validator" validate "$valid" > "$ambient_out" 2> "$ambient_err") +[ ! -s "$ambient_out" ] && [ ! -s "$ambient_err" ] || fail 'ambient independence' +pass 'ambient cwd and environment independence' + +relative_cwd="$tmp/relative-cwd" +relative_jq_sentinel="$tmp/relative-jq-ran" +relative_bash_sentinel="$tmp/relative-bash-ran" +/bin/mkdir -m 700 "$relative_cwd" +/usr/bin/printf '%s\n' \ + '#!/bin/sh' \ + ': > "${YSTACK_FAKE_JQ_SENTINEL:?}"' \ + 'if [ "${1:-}" = --version ]; then printf "%s\n" jq-1.6; fi' \ + 'exit 0' > "$relative_cwd/jq" +/usr/bin/printf '%s\n' \ + '#!/bin/sh' \ + ': > "${YSTACK_FAKE_BASH_SENTINEL:?}"' \ + 'printf "%s\n" E_RUNTIME >&2' \ + 'exit 1' > "$relative_cwd/bash" +/bin/chmod 0700 "$relative_cwd/jq" "$relative_cwd/bash" +relative_out="$tmp/relative-path.out" +relative_err="$tmp/relative-path.err" +relative_status=0 +(cd "$relative_cwd" && YSTACK_FAKE_JQ_SENTINEL="$relative_jq_sentinel" \ + YSTACK_FAKE_BASH_SENTINEL="$relative_bash_sentinel" \ + PATH=".:$bin:/usr/bin:/bin" "$validator" validate "$valid" \ + > "$relative_out" 2> "$relative_err") || relative_status=$? +[ "$relative_status" -ne 0 ] && [ ! -s "$relative_out" ] && + [ "$(/bin/cat "$relative_err")" = E_RUNTIME ] && + [ ! -e "$relative_bash_sentinel" ] && [ ! -e "$relative_jq_sentinel" ] || + fail 'relative interpreter or jq path' +pass 'relative interpreter and jq paths are rejected before execution' + +for required in control/v1/policy-set.jq control/v1/validate.sh scripts/test/control-policy-set.test.sh; do + [ "$(/usr/bin/grep -Fxc "$required" "$root/ci/required-files.txt")" -eq 1 ] || + fail "manifest $required" +done +/usr/bin/grep -Fq 'Inactive control policy-set validator' "$root/README.md" || fail 'README docs' +/usr/bin/grep -Fq 'control-policy-set.test.sh' "$root/RESTORE.md" || fail 'RESTORE docs' +pass 'restore manifest and docs' +/usr/bin/printf 'control policy set: %s focused checks passed\n' "$passes"