Skip to content

fix: post a registry snapshot to the dashboard after each sync - #173

Open
bosquejun wants to merge 3 commits into
mainfrom
claude/domain-routing-bosque-example-0ua7td
Open

fix: post a registry snapshot to the dashboard after each sync#173
bosquejun wants to merge 3 commits into
mainfrom
claude/domain-routing-bosque-example-0ua7td

Conversation

@bosquejun

@bosquejun bosquejun commented Aug 24, 2026

Copy link
Copy Markdown
Member

Note: this is a maintenance PR, not a subdomain registration. No subdomain is being claimed, so the registration template's sections don't apply β€” the change is to .github/workflows/sync.yml and a new scripts/.

What's wrong

The dashboard lists domains from a D1 table that is a read model of this repo, refreshed only by a full-snapshot POST /api/registry/events. That POST was never implemented here β€” it existed only as an example snippet in the ecosystem repo's apps/dashboard/README.md. Neither repo referenced registry/events or REGISTRY_SYNC_SECRET anywhere. So the table has been serving whatever a one-off backfill left in it.

Both halves of the reported symptom follow from that one gap:

example.json deleted (#150) 2026-08-09 04:42
bosquejun.json added (#154) 2026-08-09 12:53

The snapshot predates both, so example is still a row and renders on the dashboard, and bosquejun was never inserted and doesn't. The fallback to the GitHub API never engages, because D1 is reachable and simply stale.

The fix

The sync job now builds the snapshot with scripts/build-snapshot.sh and posts it after the Cloudflare sync. Load-bearing details:

  • fetch-depth: 0 on checkout β€” each record is dated from the first and last commit to touch its file, and a shallow clone has no history to read.
  • The POST runs under if: always() and reports status: failed when the sync failed, so a bad run marks the rows rather than leaving the read model untouched. The job is failed again afterwards so the run still goes red.
  • It fails loudly when REGISTRY_SYNC_SECRET is unset or the endpoint rejects the payload. Silence is precisely what caused this.
  • destroy: true entries are omitted, and a file that is simply gone is absent too β€” which is what removes the stale example row.

Two details of the date command differ from the ecosystem README's original recipe, because the real data breaks it: no --follow (git scores a copied-from-a-neighbour file as a rename, dating bosquejun.json from mee.json β€” before the subdomain existed), and UTC with a literal Z (z.iso.datetime() rejects the numeric offset %cI produces, which would 400 the whole payload).

Verified by running the real reconcile() against the real generated snapshot: it passes the endpoint's own Zod schema, deletes example, and inserts bosquejun with createdAt 2026-08-09T12:53:49Z.

A correction, mid-PR

This PR originally also restored subdomains/example.json with "destroy": true, on the theory that deleting the file in #150 had left its CNAME live in Cloudflare. That was wrong, and this PR's own CI caught it.

The validation dry-run on the first commit β€” scoped to the one changed domain, with example marked for destruction, against 71 records fetched from Cloudflare β€” reported No changes needed. The DELETE branch for a destroyed domain isn't gated by --only scoping, so a record at that name would have produced one. example.is-pinoy.dev also fails to resolve, while bosquejun.is-pinoy.dev returns Cloudflare proxy addresses.

The record was removed by some other route after #150. The tombstone is dropped in 5f4434d β€” it would have sat in the registry forever describing a subdomain that neither exists nor is claimed, and the stale dashboard row is removed either way.

The underlying gap it was meant to expose β€” that deleting a domain file emits no DELETE at all β€” is real regardless, and is handled in is-pinoy-dev/ecosystem#226.

⚠️ Required before merge

Set REGISTRY_SYNC_SECRET as a repository secret, matching the value on the dashboard deployment. The sync job uses repo-level secrets, so it goes there rather than the production environment. Without it, every sync run fails after this merges β€” deliberate, but immediate.

Optionally set a DASHBOARD_URL repository variable; it defaults to https://dashboard.is-pinoy.dev.

Merging no longer self-triggers: the net diff no longer touches subdomains/**, and the workflow's paths: filter only fires on those. Either wait for the next registration, or run the sync workflow manually to refresh the dashboard immediately.

πŸ€– Generated with Claude Code

https://claude.ai/code/session_01S3VaS59vjtX2XdfhK28z7Y

The dashboard lists domains from a D1 table that is a read model of this repo,
refreshed only by a full-snapshot POST to /api/registry/events. That POST was
never implemented here β€” it existed only as an example snippet in the
ecosystem repo's apps/dashboard/README.md β€” so the table has been serving
whatever a one-off backfill left in it.

Both halves of the symptom follow from that. example.is-pinoy.dev was deleted
from the registry on 2026-08-09 and is still listed; bosquejun.is-pinoy.dev was
registered eight hours later the same day and has never appeared. The fallback
to the GitHub API never engages, because D1 is reachable and simply stale.

The sync job now builds the snapshot with scripts/build-snapshot.sh and posts
it after the Cloudflare sync. Details that are load-bearing:

- `fetch-depth: 0`, since each record is dated from the first and last commit
  to touch its file and a shallow clone has no history to read.
- The POST runs under `if: always()` and reports `status: failed` when the
  sync failed, so a bad run marks the rows instead of leaving the read model
  untouched. The job is failed again afterwards so the run still goes red.
- It fails loudly when REGISTRY_SYNC_SECRET is unset or the endpoint rejects
  the payload. Silence is what caused this.
- Destroyed entries are omitted, so reconcile deletes their rows.

Also restores subdomains/example.json with `"destroy": true`. Deleting the file
in #150 never removed the DNS record β€” sync only deletes what is still present
and marked for destruction β€” so the CNAME has stayed live in Cloudflare this
whole time. The next sync will remove it; the file can be deleted after that.

Requires REGISTRY_SYNC_SECRET as a repository secret, matching the value set on
the dashboard deployment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3VaS59vjtX2XdfhK28z7Y
@bosquejun
bosquejun requested a review from a team as a code owner August 24, 2026 23:20
@bosquejun
bosquejun deployed to production August 24, 2026 23:20 — with GitHub Actions Active
@pareng-gar

pareng-gar Bot commented Aug 24, 2026

Copy link
Copy Markdown

βœ… Subdomain Validation Passed

All subdomain files are valid.

⚠️ Warnings

⚠ @bosquejun holds 3 custom subdomains (limit 1): jappe, jun, roasi

πŸ” Sync Dry-Run


  β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—      β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—
  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•      β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘
  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘
  β–ˆβ–ˆβ•‘β•šβ•β•β•β•β–ˆβ–ˆβ•‘β•šβ•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘  β•šβ–ˆβ–ˆβ•”β•  β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•  β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•
  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘      β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• 
  β•šβ•β•β•šβ•β•β•β•β•β•β•      β•šβ•β•     β•šβ•β•β•šβ•β•  β•šβ•β•β•β• β•šβ•β•β•β•β•β•    β•šβ•β•β•šβ•β•β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β•  β•šβ•β•β•β•  
  ────────────────────────────────────────────────────────────────────────────────  v0.9.0

β„Ή Loaded 52 domain(s) from ./subdomains
β„Ή Scoped to 1 changed domain(s) from --only
β„Ή Fetched 71 DNS record(s) from Cloudflare
βœ” No changes needed. All domains are in sync.

Restoring subdomains/example.json with "destroy": true was meant to make sync
delete a CNAME that deleting the file in #150 had left behind. The record is
not there.

The validation dry-run on this PR settles it: scoped to the one changed domain
with example marked for destruction, against 71 records fetched from
Cloudflare, it reported "No changes needed." The DELETE branch for a destroyed
domain is not gated by --only scoping, so a record at that name would have
produced one. example.is-pinoy.dev also fails to resolve, while
bosquejun.is-pinoy.dev returns Cloudflare proxy addresses.

So the record was removed by some other route after #150, and the tombstone
would sit in the registry forever describing a subdomain that neither exists
nor is claimed.

Dropping it costs nothing here: the stale dashboard row is removed because the
snapshot enumerates the files that exist, and a file that is simply gone is
absent from it either way.

The underlying gap β€” that deleting a domain file emits no DELETE at all β€” is
real regardless, and is handled in is-pinoy-dev/ecosystem#226.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3VaS59vjtX2XdfhK28z7Y
@bosquejun bosquejun changed the title fix: refresh the dashboard's read model, and retire example.is-pinoy.dev fix: post a registry snapshot to the dashboard after each sync Aug 24, 2026
The workflow only fired on pushes touching subdomains/**, which was fine when
its only job was reconciling DNS from those files. Now that it also refreshes
the dashboard's read model, that filter has two gaps.

Merging a change to the sync mechanism itself does not run it, so this PR
would land without ever posting a snapshot and the dashboard would stay stale
until the next registration happened to arrive. And there was no way to
re-run a sync on demand after a failed run or a drifted read model.

Adds workflow_dispatch, and puts the workflow and the snapshot script in the
path filter so a change to either re-runs it. Sync is idempotent β€” it diffs
against Cloudflare and does nothing when there is nothing to do β€” so the extra
runs are cheap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3VaS59vjtX2XdfhK28z7Y
bosquejun added a commit to is-pinoy-dev/ecosystem that referenced this pull request Aug 24, 2026
The changeset said the record "kept answering for weeks" after its file was
deleted. It did not. The validation dry-run on is-pinoy-dev/domains#173 β€”
scoped to that domain with it marked for destruction, against 71 records
fetched from Cloudflare β€” reported no changes needed, and the name does not
resolve. It was removed by some other route after the file was deleted.

The gap this changeset describes is unaffected: deleting a domain file still
emits no DELETE, and the run still prints "All domains are in sync" either
way. That indistinguishability is the point, and is what the warning fixes β€”
so the claim is restated as what the output does not tell you, rather than as
a record that was left live.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3VaS59vjtX2XdfhK28z7Y
@bosquejun

Copy link
Copy Markdown
Member Author

WIP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant