open-standards/scripts/check-wai-ops-doors-selftest.sh
Transaction Science 417847daab wai conform prints JUnit and HTML reports, runs the independent verifiers beside an implementation or alone, and is the corpus union in one command
The change of wai/conform-output, on current main, without its wai doctor: main's doctor,
its tests, its catalogue entry, its MCP tool and its changelog entry stand as they are, and
this change's copies of them are gone, with the lines dev.rs held for them. wai/README.md,
which had no line for main's doctor, gains one in its first commands and `toolchain_doctor`
in the core tools.

- wai conform --format text|json|junit|html, reports made from the signed records.
- --independent beside a kind holds the corpus's independent verifier to the same cases;
  alone, it runs every corpus's verifier, a record each, from one table.
- wai-rs/build.rs reads that table from the `# independent:` lines of the step files
  (.github/wai-conformance/verifiers/), which take the verifiers' command lines out of the
  steps' bodies. scripts/wai-conformance-lists.py reads and checks the same lines.
- wai-conformance.yml runs the union as wai conform --independent --format junit.
- wai mcp's conformance tools read the table.
- wai-release.yml runs the quickstart from each archive as a new user runs it
  (clean-machine.sh, its PowerShell twin clean-machine.ps1), held by
  check-wai-release-quickstart.py. clean-machine.ps1 keeps clean-machine.sh's contents step
  and its check of WAI Studio. Each leg keeps main's archive_doctor.py check; the step's own
  wai hello, verify and dev --check are clean-machine.sh's now.
- check-wai-ops-doors.py reads VERBS and NEAR_SYNONYMS from catalogue.rs and keeps no list,
  and the verb-list test reads what that check reads.
- The catalogue's export and Studio's actions hold the new operations.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 10:56:00 -05:00

199 lines
10 KiB
Shell
Executable file

#!/usr/bin/env bash
#
# check-wai-ops-doors-selftest.sh — check-wai-ops-doors.py can fail.
#
# A check that cannot fail is decoration. This builds a scratch tree shaped like the
# repository (a catalogue export, the catalogue's source with its verbs, copied from this
# repository, an MCP tool table, an OpenAPI document, the C header and a Swift and a Kotlin
# source) whose every door names its operations as the catalogue does, and asserts that the
# check passes it and fails each way a door can misname an operation, saying which.
set -uo pipefail
here=$(cd "$(dirname "$0")" && pwd)
check="$here/check-wai-ops-doors.py"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
mk() {
rm -rf "$tmp/wai" "$tmp/joule-code"
mkdir -p "$tmp/wai/catalogue" "$tmp/wai/wai-rs/src/bin/wai_cli/mcp" "$tmp/wai/wai-rs/include" \
"$tmp/joule-code/joule-code-rs/crates/svc" "$tmp/wai/wai-sink-swift/Sources/WaiSink" \
"$tmp/wai/wai-sink-android/core/src/main/kotlin/wai/sink"
cp "$here/../wai/wai-rs/src/catalogue.rs" "$tmp/wai/wai-rs/src/catalogue.rs"
cat > "$tmp/wai/catalogue/ops.json" <<'J'
{"format": "wai.ops/1", "operations": [
{"id": "envelope.inspect", "placement": "local", "route": null},
{"id": "receipt.verify", "placement": "local", "route": null},
{"id": "room.grant", "placement": "service", "route": {"method": "POST", "path": "/v1/rooms/{room}/grants"}}
]}
J
cat > "$tmp/wai/wai-rs/src/bin/wai_cli/mcp/tools.rs" <<'R'
pub(crate) const TOOLS: &[Tool] = &[
Tool { name: "envelope_inspect", title: "Inspect", run: inspect },
Tool {
name: "room_grant",
title: "Grant",
run: grant,
},
];
R
cat > "$tmp/joule-code/joule-code-rs/crates/svc/openapi.json" <<'J'
{"openapi": "3.1.0", "paths": {"/v1/rooms/{room}/grants": {"post": {"operationId": "room.grant"}, "parameters": []}}}
J
cat > "$tmp/wai/wai-rs/include/wai.h" <<'H'
int32_t wai_envelope_inspect(const uint8_t *p, size_t n);
int32_t wai_envelope_pack(const uint8_t *p, size_t n);
void wai_buffer_free(struct WaiBuffer b);
int32_t wai_image_png_decode(const uint8_t *p);
H
cat > "$tmp/wai/wai-sink-swift/Sources/WaiSink/Envelope.swift" <<'S'
public final class WaiEnvelope: @unchecked Sendable {
public init() {}
public func inspect(_ path: String) -> String { "{" }
public func pack() {}
private func verifyQuietly() {}
}
public final class WaiSink {
public func info() -> String { "" }
}
S
cat > "$tmp/wai/wai-sink-android/core/src/main/kotlin/wai/sink/WaiReceipt.kt" <<'K'
package wai.sink
class WaiReceipt(
val owner: String,
private val pool: Runnable = Runnable { },
) {
fun verify(bytes: ByteArray): Boolean = bytes.isNotEmpty()
private fun checkQuietly() {}
companion object {
fun verifyAll(list: List<ByteArray>) = list.all { it.isNotEmpty() }
}
}
K
}
fails=0
expect() { # expect <pass|fail> <what> [text the output must hold]
local want=$1 what=$2 needle=${3:-} out rc
out=$(python3 -I "$check" --root "$tmp" 2>&1); rc=$?
if [ "$want" = pass ] && [ $rc -ne 0 ]; then echo "FAIL $what: the check failed"; printf '%s\n' "$out" | sed 's/^/ /'; fails=$((fails+1)); return; fi
if [ "$want" = fail ] && [ $rc -eq 0 ]; then echo "FAIL $what: the check passed"; printf '%s\n' "$out" | sed 's/^/ /'; fails=$((fails+1)); return; fi
if [ -n "$needle" ] && ! grep -qF -- "$needle" <<<"$out"; then echo "FAIL $what: the output does not say '$needle'"; printf '%s\n' "$out" | sed 's/^/ /'; fails=$((fails+1)); return; fi
echo "ok $what"
}
sub() { # sub <file> <from> <to>: one replacement, which must apply
python3 - "$1" "$2" "$3" <<'P' || { echo "FAIL the fixture edit did not apply: $2"; fails=$((fails+1)); }
import sys
p, a, b = sys.argv[1:]
s = open(p).read()
if s.count(a) != 1:
sys.exit(1)
open(p, "w").write(s.replace(a, b))
P
}
mk; expect pass "a tree whose every door names its operations as the catalogue does" "MCP: 2 tools, each an operation"
mk; expect pass "the C door's held names and its library API" "C: 1 functions name operations of the catalogue"
mk; expect pass "a Swift and a Kotlin method that name operations" "Kotlin: 2 methods name operations"
mk; rm "$tmp/wai/wai-rs/src/bin/wai_cli/mcp/tools.rs"
expect pass "a door with no operation yet says so" "no MCP door yet"
mk; sub "$tmp/wai/wai-rs/src/bin/wai_cli/mcp/tools.rs" '"envelope_inspect"' '"inspect_envelope"'
expect fail "an MCP tool misnamed" "tool 'inspect_envelope' names inspect.envelope, which the catalogue does not hold"
mk; sub "$tmp/wai/wai-rs/src/bin/wai_cli/mcp/tools.rs" '"room_grant"' '"dev_room_grant"'
expect fail "an MCP tool that is not noun_verb" "tool 'dev_room_grant' is not noun_verb"
mk; cat > "$tmp/wai/wai-rs/src/bin/wai_cli/mcp/tools.rs" <<'R'
pub(crate) const TOOLS: &[Tool] = &[
Tool { op: "envelope.inspect", toolset: Toolset::Core, effect: Effect::Reads, run: inspect,
description: "What an envelope holds." },
Tool { op: "room.grant", toolset: Toolset::Dev, effect: Effect::DevRoom, run: grant,
description: "A grant." },
];
R
expect pass "a tool table that names each tool by its operation" "MCP: 2 tools, each an operation"
mk; cat > "$tmp/wai/wai-rs/src/bin/wai_cli/mcp/tools.rs" <<'R'
pub(crate) const TOOLS: &[Tool] = &[
Tool { op: "envelope.inspect", toolset: Toolset::Core, run: inspect, description: "x" },
Tool { op: "receipt.check", toolset: Toolset::Core, run: check, description: "y" },
];
R
expect fail "a tool named by an operation the catalogue does not hold" "tool 'receipt_check' names receipt.check"
mk; printf 'pub(crate) const TOOLS: &[Tool] = &[];\n' > "$tmp/wai/wai-rs/src/bin/wai_cli/mcp/tools.rs"
expect fail "an MCP tool table the check cannot read" "no tool name could be read"
mk; sub "$tmp/joule-code/joule-code-rs/crates/svc/openapi.json" '"room.grant"' '"grant"'
expect fail "an OpenAPI operationId misnamed" "operationId 'grant' is not an operation of the catalogue"
mk; sub "$tmp/joule-code/joule-code-rs/crates/svc/openapi.json" '/v1/rooms/{room}/grants' '/grant'
expect fail "an OpenAPI route that is not the catalogue's" "the catalogue routes room.grant to POST /v1/rooms/{room}/grants"
mk; sub "$tmp/joule-code/joule-code-rs/crates/svc/openapi.json" '"room.grant"' '"envelope.inspect"'
expect fail "an OpenAPI operation the catalogue places on this machine" "envelope.inspect is placed local, not on a service"
mk; printf 'int32_t wai_envelope_decode(const uint8_t *p);\n' >> "$tmp/wai/wai-rs/include/wai.h"
expect fail "a C function misnamed" "wai_envelope_decode names envelope.decode, which the catalogue does not hold"
mk; printf 'int32_t wai_receipt_get(const uint8_t *p);\n' >> "$tmp/wai/wai-rs/include/wai.h"
expect fail "a C function with a near-synonym verb" "wai_receipt_get: the verb get is a near-synonym; the operation verb is show"
mk; printf 'int32_t wai_room_up(const uint8_t *p);\n' >> "$tmp/wai/wai-rs/include/wai.h"
expect fail "a C function whose verb is up, start's near-synonym" "wai_room_up: the verb up is a near-synonym; the operation verb is start"
# upscale and tonemap joined N3's list after its approval: a door's name with either verb is an
# operation's name, held like any other.
mk; printf 'int32_t wai_envelope_upscale(const uint8_t *p);\n' >> "$tmp/wai/wai-rs/include/wai.h"
expect fail "a C function whose verb is upscale" "wai_envelope_upscale names envelope.upscale, which the catalogue does not hold"
mk; sub "$tmp/wai/wai-sink-swift/Sources/WaiSink/Envelope.swift" 'public func pack() {}' 'public func tonemap() {}'
expect fail "a Swift method whose verb is tonemap" "WaiEnvelope.tonemap names envelope.tonemap, which the catalogue does not hold"
mk; sub "$tmp/wai/wai-sink-swift/Sources/WaiSink/Envelope.swift" 'public func pack() {}' 'public func verify() {}'
expect fail "a Swift method misnamed" "WaiEnvelope.verify names envelope.verify, which the catalogue does not hold"
mk; sub "$tmp/wai/wai-sink-android/core/src/main/kotlin/wai/sink/WaiReceipt.kt" 'fun verify(bytes' 'fun check(bytes'
expect fail "a Kotlin method misnamed" "WaiReceipt.check names receipt.check, which the catalogue does not hold"
mk; sub "$tmp/wai/wai-sink-android/core/src/main/kotlin/wai/sink/WaiReceipt.kt" 'fun verifyAll(' 'fun describeAll('
expect fail "a Kotlin companion method with a near-synonym verb" "WaiReceipt.describeAll: the verb describe is a near-synonym"
mk; sub "$tmp/wai/wai-sink-android/core/src/main/kotlin/wai/sink/WaiReceipt.kt" 'private fun checkQuietly' 'fun checkQuietly'
expect fail "a private Kotlin method made public under an operation verb" "WaiReceipt.checkQuietly names receipt.check"
# The verbs are the catalogue's own list, read from its source: each verb it has added since
# naming rule N3 was approved is held, and a near-synonym it lists is refused.
mk; printf 'int32_t wai_envelope_doctor(const uint8_t *p);\n' >> "$tmp/wai/wai-rs/include/wai.h"
expect fail "a C function under the catalogue's verb doctor" "wai_envelope_doctor names envelope.doctor, which the catalogue does not hold"
mk; printf 'int32_t wai_envelope_upscale(const uint8_t *p);\n' >> "$tmp/wai/wai-rs/include/wai.h"
expect fail "a C function under the catalogue's verb upscale" "wai_envelope_upscale names envelope.upscale"
mk; sub "$tmp/wai/wai-sink-swift/Sources/WaiSink/Envelope.swift" 'public func pack() {}' 'public func tonemap() {}'
expect fail "a Swift method under the catalogue's verb tonemap" "WaiEnvelope.tonemap names envelope.tonemap"
mk; printf 'int32_t wai_receipt_up(const uint8_t *p);\n' >> "$tmp/wai/wai-rs/include/wai.h"
expect fail "a C function under the catalogue's near-synonym up" "wai_receipt_up: the verb up is a near-synonym"
mk; sub "$tmp/wai/wai-rs/src/catalogue.rs" '"tonemap", "doctor",' '"tonemap",'
printf 'int32_t wai_envelope_doctor(const uint8_t *p);\n' >> "$tmp/wai/wai-rs/include/wai.h"
expect pass "a verb the catalogue does not list is library API: the list is the catalogue's" "C: 1 functions name operations of the catalogue, each held; 4 are library API"
mk; sub "$tmp/wai/wai-rs/src/catalogue.rs" 'pub const VERBS:' 'pub const RETIRED_VERBS:'
expect fail "a catalogue whose verbs cannot be read" "no operation verbs could be read"
mk; sub "$tmp/wai/wai-rs/src/catalogue.rs" 'pub const NEAR_SYNONYMS:' 'pub const RETIRED_NEAR:'
expect fail "a catalogue whose near-synonyms cannot be read" "no near-synonyms could be read"
mk; rm "$tmp/wai/wai-rs/src/catalogue.rs"
expect fail "no catalogue source to read the verbs from" "catalogue.rs"
if [ "$fails" -ne 0 ]; then
echo "check-wai-ops-doors-selftest: $fails case(s) failed"
exit 1
fi
echo "check-wai-ops-doors-selftest: every case held"