[schemas] Persistent wiki pages#365
Conversation
Wiki generators usually rewrite a page wholesale, so any human edit is lost on the next run. This schema makes pages durable database objects with per-section ownership: a machine write to a human-owned section parks a pending draft instead of overwriting it, and a human promotes it via wiki_accept_pending. The guard lives in one RPC so every writer obeys it. UUID id chains throughout (page->section->revision FKs; evidence_thought_ids is UUID[]). Distinct from the wiki-compiler/wiki-synthesis recipes, which compile throwaway artifacts with no persistence or override protection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address Codex review on the wiki-pages schema PR: - wiki_write_section: replace the existence-check-then-INSERT race (the SELECT ... FOR UPDATE locks nothing when the row is absent) with INSERT ... ON CONFLICT (page_id, section_key) DO NOTHING. Two concurrent first writes can no longer raise a unique violation; the loser falls through to the locked existing-row path and still returns created/ updated/pending correctly. - wiki_section_revisions: grant only SELECT, INSERT to service_role so the advertised append-only history cannot be rewritten outside the RPCs. - README: add a "More from Nate" provenance section linking his Substack and site, per the AGENTS.md guardrail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06770eb055
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| -- the advertised immutable history cannot be rewritten outside the RPCs. (Rows | ||
| -- are still removed by the ON DELETE CASCADE when their parent section is | ||
| -- deleted — that is a section deletion, not history mutation.) | ||
| GRANT SELECT, INSERT ON public.wiki_section_revisions TO service_role; |
There was a problem hiding this comment.
Grant revision identity sequence access
This table uses a BIGINT ... IDENTITY column, but the migration only grants table INSERT to service_role. In Postgres/Supabase the caller still needs privileges on the backing identity sequence when these SECURITY INVOKER RPCs insert revision rows, so a fresh install can fail on the first wiki_write_section or wiki_accept_pending revision insert instead of creating/updating sections. Add an explicit grant for public.wiki_section_revisions_id_seq, as the existing BIGSERIAL schemas do.
Useful? React with 👍 / 👎.
| UPDATE public.wiki_sections | ||
| SET pending_generated_md = coalesce(p_body_md, ''), | ||
| pending_generated_at = v_now, | ||
| generation_source = coalesce(p_generation_source, generation_source), | ||
| updated_at = v_now, | ||
| updated_by = v_actor | ||
| WHERE id = v_row.id; |
There was a problem hiding this comment.
Preserve evidence for pending drafts
When a generated write hits a manual or locked section, this branch parks only the new markdown and source; any p_evidence_thought_ids for that generated draft are discarded. If the human later accepts the pending draft, the body changes but evidence_thought_ids still describes the old live text, leaving accepted regenerations with stale or empty supporting thought IDs. Store pending evidence alongside the pending draft and apply it during wiki_accept_pending.
Useful? React with 👍 / 👎.
The regen guard
Wiki generators usually treat a page as disposable: regenerate it and every human edit is gone on the next run. This schema fixes that.
Pages become persistent database objects split into sections, and each section has an owner. A machine-owned section (
origin = 'generated') regenerates freely. A human-owned section (origin = 'manual', or anylockedsection) is protected — a generated write to it does not overwrite the live text. The new draft parks in a pending buffer, and a human promotes it withwiki_accept_pending. Same trust model as everywhere else in Open Brain: machine writes propose, a human accepts.The rule lives in exactly one place — the
wiki_write_sectionRPC — so every writer goes through the same guard instead of each generator re-implementing it.How it differs from wiki-compiler / wiki-synthesis
Those recipes compile a throwaway markdown artifact each run — no stored page, no section ownership, no history, no protection for a human edit, by design. This schema is the durable storage layer they were missing. A compiler can point its writes at
wiki_write_sectionand immediately gain the override guard and revision history. They are complementary, not competing.What this PR adds
A new
schemas/wiki-pages/folder withschema.sql,README.md, andmetadata.json. The schema installs:wiki_pages,wiki_sections,wiki_section_revisions(append-only history)wiki_upsert_page,wiki_write_section(the guard),wiki_accept_pendingID contract
UUID throughout, matching OB1's
public.thoughts.id:wiki_pages.id,wiki_sections.idare UUIDwiki_sections.page_idtowiki_pages.idandwiki_section_revisions.section_idtowiki_sections.idare UUID FKswiki_sections.evidence_thought_idsisUUID[]referencingpublic.thoughts(id)wiki_section_revisions.idis a bigint identity surrogate — an internal revision sequence, never a thought id. No bigint thought ids and no view triggers; targets public.thoughts(id) UUID directly.Safety and sanitization
service_roleonly, with an explicitREVOKE ALL ... FROM PUBLIC, anon, authenticated(defense in depth on projects that blanket-grant new tables).SECURITY INVOKER.CREATE TABLE IF NOT EXISTS,CREATE OR REPLACE FUNCTION,CREATE INDEX IF NOT EXISTS, and a guardedON CONFLICT DO NOTHINGseed. No destructive table-removal statements; never touchespublic.thoughtscolumns.getting-started) and one generated section. No real page content.Verification
Live-validated on a throwaway Postgres 18 cluster:
updated)manual)pending) and leaves the live body untouchedlockedgenerated section also parks (pending)wiki_accept_pendingpromotes the draft, clears the buffer, keeps the section human-owned, and snapshots a revision (accepted); a second call returnsno_pendingschema.sqlinserts zero rows (idempotent)All
ob1-gate-v2rules were simulated locally and pass.