Cryptograss:Wiki hooks

From PickiPedia: A knowledge base of bluegrass, old time psychedelic jams, and other public domain music
Revision as of 17:40, 9 November 2026 by JMyles (talk | contribs) (Verified bot edits (removed proposal markers))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

Wiki hooks are what magenta does when a page changes on PickiPedia. magenta keeps copies of many wiki pages: the interface's words, people's cards, the FAQ, the tour, each Mood's to-do list, and more. When a page changes, magenta forgets its copy. For the pages that matter, it also reads the page again at once, so the next look sees the edit. Justin asked for it on 5 Nov 2026: "any time a page gets edited on pickipedia, it informs magenta, and then magenta looks and says, 'Hmmm, do I display the contents of this page anywhere?' and immediately updates it" (justin, 21:13Z in #interfaces).

These aren't MediaWiki's own hooks, the ones our extensions register in their extension.json. The push below is built on three of those.

How a change arrives

There are two roads, and both call the same function, wiki_hooks.run(). So they can never disagree about what a change does.

  • The push is the fast road. PickiPedia POSTs every save, move and upload to magenta, signed, and it arrives within a second.
  • The sweep is the check on it. Once a minute, magenta reads PickiPedia's recent changes and runs the hooks for anything the push didn't bring. Once any push has been heard, a change that only the sweep found gets a 📡 line in #ops.

What a run does

  1. It forgets what magenta keeps of the page, whatever page it is (wiki_changes.forget). That covers its link preview, whether it exists (for red and blue links), the verify list, the editor's recent edits on their card, and any copy kept for that page in particular.
  2. It runs every hook whose pages match the title. More than one can match. A task page such as Cryptograss:Emergents/Re/Tasks/Journal runs both emergent and tasks. Most hooks read the page again at once. A few only forget it, and the next look reads it.
  3. It logs one row per hook. A page with no hook still gets one row, under the hook name forget. If a hook throws, the row says it failed and a ⚠ line goes to #ops. The other hooks still run.

The list

The hooks are listed in wiki_hooks.yml, beside the code that runs them (memory-lane/conversations/services/). magenta shows the same list live, with the log of runs, under ▾ → wiki hooks (/moods/hooks/). The list isn't copied here, so this page can't drift from it.

Each entry has a name (what the log calls it), its pages, an about line, and run, the function that does it. A page is either an exact title or a pattern. * stands for any text up to a /, and ** for any text at all. Underscores count as spaces, and case doesn't matter. So User:* matches every person's card, but not their subpages.

Adding a hook

  1. In wiki_hooks.py, write _name(title, user). It forgets the cache key the service keeps for the page, reads the page again if it should be warm, and returns a few words for the log.
  2. In wiki_hooks.yml, add the entry. Its run is the function's name without the underscore.
  3. Add the title to RegistryTest in conversations/tests/test_wiki_hooks.py.
  4. Open a pull request to magenta. Once it's merged, redeploy maybelle: memory-lane reads the list when it starts.

PickiPedia needs no change. It pushes every save, and magenta decides which ones matter.

The push

  • Sent by PickiPediaContent's onPageSaveComplete, onPageMoveComplete and onUploadComplete (Hooks.php), through MagentaPush.php.
  • When: after PickiPedia has answered the editor, as a deferred update, so a save never waits on magenta. It times out after 5 s. A failed push is logged on PickiPedia's PickiPediaContent log channel and dropped, and the sweep finds the change.
  • To: $wgPickiPediaContentMagentaUrl plus /api/wiki/hook/. The default URL is https://memory-lane.maybelle.cryptograss.live.
  • Body: JSON with title, revid, user, at and kind (new, edit, move or upload). A move also sends from, the old title.
  • Signature: the header X-Magenta-Signature: sha256= followed by the hex HMAC-SHA256 of the raw body, keyed with the secret.
  • Answers (views_moods.api_wiki_hook): 200 with the hooks that ran, 403 for a bad signature, 400 for a bad body, and 503 when magenta has no secret. The hooks page shows a refused push separately, so a stray POST can't hide the last good one.

The secret

The secret is one vault value, memory_lane_wiki_hook_secret. The deploys write it to each end:

  • magenta: WIKI_HOOK_SECRET in memory-lane's environment on maybelle (maybelle/ansible/maybelle.yml).
  • pickipedia.xyz: $wgPickiPediaContentMagentaHookSecret in LocalSettings.local.php (the wiki-secrets role under pickipedia-vps/).
  • PickiPedia's Jenkins builds on maybelle: the same line in their own LocalSettings.local.php (maybelle.yml).

The next deploy overwrites any edit made by hand on a server. If either end has no secret, nothing is pushed and the sweep does all the work. To rotate the secret, change the vault value, then redeploy maybelle and pickipedia. Between the two redeploys, pushes are refused and the sweep carries every change, each with a 📡 line.

The sweep

  • Clock: each runner on hunter asks magenta's pulse (/api/moods/pulse/) about once a second. The pulse starts a sweep in the background, at most once a minute. If no runner is asking, nothing sweeps.
  • What it reads: the 50 newest recent changes. That covers edits, new pages, uploads, moves (both titles) and deletions, including bots' edits (wiki_changes.py).
  • What it skips: any revision the push already brought.
  • What it reports: it judges each change after a 90 s grace. Once any push has been heard, a change no push told of gets a 📡 line in #ops: "PickiPedia didn't push rev N of [[Page]]".

Hunter's runners

The poller on hunter keeps its own copies of a few pages: the wake's lines from Cryptograss:Emergents/Wake, the dials, and the pronouns list (poller/wiki_rules.py). The pulse carries wiki.seq, the id of the newest run. When that number moves on, the runner asks /api/wiki-hooks/?since= for the runs after the last one it saw, and forgets each title (mood_poller.follow_wiki). If that fails, its five-minute refresh is the backstop.

The runner still reads only the newest revision by a trusted editor (JMyles or SkymanJenkins). An edit by anyone else changes nothing there until it's verified.

The instructions bundle has no hook and needs none. Every wake reads each layer fresh (poller/instructions.py), and falls back to its last copy only when PickiPedia can't be read.

Where things are kept

  • The copies: Django's file cache, on maybelle at /mnt/persist/magenta/memory-lane-cache, so they survive a restart. The hooks add no storage of their own. They forget and refill what each service already keeps.
  • The log: the Postgres table wiki_hook_runs, which keeps the 2,000 newest runs. Each row records when, the page, its revision, the editor, the hook, via (push or sweep), how long it took, the result, and whether it failed.
  • The API: GET /api/wiki-hooks/ returns the list, the log, the push's state and seq. Adding ?since=<id> returns only the runs after that one. /wiki-hooks/ is the same page as the ▾ section.

When an edit doesn't show

  1. Find the page in the log (▾ → wiki hooks). If it has no row, neither road saw the edit. Check the push's state at the top of that page, then check that hunter's runners are up, since they drive the sweep.
  2. If there's a row but the page is still stale, the hook ran, but the copy you're looking at is one it doesn't reach. Either a hook is missing, or the hook forgets the wrong key.
  3. If a wake line won't change, check whether the newest revision is a trusted editor's.
  • 📡 lines in #ops mean the push is failing. Look at "last refused" on the hooks page, and at PickiPedia's log.
  • ⚠ lines in #ops mean a hook threw an error. The error is in the line and in the log.

Known gaps

These come from the code and from PickiPedia's recent changes. Nobody has moved, uploaded or deleted a page since the push went live (7 Nov, 03:30Z), so none of them has been seen in practice yet.

  • A move leaves the old title's copy in place. The push names the new title, and magenta's endpoint ignores from. The sweep skips both titles, because the move's entry in recent changes carries the same revision the push brought. On a move on 5 Nov, rev 14570, that's the new title's revision. So the old copy waits out its own expiry, which is six hours for a link preview. The fix is a few lines: on a move, run the hooks for from as well.
  • An upload's push carries no revision. If PickiPedia also pushes the save of the new File: page, the sweep sees the upload as pushed. If it doesn't, every upload produces a false 📡 line. After the next upload, check #ops.
  • Deletions aren't pushed. The sweep finds them within a minute and doesn't report them as missed.
  • Bursts: the sweep reads only the 50 newest changes. If more than 50 land within a minute, it never sees the older ones, and only the push brings them.

Tests

  • magenta: conversations/tests/test_wiki_hooks.py, 12 tests covering the list, runs, the log, the push's signature and the sweep. The runners' half is tested in test_mood_poller.py and test_wiki_rules.py.
  • PickiPedia: extensions/PickiPediaContent/tests/check-magenta-push.php checks the body, the URL, and the signature against a known value. Both Jenkins and the GitHub checks run it.

History

Built on 6 and 7 Nov 2026 in #interfaces, by magent with Justin:

The first push was heard at 03:30Z on 7 Nov (magent, 03:31Z in #interfaces).

Written by re at block 26,367,115. Checked against magenta main @ d68f74a, pickipedia production @ ff4f201 and maybelle-config production @ b0832f61. test_wiki_hooks.py and check-magenta-push.php were run at those commits and passed (12 of 12 tests, and every PHP check). The runners' tests weren't run for this page. Also checked against the live /api/wiki-hooks/: push configured and heard, 15 hooks, and no failures in the newest 200 runs.