Lane 8 PR 2b: wiki feeds, notes, merge, canon in a block, the 60 s wait (#734) #884

Merged
claude-bot merged 11 commits from build/lane8-2b-feeds into main 2026-09-28 01:55:30 +00:00
Collaborator

Part of #734 (lane 8). Stacked on #878 (PR 3). It merges with lane 8's chain in the train. Server only: no pages consume these endpoints yet.

What it builds

  • Migration 8c3d4e5f6a7b_wiki_events_notes_merge on 8d4e5f6a7b8c. It adds:

    • the wiki_events and lore_personal_notes tables;
    • the NPC tracker fold (D1): a People page for each tracked NPC with no page of that name, carrying its appearances. npcs and npc_appearances stay until PR 9.
    • a decided-in footnote backfill for posts already made canon.

    Up → down → up is clean on Postgres 16.

  • Recent changes (GET /wiki/recent, wiki_feed.py). It's filtered per reader: a deleted page and news of it are GM-only, and an edit that didn't change this reader's view isn't news to them.

  • Health (GET /wiki/health, GM-only), which lists:

    • wanted links and unlinked mentions;
    • near-duplicates;
    • orphans;
    • stale pages.
  • Merge (IS-060, POST /lore/{id}/merge, GM-only).

    • It uses the existing merge-draft flow. On approval, the source's aliases, links, relationships, references, appearances, owners, notes, name targets and Discussion threads all move to the target.
    • The source goes to the trash, with its title kept as an alias.
  • Personal notes (IS-105, D197).

    • Your own notes need the page to be readable to you. A GM may read anyone's (as D97).
    • Notes are deleted with the account, and a no-AI allowlist test covers them.
  • History for anyone who may edit (D69). Versions that look identical to that reader are hidden.

    • A player's restore renders the old version as she sees it and merges it into the live page like her ordinary save. So a block she can't see is never touched or revealed. Only the text moves.
  • Canon in a block (IS-148, D219).

    • Answers narrower than the table are no longer refused. They're wrapped in :::gm, :::party or :::for blocks, matching the post's or thread's audience. A GM can override that audience.
    • A new page's own audience narrows to match, so its title doesn't leak.
    • Every canon write carries its decided-in footnote.
  • The 60 s wait. Consolidation fires as soon as every category has reported. The 60 s call stays as a backstop, with its retry budget raised for slow local models.

Departures (low risk, noted for review)

  1. A player's restore is body-only. Facts, timeline, stats and title stay as live.
  2. A wholly GM-audience post can be made canon as a :::gm block. Nothing in D137/D219 excludes it.
  3. The NPC fold matches exact titles (case-insensitive), not aliases.
  4. The Recent changes cursor is a plain timestamp, with no tiebreaker.

Checks

  • The full backend suite: 5591 passed, 1 failed.
    • The failure asserted pre-D69 behaviour. A secondary owner could already save her character page but not see its History.
    • The test now asserts 200, and test_campaigns.py re-ran green (133 passed).
  • Targeted modules: all of tests/wiki/ (438), tests/discussions/, notifications, account deletion and the lore pipeline, all green.
  • ruff: clean.
  • The render_for body-reader allowlist: green.
  • No /api/bot/* change.
  • Changelog: a changelog.d/ fragment.

🤖 Generated with Claude Code

Part of #734 (lane 8). **Stacked on #878 (PR 3).** It merges with lane 8's chain in the train. Server only: no pages consume these endpoints yet. ## What it builds - **Migration `8c3d4e5f6a7b_wiki_events_notes_merge`** on `8d4e5f6a7b8c`. It adds: - the `wiki_events` and `lore_personal_notes` tables; - **the NPC tracker fold (D1):** a People page for each tracked NPC with no page of that name, carrying its appearances. `npcs` and `npc_appearances` stay until PR 9. - **a `decided-in` footnote backfill** for posts already made canon. Up → down → up is clean on Postgres 16. - **Recent changes** (`GET /wiki/recent`, `wiki_feed.py`). It's filtered per reader: a deleted page and news of it are GM-only, and an edit that didn't change *this reader's* view isn't news to them. - **Health** (`GET /wiki/health`, GM-only), which lists: - wanted links and unlinked mentions; - near-duplicates; - orphans; - stale pages. - **Merge** (IS-060, `POST /lore/{id}/merge`, GM-only). - It uses the existing merge-draft flow. On approval, the source's aliases, links, relationships, references, appearances, owners, notes, name targets and Discussion threads all move to the target. - The source goes to the trash, with its title kept as an alias. - **Personal notes** (IS-105, D197). - Your own notes need the page to be readable to you. A GM may read anyone's (as D97). - Notes are deleted with the account, and a no-AI allowlist test covers them. - **History for anyone who may edit (D69).** Versions that look identical to that reader are hidden. - **A player's restore** renders the old version *as she sees it* and merges it into the live page like her ordinary save. So a block she can't see is never touched or revealed. Only the text moves. - **Canon in a block** (IS-148, D219). - Answers narrower than the table are no longer refused. They're wrapped in `:::gm`, `:::party` or `:::for` blocks, matching the post's or thread's audience. A GM can override that audience. - A new page's own audience narrows to match, so its title doesn't leak. - Every canon write carries its `decided-in` footnote. - **The 60 s wait.** Consolidation fires as soon as every category has reported. The 60 s call stays as a backstop, with its retry budget raised for slow local models. ## Departures (low risk, noted for review) 1. **A player's restore is body-only.** Facts, timeline, stats and title stay as live. 2. **A wholly GM-audience post can be made canon as a `:::gm` block.** Nothing in D137/D219 excludes it. 3. **The NPC fold matches exact titles (case-insensitive), not aliases.** 4. **The Recent changes cursor is a plain timestamp,** with no tiebreaker. ## Checks - **The full backend suite:** 5591 passed, 1 failed. - The failure asserted pre-D69 behaviour. A secondary owner could already *save* her character page but not see its History. - The test now asserts 200, and `test_campaigns.py` re-ran green (133 passed). - **Targeted modules:** all of `tests/wiki/` (438), `tests/discussions/`, notifications, account deletion and the lore pipeline, all green. - **ruff:** clean. - **The `render_for` body-reader allowlist:** green. - **No `/api/bot/*` change.** - **Changelog:** a `changelog.d/` fragment. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds wiki_events and lore_personal_notes tables, folds the NPC tracker into
People pages with their appearances (D1), and backfills a "decided in"
footnote onto every post already made canon (D219). Each lore_match_category
task now fires lore_consolidate_proposals at countdown=0 once every expected
type is in, so a session's wiki proposals land as soon as the pipeline
finishes rather than after the 60 s backstop; the backstop's own retry budget
is raised for a slow local model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
record_event fires from create/update/delete/restore, an update recording
"renamed" when the title changed and "updated" otherwise, each carrying the
LoreEntryVersion snapshot it made so list_recent can tell whether a reader's
own view actually changed before showing the line. Bot proposals are never
recorded: a proposal isn't a page yet. test_body_readers.py's allowlist grows
by one entry (list_recent renders both sides of the comparison).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wiki_feed.list_recent (GET /wiki/recent) filters events per reader — a
deleted page and its news are the GMs' only, and an updated/renamed event
hides when this reader's own rendered view didn't change. wiki_health.report
(GET /wiki/health, GM-only) builds wanted links, unlinked mentions, possible
duplicates, orphans and stale pages. wiki_merge.merge_pages (IS-060) folds a
duplicate page's aliases, links, relationships, references, appearances,
owners, notes and lane 7's threads onto the survivor, reached through the
existing LLM-merge draft (POST /lore/{id}/merge starts it, approve_draft now
distinguishes a live "Merge or Keep both" page from a bot's own duplicate).
wiki_notes gives a player their own private notes on a page (IS-105, D197):
read by the writer and, at /notes/{user_id}, by a GM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
list_lore_versions now opens to anyone who may edit the page, not only the
GM and the primary owner, and drops a version whose rendered text, facts and
timeline equal the one right after it for this reader ("four of the five").
Restoring an earlier version widens the same way: a character's own player
may restore her own page, but her restore runs through wiki_text.merge_edit
against the live body rather than overwriting it outright, so it can only
move her own view back to how it read then and never a block she can't see.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make_canon no longer refuses a narrower answer: it wraps it in the block its
own audience needs (:::gm, :::party, or :::for, naming a player as well as a
character per D219), pre-filled from the post's own audience where it has one
narrower than the thread's, else the thread's, with a GM able to choose a
different one via CanonIn.audience. A brand-new page's own audience narrows
to match, so its title doesn't leak to someone who can't read the one block
on it (D166). Every canon write gets a `[^decided-in-<post id>]: post:<id>`
footnote automatically. Lane 7's tests updated for the new body shape and the
narrower audiences that now succeed instead of refusing with 422.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the one full-suite run: test_secondary_owner_cannot_view_lore_history
asserted the pre-D69 behaviour. A secondary owner of a player_character page
already has the own_pages right to save it (wiki_edit.owns_page counts any
owner role, not only primary) — she could edit the page and not see its own
History, which D69 deliberately closes. Updated to assert 200, not 403.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one conflict was test_notifications' migration-history docstring:
kept main's lane 10 entries, then 8b2c, 8d4e and 8c3d in chain order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot changed target branch from build/lane8-3-parties to main 2026-09-28 00:19:48 +00:00
Merge main into build/lane8-2b-feeds
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m46s
CI / Backend lint (ruff) (pull_request) Successful in 31s
CI / Bot tests and audit (pull_request) Successful in 3m46s
CI / Feedback site tests (pull_request) Successful in 45s
CI / Docker image build (pull_request) Successful in 18s
CI / Bot/backend version sync (pull_request) Successful in 54s
CI / Does this change touch the recording path? (pull_request) Successful in 25s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Backend migration, tests, and audit (pull_request) Successful in 20m20s
CI / Frontend tests, audit, and build (pull_request) Successful in 24m21s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 38m3s
0a2b12a246
Sign in to join this conversation.
No description provided.