#!/usr/bin/env bash # Offline selftests for the foundation synthetic inspector (charter candidate 3). # # No Docker, no network, no credentials, no live data: every check runs against # checked-in synthetic fixtures or a disposable sandbox. Tests spawn the inspector # as a subprocess; the inspector itself never spawns anything. # # scripts/test-foundation.sh full run # NO_COLOR=1 scripts/test-foundation.sh plain output set -uo pipefail cd "$(dirname "$0")/.." SANDBOX="$(mktemp -d)" trap 'rm -rf "$SANDBOX"' EXIT PASS=0 # Status colors: terminal-only, NO_COLOR-respecting; plain when piped. if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then C_OK=$'\033[0;32m'; C_FAIL=$'\033[0;31m'; C_RESET=$'\033[0m' else C_OK=""; C_FAIL=""; C_RESET="" fi FAIL=0 check() { if [ "$2" = "0" ]; then PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $1"; else FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $1"; fi } # expect_exit NAME EXPECTED_RC -- command... expect_exit() { local name="$1" expected="$2" shift 3 # name, expected, "--" local rc "$@" >/dev/null 2>&1 rc=$? if [ "$rc" -eq "$expected" ]; then PASS=$((PASS + 1)) echo "${C_OK}OK${C_RESET} $name (exit $rc)" else FAIL=$((FAIL + 1)) echo "${C_FAIL}FAIL${C_RESET} $name (exit $rc, expected $expected)" fi } INSPECT="node scripts/foundation-inspect.mjs" FIXTURES="scripts/foundation/fixtures" DEMO="$FIXTURES/demo" echo "toolchain: node $(node --version), python $(python3 --version 2>&1 | cut -d' ' -f2), jsonschema $(python3 -c 'import importlib.metadata as m; print(m.version("jsonschema"))' 2>/dev/null || echo missing)" echo # --- syntax --- for f in scripts/foundation-inspect.mjs scripts/foundation/strict-json.mjs scripts/foundation/canonical.mjs \ scripts/foundation/resolve.mjs scripts/foundation/validate-record.mjs scripts/foundation/fixtures/build-fixtures.mjs \ scripts/foundation/*.test.mjs; do node --check "$f" >/dev/null 2>&1 check "syntax: $f" $? done python3 -c 'import ast, sys; ast.parse(open(sys.argv[1], encoding="utf-8").read(), sys.argv[1])' scripts/foundation/verify-schema.py >/dev/null 2>&1 check "syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)" $? # --- fixtures are exactly what the generator produces --- node scripts/foundation/fixtures/build-fixtures.mjs "$SANDBOX/fx" >/dev/null 2>&1 check "fixture generator runs" $? for d in bundles raw index.json; do diff -r "$SANDBOX/fx/$d" "$FIXTURES/$d" >/dev/null 2>&1 check "checked-in fixtures/$d equal a fresh generation" $? done DEMO_OK=0 for f in "$SANDBOX"/fx/demo/*.json; do cmp -s "$f" "$DEMO/$(basename "$f")" || DEMO_OK=1 done check "checked-in demo bundles equal a fresh generation" $DEMO_OK # --- unit, CLI, privacy, non-effect and fixture-index tests --- node --test scripts/foundation/ >"$SANDBOX/node-test.log" 2>&1 NODE_RC=$? check "node --test scripts/foundation/ ($(grep -E '^ℹ pass' "$SANDBOX/node-test.log" | tr -d '\n' || echo 'summary missing'))" $NODE_RC [ "$NODE_RC" -ne 0 ] && grep -E "^✖|AssertionError" "$SANDBOX/node-test.log" | head -20 # --- differential schema oracle (pinned hashes; explicit jsonschema 4.26.0; never installs or skips) --- python3 scripts/foundation/verify-schema.py >"$SANDBOX/oracle.log" 2>&1 ORACLE_RC=$? check "differential schema oracle: $(tail -1 "$SANDBOX/oracle.log")" $ORACLE_RC grep -E "^(platform witness|node|schema column|profile column|profile refusals)" "$SANDBOX/oracle.log" | sed 's/^/ /' grep -qE "^schema column: .*DISAGREEMENTS 0;" "$SANDBOX/oracle.log" check "oracle: zero schema-column disagreements with the pinned checker" $? grep -qE "^profile refusals asserted: [1-9][0-9]* " "$SANDBOX/oracle.log" check "oracle: strict-only profile refusals are counted and asserted" $? [ "$ORACLE_RC" -ne 0 ] && grep -E "problem|FAIL" "$SANDBOX/oracle.log" | head -20 # --- owner demo paths and exit codes (shell-level receipts) --- expect_exit "demo: permitted read preview exits 0" 0 -- $INSPECT "$DEMO/demo-read-w1.json" expect_exit "demo: permitted file.change preview exits 0" 0 -- $INSPECT "$DEMO/demo-file-change-src.json" expect_exit "demo: assignment.change proposal is unresolved (exit 3)" 3 -- $INSPECT "$DEMO/demo-change-w1.json" expect_exit "demo: revoked registration is refused (exit 3)" 3 -- $INSPECT "$DEMO/adm-registration-revoked.json" expect_exit "demo: message is not authority (exit 3)" 3 -- $INSPECT "$DEMO/prop-message-is-not-authority.json" expect_exit "usage: no arguments exits 2" 2 -- $INSPECT expect_exit "io: missing file exits 4" 4 -- $INSPECT "$SANDBOX/does-not-exist.json" expect_exit "io: directory exits 4" 4 -- $INSPECT "$SANDBOX" ln -s "$DEMO/demo-read-w1.json" "$SANDBOX/link.json" expect_exit "io: symlink exits 4 (O_NOFOLLOW)" 4 -- $INSPECT "$SANDBOX/link.json" expect_exit "bound: oversize fixture exits 2" 2 -- $INSPECT "$FIXTURES/raw/raw-oversize-file.json" expect_exit "profile: one final LF in a typed selection id is refused before admission (exit 2)" 2 -- $INSPECT "$FIXTURES/bundles/profile-selection-agent-id-one-final-lf.json" expect_exit "profile: two final LFs fail the schema pattern itself (exit 2)" 2 -- $INSPECT "$FIXTURES/bundles/profile-id-record-id-two-final-lf.json" expect_exit "profile: escaped newlines in free-form text stay allowed (exit 0)" 0 -- $INSPECT "$FIXTURES/bundles/profile-escaped-newline-free-text-allowed.json" # (captured to a file first: under pipefail the inspector's own exit 2 would mask the python verdict) $INSPECT --json "$FIXTURES/bundles/profile-selection-agent-id-one-final-lf.json" > "$SANDBOX/profile-refusal.json" 2>/dev/null python3 -c 'import json,sys; r=json.load(open(sys.argv[1])); sys.exit(0 if r["result"]=="invalid" and r["reason"]=="invalid-request" and r["rule"]=="profile-pattern-mismatch" and r["selection"] is None and r["operation"] is None and "agent-a" not in json.dumps(r) else 1)' "$SANDBOX/profile-refusal.json" check "profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed" $? $INSPECT "$DEMO/demo-read-w1.json" | head -1 | grep -q '^SYNTHETIC PREVIEW — NO LIVE EFFECTS$' check "text output starts with the disclaimer" $? $INSPECT --json "$DEMO/demo-read-w1.json" | python3 -c 'import json,sys; r=json.load(sys.stdin); sys.exit(0 if r["result"]=="allowed" and r["rule"] is None and "exit" not in r and list(r)==["disclaimer","preview","bundleVersion","authentication","declarations","result","reason","rule","selection","operation","proposal","diagnostic"] else 1)' check "json output is valid JSON with result allowed and exactly the charter §7 fields" $? $INSPECT --json "$DEMO/demo-read-w1.json" | cmp -s - "$DEMO/demo-read-w1.expected.json" check "json golden matches byte-for-byte" $? # --- non-effect: sandbox inventory unchanged across runs; canaries never printed --- mkdir -p "$SANDBOX/home/.mosaic" "$SANDBOX/cwd" printf '{"token":"CANARY-DO-NOT-PRINT-shell"}\n' > "$SANDBOX/home/.mosaic/credentials.json" cp "$DEMO/demo-change-w1.json" "$SANDBOX/cwd/bundle.json" inventory() { (cd "$SANDBOX" && find . -path ./node-test.log -prune -o -path ./oracle.log -prune -o -print0 | sort -z | xargs -0 stat -c '%n %F %s %a %u %g %i %Y' ; find . -type f ! -name '*.log' -print0 | sort -z | xargs -0 sha256sum); } inventory > "$SANDBOX.before" REPO="$PWD"; NODE_BIN="$(command -v node)" OUT="$(cd "$SANDBOX/cwd" && env -i HOME="$SANDBOX/home" PATH=/nonexistent "$NODE_BIN" "$REPO/scripts/foundation-inspect.mjs" bundle.json 2>&1; \ env -i HOME="$SANDBOX/home" PATH=/nonexistent "$NODE_BIN" "$REPO/scripts/foundation-inspect.mjs" --json "$SANDBOX/home/.mosaic/credentials.json" 2>&1)" printf '%s' "$OUT" | grep -q '^result: unresolved$' check "sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal" $? inventory > "$SANDBOX.after" cmp -s "$SANDBOX.before" "$SANDBOX.after" check "sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs" $? rm -f "$SANDBOX.before" "$SANDBOX.after" ! printf '%s' "$OUT" | grep -q "CANARY-DO-NOT-PRINT" check "canary never printed (bundle run and credential-file run)" $? printf '%s' "$OUT" | grep -q '"rule": "shape-' check "a non-bundle JSON file is refused at the shape gate, not read into output" $? ! printf '%s' "$OUT" | grep -q '"token"' check "no field of the non-bundle file is echoed" $? echo echo "selftest: $PASS passed, $FAIL failed" [ "$FAIL" -eq 0 ]