#!/bin/sh
# Drift guard for the ACCESS-CONTROL vocabulary we teach — the manifest's knock `[[hints]]` and the
# `/sptc:knock` operative body. spt-core 0.54.0 renamed the directionality flags with NO deprecation
# aliases, so text teaching the old spelling is not merely stale: it hands the agent a hard parse
# error. The body was amended for that rename; nothing stopped a later edit from undoing it.
#
# Shape-aware on purpose: the body's migration table NAMES the retired flags, which is correct and
# must stay legal. What is refused is teaching them as CURRENT usage — an occurrence on a line that
# does not mark them retired/pre-0.54 — and any occurrence at all in a hint, which has no room for a
# migration table and is read as instruction.
# The reply-exemption wording is written against a docs-site section that is amended at source but
# NOT YET PUBLISHED (as of 2026-08-21). This guard checks the WORDS, never a live anchor — the
# gates are offline-deterministic. The re-check, and who to ask for the URL, is recorded on the
# requirement itself.
# [unit->REQ-KNOCK-DIRECTIONALITY-TAUGHT]
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
cd "$ROOT" || exit 2
rc=0
ok()   { printf 'ok   %s\n' "$1"; }
fail() { printf 'FAIL: %s\n' "$1"; rc=1; }

MANIFEST="adapter/claude-spt.toml"
BODY="adapter/strings/skills/knock.md"
[ -f "$MANIFEST" ] && [ -f "$BODY" ] || { echo "FAIL: missing $MANIFEST or $BODY"; exit 1; }

# The hint lines that teach knock (both the verb hint and the code/redeem hint).
hints=$(grep -n '^text = "Tip: /sptc:knock' "$MANIFEST")
[ -n "$hints" ] || { fail "no /sptc:knock hint lines found in $MANIFEST"; exit 1; }

# 1. Both current flags are named by the hints — the operator ask behind them is that an agent
#    learns it MUST choose, and what the choice means, before it runs the command.
printf '%s\n' "$hints" | grep -q -- '--send-receive' \
  && ok "hints name --send-receive" || fail "no hint names --send-receive"
printf '%s\n' "$hints" | grep -q -- '--send-only' \
  && ok "hints name --send-only" || fail "no hint names --send-only"

# 2. No hint teaches a RETIRED flag. There is no alias: the old spelling is a parse error.
if printf '%s\n' "$hints" | grep -qE -- '--mutual|--one-way'; then
  fail "a knock hint still teaches a retired flag (--mutual/--one-way)"
else
  ok "hints teach no retired flag"
fi

# 3. The reply exemption is taught, and taught as CORRELATED — declining the reverse direction does
#    not silence the far side. Both surfaces carry it: the hint (one line, often all an agent sees)
#    and the operative body.
printf '%s\n' "$hints" | grep -qi 'still answer' \
  && ok "hints say --send-only does not silence the far side" \
  || fail "no hint teaches that --send-only still allows replies"
grep -qi 'correlated to your own outbound' "$BODY" \
  && ok "body teaches the reply exemption as correlated" \
  || fail "body does not teach the correlated-reply exemption"

# 4. And it is never taught as an open back-channel — doyle's phrasing constraint, because the
#    correlation bound is real and its width is implementation detail.
if grep -qiE 'permanent back-channel|always reach you back|open a back-channel' "$BODY" \
   && ! grep -qi 'never as "messaging someone once' "$BODY"; then
  fail "body overstates the exemption as an open back-channel"
else
  ok "exemption not overstated as an open back-channel"
fi

# 5. Every retired-flag mention in the BODY sits on a line that marks it retired (the migration
#    table is legal; a line teaching `--mutual` as usage is not).
# A migration-table ROW is excused, but only when it is a COMPARISON row: it must also name a
# CURRENT flag (or say the verb takes none), which is what makes it a then-vs-now row rather
# than an instruction. A row teaching `--mutual` on its own still fails, and so does any prose
# mention outside a retirement context. The table itself must exist, header and all.
hits=$(grep -n -- '--mutual\|--one-way' "$BODY" \
       | grep -viE 'retired|no longer|before 0\.54|pre-0\.54|renamed|parse error|untouched|armed under the old')
bad=""
if grep -qE '^\|.*before .*0\.54' "$BODY"; then
  bad=$(printf '%s\n' "$hits" | grep -vE '^[0-9][0-9]*:\|.*(--send-receive|--send-only|no directionality flag)')
else
  bad="$hits"   # no migration table => nothing is excused
fi
bad=$(printf '%s' "$bad" | sed '/^$/d')
if [ -n "$bad" ]; then
  fail "body mentions a retired flag outside a retirement/migration context:"
  printf '%s\n' "$bad"
else
  ok "retired flags appear only in retirement context"
fi

# 6. Subnet sealing on new-code is taught (capability observed on 0.59.0; omitting --subnet seals to
#    every subnet you belong to, which changes WHO can redeem).
grep -q -- '--subnet' "$BODY" && ok "body teaches new-code --subnet" \
                             || fail "body does not teach new-code --subnet sealing"

exit $rc
