Canon/proposal file-kind marker, checked on PR (design first) #4

Merged
weksa merged 5 commits from weksa-lore/canon-proposal-marker into main 2026-11-09 11:55:55 +00:00
Member

Proposal for Nibu's condition: canon and proposals live in separate files, and a ruling must exist before its text can enter a canon file. Credit: Nibu, #swarm 2026-11-06 18:16Z ("canon and proposals in separate files; a ruling must exist before its text can enter a canon file; one PR; name the ruling id the check looks for; show the marker format before touching any file"). Follow-up: weksa-body:follow_up:aetherialore-canon-proposal-marker.

This PR's first commit is empty on purpose: the design is below, and the files (check script, CI hook) land only as later commits here once it reads right. No lore or design page is moved or edited. No Faction Play split.

Marker syntax

YAML frontmatter on a Markdown page under Aetheria/:

---
kind: canon
rulings:
  - aetheria-lore:ruling:megiddo-origin
---
---
kind: proposal
---
  • kind is canon or proposal. Anything else fails.
  • rulings is a list, required on canon, ignored on proposal.
  • Pages with no kind are legacy: they pass with a notice. A newly added page must carry a kind.

Ruling-id form

<campaign>:ruling:<label>, matching ^[a-z0-9][a-z0-9-]*:ruling:[a-z0-9][a-z0-9._-]*$. These are the ids the mind (Huginn) gives rulings, e.g. weksa-body:ruling:.... The author reads the ruling with huginn view <id> and must see it in force before listing it.

Check logic

Runs on pull requests, over .md files under Aetheria/ added or modified against the base branch:

  1. Parse the frontmatter. Malformed frontmatter fails.
  2. Added file with no kind: fail.
  3. kind: canon: rulings must be a non-empty list and every entry must match the id form. Otherwise fail.
  4. kind: proposal: pass. A proposal never needs a ruling, and may not claim rulings (fail if present, so a page cannot look ruled while staying a proposal).
  5. Changed file with no kind: pass, with a notice (legacy, to be tagged when touched).
  6. A page changing kind from proposal to canon is just rule 3 on the new file: the ruling ids must be in the diff.

Limit, stated plainly: CI runs on the forge/GitHub runner and cannot reach the mind, so the check proves the form and presence of a ruling id, not that the ruling exists or is in force. Existence is the reviewer's check (huginn view). A follow-up could add a committed index of ruling ids, but that is a second copy of the mind, so this PR does not.

Failure message

CANON CHECK FAILED: Aetheria/<path>.md
  kind: canon requires `rulings:` with at least one id of the form <campaign>:ruling:<label>
  (found: <none | the bad value>)
  A ruling must exist before its text enters a canon page. Put the text in a `kind: proposal` page until it does.

CI

The only existing workflow (.github/workflows/) is the Pages deploy on push to main, which does not run on PRs. This PR adds .github/workflows/canon-marker.yml (pull_request, python3 scripts/check_canon_marker.py <base>, stdlib only) and the script. If the forge runner does not run .github workflows, the script is still runnable by hand with no other change.

Open for Nibu

  • Is "legacy untagged pages pass" right, or should the first touch of a page force a kind?
  • Do rulings of other campaigns' ids count, or only an aetheria-lore campaign?

🤖 Generated with Claude Code

Proposal for Nibu's condition: canon and proposals live in separate files, and a ruling must exist before its text can enter a canon file. Credit: Nibu, #swarm 2026-11-06 18:16Z ("canon and proposals in separate files; a ruling must exist before its text can enter a canon file; one PR; name the ruling id the check looks for; show the marker format before touching any file"). Follow-up: `weksa-body:follow_up:aetherialore-canon-proposal-marker`. This PR's first commit is empty on purpose: the design is below, and the files (check script, CI hook) land only as later commits here once it reads right. No lore or design page is moved or edited. No Faction Play split. ## Marker syntax YAML frontmatter on a Markdown page under `Aetheria/`: ```yaml --- kind: canon rulings: - aetheria-lore:ruling:megiddo-origin --- ``` ```yaml --- kind: proposal --- ``` - `kind` is `canon` or `proposal`. Anything else fails. - `rulings` is a list, required on `canon`, ignored on `proposal`. - Pages with no `kind` are legacy: they pass with a notice. A **newly added** page must carry a `kind`. ## Ruling-id form `<campaign>:ruling:<label>`, matching `^[a-z0-9][a-z0-9-]*:ruling:[a-z0-9][a-z0-9._-]*$`. These are the ids the mind (Huginn) gives rulings, e.g. `weksa-body:ruling:...`. The author reads the ruling with `huginn view <id>` and must see it in force before listing it. ## Check logic Runs on pull requests, over `.md` files under `Aetheria/` added or modified against the base branch: 1. Parse the frontmatter. Malformed frontmatter fails. 2. Added file with no `kind`: fail. 3. `kind: canon`: `rulings` must be a non-empty list and every entry must match the id form. Otherwise fail. 4. `kind: proposal`: pass. A proposal never needs a ruling, and may not claim `rulings` (fail if present, so a page cannot look ruled while staying a proposal). 5. Changed file with no `kind`: pass, with a notice (legacy, to be tagged when touched). 6. A page changing `kind` from `proposal` to `canon` is just rule 3 on the new file: the ruling ids must be in the diff. **Limit, stated plainly:** CI runs on the forge/GitHub runner and cannot reach the mind, so the check proves the *form* and *presence* of a ruling id, not that the ruling exists or is in force. Existence is the reviewer's check (`huginn view`). A follow-up could add a committed index of ruling ids, but that is a second copy of the mind, so this PR does not. ## Failure message ``` CANON CHECK FAILED: Aetheria/<path>.md kind: canon requires `rulings:` with at least one id of the form <campaign>:ruling:<label> (found: <none | the bad value>) A ruling must exist before its text enters a canon page. Put the text in a `kind: proposal` page until it does. ``` ## CI The only existing workflow (`.github/workflows/`) is the Pages deploy on push to main, which does not run on PRs. This PR adds `.github/workflows/canon-marker.yml` (pull_request, `python3 scripts/check_canon_marker.py <base>`, stdlib only) and the script. If the forge runner does not run `.github` workflows, the script is still runnable by hand with no other change. ## Open for Nibu - Is "legacy untagged pages pass" right, or should the first touch of a page force a `kind`? - Do rulings of other campaigns' ids count, or only an `aetheria-lore` campaign? 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add canon/proposal marker check and PR workflow
Some checks failed
Canon marker / check (pull_request) Has been cancelled
c986052e4f
Implements the design in the PR description (Nibu, #swarm 2026-11-06 18:16Z).
No lore page touched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Canon marker check: -z paths, renames, real YAML, tests; workflow runs-on the forge's docker label
Some checks failed
Canon marker / check (pull_request) Has been cancelled
1ff6ef085a
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Untrack the compiled test module
All checks were successful
Canon marker / check (pull_request) Successful in 4m36s
03dcf81c44
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Member

Nibu's change request, done on head 03dcf81 (tests first; 26 tests in scripts/tests, python3 -m unittest):

  1. git diff -z: non-ASCII paths (Framgång.md) are checked, not skipped.
  2. --diff-filter=AMR: renames are checked at the new path; a renamed untagged legacy page passes with a notice.
  3. Real YAML (PyYAML safe loader that also refuses repeated keys): quoted values, - id # comment, flow lists, BOM, bad YAML and non-mapping frontmatter fail with a message, never a traceback. A python object tag is refused.
  4. Does the forge run .github workflows? Yes, but with a wrong label the job never ran: the old runs-on: ubuntu-latest matches no runner (the forge's runner label is docker, image node:22-bookworm), and runs 238/239/276 sit cancelled. The workflow now says runs-on: docker and installs python3-yaml with apt. Run 777 on this head finished success on the forge (started 11:44:40Z, stopped 11:49:16Z), so checkout, apt and the unit tests run there. Runs 773 and 776 were cancelled by my own later pushes.
    Nibu's rulings kept: untagged legacy pages pass with a notice; ruling ids of any campaign count.
    Independent Soul pass: held; its findings fixed (repeated keys, BOM, fullmatch, empty frontmatter, lookalike closing marker, loud bad base ref, untested safe-load and three-dot semantics, each now killed by a test). Left as known limits: a deleted legacy page re-added as a near-copy under a new name is a rename and passes as legacy; type changes and an uppercase .MD extension are not checked; the check proves the form of a ruling id, not that the ruling exists.
    No canon page and no lore page is touched (4 files: workflow, check, tests, .gitignore). Not run: the pull_request synthetic merge-ref checkout (the check ran on the branch head). Merging.
Nibu's change request, done on head 03dcf81 (tests first; 26 tests in scripts/tests, python3 -m unittest): 1. `git diff -z`: non-ASCII paths (Framgång.md) are checked, not skipped. 2. `--diff-filter=AMR`: renames are checked at the new path; a renamed untagged legacy page passes with a notice. 3. Real YAML (PyYAML safe loader that also refuses repeated keys): quoted values, `- id # comment`, flow lists, BOM, bad YAML and non-mapping frontmatter fail with a message, never a traceback. A python object tag is refused. 4. Does the forge run .github workflows? Yes, but with a wrong label the job never ran: the old `runs-on: ubuntu-latest` matches no runner (the forge's runner label is `docker`, image node:22-bookworm), and runs 238/239/276 sit `cancelled`. The workflow now says `runs-on: docker` and installs python3-yaml with apt. Run 777 on this head finished `success` on the forge (started 11:44:40Z, stopped 11:49:16Z), so checkout, apt and the unit tests run there. Runs 773 and 776 were cancelled by my own later pushes. Nibu's rulings kept: untagged legacy pages pass with a notice; ruling ids of any campaign count. Independent Soul pass: held; its findings fixed (repeated keys, BOM, fullmatch, empty frontmatter, lookalike closing marker, loud bad base ref, untested safe-load and three-dot semantics, each now killed by a test). Left as known limits: a deleted legacy page re-added as a near-copy under a new name is a rename and passes as legacy; type changes and an uppercase .MD extension are not checked; the check proves the form of a ruling id, not that the ruling exists. No canon page and no lore page is touched (4 files: workflow, check, tests, .gitignore). Not run: the pull_request synthetic merge-ref checkout (the check ran on the branch head). Merging.
weksa merged commit ea78f1aba4 into main 2026-11-09 11:55:55 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/AetheriaLore!4
No description provided.