Skip to content

test: guard the entity table against the platform's schema - #4

Merged
skeeeon merged 1 commit into
mainfrom
test/schema-drift-guard
Aug 22, 2026
Merged

skeeeon merged 1 commit into
mainfrom
test/schema-drift-guard

Conversation

@skeeeon

@skeeeon skeeeon commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Chore #4, with vendoring. The field table is hand-maintained rather than generated, and the docs have always warned it can lag a platform release — but nothing would ever tell you it had. PocketBase silently discards writes to fields a collection doesn't have, so a flag for a removed field keeps reporting success and doing nothing. That has already happened in this repo: nebula-ca --rotate-keys existed for a while against a collection with no such field.

The guard

cmd/testdata/schema.json is a vendored copy of the platform's collection schema, and the tests fail in both directions:

  • Spec → schema. A field (or KeyColumn, or LookupKey) the platform doesn't have. Catches writing into the void.
  • Schema → spec. A platform field the CLI neither exposes nor records a reason for. Catches lagging behind a release — this is the one chore test: guard the entity table against the platform's schema #4 was really about.

Refresh is one command, documented in the README and in the test itself:

cp ../platform/schema.json cmd/testdata/schema.json
go test ./...

Why vendored and not fetched in CI: the drift being guarded against is exactly the kind you want to see in a diff and approve, and a stale copy fails loudly the same way a missing one would. Fetching the platform's default branch would instead let an unrelated platform commit turn this repo's CI red. cmd/testdata/schema-source.txt records the version the copy came from (currently platform v0.2.0).

Two lists carry the judgement, and a third test stops them rotting — a stale entry hides the next real drift:

  • deliberatelyOmitted — per-collection, with the reason. Secrets (private_key, seed, creds_file), file uploads the CLI can't do (floorplan, logo), operator-only account keys, and nats_users.active, which is omitted because pb-nats consults it nowhere.
  • libraryOwnedFields — see below.

One finding worth your attention

nats_roles carries allow_response, allow_response_max and allow_response_ttl in this CLI, and the platform's schema.json declares none of them. The CLI isn't wrong — pb-nats creates that collection with those fields (internal/collections/manager.go, createRolesCollection) and reads them back when generating a user JWT (internal/types/converters.go). So they exist on a live database while being absent from the platform's dump, which is why they're listed as library-owned rather than as an error.

The part worth a second look is in the comment: createRolesCollection returns early when the collection already exists, so a database created before those fields were added never acquires them — and since the platform's schema.json doesn't declare them either, migrate up won't add them. That's the same shape as the system_account_id bug the platform fixed in v0.2.0, where record.Set on an undeclared field persisted nothing on every boot. If a database is in that state, allow_response reads back false forever and role-driven response permissions never reach the JWT. I haven't touched it — it's a platform/pb-nats question, not a CLI one.

Verified to fail

planted caught by
spec field absent upstream PocketBase discards the write
new platform field, unexplained neither exposes nor explains
omission entry for a removed field drop the entry
library-owned field now declared upstream no longer library-owned
collection missing from the schema is not in the platform schema

Companion: the docs fix for the verb columns is stone-age-io/platform-docs#4.

🤖 Generated with Claude Code

The field table is hand-maintained rather than generated, and the docs have
always said it can lag a platform release. What they never said is how you would
find out: PocketBase silently discards writes to fields a collection does not
have, so a flag for a field the platform removed keeps reporting success and
doing nothing. That has already happened here -- `nebula-ca --rotate-keys`
existed for a while against a collection with no such field.

So the tests now compare the table against a vendored copy of the platform's
schema, and fail in both directions: a spec field (or KeyColumn, or LookupKey)
the platform does not have, and a platform field the CLI neither exposes nor
records a reason for. The second is the one that catches lagging behind a
release; the first catches writing into the void.

Vendored rather than fetched in CI, deliberately. The drift this guards against
is exactly the kind you want to see in a diff and approve, and a stale copy fails
loudly in the same way a missing one would -- whereas fetching the platform's
default branch would let an unrelated platform commit turn this repo's CI red.
`cp ../platform/schema.json cmd/testdata/schema.json` is the refresh, documented
in the README and in the test, and schema-source.txt records the version.

Two lists carry the judgement, and a third test keeps them from rotting:
deliberatelyOmitted, which answers "why is there no flag for this" per field,
and libraryOwnedFields, which covers fields pb-nats creates on nats_roles that
the platform's schema.json does not declare -- allow_response and its two
companions. Worth reading that comment: pb-nats's createRolesCollection returns
early when the collection exists, so a database created before those fields were
added never acquires them and schema.json will not add them either. Same shape
as the system_account_id bug the platform fixed in v0.2.0, flagged upstream.

Each check was verified by planting the defect it describes -- a fictional spec
field, a new platform field, a stale omission entry, a library-owned field the
platform has since declared, a missing collection -- and confirming the failure
before reverting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@skeeeon
skeeeon merged commit 302d64f into main Aug 22, 2026
1 check passed
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