55 Commits

Author SHA1 Message Date
wan 3ab59b493e docs: READMEs describe current behaviour, not history
tests / pytest (push) Successful in 6s
Removes the decision logs, the dated rows, the strikethrough retirements and
the setup progress checkboxes from all four READMEs. A decision log records how
the system came to be; a reader needs to know how it is. The two had drifted
far enough apart that the docs asserted things the shipped config contradicted.

Corrected, all of which were stale rather than merely verbose:

- the architecture section described the source-repo workflow as self-contained
  and called the reusable workflows here "not actively called; may be stale".
  They are the pipelines; the source-repo workflow is a thin caller.
- `workflow_call` was recorded as abandoned, and the instance as having no orgs.
  Both pipelines run through cross-repo `workflow_call` under an org.
- tech-blogs was described as not yet initialised. It has published.
- ses and tech invocation sections claimed a self-contained workflow too.

Kept, rewritten as behaviour rather than as dated decisions, because they are
live operational constraints and not history:

- the callee workflow cache, why a `publish-*.yml` edit appears not to take,
  and the flush command with its `--no-deps` and `--pull` caveats
- why this repo has to stay public
- the `URL_TO_GITEA` / `PUBLIC_URL_TO_GITEA` split and what validates it
- host network mode, and that `kaniko-act` cannot be rebuilt by build-image.yml

Also documents what the renderer, its failure modes and its publish scope
actually are, which the decision logs had been standing in for, and adds the
architecture note that renderer code is cloned fresh per run while workflow
YAML is cached — the distinction that governs whether a change takes effect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:39:28 +09:00
mikkeli f19538b607 docs: --no-deps is required when flushing the act_runner cache
tests / pytest (push) Successful in 6s
act_runner declares depends_on: gitea, so a bare --force-recreate cascades
and recreates the Gitea instance along with the runner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QwzQyCzCCLriTgs7b9uxwY
2026-09-09 17:05:47 +09:00
mikkeli 9c293f75c3 Merge branch 'automation/public-source-url': split public banner URL from clone URL
tests / pytest (push) Successful in 5s
The publish workflows composed the {{Auto-generated}} banner's source link
from URL_TO_GITEA, the runner-internal clone address (http://localhost:3005),
so published pages cited a URL no reader could resolve. The banner URL now
comes from the PUBLIC_URL_TO_GITEA variable, validated by public_source_base()
in lib/wiki.py: unset, schemeless, loopback, *.local and private-range values
raise rather than reach the wiki. URL_TO_GITEA keeps its clone-address role.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QwzQyCzCCLriTgs7b9uxwY
2026-09-09 16:59:50 +09:00
wan e1810facd6 Split the public banner URL from the runner-internal clone URL
tests / pytest (push) Successful in 7s
Every auto-published page, in all three pipelines, carried a source link to
`localhost:3005`. The template was never at fault — it links `{{{source}}}`
verbatim; the bad URL was baked into each page's wikitext by the publisher.

Root cause was one variable serving two incompatible purposes. `URL_TO_GITEA`
has to be an address the runner container can clone from, and since the runner
uses host networking that is `http://localhost:3005`. All three workflows then
composed `GITEA_REPO_URL` — the base of the public banner link, written into
the wiki for anyone to read — out of that same loopback value.

Adds a second org variable, `PUBLIC_URL_TO_GITEA`, for the public base
(`https://git.novoyuuparosk.org`), and points the banner at it. Cloning keeps
using `URL_TO_GITEA`. The two are now documented as deliberately separate,
because collapsing them looks like tidying up.

`lib/wiki.public_source_base()` validates the banner base rather than trusting
it: unset, relative, loopback, mDNS (`*.local`) or private-range values raise
with the variable to set. A misconfiguration that would publish an unreachable
link now stops the run instead. Note the failure mode this creates on purpose:
until `PUBLIC_URL_TO_GITEA` exists, publishing aborts rather than proceeding
with a wrong URL.

Also builds the source path with `rel.as_posix()`, so the separator in a URL
does not depend on the OS the publisher runs on — it produced backslashes when
the renderer was exercised on Windows.

Prerequisites before this can publish:

1. Add the org variable `PUBLIC_URL_TO_GITEA` = https://git.novoyuuparosk.org
2. Flush the act_runner callee cache — `docker compose up -d --force-recreate
   act_runner`. Per the cross-cutting decisions log, edits to `publish-*.yml`
   keep executing the stale cached copy, and this change is in those files.

Pages already published still carry the old URL in their stored wikitext. They
are only corrected by re-publishing each pipeline in full.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 16:37:06 +09:00
wan 162c053377 Merge branch 'automation/md-links': Markdown links replace the siblings macro
tests / pytest (push) Successful in 9s
Retires the wiki.siblings bill of materials in favour of ordinary relative
Markdown links, fixes the batch-scoped resolution that silently stripped
wikilinks on incremental pushes, and adds the test suite that should have
caught it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 16:28:46 +09:00
wan f5625cde98 songs: replace the wiki.siblings macro with plain Markdown links
The `wiki.siblings` bill of materials is gone. A related co-publication is
now nominated with an ordinary relative Markdown link — `[label](notes.md)` —
resolved at publish time against the target file's own `title` frontmatter.
No macro namespace, no per-file declaration, and the link still works in
Gitea's blob view and any editor preview.

The macro could not be made safe. Substitution was `str.replace(tag, …)` over
the whole rendered page: no word boundary, so a tag also occurring in lyrics or
a code block got rewritten; no collision check, so overlapping tags corrupted
each other in declaration order. SCHEMA.md documented the placeholder as
`[[TAG]]` while the code replaced the bare tag, so following the schema
produced `[[[[Title|label]]]]`.

Fixes a live corruption bug in the process. `sibling_map` was built only from
the files in the publish batch, and an unresolved sibling degraded to plain
text — so pushing `ses/pulse.md` alone republished it with its wikilink
silently stripped to the bare words "Dev gibberish". SCHEMA.md asserted the
workflow "always runs --all", which it never did on an ordinary push.

The repository is now always scanned in full, whatever a run publishes, so
link resolution and title-collision detection can never depend on which files
a push touched. Writes stay bounded to the push diff plus one hop in each link
direction: the page a changed page links to (so a link cannot point at an
unpublished page) and the pages linking to it (so an edited `title` does not
leave stale inbound links). Publishing is not instant; reading is cheap.

An unresolvable `.md` link is now a hard error, never graceful degradation, and
linking to a file still does not publish it — `wiki.publish: true` remains the
only gate. A leftover-link guard fails the run on any link shape the resolver
did not recognise, including a Markdown link written inside a verbatim
```columns fence, so a pandoc upgrade cannot silently publish literal text.

Also:

- render before write: every page in a run is rendered before the first is
  published, so a broken page leaves the wiki untouched instead of
  half-updated. `publish_one` is split into `render_page` (pure) and the write.
- pin pandoc's stdout to UTF-8. `text=True` decoded it with the ambient locale
  encoding; on any non-UTF-8 locale the Japanese and Chinese lyrics died inside
  subprocess's reader thread, `stdout` came back None, and the real error was
  lost behind a TypeError. Latent on the Debian runner via PEP 538 only.
- title collisions are detected repo-wide, not just within a push, and before
  any wiki contact.
- tests/, run by .gitea/workflows/test.yml in the publish runner image. The
  pandoc output contract the resolver depends on is now pinned by a test
  instead of by prose — which is how SCHEMA.md came to assert something false.
- drop an f-string with nothing to interpolate.
- quote the SOURCE_REF interpolation in the workflow instead of pasting an
  input straight into shell.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 16:24:36 +09:00
mikkeli 4e7d84d665 fix(ci): point kaniko-act builder at the only working copy
The org copy this workflow produced cannot build (no /tmp), so pointing
its own container at it made the workflow unrunnable.
2026-08-11 20:41:09 +09:00
mikkeli e4ae304954 docs: record act cache staleness and the kaniko-act /tmp limitation 2026-08-11 20:38:27 +09:00
wiki-runner image bot bdca12837e build: pin runner image to 4f134af 2026-08-11 11:35:27 +00:00
mikkeli 4f134af7bc fix(ci): build in the known-good kaniko-act
/ build (push) Successful in 1m5s
/ pin (push) Successful in 10s
The org rebuild exits 0 but produces an image without /tmp, so the
source fetch fails. Runner image still publishes to the org namespace;
only the builder stays put.
2026-08-11 20:34:07 +09:00
mikkeli 905a1051a8 ci: move container images to the org registry namespace
/ build (push) Failing after 2s
/ pin (push) Has been skipped
Build destination, both builder-image references and the three publish
pins now point at novoyuuparosk-wiki/*. The publish pins name a tag that
does not exist in the new namespace until this run's build job pushes it
and the pin job rewrites them, so publishes are briefly broken mid-run.
2026-08-11 20:32:45 +09:00
mikkeli 4919ce9450 ci: add dispatch-only kaniko-act build, targeting the org registry
Bootstrap step for moving container images off the personal namespace.
Builds using the existing mikkeli/kaniko-act; the org copy is what this
run produces.
2026-08-11 20:31:04 +09:00
mikkeli 03be97799d docs: record why this repo must stay public
Correcting a wrong claim in the previous commit: sharing an owner with
the caller does not grant read access to a private callee. act_runner
clones the reusable workflow anonymously, so the repo has to be public.

Also notes the act cache key, which hid this for months, and the stale
Dubrowski bot identity.
2026-08-11 20:26:33 +09:00
wiki-runner image bot 01901b3821 build: pin runner image to 6dc9f68 2026-08-11 10:48:50 +00:00
mikkeli 6dc9f68b12 chore: repoint pipeline at the novoyuuparosk-wiki org
/ build (push) Successful in 1m3s
/ pin (push) Successful in 2s
This repo and the three source repos moved from the `mikkeli` account
into the `novoyuuparosk-wiki` org. Rewrites cover repo paths only —
the `mikkeli:` HTTP basic-auth usernames stay, since FAPAT/PKGRW_PAT
remain personally owned, as do the `3005/mikkeli/*` image paths
(Gitea cannot transfer packages).

Bot identity moves to `Mikkeli@giteaBot`; both accounts hold the bot
right, so edits stay flagged as bot edits.
2026-08-11 19:47:35 +09:00
mikkeli 83a5d86115 Merge branch 'automation/columns-md-emphasis'
Convert Markdown emphasis (*italic*, **bold**, ***bold-italic***) to wikitext inside columns blocks.
2026-06-14 21:13:31 +09:00
mikkeli 2072db4980 feat: Markdown emphasis inside columns blocks
Inside a columns fenced block (verbatim, so Pandoc doesn't process
Markdown), convert *italic* / **bold** / ***bold-italic*** to wikitext
emphasis. Asterisk style, single line; precedence bold-italic > bold >
italic. Raw inline HTML still works via the existing HTML-unescape path.

Docs + example updated to use ** ** rather than <b>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:13:31 +09:00
mikkeli 00527e46b1 Merge branch 'automation/columns-shorthand'
Side-by-side columns shorthand: a 'columns' fenced block (columns split by a line of ===) is expanded post-Pandoc in shared lib/wiki.py into a flex div of poem columns. Universal across pipelines; no wiki template or PHP extension.
2026-06-14 21:02:35 +09:00
mikkeli 34a278870d feat: side-by-side columns shorthand (```columns fence)
Authors write a ```columns fenced block (columns separated by a line of
===); Pandoc passes the body through verbatim as <pre class="columns">,
and a new post-Pandoc transform in lib.wiki.expand_columns expands it
into a flex <div> of <poem> columns. Runs entirely Pi-side before the
MediaWiki API write — no wiki template or PHP extension required.

The transform lives in shared lib/wiki.py (called from markdown_to_wikitext),
so it is universal across all pipelines. Stdlib only; no new deps.

Docs: SCHEMA.md author contract + songs/root decision logs.
Also ignore __pycache__/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:02:24 +09:00
mikkeli 736f40f953 Update README.md 2026-06-11 04:08:05 +00:00
wiki-runner image bot 73a8b5834a build: pin runner image to b2714ab 2026-06-11 04:07:09 +00:00
mikkeli b2714ab5af feat(ci): auto-pin publish workflows to freshly built runner image
/ build (push) Successful in 59s
/ pin (push) Successful in 10s
build-image.yml now pushes a moving :latest tag alongside :<short-sha>,
and a follow-up pin job (running in :latest) rewrites the image pin in all
three publish-*.yml to the new sha and commits it back to master with
FAPAT. Publish workflows keep immutable sha pins, kept current with no
manual bump. No rebuild loop: the pin commit only touches workflow_call
files. Root README image-flow section updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 13:05:43 +09:00
mikkeli bf96cd10e7 fix(tech): default Blog year category to execution year
Dateless posts silently lost their Blog:<year> category. build_wikitext
now falls back to datetime.now().year when no date is declared, so every
published post lands in a year category. Docs corrected accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 13:05:29 +09:00
mikkeli bb57f319c5 fix(ci): authenticate source fetch; repo is private again
/ build (push) Successful in 1m20s
Fetch uses the job's per-run auto-token (repo-scoped, nothing stored);
push moves to the PKGRW_PAT user secret, separating CI credentials
from the host's docker login token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 12:17:43 +09:00
mikkeli e838d3b105 build: bump runner image to CI-built 217d633
First image produced by the kaniko build workflow; replaces the
manually pushed v0 bootstrap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:32:27 +09:00
mikkeli 217d633853 fix(ci): add /tmp to kaniko-act (r2)
/ build (push) Failing after 1s
kaniko's scratch image has no /tmp; the build script and act's
RUNNER_TEMP both assume it exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:20:59 +09:00
mikkeli 3d7af25aed fix(ci): wrap kaniko in act-compatible image
/ build (push) Failing after 1s
act starts job containers with entrypoint /bin/sleep, which kaniko's
scratch-based image lacks (busybox lives under /busybox). kaniko-act
adds the single missing symlink; bootstrap build is manual, documented
in the Dockerfile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:18:17 +09:00
mikkeli 47e5127299 feat(ci): build and push runner image with kaniko
/ build (push) Failing after 12s
Daemonless image build inside the job container itself
(kaniko :debug). Source fetched via Gitea archive API, pushed to the
Gitea registry tagged with the short commit SHA. Triggered by changes
to Dockerfile, any pipeline requirements.txt, or this workflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:13:11 +09:00
mikkeli 2103a423fe build: pin job container image to Gitea registry (v0)
Workflows now pull pi5-16.local:3005/mikkeli/novoyuuparosk-wiki-runner:v0
instead of relying on the host dockerd image cache. Registry is the
authority; act_runner pull_image flipped to true accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 10:27:41 +09:00
mikkeli f7d84b4f0f fix(tech): preserve h1 elements — do not strip first heading
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 00:02:02 +09:00
mikkeli 1bfad8332c feat(tech): add tech blog pipeline
Publishes mikkeli/tech-blogs to the wiki at bare titles (no prefix).
Every page gets Category:Tech blog; Category:Blog:<year> from the date
field; Category:<tag> per tag entry (no prefix).

Fields: title (required), wiki.publish (required), date (optional,
YYYY-MM-DD), tags (optional list).

Source repo tech-blogs not yet initialised — pipeline ships first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 00:00:18 +09:00
mikkeli 230a3badc3 fix(ses): preserve h1 elements — do not strip first heading
Songs always carries a # Song title line so stripping makes sense there,
but SES body structure is freeform and may use h1 deliberately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 00:00:08 +09:00
mikkeli dfe1f9c631 feat(ses): add SES light novel pipeline; extract shared lib/wiki.py
New pipeline at pipelines/ses/ publishes mikkeli/ses-light-novel to
the SES: MediaWiki namespace. All pages get Category:SES and
Category:SES:<type> (type from frontmatter, fallback to parent dir name).

Shared functions (connect_wiki, markdown_to_wikitext, strip_first_h1,
AUTO_BANNER_PREFIX) extracted from songs/publish.py into lib/wiki.py;
songs refactored to import from there.

Also adds publish-ses.yml workflow stub and updates Dockerfile and
root README.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 16:23:26 +09:00
mikkeli 6144d1f399 feat(songs): inject Category:Songs for pages with an album
Pages with a valid `album` field now get [[Category:Songs]] prepended
before [[Category:Album:<name>]]. Companion/sibling pages without an
album are unaffected. Updated SCHEMA.md and pipeline README accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 10:17:22 +09:00
mikkeli db159fd63d Merge pull request 'automation/songs' (#4) from automation/songs into master
Reviewed-on: mikkeli/novoyuuparosk-auto-wiki#4
2026-06-10 00:54:28 +00:00
mikkeli d08f95fc96 Merge branch 'master' into automation/songs 2026-06-10 00:54:22 +00:00
mikkeli f135906392 chore: update workflow stub to use prebuilt runner image
Switch from python:3.12-slim + apt/pip installs to
novoyuuparosk-wiki-runner:latest. Removes the two install steps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 09:40:01 +09:00
mikkeli 38e76ff56d docs: remove plaintext bot secret, update runner image references
Credentials moved to Gitea user-scope vault — removed from README.
Architecture diagram and songs pipeline deps section updated to reflect
prebuilt novoyuuparosk-wiki-runner:latest image (no runtime dep installs).
Noted workflow stubs in .gitea/workflows/ may be stale.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 09:38:27 +09:00
mikkeli f2fe4da667 fix(songs): sibling substitution uses bare tag match, not [[TAG]] wrapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 00:46:57 +09:00
mikkeli 87874fa935 feat(songs): optional album, categories list, sibling placeholder resolution
- `album` no longer required; category format changed to Category:Album:<name>
- New optional `categories` list for additional arbitrary categories
- `wiki.siblings` bill of materials: path+tag required for resolution, label optional
- Post-Pandoc [[TAG]] substitution with four-tier fallback (link+label, link, plain label, plain tag)
- Sibling map built from all published posts in the same run; unresolved siblings degrade gracefully
- SCHEMA.md fully updated; README.md deferred items moved to implemented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 00:45:42 +09:00
mikkeli ef8ba3f01f feat: add shared runner Dockerfile and document rebuild process
All wiki pipelines share novoyuuparosk-wiki-runner:latest — a
locally-built image with git, pandoc, ca-certificates, and Python
packages pre-installed. Eliminates the apt-get and pip install
steps from every job run. Rebuild manually on the Pi when deps change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 00:15:29 +09:00
mikkeli 580ef2109e feat(songs): LRC file upload and footer link
For songs with an `lrc` field in frontmatter:
- Verifies the file exists and is valid UTF-8 (warns and skips otherwise)
- Uploads to wiki as `File:<title>.lrc` via MediaWiki file API
- Idempotent: skips upload if SHA1 matches existing wiki file
- Appends `[[Media:<title>.lrc|Synced lyrics (.lrc)]]` before the
  category tag at the bottom of the wiki page

Songs without `lrc` in frontmatter are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 23:11:00 +09:00
mikkeli 33f47435ee refactor(songs): rename render.py to publish.py
render.py was a misleading name — the script validates, renders,
and writes to MediaWiki (and will soon upload LRC files too).
publish.py better describes the full scope of the entry point.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:38:52 +09:00
mikkeli afc6a62791 docs: update all docs to reflect v1 live state
- template_auto_generated.wikitext: generated_at → commit parameter
- SCHEMA.md: same banner fix; clarify that frontmatter-less files are
  silently skipped, not errors
- pipelines/songs/README.md: full rewrite — v1 live status, self-
  contained workflow pattern, correct env table, modes, one-off setup
  marked done, full decisions log
- README.md: architecture reflects actual pattern (no workflow_call),
  URL_TO_GITEA variable added, setup checklist marked complete, host
  network mode documented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:39:36 +09:00
mikkeli cf6567cd77 Merge pull request 'fix(songs): skip pages with existing non-auto-generated content' (#2) from automation/songs into main
Reviewed-on: mikkeli/novoyuuparosk-auto-wiki#2
2026-06-09 09:31:02 +00:00
mikkeli 65aa617935 fix(songs): skip pages with existing non-auto-generated content
If a wiki page exists but was written manually (no Auto-generated
banner), the bot now skips it instead of attempting an overwrite
that triggers MediaWiki CAPTCHA. Clear stderr warning tells user
to delete the page or add the banner to hand ownership to the bot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:23:14 +09:00
mikkeli 3895cb5c25 fix(songs): skip files without wiki.publish:true before validation
Files with no frontmatter or wiki.publish not set should be silently
ignored rather than failing the whole pipeline run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:13:55 +09:00
mikkeli d49b2f0bca Merge pull request 'automation/songs' (#1) from automation/songs into main
Just get it done
2026-06-09 09:03:35 +00:00
mikkeli df7195511f render: replace generated_at timestamp with commit SHA in banner
Timestamps made the idempotency check always false — every run would
rewrite every page. Commit SHA is stable: same source commit produces
identical wikitext, so unchanged pages are correctly skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:01:46 +09:00
mikkeli 58507edfd5 workflows: replace github.server_url with vars.URL_TO_GITEA
Gitea blocks vars/secrets prefixed with GITEA_ or GITHUB_.
Also avoids relying on github.server_url which resolves to
github.com rather than the local Gitea instance on this runner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 17:48:32 +09:00