Inventory: Phase 1 (schema + RLS + audit trigger) + Phase 2 (auth + roles) — gates passed#6192
Conversation
Source-of-truth migration (inventory/sql/phase1.sql) per spec.md, to be run by the owner in the Supabase SQL editor. - Tables: profiles, items, field_definitions, change_log. - current_user_role(): SECURITY DEFINER, SET search_path='', no params, returns role for auth.uid() only; EXECUTE to authenticated (not anon). - handle_new_user trigger -> auto-provision profile default 'viewer'; one-time admin backfill/elevate. - log_item_change audit trigger -> who/what/when field-level before->after. - change_log immutability: RLS (no UPDATE/DELETE policy) + BEFORE U/D trigger that RAISEs (blocks even the privileged owner). - RLS ENABLE (not FORCE) on every table; least-privilege policies scoped to role AND operation; no USING(true); anon default-denied everywhere. Owner-approved decisions: no client INSERT on change_log; single category column; status text+CHECK; changed_at rename; GIN index deferred to Phase 5. https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
- proofs/phase1_proofs.sql: privileged-path immutability proofs (#1a UPDATE, #1b DELETE blocked) + broken-vs-fixed (#2: guard disabled -> tamper succeeds), all inside one BEGIN..ROLLBACK so no artifacts persist; returns a results table. - proofs/anon_checks.md: proof #3 (anon reads return []) + proof #1 client path (logged-in user refused by RLS). Public anon key referenced via env var only. - RUN.md: run order + owner/Claude responsibility split. https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
CREATE OR REPLACE cannot change a function's return type. A prior current_user_role() (different return type) existed, so the migration failed at section 2. Add DROP FUNCTION IF EXISTS ... CASCADE before the create so the script is fully idempotent across return-type changes; section 7 recreates the dependent policies regardless. https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
…dence Three proofs landed with observed output: - #3 anon reads all [] (+ RPC 42501 permission denied) — Claude ran - A: SQL-editor immutability all PASSED incl. broken-vs-fixed — owner ran - B: client editor session PATCH/DELETE change_log -> [] / [] / unchanged — Claude ran Phase 0b [x] (URL + publishable key delivered, admin user created). Decisions log updated with Phase 1 schema decisions + pre-Phase-3 proof-item cleanup follow-up. Phase 2 stays [ ]. https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
The required markdownlint gate failed on PR #6192 because the "Follow-ups recorded in the Decisions log:" paragraph butted directly against the list beneath it with no separating blank line (MD032). Insert the blank line; verified locally with markdownlint-cli2 (exit 0) using the repo's .markdownlint-cli2.jsonc config. The other two red lints (tick-history order, backlog ID uniqueness) were transient install-step flakes — mise 502 Bad Gateway fetching actionlint@1.7.12 from the GitHub releases CDN — not content failures; they re-run green on push. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🤖 CI fix only — not touching your draft hold. Pushed The other two reds you may have seen — Left the PR in draft deliberately — — Otto (background worker) |
… trust, role single-source, sign-out clears data) inventory/index.html — self-contained static client: - SIGN-IN form ONLY (no sign-up / create-account / reset-via-signup). - Trust via getUser() (verified), never getSession(); role via rpc current_user_role() (same single source RLS uses); autoRefresh on; password cleared from DOM after login. - Edit probe button (visible to all roles) -> UPDATE items; DB/RLS decides (proof a). - One sample-item read as sign-out scaffolding (NOT Phase 3 read path). - Sign-out: signOut() + null in-memory state + wipe rendered DOM + verify session ended. - Baseline CSP meta (connect-src pinned to the Supabase host; form-action/object-src 'none'). SRI + exact-version pin + drop 'unsafe-inline' tracked for Phase 7 (not pulled forward). - publishable anon key only (public by design). https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
… RLS Owner-run, rolled-back. Simulates the viewer (set local role authenticated + request.jwt.claims) and shows the least-privilege items_update policy refuses a viewer UPDATE (0 rows); adding a permissive USING(true) policy makes it succeed (1 row = the breach); ROLLBACK restores least-privilege. Mirrors Phase 1 proof #2. https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
…ess) Keeps the vendored supabase-js copy + same-origin proof proxy + proof page (used to drive the real-browser (a)/(b)/(c) proofs around a test-proxy TLS cert wall) out of the PR. Committed index.html stays CDN-based as approved. https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
…~]; burn-test-users -> risk register + Phase 7 Real-browser (Playwright) proofs vs live Supabase: - sign-in-only UI verified in live DOM (no signup/create-account) - (b) role per user from current_user_role(): viewer->viewer, editor->editor (caught+fixed a profiles data discrepancy: test2 was editor, owner set viewer) - (a) viewer 'Attempt edit' -> DB REFUSED 0 rows (RLS); editor same button -> ALLOWED (negative control) - audit row written on the editor UPDATE (Phase-1 trigger confirmed under client edit) - (c) sign-out -> DOM cleared + in-memory nulled + localStorage token gone + verified getUser() null Phase 2 left at [~]: owner still runs broken-vs-fixed (a) SQL (privileged set role) before [x]. Risk register + Phase 7 gate updated: burn/rotate all build test users; supabase-js SRI+pin; drop CSP 'unsafe-inline'. https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
…OTICE The Supabase SQL editor surfaces the results grid, not NOTICES, so the 0-row/1-row evidence was invisible. Stash each UPDATE row_count in a txn-local GUC (survives the set role/reset role switch) and SELECT a 2-row results table at the end. Same observable-grid pattern as the Phase 1 proof. Still one BEGIN..ROLLBACK; least-privilege restored, no artifacts. https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
…ce recorded Owner ran proofs/phase2_rls_brokenfix.sql (results grid): FIXED viewer UPDATE = 0 rows (refused by least-privilege items_update); BROKEN (added USING(true)) viewer UPDATE = 1 row (breach); ROLLBACK restored least-privilege (no _tmp_permissive_update persisted). Combined with the real-browser (a)/(b)/(c)+negative-control proofs, all Phase 2 gate criteria met with observed evidence. Phase 2 [x]. https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Draft — in progress, gates not yet passed. Review surface for the inventory auth/roles work.
Phase 1 — Schema + RLS + audit trigger (this commit set)
Source-of-truth SQL authored in
inventory/sql/for the owner to run in the Supabase SQL editor (Claude has no privileged DB access;service_roleforbidden).inventory/sql/phase1.sql—profiles,items,field_definitions,change_log;current_user_role()(SECURITY DEFINER,SET search_path='', no params, returns role forauth.uid()only, EXECUTE →authenticatedonly);handle_new_usertrigger (auto-provisionviewer) + one-time admin elevate;log_item_changeaudit trigger (who/what/when, field-level before→after);change_logimmutability (RLS no-UPDATE/DELETE + BEFORE U/D trigger that RAISEs); RLS ENABLE (not FORCE) on every table; least-privilege policies scoped to role and operation; noUSING(true); anon default-denied.inventory/sql/proofs/phase1_proofs.sql— privileged-path immutability proofs + broken-vs-fixed, inside oneBEGIN…ROLLBACK(no artifacts).inventory/sql/proofs/anon_checks.md— anon-returns-[](proof Round 27 — plugin API + governance split + memory-in-repo #3) + logged-in-user-refused-by-RLS (proof deps: Bump FsUnit.xUnit from 7.1.0 to 7.1.1 #1 client path).inventory/sql/RUN.md— run order + owner/Claude responsibility split.Owner-approved decisions: no client INSERT on
change_log; singlecategorycolumn;statustext+CHECK;changed_atrename; GIN index deferred to Phase 5.Phase 1 gate (passes only with observed output)
change_log→ refused (RLS 0-rows);[].PROGRESS.md will be updated (Phase 0b →
[x]with evidence; Phase 1 stays[ ]until proofs land).Phase 2 — Auth + roles
Planned next on this branch after Phase 1's gate passes: email/password sign-in (SIGN-IN form only — no sign-up/create-account/reset),
getUser()-verified trust, role viacurrent_user_role()single-source, sign-out clears session + DOM/memory. Proofs (a) Viewer edit refused by DB, (b) RLS role per user, (c) sign-out clears rendered data.https://claude.ai/code/session_011CojXdZyNjq4449qBWGYJX
Generated by Claude Code