Goldie Blocks

Content Writes Receive One Server-Constructed Authority

Why content mutations resolve their actor and capabilities from a server-held Document session instead of accepting createdBy from the client

Decision

Every mutation that changes an existing Document, Module, or Block constructs its write authority from a server-held session binding. Ordinary registered writes enter with authSessionId plus the target documentKey; interview-agent writes enter with the Agent thread ID and resolve the session and its bound Document from authSessions.by_agent_thread_id. Both inputs normalize to one Document-scoped authority. A client-supplied createdBy string never grants write access and is not copied onto newly written rows.

The edit envelopes and kind-specific executors keep their existing invariants. They receive the constructed authority; they do not each re-read ownership or invent capability conditions. When a mutation corresponds exactly to a Folio operation, server enforcement reconstructs its facts and requires that resolved operation to be available before the executor runs.

Current implementation and boundary

The two server-held session inputs normalize through one authority constructor to the same DocumentWriteAuthority. withDocumentEdit, withModuleEdit, and withBlockEdit resolve their target kind and construct trusted Folio facts before invoking convex/folio/writePolicy.ts. Mutation rules receive the resolved rows and actor they need, but cannot provide access, lifecycle, relationship, payload, lock, or availability facts.

The interview Module path additionally proves that its stored thread-bound Document version is the current live head before it constructs the Module edit view. Exact Block-agent writes no longer run a separate capability guard or construct authority twice.

Pre-binding creation from a Library source remains a bootstrap case because the target Document does not exist when its session/source guards run.

Current Builder-first adapter

This seam does not yet mean authenticated multi-user authorization. In the current Builder-first MVP, authSessions is a server-held Document binding but the session creation/attachment flow is public, no access record persists the real role, and the identity-provider claim step has not landed. The adapter therefore uses the bound Document's stored owner as the current actor proxy and resolves Builder capabilities.

That is still materially safer and structurally important: knowing an owner key is no longer sufficient to cross a content mutation, a session bound to another Document is refused, and future identity/access work has one adapter to replace. It is not a claim that the permissions gap is closed. The access record and claim step remain owned by Permissions and Locking and the anonymous-drafting plan respectively.

Why not keep createdBy until authentication lands

createdBy is authorship data, not a credential. Accepting it from the client lets any caller that knows an owner key satisfy the ownership guard, and it makes every mutation carry a second answer to who is acting. Waiting would make the access-record migration revisit every write path. Constructing authority now gives that later work one seam while keeping the present limitation explicit.

Boundaries

  • Session lifecycle, payment simulation, seeding, backfills, and append-only event recording are operational functions rather than Folio content edits.
  • createdBy remains on stored rows as immutable authorship/provenance. The server stamps it from constructed authority when a content mutation creates a new row.
  • Direct Content/About/Field-structure edits, Module output, Module About, and lock changes do not yet have exact Folio-operation vocabulary. They still pass through the authority seam; they must not be mislabelled as propose-edit to manufacture a resolver case.

On this page