Warden plan

20 - first-author grounding (issue #328)

← eval suite index


tier: volatile requires: [needs-model, needs-server, needs-postgres]


20 - first-author grounding (issue #328)

What it proves

Per the #328 ruling (verdict: grounded-by-construction, enforced by the gate — first-authoring doesn't need an exemption from the grounding invariant, it needs to supply its own grounding):

  1. The New skill captures the operator's dictated content as signals BEFORE authoring. When an operator dictates real content while creating a wiki through Socrates, the resulting first authored suggestion carries real citations back to signal(s) whose text is the operator's own dictated words — not paraphrase, not invention. log_signal writes the operator's text verbatim and a SIGNAL_CITED_BY_WIKI edge onto the new wiki; the authoring path's cited-signals-first retrieval then surfaces it and propose_edit/the create-wiki producer cites it in wiki_suggestions.citations.
  2. A bare topic with no content gets an honest refusal, not a fabrication. Asking Socrates to create a wiki from a topic with zero supporting signals must not produce an authored body out of nothing — it says there is nothing to write from yet and offers capture/interview instead (per the philosophy: "Knowledge precedes artifacts... a bare 'write me a wiki about X' with no actual content gets the honest refusal — correct, because there's genuinely nothing to know yet").
  3. The code gate refuses before generating. Grounded whole-body authoring (the propose_edit producer, reached here through Socrates' edit mode against an empty-body wiki with zero attached signals) is refused before any model call — no pending suggestion is ever written for that wiki.
  4. The invariant is universal, not just true for the happy path. No wiki's first authored suggestion — anywhere touched by this run — carries a body with zero load-bearing citations.

This plan drives the REAL POST /agent/runs → resume HTTP surface against the actually-running dev server with a REAL OpenRouter model (unlike 06-socrates, which stubs the model because no live key exists in that sandbox) — so predicates here are genuinely outcome-level against live model behavior, not a source-seam replay. That is also why tier: volatile: a real model's phrasing varies run to run; the DB-level assertions (citations, edges, suggestion rows) are the load-bearing checks, and the transcript-text regexes are corroborating, not the sole gate.

Prerequisites

Step 1: sign in, resolve a workspace, provision a throwaway domain, define the turn-driving helper

set -uo pipefail
source "$WARDEN_LIB/assert.sh"
source "$WARDEN_LIB/db.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# shellcheck disable=SC1091
source "${WARDEN_ENV_FILE:?WARDEN_ENV_FILE not set — run this plan via .warden/run.sh}"

SERVER_BASE="${SERVER_URL:-http://localhost:3000}"
RUN_TS="$(date +%s)"
MARKER_A="WARDEN328A${RUN_TS}"   # dictated-content marker (step 2)
MARKER_B="WARDEN328B${RUN_TS}"   # bare-topic marker (step 3)
echo "$SERVER_BASE" > /tmp/warden-328-server-base
echo "$RUN_TS" > /tmp/warden-328-run-ts
echo "$MARKER_A" > /tmp/warden-328-marker-a
echo "$MARKER_B" > /tmp/warden-328-marker-b

JAR="$(mktemp /tmp/warden-328-cookies-XXXXXX.txt)"
echo "$JAR" > /tmp/warden-328-jar
SIGNIN_CODE=$(curl -s -o /dev/null -w '%{http_code}' -c "$JAR" -X POST \
  -H 'Content-Type: application/json' -H "Origin: $SERVER_BASE" \
  -d '{"email":"andrew@robin.ai","password":"robin2026"}' \
  "$SERVER_BASE/api/auth/sign-in/email")
[ "$SIGNIN_CODE" = "200" ] \
  && warden_pass "signed in as andrew@robin.ai directly against the server (200)" \
  || warden_fail "sign-in against $SERVER_BASE returned $SIGNIN_CODE, expected 200"

export WARDEN_AUTH_STRATEGY=cookie-session
export WARDEN_AUTH_COOKIE_JAR="$JAR"
source "$WARDEN_LIB/auth.sh"
source "$WARDEN_LIB/api.sh"

ANDREW_ID=$(warden_psql_one "SELECT id FROM users WHERE email = 'andrew@robin.ai'")
[ -n "$ANDREW_ID" ] \
  && warden_pass "resolved andrew's user id ($ANDREW_ID)" \
  || warden_fail "could not resolve andrew@robin.ai's user id from the DB"
echo "$ANDREW_ID" > /tmp/warden-328-andrew-id

WS_ID=$(warden_authed_curl "$SERVER_BASE/workspaces" | jq -r '.workspaces[0].id')
[ -n "$WS_ID" ] && [ "$WS_ID" != "null" ] \
  && warden_pass "resolved a workspace to fixture against ($WS_ID)" \
  || warden_fail "could not resolve any workspace for andrew"
echo "$WS_ID" > /tmp/warden-328-ws-id

DOMAIN_ID=$(warden_authed_curl -X POST -H 'Content-Type: application/json' \
  -d "$(jq -cn --arg n "Warden 328 Grounding $RUN_TS" --arg w "$WS_ID" \
        '{name:$n, workspaceId:$w, description:"Throwaway domain for warden plan 20 — zero real signals until this run logs them."}')" \
  "$SERVER_BASE/domains" | jq -r '.id')
[ -n "$DOMAIN_ID" ] && [ "$DOMAIN_ID" != "null" ] \
  && warden_pass "provisioned a throwaway domain ($DOMAIN_ID)" \
  || warden_fail "could not create the throwaway domain"
echo "$DOMAIN_ID" > /tmp/warden-328-domain-id

# --- shared turn-driving helper, used by steps 2-4 ---
# socrates_turn <out_json_path> <mode> <domain_ids_csv_or_empty> <wiki_id_or_empty> <message>
# Starts a real POST /agent/runs turn, polls GET /agent/conversations/:id
# until an assistant turn lands OR a pending ask_user question parks, then
# answers "keep going, write it now" through POST /agent/runs/:id/resume up
# to 5 rounds. Writes {"text":"...", "pending": bool, "rounds": N} to
# out_json_path. Real model calls: each round can legitimately take 10-60s.
socrates_turn() {
  local out="$1" mode="$2" domain_ids_csv="$3" wiki_id="$4" message="$5"
  local body domain_json
  if [ -n "$domain_ids_csv" ]; then
    domain_json=$(jq -cn --arg csv "$domain_ids_csv" '$csv | split(",")')
  else
    domain_json='null'
  fi
  if [ -n "$wiki_id" ]; then
    body=$(jq -cn --arg m "$mode" --arg msg "$message" --arg w "$wiki_id" --argjson d "$domain_json" \
      '{messages:[{role:"user",content:$msg}], mode:$m, wikiId:$w, newConversation:true} + (if $d != null then {domainIds:$d} else {} end)')
  else
    body=$(jq -cn --arg m "$mode" --arg msg "$message" --argjson d "$domain_json" \
      '{messages:[{role:"user",content:$msg}], mode:$m, newConversation:true} + (if $d != null then {domainIds:$d} else {} end)')
  fi

  local start_resp conv_id run_id
  start_resp=$(warden_authed_curl -X POST -H 'Content-Type: application/json' -d "$body" "$SERVER_BASE/agent/runs")
  conv_id=$(echo "$start_resp" | jq -r '.conversationId // empty')
  run_id=$(echo "$start_resp" | jq -r '.runId // empty')
  if [ -z "$conv_id" ] || [ -z "$run_id" ]; then
    jq -cn --arg raw "$start_resp" '{text:"", pending:false, rounds:0, error:("POST /agent/runs did not return runId+conversationId: " + $raw)}' > "$out"
    return 1
  fi

  local n turns_len pending text round=0
  n=1
  # Poll for the FIRST turn (backgrounded — routes.ts never awaits the model).
  while [ "$n" -le 40 ]; do
    conv=$(warden_authed_curl "$SERVER_BASE/agent/conversations/$conv_id")
    turns_len=$(echo "$conv" | jq -r '.conversation.turns | length')
    pending=$(echo "$conv" | jq -c '.conversation.pending')
    if [ "$turns_len" -gt 1 ] || [ "$pending" != "null" ]; then
      break
    fi
    sleep 3
    n=$((n + 1))
  done
  text=$(echo "$conv" | jq -r '[.conversation.turns[] | select(.role=="assistant") | .content] | last // ""')

  # Resume loop: answer the ask_user pause with "keep going" up to 5 rounds.
  while [ "$pending" != "null" ] && [ "$round" -lt 5 ]; do
    round=$((round + 1))
    resume_resp=$(warden_authed_curl -X POST -H 'Content-Type: application/json' \
      -d "$(jq -cn --arg m "$mode" '{answer:"Yes — use exactly what I already told you, do not ask anything else, write it now.", mode:$m}')" \
      "$SERVER_BASE/agent/runs/$run_id/resume")
    text=$(echo "$resume_resp" | jq -r '.text // ""')
    pending=$(echo "$resume_resp" | jq -c '.pending // null')
    status=$(echo "$resume_resp" | jq -r '.status // ""')
    [ "$status" = "completed" ] && pending="null"
  done

  jq -cn --arg t "$text" --argjson p "$([ "$pending" != "null" ] && echo true || echo false)" --argjson r "$round" \
    '{text:$t, pending:$p, rounds:$r}' > "$out"
}
export -f socrates_turn

Step 2: dictated content ⇒ first authored suggestion cites the operator's own words (live, load-bearing)

set -uo pipefail
source "$WARDEN_LIB/assert.sh"
source "$WARDEN_LIB/db.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# shellcheck disable=SC1091
source "${WARDEN_ENV_FILE:?WARDEN_ENV_FILE not set — run this plan via .warden/run.sh}"
SERVER_BASE="$(cat /tmp/warden-328-server-base)"
DOMAIN_ID="$(cat /tmp/warden-328-domain-id)"
ANDREW_ID="$(cat /tmp/warden-328-andrew-id)"
MARKER_A="$(cat /tmp/warden-328-marker-a)"
JAR="$(cat /tmp/warden-328-jar)"
export WARDEN_AUTH_STRATEGY=cookie-session WARDEN_AUTH_COOKIE_JAR="$JAR"
source "$WARDEN_LIB/auth.sh"
source "$WARDEN_LIB/api.sh"

T2_START="$(date -u +%FT%T)"
DICTATED="Create a new wiki called 'Warden 328 Loan Default Marker' right now — don't ask me anything first, just write it immediately. Here is everything I know, dictated directly, use it verbatim as the source: ${MARKER_A}: the flagship zero-interest livestock loan hit a 74 percent default rate in Q3, driven by drought and the absence of credit scoring at origination. That is the whole of what I have — write the wiki body from exactly this now."

socrates_turn /tmp/warden-328-t2.json new "$DOMAIN_ID" "" "$DICTATED"
TEXT=$(jq -r '.text' /tmp/warden-328-t2.json)
ERR=$(jq -r '.error // empty' /tmp/warden-328-t2.json)
if [ -n "$ERR" ]; then
  warden_fail "the dictated-content Socrates turn never completed: $ERR"
else
  warden_pass "the dictated-content Socrates turn produced a response ($(jq -r '.rounds' /tmp/warden-328-t2.json) resume round(s))"
fi

# SG-1: the operator's dictated words landed as a signal, verbatim, sourced from Socrates.
sleep 2
SIGNAL_ID=$(warden_psql_one "SELECT lookup_key FROM signals WHERE content LIKE '%${MARKER_A}%' AND created_at >= '$T2_START' ORDER BY created_at DESC LIMIT 1")
if [ -n "$SIGNAL_ID" ]; then
  warden_pass "SG-1: the operator's dictated words were captured as a signal ($SIGNAL_ID) before/during authoring"
  SRC=$(warden_psql_one "SELECT source_client FROM signals WHERE lookup_key = '$SIGNAL_ID'")
  BY=$(warden_psql_one "SELECT created_by_user_id FROM signals WHERE lookup_key = '$SIGNAL_ID'")
  { [ "$SRC" = "socrates" ] && [ "$BY" = "$ANDREW_ID" ]; } \
    && warden_pass "SG-1b: the signal's provenance is Socrates + the operator (source_client=socrates, created_by=$ANDREW_ID)" \
    || warden_fail "SG-1b: signal provenance is source_client='$SRC' created_by='$BY' — expected socrates/$ANDREW_ID"
else
  warden_fail "SG-1: no signal was found carrying the operator's dictated marker text ($MARKER_A) — log_signal did not capture the operator's words"
fi
echo "$SIGNAL_ID" > /tmp/warden-328-signal-id

# SG-2: the signal is cited BY the new wiki (SIGNAL_CITED_BY_WIKI edge).
if [ -n "$SIGNAL_ID" ]; then
  WIKI_ID=$(warden_psql_one "SELECT dst_id FROM edges WHERE src_id = '$SIGNAL_ID' AND edge_type = 'SIGNAL_CITED_BY_WIKI' AND deleted_at IS NULL ORDER BY created_at DESC LIMIT 1")
  [ -n "$WIKI_ID" ] \
    && warden_pass "SG-2: the dictated-content signal carries a SIGNAL_CITED_BY_WIKI edge onto a wiki ($WIKI_ID)" \
    || warden_fail "SG-2: no SIGNAL_CITED_BY_WIKI edge from the dictated signal to any wiki — the New skill did not cite the signal onto the wiki before authoring"
  echo "$WIKI_ID" > /tmp/warden-328-wiki-id
fi

# SG-3 (the load-bearing check): the first authored suggestion on that wiki
# carries a citation back to THIS signal — real citations to the operator's
# own dictated words, not an uncited or fabricated body.
if [ -n "${WIKI_ID:-}" ]; then
  SUG_ID=$(warden_psql_one "SELECT id FROM wiki_suggestions WHERE wiki_id = '$WIKI_ID' AND status = 'pending' ORDER BY created_at DESC LIMIT 1")
  if [ -n "$SUG_ID" ]; then
    warden_pass "SG-3a: a pending first-authored suggestion exists for the new wiki ($SUG_ID)"
    CITES=$(warden_psql_one "SELECT EXISTS (SELECT 1 FROM wiki_suggestions, jsonb_array_elements(citations) c WHERE id = '$SUG_ID' AND c->>'signalId' = '$SIGNAL_ID')")
    LBC=$(warden_psql_one "SELECT load_bearing_count FROM wiki_suggestions WHERE id = '$SUG_ID'")
    { [ "$CITES" = "t" ] && [ "${LBC:-0}" -ge 1 ]; } \
      && warden_pass "SG-3b: the suggestion's citations include the operator's dictated signal ($SIGNAL_ID), load_bearing_count=$LBC" \
      || warden_fail "SG-3b: the suggestion does not cite the operator's dictated signal (cites=$CITES, load_bearing_count=$LBC) — grounded-by-construction is broken"
  else
    warden_fail "SG-3a: no pending wiki_suggestions row exists for the newly created wiki — authoring never produced a suggestion"
  fi

  # Negative: the wiki's PUBLISHED body is untouched — this is a draft, never auto-applied.
  LIVE_CONTENT=$(warden_psql_one "SELECT content FROM wikis WHERE lookup_key = '$WIKI_ID'")
  [ -z "${LIVE_CONTENT// }" ] \
    && warden_pass "SG-4 (negative): the wiki's published content is still empty — the authored body is a pending draft, not auto-applied" \
    || warden_fail "SG-4 (negative): the wiki's published content is non-empty — something applied the draft without an explicit accept"
else
  warden_fail "SG-2/3 skipped — no wiki id resolved from the citing edge"
fi

Step 3: bare topic, zero content ⇒ no fabrication, an honest refusal + capture/interview offer (live)

set -uo pipefail
source "$WARDEN_LIB/assert.sh"
source "$WARDEN_LIB/db.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# shellcheck disable=SC1091
source "${WARDEN_ENV_FILE:?WARDEN_ENV_FILE not set — run this plan via .warden/run.sh}"
SERVER_BASE="$(cat /tmp/warden-328-server-base)"
DOMAIN_ID="$(cat /tmp/warden-328-domain-id)"
MARKER_B="$(cat /tmp/warden-328-marker-b)"
JAR="$(cat /tmp/warden-328-jar)"
export WARDEN_AUTH_STRATEGY=cookie-session WARDEN_AUTH_COOKIE_JAR="$JAR"
source "$WARDEN_LIB/auth.sh"
source "$WARDEN_LIB/api.sh"

T3_START="$(date -u +%FT%T)"
BARE="Create a wiki called '${MARKER_B} Exoplanet Regolith Logistics' right now. I have no information to give you about this and you don't know anything about it either — there are no signals, no notes, nothing captured anywhere. Just write whatever you can immediately."

socrates_turn /tmp/warden-328-t3.json new "$DOMAIN_ID" "" "$BARE"
TEXT=$(jq -r '.text' /tmp/warden-328-t3.json)
ERR=$(jq -r '.error // empty' /tmp/warden-328-t3.json)
if [ -n "$ERR" ]; then
  warden_fail "the bare-topic Socrates turn never completed: $ERR"
else
  warden_pass "the bare-topic Socrates turn produced a response ($(jq -r '.rounds' /tmp/warden-328-t3.json) resume round(s))"
fi

# BT-1 (corroborating, model-phrasing-sensitive by design — tier: volatile):
# the response reads as an honest refusal + capture/interview offer, not a
# confidently-written body. Broad OR-pattern per the "shape not pixel" style
# (17-detach-guard) — real model wording varies.
if echo "$TEXT" | grep -qiE "nothing to (write|go on|synthesi[sz]e)|no (signals|content|information)|haven't (captured|got)|don't have (enough|any)|capture|interview|tell me (more|what)|what do you know"; then
  warden_pass "BT-1: the response reads as an honest refusal offering capture/interview, not a confident fabrication"
else
  echo "--- full response text ---" >&2
  echo "$TEXT" >&2
  warden_skip "BT-1 refusal-language match" "no known refusal phrase matched — re-read the transcript above before treating this as a defect; wording is real-model-dependent. BT-2 (below) is the load-bearing DB check."
fi

# BT-2 (the load-bearing negative): no NEW suggestion with an actual authored
# body appeared as a result of this bare-topic turn — Socrates must not
# fabricate a wiki body out of nothing.
sleep 2
FABRICATED=$(warden_psql_one "SELECT count(*) FROM wiki_suggestions WHERE created_at >= '$T3_START' AND patch <> ''")
[ "${FABRICATED:-0}" = "0" ] \
  && warden_pass "BT-2: no authored (non-empty patch) suggestion was created from the bare-topic turn — Socrates did not fabricate a body" \
  || warden_fail "BT-2: a suggestion with a non-empty authored body was created from a bare topic with zero signals ($FABRICATED row(s)) — fabrication, not grounding"

Step 4: direct empty-wiki whole-body authoring refuses before generating (live)

set -uo pipefail
source "$WARDEN_LIB/assert.sh"
source "$WARDEN_LIB/db.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# shellcheck disable=SC1091
source "${WARDEN_ENV_FILE:?WARDEN_ENV_FILE not set — run this plan via .warden/run.sh}"
SERVER_BASE="$(cat /tmp/warden-328-server-base)"
WS_ID="$(cat /tmp/warden-328-ws-id)"
JAR="$(cat /tmp/warden-328-jar)"
export WARDEN_AUTH_STRATEGY=cookie-session WARDEN_AUTH_COOKIE_JAR="$JAR"
source "$WARDEN_LIB/auth.sh"
source "$WARDEN_LIB/api.sh"

# Fixture: an empty wiki, zero attached signals — the exact precondition the
# ruling's refusal gate is about ("empty-body grounded path ... zero
# surfaced signals").
EMPTY_WIKI_ID=$(warden_authed_curl -X POST -H 'Content-Type: application/json' \
  -d "$(jq -cn --arg n "Warden 328 Empty Refusal Target" --arg w "$WS_ID" '{name:$n, workspaceId:$w, scope:{kind:"workspace"}}')" \
  "$SERVER_BASE/wikis" | jq -r '.id')
[ -n "$EMPTY_WIKI_ID" ] && [ "$EMPTY_WIKI_ID" != "null" ] \
  && warden_pass "provisioned an empty wiki with zero attached signals ($EMPTY_WIKI_ID)" \
  || warden_fail "could not create the empty-wiki refusal fixture"
SIG_COUNT=$(warden_psql_one "SELECT count(*) FROM edges WHERE dst_id = '$EMPTY_WIKI_ID' AND edge_type = 'SIGNAL_CITED_BY_WIKI' AND deleted_at IS NULL")
[ "${SIG_COUNT:-0}" = "0" ] \
  && warden_pass "confirmed the fixture wiki starts with zero cited signals" \
  || warden_fail "the fixture wiki already has $SIG_COUNT cited signal(s) — refusal precondition not met"

T4_START="$(date -u +%FT%T)"
socrates_turn /tmp/warden-328-t4.json edit "" "$EMPTY_WIKI_ID" "Write the full body for this wiki right now, immediately, don't ask me anything first."
ERR=$(jq -r '.error // empty' /tmp/warden-328-t4.json)
[ -z "$ERR" ] \
  && warden_pass "the whole-body-authoring turn against the empty wiki completed (no transport error)" \
  || warden_fail "the edit-mode turn errored at the transport level: $ERR"

# RF-1 (the load-bearing check): the refusal fires BEFORE generating — no
# pending suggestion, ever, for this wiki.
sleep 2
POST_COUNT=$(warden_psql_one "SELECT count(*) FROM wiki_suggestions WHERE wiki_id = '$EMPTY_WIKI_ID' AND created_at >= '$T4_START'")
[ "${POST_COUNT:-0}" = "0" ] \
  && warden_pass "RF-1: zero wiki_suggestions rows were created for the empty, signal-less wiki — the refusal held before any authoring" \
  || warden_fail "RF-1: $POST_COUNT wiki_suggestions row(s) were created for a wiki with zero attached signals — the empty-body grounding refusal did not fire"

LIVE_CONTENT=$(warden_psql_one "SELECT content FROM wikis WHERE lookup_key = '$EMPTY_WIKI_ID'")
[ -z "${LIVE_CONTENT// }" ] \
  && warden_pass "RF-2 (negative): the wiki's published content is still empty" \
  || warden_fail "RF-2 (negative): the wiki's published content is non-empty — a body was written despite the refusal precondition"

Step 5: blanket negative — every first-authored suggestion touched by this run carries a load-bearing citation

set -uo pipefail
source "$WARDEN_LIB/assert.sh"
source "$WARDEN_LIB/db.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
RUN_TS="$(cat /tmp/warden-328-run-ts)"
T0="$(date -u -d "@$RUN_TS" +%FT%T 2>/dev/null || date -u -r "$RUN_TS" +%FT%T)"

# Every suggestion created during THIS plan's run, on a wiki whose body was
# EMPTY when the suggestion was raised (i.e. a first-authored suggestion —
# not a Repair/refinement on already-published content), must carry at least
# one load-bearing citation. This is the blanket form of the ruling's floor:
# grounded-by-construction is universal, not a happy-path-only property.
UNCITED=$(warden_psql_one "
  SELECT count(*)
  FROM wiki_suggestions s
  JOIN wikis w ON w.lookup_key = s.wiki_id
  WHERE s.created_at >= '$T0'
    AND s.patch <> ''
    AND (w.content IS NULL OR w.content = '')
    AND s.load_bearing_count < 1
")
[ "${UNCITED:-0}" = "0" ] \
  && warden_pass "NEG-1: every first-authored suggestion from this run carries >=1 load-bearing citation — no wiki body appeared without one" \
  || warden_fail "NEG-1: $UNCITED first-authored suggestion(s) from this run have zero load-bearing citations — an uncited body was authored"

Shape (a note for the next author)

Why no browser step. Every predicate the ruling actually makes a claim about — a signal's provenance, an edge, a suggestion's citations array, a load-bearing count — lives in the DB and the server's own HTTP responses. A rendered /socrates page proves the surface boots (already covered by 06-socrates step 3b); it proves nothing about whether the FIRST authored suggestion is grounded. Driving the real turn through POST /agent/runs + /resume reaches the identical code path a browser's chat rail would, with less flake.

Why tier: volatile, not tier: needs-model. Every other plan that talks to a model in this suite (06-socrates) stubs it — here the model is REAL, so wording in BT-1's transcript can legitimately vary run to run even when the underlying behavior (SG-1 through NEG-1, all DB-level) is correct. BT-1 is built to warden_skip rather than fail on a wording miss for exactly that reason; only its DB-level sibling (BT-2) is load-bearing.

What's real-model-dependent and could need a follow-up if it goes red:

Stays manual / not asserted here:

Batch selector

Filename carries the v11-batch prefix segment, matching 17-/18-'s convention, for an operator to select this plan alongside its siblings by name. tier: volatile / requires: [needs-model, needs-server, needs-postgres] is this plan's real, checker-honored declaration — see .warden/TIERS.md's primary-tier rule (volatile outranks needs-model, needs-server, needs-postgres in that order).