Warden plan
tier: destructive requires: [needs-postgres]
The member-authoring governance model (the shipped #141 data model + #143 backend + the Wave-II sponsor-at-creation / ownership-parity work) holds as a dedicated, granular regression floor — deeper than plan 13's bundled #132 step. Specifically:
wikis.ownership (company|member), wikis.author_id, wikis.parent_wiki_id, the guardian_grants.kind discriminator, and the wiki_promotion_requests table (with its one-open-request-per-wiki unique index) exist in the schema; a Member Wiki fork carries ownership='member', its author, and its parent.member can author a Member Wiki, edit only their own, and suggest against a Company Wiki; a member with no root membership is refused (403) rather than silently written to root; and non-privileged callers are refused on accept/decline (403, workspace_admin only) and sponsor (403, guardians only), with outsiders 404'd on the promotion-request and edit-proposal reads.request → sponsor → accept flips ownership to company AND upgrades the sponsor's guardian_grants.kind to guardian in one transaction, and the author is retained (author history survives promotion). Edit proposals store the full spliced body, not the raw passage.Fast schema/route/ability greps fail early on a structural regression; the live-DB steps re-run the committed governance dbtests (routes.promotion, routes.authz, publish.member) as the durable floor.
greenlight-pg :5433 with vector, greenlight-redis :6380).PROJECT_ROOT = a tree with the member-authoring model merged — canary once PR #154 (member-authoring backend) and PR #155 (Wave-II sponsor-at-creation + ownership parity) land. Invoke via that tree's .warden symlink.pnpm, grep, awk, psql on PATH. The env file is provisioned automatically by .warden/run.sh from the tracked .warden/env/ci-env.template.sh plus the machine-local secrets file ~/.config/robin/warden-secrets.sh (mode 600, outside every git repository; override with WARDEN_SECRETS_FILE); first-time setup: mkdir -p ~/.config/robin && cp .warden/env/secrets.local.example.sh ~/.config/robin/warden-secrets.sh && chmod 600 ~/.config/robin/warden-secrets.sh.set -uo pipefail
source "$WARDEN_LIB/assert.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}"
if psql "$DATABASE_URL" -q -X -c "DROP SCHEMA IF EXISTS public CASCADE; DROP SCHEMA IF EXISTS drizzle CASCADE; CREATE SCHEMA public; CREATE EXTENSION IF NOT EXISTS vector;" >/dev/null 2>&1; then
warden_pass "reset robin_ci to a clean public schema with the vector extension"
else
warden_fail "could not reset robin_ci schema — is greenlight-pg (:5433) up with pgvector?"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# MG-1: the standing/authoring schema is present — the ownership discriminator,
# author + fork columns, the guardian-grant kind, and the promotion-requests
# table WITH its one-open-per-wiki unique index (the race guard #143 relies on).
SC=server/src/db/schema.ts
if grep -q "ownership: text('ownership')" "$SC" \
&& grep -q "authorId: text('author_id')" "$SC" \
&& grep -q "parentWikiId: text('parent_wiki_id')" "$SC" \
&& grep -q "wikiPromotionRequests" "$SC" \
&& grep -q "wiki_promotion_requests_one_open_per_wiki_uidx" "$SC"; then
warden_pass "standing model persists: ownership/author_id/parent_wiki_id + wiki_promotion_requests (one-open unique idx)"
else
warden_fail "the member-authoring schema regressed (ownership/author/parent/promotion-requests columns or the one-open unique index)"
fi
# MG-2: the governance routes exist on the wikis resource.
RT=server/src/modules/wikis/routes.ts
if grep -q "/:id/promotion-requests" "$RT" \
&& grep -q "/:id/promotion-requests/:reqId/accept" "$RT" \
&& grep -q "/:id/promotion-requests/:reqId/decline" "$RT" \
&& grep -q "/:id/sponsor" "$RT" \
&& grep -q "/:id/edit-proposals" "$RT"; then
warden_pass "promotion-request / sponsor / accept / decline / edit-proposal routes are present (#143)"
else
warden_fail "a member-authoring governance route is missing from wikis/routes.ts (#143)"
fi
# MG-3: the member role carries the authoring abilities — create own-ownership
# wiki, update own-authored member wiki, suggest against any workspace wiki.
AB=packages/permissions/src/ability.ts
if grep -q "ownership: 'member'" "$AB" && grep -Eq "'suggest'|\"suggest\"" "$AB"; then
warden_pass "member ability rules present: create/update {ownership:'member'} + suggest (#143)"
else
warden_fail "member authoring ability rules regressed in ability.ts — a member can no longer author/suggest (#143)"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.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}"
# Proves: request → sponsor → accept flips ownership to company AND upgrades the
# sponsor grant to guardian atomically (author retained); a supplied sponsor is
# attached as a kind=sponsor guardian_grants row in the SAME create transaction;
# an omitted sponsor creates no grant; an edit proposal stores the full spliced
# body; an x-workspace-id create lands a Member Wiki in the targeted workspace.
if pnpm --filter @robin/server exec vitest run \
src/modules/wikis/routes.promotion.dbtest.test.ts \
>/tmp/warden-mw-promotion.log 2>&1; then
warden_pass "#143/#132a promotion lifecycle + sponsor-at-creation dbtest passes against a live DB"
else
tail -25 /tmp/warden-mw-promotion.log
warden_fail "member-authoring promotion/sponsor dbtest FAILED — see /tmp/warden-mw-promotion.log"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.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}"
# Proves the governance gates hold from the deny side: outsider → 404 on
# promotion-request / edit-proposal reads; a workspace member → gets the rows;
# a member with no root membership → 403 (not silently written to root);
# accept/decline by a non-workspace-admin → 403; sponsor by a non-guardian → 403.
if pnpm --filter @robin/server exec vitest run \
src/modules/wikis/routes.authz.dbtest.test.ts \
>/tmp/warden-mw-authz.log 2>&1; then
warden_pass "#143 member-authoring negative-path authz dbtest passes against a live DB"
else
tail -25 /tmp/warden-mw-authz.log
warden_fail "member-authoring authz dbtest FAILED — a governance gate regressed; see /tmp/warden-mw-authz.log"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.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}"
# Proves: publishing a Member Wiki is refused (promote first); a Company Wiki in
# a non-root workspace publishes; the root-workspace guard (D30) still wins; and
# a Member Wiki fork persists ownership=member, its author, and its parent.
if pnpm --filter @robin/server exec vitest run \
src/services/publish.member.dbtest.test.ts \
>/tmp/warden-mw-publish.log 2>&1; then
warden_pass "#132 member-wiki publish guard + ownership/author/fork persistence dbtest passes against a live DB"
else
tail -25 /tmp/warden-mw-publish.log
warden_fail "member-wiki publish-guard/persistence dbtest FAILED — see /tmp/warden-mw-publish.log"
fi
This plan is the dedicated governance-lifecycle floor for member authoring; plan 13 covers the bug-sweep's #132 slice (ownership parity across HTTP/MCP/Socrates) and intentionally overlaps only on the shared create-wiki-ownership / run dbtests, which plan 13 owns — this plan does not re-run those, it goes deep on promotion/sponsorship/publish/fork instead. Backend governance, no web surface, so no agent-browser E2E. Step 1 resets the shared robin_ci schema (a preceding plan leaves its own state); each suite pushes its schema and truncates in its own hooks.
Stays manual / not asserted here:
run dbtest); a real conversational turn driving a promotion is not exercised (the model boundary is stubbed under the CI fake key).