Warden plan
tier: volatile requires: [needs-model, needs-server, needs-postgres]
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):
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.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.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.
:3000 with a REAL (non-CI-fake) OpenRouter key — boot via bash ~/.studio/master.withrobin.ai/scripts/dev-server.sh (pulls OPENROUTER_API_KEY from the linked Railway service; refuses to boot without it). This plan makes real, spend-bearing model calls — it is not free to run.dev-server.sh points at (:5433, robin_ci) with the vector extension. This is Andrew's live dev database, not a disposable CI schema — this plan creates rows through the real API and never touches DROP SCHEMA/pushTestSchema.bash ~/.studio/master.withrobin.ai/scripts/seed-andrew.sh (idempotent) — andrew@robin.ai / robin2026, super_admin, onboarded, with at least one workspace.curl, jq, psql on PATH. The env file is provisioned by .warden/run.sh from .warden/env/ci-env.template.sh plus ~/.config/robin/warden-secrets.sh./socrates page proves nothing additional about grounding.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
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
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"
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"
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"
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:
socrates_turn's round cap is a budget, not a spec requirement; raise it before assuming a defect if SG-1 fails but the conversation transcript (jq -r '.text' on the temp result files) shows the operator being asked more clarifying questions rather than refusing.edit mode (wikiId + mode:"edit") rather than a bare, chat-free propose_edit MCP call — no unauthenticated/API-key-only HTTP wrapper for a single MCP tool call was found without minting a real OAuth token (POST /mcp requires an OAuth bearer or the legacy ?token= JWT; the modern endpoint-URL builder no longer emits the legacy token). The task language ("however that is reachable") is satisfied by driving the same producer through the DANCE surface that ships it.Stays manual / not asserted here:
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).