Skip to content

[schemas] Persistent wiki pages#365

Open
alanshurafa wants to merge 2 commits into
NateBJones-Projects:mainfrom
alanshurafa:contrib/alanshurafa/wiki-pages-schema
Open

[schemas] Persistent wiki pages#365
alanshurafa wants to merge 2 commits into
NateBJones-Projects:mainfrom
alanshurafa:contrib/alanshurafa/wiki-pages-schema

Conversation

@alanshurafa

Copy link
Copy Markdown
Collaborator

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 any locked section) 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 with wiki_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_section RPC — 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_section and immediately gain the override guard and revision history. They are complementary, not competing.

What this PR adds

A new schemas/wiki-pages/ folder with schema.sql, README.md, and metadata.json. The schema installs:

  • wiki_pages, wiki_sections, wiki_section_revisions (append-only history)
  • wiki_upsert_page, wiki_write_section (the guard), wiki_accept_pending

ID contract

UUID throughout, matching OB1's public.thoughts.id:

  • wiki_pages.id, wiki_sections.id are UUID
  • wiki_sections.page_id to wiki_pages.id and wiki_section_revisions.section_id to wiki_sections.id are UUID FKs
  • wiki_sections.evidence_thought_ids is UUID[] referencing public.thoughts(id)

wiki_section_revisions.id is 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

  • RLS on all three tables; granted to service_role only, with an explicit REVOKE ALL ... FROM PUBLIC, anon, authenticated (defense in depth on projects that blanket-grant new tables).
  • RPCs are SECURITY INVOKER.
  • Idempotent and additive: CREATE TABLE IF NOT EXISTS, CREATE OR REPLACE FUNCTION, CREATE INDEX IF NOT EXISTS, and a guarded ON CONFLICT DO NOTHING seed. No destructive table-removal statements; never touches public.thoughts columns.
  • Seeded with exactly one fictional example page (getting-started) and one generated section. No real page content.

Verification

Live-validated on a throwaway Postgres 18 cluster:

  • machine to machine write overwrites in place (updated)
  • human edit takes ownership (manual)
  • machine write to a human-owned section parks a draft (pending) and leaves the live body untouched
  • locked generated section also parks (pending)
  • wiki_accept_pending promotes the draft, clears the buffer, keeps the section human-owned, and snapshots a revision (accepted); a second call returns no_pending
  • revision history captures every body change
  • re-applying schema.sql inserts zero rows (idempotent)

All ob1-gate-v2 rules were simulated locally and pass.

alanshurafa and others added 2 commits June 13, 2026 19:25
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>
@github-actions github-actions Bot added the schema Contribution: database extension label Jun 15, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +296 to +302
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

schema Contribution: database extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant