From 8b2c39ae0176bed05cc3cde94cdeae06c29f8266 Mon Sep 17 00:00:00 2001
From: Jack McCarthy <37917934+Deicyde@users.noreply.github.com>
Date: Tue, 13 Oct 2026 16:52:40 -0400
Subject: [PATCH 1/4] wiki: /about describes the Human-at-boundaries moderation
policy
The About page said human edits are patrolled by moderators and that AI
proposals to change a human-authored annotation are queued for human
approval. Under the policy ratified 2026-08-06 the nightly resolver decides
proposals (including ones on human annotations, which are relabeled
ai-moderated when approved), and patrol is a list, not a pre-screen. The new
copy says what happens, drops the 'growing sample' claim, and links sign-in
without naming providers (only GitHub is configured in production).
Render-cache key page:about:v1 -> v2.
---
wiki/src/home.ts | 31 +++++++++++++++++++++++--------
wiki/src/index.ts | 2 +-
wiki/test/about.test.ts | 36 +++++++++++++++++++++++++++++-------
3 files changed, 53 insertions(+), 16 deletions(-)
diff --git a/wiki/src/home.ts b/wiki/src/home.ts
index 56c6fc9ed..80d0d0283 100644
--- a/wiki/src/home.ts
+++ b/wiki/src/home.ts
@@ -3,7 +3,7 @@
// wiki/public/ — articles created via PUT /api/article/:slug now appear
// without a static rebuild. index.ts serves brainLanding() cached in
// RENDER_CACHE ('page:home:v10', TTL 300s), homePage() ('page:articles:v2',
-// TTL 300s), aboutPage() ('page:about:v1', TTL 300s), and sitemapXml()
+// TTL 300s), aboutPage() ('page:about:v2', TTL 300s), and sitemapXml()
// ('page:sitemap:v4', TTL 3600s).
//
// Visual language: warm academic-minimalist — paper background, serif display
@@ -565,7 +565,7 @@ export interface AboutStats {
nNotFormalized: number;
}
-// The About & method page, served dynamically from D1 (KV 'page:about:v1',
+// The About & method page, served dynamically from D1 (KV 'page:about:v2',
// TTL 300s). Replaces the static site/out/about.html asset (retired from
// build_static_pages.py) whose baked stats went stale the day it was built.
// Visual shell reproduced from that page: self-contained inline CSS, warm
@@ -720,14 +720,29 @@ footer p { margin:4px 0; }
documents where every external link comes from, with its Wikidata property and license.
Limitations & how to contribute
- Annotations are best-effort and cover a growing sample of WikiProject Mathematics,
+
Annotations are best-effort and cover a sample of WikiProject Mathematics,
not the whole corpus. A match reflects a judgment that a Mathlib declaration formalizes
a statement; it can be incomplete or wrong, and Mathlib itself evolves, so coverage is
- a snapshot rather than a guarantee. Corrections happen right on the site: sign in
- (GitHub or Google) to edit annotations in-context on any article, or — without an account — report
- a problem from any annotation's tooltip (the ⚑ link). Human edits are patrolled by
- moderators, and AI proposals to change a human-authored annotation are queued for
- human approval before anything lands.
+ a snapshot rather than a guarantee. Corrections happen right on the site:
+ sign in to edit annotations in-context on any article, or — without
+ an account — report a problem from any annotation's tooltip (the ⚑ link).
+
+ How edits are moderated
+ Edits by people take effect immediately and are marked as human-authored. They are
+ not screened first: each one is listed for patrollers to review, and the
+ Stats page shows how many have been patrolled. Every change, by a
+ person or by the AI pipeline, is kept in the article's history and in
+ Recent changes , and can be reverted.
+ The AI pipeline cannot silently overwrite a human-authored annotation: a pipeline save
+ that drops or alters one is refused, and the pipeline can file a proposal instead. Under
+ WikiLean's human-at-boundaries policy (adopted August 2026), those proposals are decided
+ by software rather than held for a person. Each night a deterministic resolver, which
+ uses no language model, approves or rejects the proposals that match its fixed rules;
+ the rest stay pending until a patroller decides them. When an AI change to a
+ human-authored annotation is approved, the annotation is relabeled AI-moderated, so
+ AI-changed text is never presented as human work. People remain the gate where WikiLean
+ contributes to projects that take only human-reviewed changes, such as Mathlib and
+ Wikidata.
Article text from Wikipedia ,
diff --git a/wiki/src/index.ts b/wiki/src/index.ts
index 022f54ac9..4459ccb5f 100644
--- a/wiki/src/index.ts
+++ b/wiki/src/index.ts
@@ -613,7 +613,7 @@ app.get("/api/articles", async (c) => {
// static wiki/public/about.html asset, which used to shadow this path with
// stats baked at build time). One aggregate over the D-C5 count columns.
app.get("/about", async (c) => {
- const cacheKey = "page:about:v1"; // v1: dynamic port of the static About page
+ const cacheKey = "page:about:v2"; // v2: moderation copy matches Human-at-boundaries
const cached = await c.env.RENDER_CACHE.get(cacheKey);
if (cached) return c.html(cached);
const db = drizzle(c.env.DB);
diff --git a/wiki/test/about.test.ts b/wiki/test/about.test.ts
index 55212c0d3..558411dc5 100644
--- a/wiki/test/about.test.ts
+++ b/wiki/test/about.test.ts
@@ -2,8 +2,9 @@
// replaced the static wiki/public/about.html asset. Pins: 200 with live D1
// counts (COUNT(*) + summed count columns, SUM skipping pre-backfill NULL
// rows), the unified nav + Concepts + MCP links, the rewritten contribution
-// copy + licensing notices, and the KV cache (page:about:v1, TTL 300,
-// TTL-only invalidation). The harness ASSETS binding always 404s, so a 200
+// copy, the moderation copy (Human-at-boundaries, ROADMAP 2026-08-06),
+// licensing notices, and the KV cache (page:about:v2, TTL 300, TTL-only
+// invalidation). The harness ASSETS binding always 404s, so a 200
// here also proves the route no longer depends on the retired asset.
import { describe, it, expect } from "vitest";
@@ -43,15 +44,35 @@ describe("GET /about", () => {
// The stale "corrections via the source repository" limitation copy is
// replaced by the live contribution loop.
expect(html).not.toContain("Corrections are welcome via the source repository");
- expect(html).toContain("sign in");
- expect(html).toContain("GitHub or Google");
+ expect(html).toContain('sign in ');
+ // Providers are configured per deployment (auth.ts availableProviders);
+ // the static copy must not promise one that may be absent.
+ expect(html).not.toContain("GitHub or Google");
expect(html).toContain("⚑");
- expect(html).toContain("human approval");
// Licensing: article text CC BY-SA, annotations CC0.
expect(html).toContain("https://creativecommons.org/licenses/by-sa/4.0/");
expect(html).toContain("https://creativecommons.org/publicdomain/zero/1.0/");
});
+ it("describes the Human-at-boundaries moderation policy, not a human gate on proposals", async () => {
+ const h = setup();
+ const html = await (await get(h.env, "/about")).text();
+ // The pre-2026-08-06 promises are gone: proposals are no longer held for
+ // human approval, and human edits are not claimed to be patrolled.
+ expect(html).not.toContain("queued for human approval");
+ expect(html).not.toContain("patrolled by moderators");
+ expect(html).not.toContain("growing sample");
+ // What actually happens.
+ expect(html).toContain("
How edits are moderated ");
+ expect(html).toContain("Edits by people take effect immediately");
+ expect(html).toContain('Stats page shows how many have been patrolled');
+ expect(html).toContain("a pipeline save\n that drops or alters one is refused");
+ expect(html).toContain("decided\n by software rather than held for a person");
+ expect(html).toContain("deterministic resolver, which\n uses no language model");
+ expect(html).toContain("relabeled AI-moderated");
+ expect(html).toContain("such as Mathlib and\n Wikidata");
+ });
+
it("handles an empty corpus without dividing by zero", async () => {
const h = setup();
h.db.prepare("DELETE FROM revisions").run(); // FK before articles
@@ -62,10 +83,11 @@ describe("GET /about", () => {
expect(html).not.toContain("NaN");
});
- it("is KV-cached under page:about:v1 for 300s (TTL-only invalidation)", async () => {
+ it("is KV-cached under page:about:v2 for 300s (TTL-only invalidation)", async () => {
const h = setup();
const first = await (await get(h.env, "/about")).text();
- expect(h.renderCache.store.has("page:about:v1")).toBe(true);
+ expect(h.renderCache.store.has("page:about:v2")).toBe(true);
+ expect(h.renderCache.store.has("page:about:v1")).toBe(false);
insertArticle(h.db, "After_Cache");
const second = await (await get(h.env, "/about")).text();
expect(second).toBe(first);
From f2b8d7b45730b5fba61ed1b9c954cb90bce9bd4c Mon Sep 17 00:00:00 2001
From: Jack McCarthy <37917934+Deicyde@users.noreply.github.com>
Date: Tue, 13 Oct 2026 17:20:42 -0400
Subject: [PATCH 2/4] wiki: /about moderation copy review fixes; render-key
list page:about:v2
- A patroller's approval stamps the result human (index.ts approve path), so
the copy now states both directions instead of claiming AI-changed text is
never shown as human work, and uses the reader-visible 'human-curated'.
- Proposals 'do not wait for a person by default'; unmatched ones stay
pending for a patroller or lapse when their target is removed or already
matches.
- History claim scoped to annotation changes; revert is a patroller action.
- The human gate is described as WikiLean's outside contributions (Mathlib
PRs, Wikidata statements), not a property of those projects.
- CLAUDE.md/AGENTS.md render-key list: page:about:v1 -> v2.
- Tests assert on whitespace-normalized text.
---
AGENTS.md | 2 +-
CLAUDE.md | 2 +-
wiki/src/home.ts | 30 ++++++++++++++++--------------
wiki/test/about.test.ts | 27 +++++++++++++++------------
4 files changed, 33 insertions(+), 28 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 7ee2680e6..b1e3859c0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -93,7 +93,7 @@ brain/acquire-wikidata-entities.sh /absolute/private/request-plan.json \
them `ai-moderated` — **AI-changed bytes are never left marked `human`, and bots never
mint `human`** (endorse stays session-only; 403). Humans gate only cross-site pushes.
- **Render-cache keys are manually versioned + load-bearing** (currently `render:v17:`,
- `page:home:v10`, `page:articles:v2`, `page:articles-index:v1`, `page:about:v1`,
+ `page:home:v10`, `page:articles:v2`, `page:articles-index:v1`, `page:about:v2`,
`page:sitemap:v4`, `page:stats:v5`, `page:wikifunctions:v3`,
`page:wikifunctions-verify:v3` — all in `index.ts`) — bump the
prefix whenever output bytes change, or readers get stale HTML for up to 30 days. Asset
diff --git a/CLAUDE.md b/CLAUDE.md
index 96dfd20a5..9e8cffcb0 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -93,7 +93,7 @@ brain/acquire-wikidata-entities.sh /absolute/private/request-plan.json \
them `ai-moderated` — **AI-changed bytes are never left marked `human`, and bots never
mint `human`** (endorse stays session-only; 403). Humans gate only cross-site pushes.
- **Render-cache keys are manually versioned + load-bearing** (currently `render:v17:`,
- `page:home:v10`, `page:articles:v2`, `page:articles-index:v1`, `page:about:v1`,
+ `page:home:v10`, `page:articles:v2`, `page:articles-index:v1`, `page:about:v2`,
`page:sitemap:v4`, `page:stats:v5`, `page:wikifunctions:v3`,
`page:wikifunctions-verify:v3` — all in `index.ts`) — bump the
prefix whenever output bytes change, or readers get stale HTML for up to 30 days. Asset
diff --git a/wiki/src/home.ts b/wiki/src/home.ts
index 80d0d0283..4d4df0eac 100644
--- a/wiki/src/home.ts
+++ b/wiki/src/home.ts
@@ -728,21 +728,23 @@ footer p { margin:4px 0; }
an account — report a problem from any annotation's tooltip (the ⚑ link).
How edits are moderated
- Edits by people take effect immediately and are marked as human-authored. They are
- not screened first: each one is listed for patrollers to review, and the
- Stats page shows how many have been patrolled. Every change, by a
- person or by the AI pipeline, is kept in the article's history and in
- Recent changes , and can be reverted.
- The AI pipeline cannot silently overwrite a human-authored annotation: a pipeline save
+
Annotation edits by people take effect immediately and are shown as human-curated.
+ They are not screened first: each one appears in Recent
+ changes marked as awaiting patrol, and the Stats page shows how
+ many have been patrolled. Every change to an article’s annotations, by a person or
+ by the AI pipeline, is recorded in the article’s history, and patrollers can
+ restore an article to any earlier revision.
+ The AI pipeline cannot silently overwrite a human-curated annotation: a pipeline save
that drops or alters one is refused, and the pipeline can file a proposal instead. Under
- WikiLean's human-at-boundaries policy (adopted August 2026), those proposals are decided
- by software rather than held for a person. Each night a deterministic resolver, which
- uses no language model, approves or rejects the proposals that match its fixed rules;
- the rest stay pending until a patroller decides them. When an AI change to a
- human-authored annotation is approved, the annotation is relabeled AI-moderated, so
- AI-changed text is never presented as human work. People remain the gate where WikiLean
- contributes to projects that take only human-reviewed changes, such as Mathlib and
- Wikidata.
+ WikiLean’s human-at-boundaries policy (adopted August 2026), those proposals do
+ not wait for a person by default. Each night a deterministic resolver, which uses no
+ language model, approves or rejects the proposals that match its fixed rules; the rest
+ stay pending until a patroller decides them, or lapse when their target annotation is
+ removed or already matches them. When the pipeline approves a change to a human-curated
+ annotation, the annotation stops being shown as human-curated; when a patroller approves
+ a proposal, the result is shown as human-curated. People remain the gate wherever
+ WikiLean contributes to outside projects, such as pull requests to Mathlib and
+ statements added to Wikidata.
Article text from Wikipedia ,
diff --git a/wiki/test/about.test.ts b/wiki/test/about.test.ts
index 558411dc5..921abad94 100644
--- a/wiki/test/about.test.ts
+++ b/wiki/test/about.test.ts
@@ -57,20 +57,23 @@ describe("GET /about", () => {
it("describes the Human-at-boundaries moderation policy, not a human gate on proposals", async () => {
const h = setup();
const html = await (await get(h.env, "/about")).text();
- // The pre-2026-08-06 promises are gone: proposals are no longer held for
- // human approval, and human edits are not claimed to be patrolled.
- expect(html).not.toContain("queued for human approval");
- expect(html).not.toContain("patrolled by moderators");
- expect(html).not.toContain("growing sample");
+ const text = html.replace(/\s+/g, " ");
+ // Copy that no longer matches the code or the ratified policy is gone.
+ expect(text).not.toContain("queued for human approval");
+ expect(text).not.toContain("patrolled by moderators");
// What actually happens.
expect(html).toContain("
How edits are moderated ");
- expect(html).toContain("Edits by people take effect immediately");
- expect(html).toContain('Stats page shows how many have been patrolled');
- expect(html).toContain("a pipeline save\n that drops or alters one is refused");
- expect(html).toContain("decided\n by software rather than held for a person");
- expect(html).toContain("deterministic resolver, which\n uses no language model");
- expect(html).toContain("relabeled AI-moderated");
- expect(html).toContain("such as Mathlib and\n Wikidata");
+ expect(text).toContain("Annotation edits by people take effect immediately and are shown as human-curated");
+ expect(text).toContain('Stats page shows how many have been patrolled');
+ expect(text).toContain("a pipeline save that drops or alters one is refused");
+ expect(text).toContain("those proposals do not wait for a person by default");
+ expect(text).toContain("deterministic resolver, which uses no language model");
+ expect(text).toContain("the rest stay pending until a patroller decides them");
+ // index.ts approve path: a bot approval re-labels human → ai-moderated,
+ // a patroller approval stamps human. The copy states both directions.
+ expect(text).toContain("When the pipeline approves a change to a human-curated annotation, the annotation stops being shown as human-curated");
+ expect(text).toContain("when a patroller approves a proposal, the result is shown as human-curated");
+ expect(text).toContain("pull requests to Mathlib and statements added to Wikidata");
});
it("handles an empty corpus without dividing by zero", async () => {
From 8d572f49db70ce3f6335dcb6cc2a99474173333a Mon Sep 17 00:00:00 2001
From: Jack McCarthy <37917934+Deicyde@users.noreply.github.com>
Date: Tue, 13 Oct 2026 17:43:42 -0400
Subject: [PATCH 3/4] wiki: /about moderation copy, second review pass
- 'no longer need a person's approval' (unmatched proposals still wait).
- The Mathlib gate is reviewer approval before merge; the bot opens PRs on
its own. Wikidata statements only for human-reviewed mappings.
- Patrollers restore annotations (the Wikipedia pin is not rolled back).
- Recent changes link filters to human edits; lapse happens on the next
pipeline save of the article; patroller approval of a change stamps
human-curated.
---
wiki/src/home.ts | 29 ++++++++++++++++-------------
wiki/test/about.test.ts | 15 +++++++++++----
2 files changed, 27 insertions(+), 17 deletions(-)
diff --git a/wiki/src/home.ts b/wiki/src/home.ts
index 4d4df0eac..96f322e6c 100644
--- a/wiki/src/home.ts
+++ b/wiki/src/home.ts
@@ -729,22 +729,25 @@ footer p { margin:4px 0; }
How edits are moderated
Annotation edits by people take effect immediately and are shown as human-curated.
- They are not screened first: each one appears in Recent
- changes marked as awaiting patrol, and the Stats page shows how
- many have been patrolled. Every change to an article’s annotations, by a person or
- by the AI pipeline, is recorded in the article’s history, and patrollers can
- restore an article to any earlier revision.
+ They are not screened first: each one is listed, newest first, in
+ Recent changes with an awaiting-patrol marker,
+ and the Stats page shows how many have been patrolled. Every change
+ to an article’s annotations, by a person or by the AI pipeline, is recorded in the
+ article’s history, and patrollers can restore an article’s annotations to
+ any earlier version in that history.
The AI pipeline cannot silently overwrite a human-curated annotation: a pipeline save
that drops or alters one is refused, and the pipeline can file a proposal instead. Under
- WikiLean’s human-at-boundaries policy (adopted August 2026), those proposals do
- not wait for a person by default. Each night a deterministic resolver, which uses no
+ WikiLean’s human-at-boundaries policy (adopted August 2026), those proposals no
+ longer need a person’s approval. Each night a deterministic resolver, which uses no
language model, approves or rejects the proposals that match its fixed rules; the rest
- stay pending until a patroller decides them, or lapse when their target annotation is
- removed or already matches them. When the pipeline approves a change to a human-curated
- annotation, the annotation stops being shown as human-curated; when a patroller approves
- a proposal, the result is shown as human-curated. People remain the gate wherever
- WikiLean contributes to outside projects, such as pull requests to Mathlib and
- statements added to Wikidata.
+ stay pending until a patroller or a later nightly run decides them, or lapse when the
+ pipeline next saves the article and finds their target annotation removed or already
+ matching them. When the pipeline approves a change to a human-curated annotation, the
+ annotation stops being shown as human-curated; when a patroller approves a change, the
+ result is shown as human-curated. People remain the gate wherever WikiLean contributes
+ to outside projects: the tagging bot opens pull requests to Mathlib on its own, but they
+ merge only after Mathlib reviewers approve them, and statements are added to Wikidata
+ only for mappings people have reviewed.
Article text from Wikipedia ,
diff --git a/wiki/test/about.test.ts b/wiki/test/about.test.ts
index 921abad94..e9e22c3c7 100644
--- a/wiki/test/about.test.ts
+++ b/wiki/test/about.test.ts
@@ -61,19 +61,26 @@ describe("GET /about", () => {
// Copy that no longer matches the code or the ratified policy is gone.
expect(text).not.toContain("queued for human approval");
expect(text).not.toContain("patrolled by moderators");
+ expect(text).not.toContain("growing sample");
// What actually happens.
expect(html).toContain("
How edits are moderated ");
expect(text).toContain("Annotation edits by people take effect immediately and are shown as human-curated");
+ expect(text).toContain('in Recent changes with an awaiting-patrol marker');
expect(text).toContain('Stats page shows how many have been patrolled');
+ // Revert restores annotations only; the Wikipedia pin is not rolled back.
+ expect(text).toContain("restore an article’s annotations to any earlier version in that history");
expect(text).toContain("a pipeline save that drops or alters one is refused");
- expect(text).toContain("those proposals do not wait for a person by default");
+ expect(text).toContain("those proposals no longer need a person’s approval");
expect(text).toContain("deterministic resolver, which uses no language model");
- expect(text).toContain("the rest stay pending until a patroller decides them");
+ expect(text).toContain("the rest stay pending until a patroller or a later nightly run decides them");
+ expect(text).toContain("lapse when the pipeline next saves the article");
// index.ts approve path: a bot approval re-labels human → ai-moderated,
// a patroller approval stamps human. The copy states both directions.
expect(text).toContain("When the pipeline approves a change to a human-curated annotation, the annotation stops being shown as human-curated");
- expect(text).toContain("when a patroller approves a proposal, the result is shown as human-curated");
- expect(text).toContain("pull requests to Mathlib and statements added to Wikidata");
+ expect(text).toContain("when a patroller approves a change, the result is shown as human-curated");
+ // The bot opens Mathlib PRs unattended; the human gate is Mathlib review.
+ expect(text).toContain("the tagging bot opens pull requests to Mathlib on its own, but they merge only after Mathlib reviewers approve them");
+ expect(text).toContain("statements are added to Wikidata only for mappings people have reviewed");
});
it("handles an empty corpus without dividing by zero", async () => {
From ded364f19fd31a66a02993efab1f8c2b34c6c89a Mon Sep 17 00:00:00 2001
From: Jack McCarthy <37917934+Deicyde@users.noreply.github.com>
Date: Wed, 14 Oct 2026 12:56:02 -0400
Subject: [PATCH 4/4] wiki: /proposals and /about say what the resolver and
relabel do
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- /proposals intro no longer says the AI never applies proposals: it names
the nightly resolver's rules (reject no-change proposals, approve verified
Mathlib renames, approve 'partial' for proof_wanted stubs) and says the
rest wait for a patroller. The route is not KV-cached.
- /about: a pipeline approval on a human-curated annotation now says what
the relabel means: the annotation is shown as 'AI-generated · not yet
reviewed' and loses the human-curated protection, so later pipeline saves
can change it directly.
- /about and /articles name the existing Wikidata property, Mathlib
Declaration ID (P14534), instead of the proposal-era 'formalized as
(Lean/Mathlib)'. /about stays on page:about:v2 (unreleased); /articles
page:articles:v2 -> v3, and the CLAUDE.md/AGENTS.md key list with it.
Co-Authored-By: Claude Opus 5.5
---
AGENTS.md | 2 +-
CLAUDE.md | 2 +-
wiki/src/home.ts | 20 +++++++++++---------
wiki/src/index.ts | 2 +-
wiki/src/pages.ts | 5 ++++-
wiki/test/about.test.ts | 24 +++++++++++++++++-------
wiki/test/home.test.ts | 7 +++++++
wiki/test/proposals.test.ts | 4 ++++
8 files changed, 46 insertions(+), 20 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index b1e3859c0..69530a27c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -93,7 +93,7 @@ brain/acquire-wikidata-entities.sh /absolute/private/request-plan.json \
them `ai-moderated` — **AI-changed bytes are never left marked `human`, and bots never
mint `human`** (endorse stays session-only; 403). Humans gate only cross-site pushes.
- **Render-cache keys are manually versioned + load-bearing** (currently `render:v17:`,
- `page:home:v10`, `page:articles:v2`, `page:articles-index:v1`, `page:about:v2`,
+ `page:home:v10`, `page:articles:v3`, `page:articles-index:v1`, `page:about:v2`,
`page:sitemap:v4`, `page:stats:v5`, `page:wikifunctions:v3`,
`page:wikifunctions-verify:v3` — all in `index.ts`) — bump the
prefix whenever output bytes change, or readers get stale HTML for up to 30 days. Asset
diff --git a/CLAUDE.md b/CLAUDE.md
index 9e8cffcb0..47a385348 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -93,7 +93,7 @@ brain/acquire-wikidata-entities.sh /absolute/private/request-plan.json \
them `ai-moderated` — **AI-changed bytes are never left marked `human`, and bots never
mint `human`** (endorse stays session-only; 403). Humans gate only cross-site pushes.
- **Render-cache keys are manually versioned + load-bearing** (currently `render:v17:`,
- `page:home:v10`, `page:articles:v2`, `page:articles-index:v1`, `page:about:v2`,
+ `page:home:v10`, `page:articles:v3`, `page:articles-index:v1`, `page:about:v2`,
`page:sitemap:v4`, `page:stats:v5`, `page:wikifunctions:v3`,
`page:wikifunctions-verify:v3` — all in `index.ts`) — bump the
prefix whenever output bytes change, or readers get stale HTML for up to 30 days. Asset
diff --git a/wiki/src/home.ts b/wiki/src/home.ts
index 96f322e6c..60ead64e3 100644
--- a/wiki/src/home.ts
+++ b/wiki/src/home.ts
@@ -2,7 +2,7 @@
// Replaces the static site/out/ pages that build-public.ts used to copy into
// wiki/public/ — articles created via PUT /api/article/:slug now appear
// without a static rebuild. index.ts serves brainLanding() cached in
-// RENDER_CACHE ('page:home:v10', TTL 300s), homePage() ('page:articles:v2',
+// RENDER_CACHE ('page:home:v10', TTL 300s), homePage() ('page:articles:v3',
// TTL 300s), aboutPage() ('page:about:v2', TTL 300s), and sitemapXml()
// ('page:sitemap:v4', TTL 3600s).
//
@@ -471,8 +471,8 @@ footer a:hover { text-decoration:underline; }
cross-database pages, and literature — with machine-checkable
provenance on every edge.
Wikidata concept links — every formalized concept keyed
- to its Wikidata item, as an open RDF dataset (the basis for a proposed
- “formalized as (Lean/Mathlib)” Wikidata property).
+ to its Wikidata item, as an open RDF dataset (Wikidata’s property for these
+ links is “Mathlib Declaration ID” , P14534).
Wikibrain MCP — a remote MCP server any AI agent can
connect to mid-proof: jump between informal mathematics and formal
Mathlib declarations (full REST reference at /brain/api ).
@@ -695,10 +695,10 @@ footer p { margin:4px 0; }
Wikidata concept links
Every concept matched to a formalized declaration is also keyed to its
Wikidata item and published as an open
- RDF dataset . This is the basis for a proposed Wikidata
- property, “formalized as (Lean/Mathlib)” — the long-term goal is
- for these links to live in Wikidata itself, maintainable by the community and queryable
- via SPARQL.
+ RDF dataset . Wikidata has a property for these links,
+ “Mathlib Declaration
+ ID” (P14534) ; the long-term goal is for them to live in Wikidata
+ itself, maintainable by the community and queryable via SPARQL.
For AI agents: the Wikibrain MCP
For AI agents, the same graph is queryable as the
@@ -743,8 +743,10 @@ footer p { margin:4px 0; }
stay pending until a patroller or a later nightly run decides them, or lapse when the
pipeline next saves the article and finds their target annotation removed or already
matching them. When the pipeline approves a change to a human-curated annotation, the
- annotation stops being shown as human-curated; when a patroller approves a change, the
- result is shown as human-curated. People remain the gate wherever WikiLean contributes
+ annotation is relabeled: it is then shown as “AI-generated · not yet
+ reviewed” and loses the human-curated protection, so later pipeline saves can
+ change it directly. When a patroller approves a change, the result is shown as
+ human-curated. People remain the gate wherever WikiLean contributes
to outside projects: the tagging bot opens pull requests to Mathlib on its own, but they
merge only after Mathlib reviewers approve them, and statements are added to Wikidata
only for mappings people have reviewed.
diff --git a/wiki/src/index.ts b/wiki/src/index.ts
index 4459ccb5f..396530064 100644
--- a/wiki/src/index.ts
+++ b/wiki/src/index.ts
@@ -567,7 +567,7 @@ app.get("/", async (c) => {
});
app.get("/articles", async (c) => {
- const cacheKey = "page:articles:v2"; // v2: Recent changes + Stats links in the nav
+ const cacheKey = "page:articles:v3"; // v3: Wikidata property named (P14534)
const cached = await c.env.RENDER_CACHE.get(cacheKey);
if (cached) return c.html(cached);
const html = homePage(await homeRows(c));
diff --git a/wiki/src/pages.ts b/wiki/src/pages.ts
index 1adb918e1..007928a9c 100644
--- a/wiki/src/pages.ts
+++ b/wiki/src/pages.ts
@@ -1091,7 +1091,10 @@ export function proposalsQueuePage(
const body =
PROPOSALS_CSS +
`AI proposals ` +
- `The AI may propose updates to human-owned annotations but never applies them ` +
+ `
The AI pipeline proposes updates to human-owned annotations instead of overwriting them. ` +
+ `Each night a deterministic resolver decides the proposals that match its fixed rules: it rejects ones that ` +
+ `change nothing, approves verified Mathlib declaration renames, and approves marking a proof_wanted ` +
+ `stub as partial. The rest wait here for a patroller ` +
`(how moderation works ). Approving applies the change and keeps the annotation yours; ` +
`rejecting remembers the delta so it is not re-proposed.
` +
truncNote +
diff --git a/wiki/test/about.test.ts b/wiki/test/about.test.ts
index e9e22c3c7..2cd5b7d77 100644
--- a/wiki/test/about.test.ts
+++ b/wiki/test/about.test.ts
@@ -2,10 +2,11 @@
// replaced the static wiki/public/about.html asset. Pins: 200 with live D1
// counts (COUNT(*) + summed count columns, SUM skipping pre-backfill NULL
// rows), the unified nav + Concepts + MCP links, the rewritten contribution
-// copy, the moderation copy (Human-at-boundaries, ROADMAP 2026-08-06),
-// licensing notices, and the KV cache (page:about:v2, TTL 300, TTL-only
-// invalidation). The harness ASSETS binding always 404s, so a 200
-// here also proves the route no longer depends on the retired asset.
+// copy, the moderation copy (Human-at-boundaries, ROADMAP 2026-08-06), the
+// Wikidata property name (P14534), licensing notices, and the KV cache
+// (page:about:v2, TTL 300, TTL-only invalidation). The harness ASSETS binding
+// always 404s, so a 200 here also proves the route no longer depends on the
+// retired asset.
import { describe, it, expect } from "vitest";
import { setup, get, insertArticle, blockNetwork, type Harness } from "./helpers/harness.js";
@@ -49,6 +50,11 @@ describe("GET /about", () => {
// the static copy must not promise one that may be absent.
expect(html).not.toContain("GitHub or Google");
expect(html).toContain("⚑");
+ // The Wikidata property exists (P14534); the old proposal-era name is gone.
+ expect(html).toContain('href="https://www.wikidata.org/wiki/Property:P14534"');
+ expect(html.replace(/\s+/g, " ")).toContain("“Mathlib Declaration ID” (P14534)");
+ expect(html).not.toContain("formalized as (Lean/Mathlib)");
+ expect(html).not.toContain("basis for a proposed");
// Licensing: article text CC BY-SA, annotations CC0.
expect(html).toContain("https://creativecommons.org/licenses/by-sa/4.0/");
expect(html).toContain("https://creativecommons.org/publicdomain/zero/1.0/");
@@ -75,9 +81,13 @@ describe("GET /about", () => {
expect(text).toContain("the rest stay pending until a patroller or a later nightly run decides them");
expect(text).toContain("lapse when the pipeline next saves the article");
// index.ts approve path: a bot approval re-labels human → ai-moderated,
- // a patroller approval stamps human. The copy states both directions.
- expect(text).toContain("When the pipeline approves a change to a human-curated annotation, the annotation stops being shown as human-curated");
- expect(text).toContain("when a patroller approves a change, the result is shown as human-curated");
+ // a patroller approval stamps human. The copy states both directions and
+ // what the relabel means: script.js shows every non-'human' provenance as
+ // "AI-generated · not yet reviewed", and the anti-clobber guards
+ // (findLostHuman, _preserve_human) protect only provenance 'human'.
+ expect(text).toContain("When the pipeline approves a change to a human-curated annotation, the annotation is relabeled: it is then shown as “AI-generated · not yet reviewed” and loses the human-curated protection, so later pipeline saves can change it directly");
+ expect(text).not.toContain("stops being shown as human-curated");
+ expect(text).toContain("When a patroller approves a change, the result is shown as human-curated");
// The bot opens Mathlib PRs unattended; the human gate is Mathlib review.
expect(text).toContain("the tagging bot opens pull requests to Mathlib on its own, but they merge only after Mathlib reviewers approve them");
expect(text).toContain("statements are added to Wikidata only for mappings people have reviewed");
diff --git a/wiki/test/home.test.ts b/wiki/test/home.test.ts
index f174a812c..f1f0fa20d 100644
--- a/wiki/test/home.test.ts
+++ b/wiki/test/home.test.ts
@@ -126,6 +126,13 @@ describe("homePage", () => {
expect(html).toContain("https://github.com/Deicyde/WikiLean");
});
+ it("names the existing Wikidata property (P14534), not the proposal-era one", () => {
+ const html = homePage([row()]).replace(/\s+/g, " ");
+ expect(html).toContain("“Mathlib Declaration ID” , P14534");
+ expect(html).not.toContain("formalized as (Lean/Mathlib)");
+ expect(html).not.toContain("basis for a proposed");
+ });
+
it("labels the search input for assistive tech", () => {
const html = homePage([row()]);
expect(html).toContain('');
diff --git a/wiki/test/proposals.test.ts b/wiki/test/proposals.test.ts
index 5222c40a4..b0ad4cb13 100644
--- a/wiki/test/proposals.test.ts
+++ b/wiki/test/proposals.test.ts
@@ -364,6 +364,10 @@ describe("POST /api/article/:slug (proposals)", () => {
expect(page.status).toBe(200);
const html = await page.text();
expect(html).toContain("AI proposals");
+ // Human-at-boundaries: the nightly resolver decides some proposals, so the
+ // intro must not promise the AI never applies them.
+ expect(html).not.toContain("never applies");
+ expect(html).toContain("Each night a deterministic resolver decides the proposals that match its fixed rules");
expect(html).toContain(pending(db)[0].proposalId);
expect(html).toContain("wl-prop-approve");
expect(html).toContain("reject: why?");