Skip to content

chore(deps): bump core to 0.37 and commons to 0.31 - #4

Merged
cuibonobo merged 1 commit into
mainfrom
deps/core-0.37-permissions-as-associations
Sep 23, 2026
Merged

cuibonobo merged 1 commit into
mainfrom
deps/core-0.37-permissions-as-associations

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Syncs the plugin to @haverstack/core 0.37.0 / @haverstack/commons 0.31.0, up from 0.31.0 / 0.25.0. The headline change is that record permissions are associations now.

Permissions are associations

Permission is gone. A record's ACL is AuthorityAssociation[] — a permission element whose bit is its label and whose grantee carries a required role, or an anyone element spelling world-read affirmatively. [{ access: 'public' }] becomes [{ kind: 'anyone', label: 'read' }], and the associations key narrows to DataAssociation[].

No src/ module needed a code change for it. The plugin reads both sets and writes neither, and core partitions them into two projections over one table, so an association loop can never be handed an ACL element. The churn is test fixtures plus the playground seed.

The part the compiler could not catch

docs/design.md § Rebuilding on change documented a "did anything change?" gate built on RecordFilter.updatedAt. That query still typechecks and is now unsound.

Six ops — associate, dissociate, permissions, reparent, unlist, list — leave version and updatedAt exactly where they stand (NO_BUMP_OPS in core's record-changes.ts). Between them they cover site membership, for-site scoping, embed attachments, page moves and publishing a draft — most of what a site owner changes between builds. The gate would have skipped rebuilds for precisely those edits.

The section now puts the gate after load, fingerprinting the index including associations and permissions. That shape also catches purges, which the updatedAt gate never did. The watch-mode section reads RecordChange.ops, never version.

Smaller calls

  • Attachment candidates sort by core's own total order (createdAt, then id), so two uploads landing in the same millisecond stage identically on every build.
  • The new attachmentRecordId pointer is deliberately not read, and assets.ts says why: content addressing gives a fileId exactly one staged path and the embed map points every known filename at it, so resolving the originating upload would pick the same string.

Breaking: existing databases must be recreated

Core 0.35 moved permissions into the associations table and took parentId off the version tier. The SQLite schema is CREATE TABLE IF NOT EXISTS only and ships no migration, so an older database opens and then fails on the first permission write — its associations table keeps a kind constraint refusing permission/anyone and has no related_role or attachment_record_id column.

Verification

All five CI checks pass locally (format:check, lint, 86 tests, build, typecheck).

Dogfooded end to end in eleventy-example against a recreated stack: seed → inspect → check → build for both sites across default / custom / ejected templates → publish dry-run, real write, idempotent re-run, and cross-site canonical. 19 files out, embeds substituted to distinct fileIds, unlisted records built but absent from sitemap and feeds, collections.* populated.

The no-bump semantics were confirmed empirically rather than taken from the changelog: associate + mutate({ unlisted: false }) + grantAccess on a seeded article moved it onto a second site's listing, into the sitemap and into the feed, while version stayed at 2 and updatedAt never moved.

Release

Carries one pending minor changeset and no version commit, so merging lets the changesets bot open its usual chore: version packages PR and cut 0.2.1 → 0.3.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_0159RJgPRGz3VpwQGUoALrqB

Permissions are associations now. `Permission` is replaced by
`AuthorityAssociation` — a `permission` element whose bit is its label and
whose grantee carries a required `role`, or an `anyone` element spelling
world-read affirmatively — and `associations` narrows to `DataAssociation[]`
so an app editing tags is never handed the ACL.

The plugin reads both sets rather than writing them, so no module needed a
code change: `StackRecord`'s two projections keep an association loop from
ever seeing an authority element. The test fixtures and the playground seed
move to `[{ kind: 'anyone', label: 'read' }]`.

Attachment candidates now sort by core's own total order (`createdAt`, then
`id`) rather than `createdAt` alone, so two uploads landing in the same
millisecond stage the same way on every build. The association's new
`attachmentRecordId` stays unread on purpose, and `assets.ts` says why:
content addressing gives a fileId exactly one staged path, so resolving
which upload a reference came from would pick the same string.

Correct `docs/design.md § Rebuilding on change`, which the no-bump work
invalidated. Since 0.33-0.35 the journal-tier ops — associate, dissociate,
permissions, reparent, unlist, list — leave `version` and `updatedAt` exactly
where they stand, so the sketched `updatedAt: { after }` gate would skip a
rebuild for precisely the edits this plugin cares about: adding a record to a
site, scoping one with `for-site`, embedding an attachment, moving a page,
publishing a draft. The gate moves after load and fingerprints the index,
associations and permissions included; a watching `--serve` reads
`RecordChange.ops`, never `version`.

Existing databases must be recreated — the SQLite schema is create-if-not-
exists only and ships no migration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cuibonobo
cuibonobo merged commit ec7651d into main Sep 23, 2026
8 checks passed
@cuibonobo
cuibonobo deleted the deps/core-0.37-permissions-as-associations branch September 23, 2026 21:12
@github-actions github-actions Bot mentioned this pull request Sep 23, 2026
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