Record lastConfirmedAt in snapshot metadata when a source is confirmed (200 or 304). This ensures git-committed snapshot metadata reflects when sources like wiki.gg were successfully pulled even when page HTML did not change, avoiding false pull overdue notices on downstream builds like GitHub Pages.
Document the forge responsibilities across AGENTS.md, README.md, ARCHITECTURE.md, INGESTION.md, and the CI workflow headers: GitHub Actions only builds and serves (Pages deployment) without fetching data sources, while Gitea Actions exclusively runs the scheduled refresh and publishes the container image.
When sources return 304, no git commit is created, but the cache holds updated confirmation timestamps. Publish the refreshed feed unconditionally on non-dry runs so CI rebuilds events.v1.json with fresh confirmation dates.
Derive source pull date from confirmation and recent content updates, preventing recently updated sources from displaying as pulled never or pull overdue.
The Blue Archive Wiki (bluearchive.wiki/wiki/Events) redesigned its
schedule, replacing the per-version MediaWiki tabber with a unified
#eventtable displaying JP and Global periods side-by-side.
- Parse #eventtable resolving GL Period, event titles, and article links
from .event-region-gl, with fallback to .event-region-jp.
- Maintain backwards compatibility with legacy tabber schedule format.
- Add pinned fixture fixtures/ba/bawiki-events-2026-09-17 and tests.
- Update snapshot, AGENTS.md, ARCHITECTURE.md, and INGESTION.md.
Update README, AGENTS.md, ARCHITECTURE.md, and INGESTION.md to reflect
that the scheduled refresh and container image publishing run on Gitea
Actions under .gitea/workflows/, while GitHub Actions under .github/
builds with BASE_PATH and deploys to GitHub Pages when synced.
Move the scheduled refresh and container-registry CI workflows into
.gitea/workflows/ so Gitea Actions runs them natively. Update
.github/workflows/ci.yml to build with BASE_PATH and deploy to GitHub
Pages upon push from the mirror sync, removing the Gitea-specific
container registry step. Update Dockerfile, .dockerignore, and test
harness to test and copy workflows from .gitea/.
`docker login` was refused with a bare `unauthorized`. The root cause is
not a typo in the credentials but a difference in what a Gitea package
is: it belongs to a user or org **owner**, not to a repository, so the
ephemeral repo-scoped Actions token carries no authority over it and the
registry rejects it outright. GHCR accepted GITHUB_TOKEN and GitLab
issues working per-job credentials in $CI_REGISTRY_PASSWORD; Gitea
deliberately does neither, so this needs a real access token with the
package scope, supplied as REGISTRY_TOKEN.
The username moves from github.actor to github.repository_owner for the
same reason. The image is pushed into the owner's namespace and the
token belongs to that owner, while actor is merely whoever triggered the
run — a different person on a dispatch, the wrong account on a fork.
Added a preflight check on the secret, because the failure it replaces
said nothing about what to do. It also settles a question the evidence
left open: the reports of this problem describe login succeeding and the
*push* failing, while ours failed at login, which is consistent with the
token being rejected but equally with its never having been set. The
check distinguishes the two the next time it happens.
No test pins this. The three workflow assertions in test/refresh.test.ts
exist because those defects are silent; this one turns CI red on the
spot, which is the condition that made them worth writing.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The pipeline change made a set of sentences false, and they are the only
record of why things are the way they are, so they move with it: the CI
section and deploy target in README, the cron and BASE_PATH notes in
ARCHITECTURE, the documented User-Agent in INGESTION, and the contact
URL in SOURCES.
Two comments in source were false in the same way and are fixed here
rather than left for the next reader. `serve.ts` said compression was
"the whole difference between the Docker image and the deployed site" —
the image *is* the deployed site now, so that negotiation is the only
compression any reader gets. `robots.ts` justified product-token
matching with "our contact URL contains the string StereotypicalCat",
which it no longer does; the hazard is the arbitrary substring, not that
one, so the comment now says so and the regression test keeps the URL
that actually caused it.
What is deliberately NOT rewritten: game8.co's refusal stays in the past
tense with the Gitea result marked unverified, in AGENTS.md, README,
SOURCES and FEEDBACK alike. A new runner address is the variable that
failure turns on, so it may well be served — but "may well be" is not
evidence, and the four places that would have to change are named
together so they change together once a cycle has actually run.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Full `uses:` URLs as in ci.yml, the fork gate repointed at the Gitea
repo, and a commit identity that is not GitHub's bot.
`gh workflow run` becomes the REST dispatch the CLI would have made,
because there is no `gh` on a Gitea runner. Worth stating plainly: Gitea
does not suppress workflow triggers on a push made with the Actions
token, which is the whole reason this step existed on GitHub, so the
snapshot commit will usually have started ci.yml by itself. The step
stays anyway — it is the only guarantee of publication if that behaviour
is ever configured off, and a duplicate run costs nothing because
ci.yml's concurrency group is per-ref with cancel-in-progress.
The crawler's fallback contact URL follows the repo. It is what wiki
operators see in our User-Agent when REFRESH_CONTACT_URL is unset, so
leaving it pointing at a repo we no longer publish from would make the
contact half of AGENTS.md § Scraping conduct a dead end.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Point `uses:` at full GitHub URLs so step resolution does not depend on
the instance's DEFAULT_ACTIONS_URL, publish the image to Gitea's own
container registry, and drop the Pages deploy.
Pages has no Gitea equivalent and the container image is the deploy
artefact now, so the job is removed rather than replaced. `build` stays:
it is what produces the site bundle, and test/refresh.test.ts pins the
refresh-bookkeeping restore to the slice between `build:` and `image:`.
Its BASE_PATH goes, though — that existed only for the /<repo>/ subpath
Pages served from, and serve.ts serves the image at the root.
The registry host is derived from github.server_url rather than
hardcoded, so a fork on another instance pushes to its own registry.
Layer cache moves from type=gha to type=registry: Gitea does expose the
Actions cache API, but buildx's gha backend is the least reliable part
of that compatibility surface and we are already authenticated to a
registry that can hold the layers.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Add karendar.com as the event source for Punishing: Gray Raven (PGR).
Karendar publishes a dedicated Global event calendar with exact UTC
timestamps in clean server-rendered HTML.
- Register 'pgr' in GameId and GAMES registry (#CC292B, Kuro Games).
- Add parseWeekdayDayMonthYearUtc in src/ingest/dates.ts with unit tests.
- Implement karendarParser in src/ingest/parsers/karendar.ts reading
'week', 'ongoing', and 'upcoming' sections, mapping indefinite/permanent
ends to endsAt: null, and categorizing tags to EventType.
- Register pgr-karendar-events in src/ingest/adapters/index.ts.
- Add pinned fixture, expected output, and comprehensive test suites.
- Update documentation in AGENTS.md, README.md, docs/INGESTION.md,
and docs/SOURCES.md.
Add genshin-impact.fandom.com as a second source for Genshin Impact,
which has been fixture-backed in CI because Game8 CloudFront-blocks the
Actions runner with 202s.
- Add fifth Fandom template parser in src/ingest/parsers/fandom.ts
extracting titles from caption display text, stripping dated subpage
suffixes, and checking currency via latestBoundaryMs.
- Register genshin-fandom-events in src/ingest/adapters/index.ts and
set genshin-game8-events priority: 10 so existing event IDs survive
near-match ties, preventing localStorage completion marks from
orphaning.
- Explicitly sort adapter groups by priority descending in
scripts/build-feed.ts to align with parseGame.
- Pin fixtures and add test coverage in test/adapters/game8.test.ts.
- Update AGENTS.md, README.md, docs/INGESTION.md, and docs/SOURCES.md.
Patch cycles are ~6 weeks, so a span over 180 days previously indicated
a misread year. However, real year-long events exist in this domain:
Genshin's anniversary 5-star selection runs 336 days (2025-10-22 to
2026-09-23), correctly dated on its wiki, and Fire Emblem Heroes runs a
seven-month new-player banner (docs/SOURCES.md § 12b).
Widening the ceiling to 365 days admits genuinely year-long events while
still catching misread years, which place the end 12 months out (span +
~365, or 405+ days for a 6-week event).
Update test/adapters/game8.test.ts duration test, AGENTS.md domain rules,
and docs/INGESTION.md sanity checks.