diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-20 09:32:29 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-20 09:35:41 +0200 |
| commit | b6aeaa8995ae81562a4bf11937959b09027116ff (patch) | |
| tree | 90da3cbdb9afa383293544e506a180bea38f9915 | |
| parent | 26d800749f5d4bb150c86215b0df4444f01ed2f6 (diff) | |
| download | bokf-b6aeaa8995ae81562a4bf11937959b09027116ff.tar.gz bokf-b6aeaa8995ae81562a4bf11937959b09027116ff.zip | |
scripts: check command and error-code drift against the docs
| -rw-r--r-- | docs/PROTOCOL.md | 10 | ||||
| -rwxr-xr-x | scripts/check-consistency.sh | 196 |
2 files changed, 206 insertions, 0 deletions
diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 47f6f26..16fd2c5 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -202,6 +202,15 @@ Cursors are opaque. ## 6. Discovery +The discovery commands themselves: + +| Command | Auth | Result | +|---|---|---| +| `health` | public | `{"status":"ok"}` | +| `meta` | public | server version, protocol version `1`, features, limits, `tcp_enabled`, `time` | +| `describe` | authenticated | full command catalogue; `{"cmd":"voucher.post"}` returns one entry | +| `agent.instructions` | authenticated | Markdown workflow rules (see 6.3) | + ### 6.1 `meta` / `health` (unauthenticated) `meta` returns server version, protocol version `1`, enabled features @@ -256,6 +265,7 @@ Arguments are shown abbreviated; `describe` is authoritative. | `session.open` | `method`, `username`, `password`, `token` | `session`, `user`, `orgs[]`, `active_org` | | `session.close` | — | `{}` | | `session.whoami` | — | `user`, `active_org`, `role`, `scopes` | +| `session.list_orgs` | — | `items[{id,name,role}]` | | `session.use_org` | `org` | `active_org`, `role` | | `org.create` | `name`, `org_nr?`, `fiscal_year_start_month?`, `moms_period?`, `framework?` | `org` | | `org.get` / `org.list` | `org?` | `org` / `items[]` | diff --git a/scripts/check-consistency.sh b/scripts/check-consistency.sh new file mode 100755 index 0000000..f67695f --- /dev/null +++ b/scripts/check-consistency.sh @@ -0,0 +1,196 @@ +#!/bin/sh +# Check that the daemon source and docs/PROTOCOL.md have not drifted apart: +# +# 1. every command in g_commands[] (src/commands.c) is listed in a +# PROTOCOL.md table whose first column is "Command", and vice versa; +# 2. every error code passed to fail()/failf()/add_error() in src/*.c is +# documented in PROTOCOL.md section 5.3, and vice versa (documented but +# unused codes are warnings only, some are reserved). +# +# Run from the repo root: sh scripts/check-consistency.sh +# Exits 0 when there is no drift, 1 when drift is found, 2 on parse errors. +# +# Environment overrides (used by the self-tests): +# REPO_ROOT repository root, default "." (run from the repo root) +# COMMANDS_C command table source, default $REPO_ROOT/src/commands.c +# PROTOCOL_MD protocol document, default $REPO_ROOT/docs/PROTOCOL.md +# SRC_GLOB shell glob of sources scanned for error codes, +# default $REPO_ROOT/src/*.c +# +# Commands deliberately kept out of the PROTOCOL.md tables. Prefer documenting +# the command in a table over adding it here. Keep sorted, space separated. +ALLOW_UNDOCUMENTED_COMMANDS="" + +set -eu +export LC_ALL=C + +REPO_ROOT=${REPO_ROOT:-.} +COMMANDS_C=${COMMANDS_C:-$REPO_ROOT/src/commands.c} +PROTOCOL_MD=${PROTOCOL_MD:-$REPO_ROOT/docs/PROTOCOL.md} +SRC_GLOB=${SRC_GLOB:-$REPO_ROOT/src/*.c} + +if [ ! -f "$COMMANDS_C" ]; then + echo "check-consistency: command source not found: $COMMANDS_C" >&2 + exit 2 +fi +if [ ! -f "$PROTOCOL_MD" ]; then + echo "check-consistency: protocol document not found: $PROTOCOL_MD" >&2 + exit 2 +fi + +tmp=$(mktemp -d "${TMPDIR:-/tmp}/bokf-consistency-XXXXXX") +trap 'rm -rf "$tmp"' EXIT HUP INT TERM + +# --- extract what the code implements -------------------------------------- + +awk ' + /g_commands[[:space:]]*\[[[:space:]]*\][[:space:]]*=/ { in_table = 1; next } + in_table && /^[[:space:]]*\};/ { in_table = 0 } + in_table && /^[[:space:]]*\{[[:space:]]*"/ { + line = $0 + sub(/^[^{]*\{[[:space:]]*"/, "", line) + sub(/".*/, "", line) + if (line != "") + print line + } +' "$COMMANDS_C" | sort -u > "$tmp/impl_commands" + +if [ ! -s "$tmp/impl_commands" ]; then + echo "check-consistency: no commands parsed from $COMMANDS_C" >&2 + exit 2 +fi + +# --- extract what the docs promise ----------------------------------------- + +# Only rows of tables whose header first cell is "Command" count; prose, +# argument names and other tables must not produce command names. +awk ' + /^\|/ { + n = split($0, cells, "|") + cell = cells[2] + gsub(/^[[:space:]]+/, "", cell) + gsub(/[[:space:]]+$/, "", cell) + if (cell == "Command") { in_table = 1; next } + if (in_table) { + rest = cell + while (match(rest, /`[a-z][a-z0-9_.]*`/)) { + print substr(rest, RSTART + 1, RLENGTH - 2) + rest = substr(rest, RSTART + RLENGTH) + } + } + next + } + { in_table = 0 } +' "$PROTOCOL_MD" | sort -u > "$tmp/doc_commands" + +if [ ! -s "$tmp/doc_commands" ]; then + echo "check-consistency: no command tables parsed from $PROTOCOL_MD" >&2 + exit 2 +fi + +# Error codes are the backticked ALL_CAPS words inside section 5.3. +awk ' + /^###[[:space:]]+5\.3/ { in_section = 1; next } + in_section && /^###/ { in_section = 0 } + in_section { + rest = $0 + while (match(rest, /`[A-Z][A-Z0-9_]+`/)) { + print substr(rest, RSTART + 1, RLENGTH - 2) + rest = substr(rest, RSTART + RLENGTH) + } + } +' "$PROTOCOL_MD" | sort -u > "$tmp/doc_codes" + +if [ ! -s "$tmp/doc_codes" ]; then + echo "check-consistency: no error codes parsed from $PROTOCOL_MD 5.3" >&2 + exit 2 +fi + +# --- compare ---------------------------------------------------------------- + +: > "$tmp/allow_commands" +for name in $ALLOW_UNDOCUMENTED_COMMANDS; do + printf '%s\n' "$name" >> "$tmp/allow_commands" +done +sort -u -o "$tmp/allow_commands" "$tmp/allow_commands" +cat "$tmp/doc_commands" "$tmp/allow_commands" | sort -u > "$tmp/known_commands" + +comm -23 "$tmp/impl_commands" "$tmp/known_commands" > "$tmp/cmd_undocumented" +comm -13 "$tmp/impl_commands" "$tmp/doc_commands" > "$tmp/cmd_unimplemented" + +for f in $SRC_GLOB; do + [ -f "$f" ] || continue + grep -E '(^|[^A-Za-z0-9_])(fail|failf|add_error)[[:space:]]*\(' "$f" | + grep -oE '"[A-Z][A-Z0-9_]{1,}"' || true +done | tr -d '"' | sort -u > "$tmp/used_codes" + +if [ ! -s "$tmp/used_codes" ]; then + echo "check-consistency: no error-code call sites parsed from $SRC_GLOB" >&2 + exit 2 +fi + +comm -23 "$tmp/used_codes" "$tmp/doc_codes" > "$tmp/code_undocumented" +comm -13 "$tmp/used_codes" "$tmp/doc_codes" > "$tmp/code_unused" + +# --- report ----------------------------------------------------------------- + +status=0 +problems=0 +warnings=0 + +section() { + echo + echo "== $1 ==" +} + +section "commands: implemented in $COMMANDS_C but not documented in $PROTOCOL_MD" +if [ -s "$tmp/cmd_undocumented" ]; then + while IFS= read -r name; do + echo "command: undocumented: $name" + problems=$((problems + 1)) + done < "$tmp/cmd_undocumented" + status=1 +else + echo "(none)" +fi + +section "commands: documented in $PROTOCOL_MD but not implemented" +if [ -s "$tmp/cmd_unimplemented" ]; then + while IFS= read -r name; do + echo "command: documented but unimplemented: $name" + problems=$((problems + 1)) + done < "$tmp/cmd_unimplemented" + status=1 +else + echo "(none)" +fi + +section "error codes: used in src but not documented in $PROTOCOL_MD 5.3" +if [ -s "$tmp/code_undocumented" ]; then + while IFS= read -r code; do + echo "error-code: undocumented: $code" + problems=$((problems + 1)) + done < "$tmp/code_undocumented" + status=1 +else + echo "(none)" +fi + +section "warnings (not failures)" +if [ -s "$tmp/code_unused" ]; then + while IFS= read -r code; do + echo "warning: documented but unused in src: $code" + warnings=$((warnings + 1)) + done < "$tmp/code_unused" + echo "(documented codes may be reserved for future use)" +else + echo "(none)" +fi + +echo +if [ "$status" -eq 0 ]; then + echo "consistency: ok ($problems problems, $warnings warnings)" +else + echo "consistency: FAILED ($problems problems, $warnings warnings)" +fi +exit "$status" |
