// `content.publish` — copy a draft into `_published` (atomic per row) and // mark the draft published. Both tables are reactive, so a publish wakes live // consumer subscriptions. A missing draft (including another tenant's, which // reads as absent) comes back as `{ ok: false }` rather than an error. import { defineMutation } from '@voltro/protocol' import { Schema } from 'effect' export const publishContent = defineMutation({ name: 'content.publish', // Editors only — this is the procedure that makes content LIVE, so it is the // one whose guard matters most. Enforced before the executor and before the // transaction opens; satisfied by a signed-in session (`auth.resolveScopes` // grants `content:write`), refused to an anonymous caller. // // When your product grows a review step, this is where the vocabulary splits: // give publishing its own `content:publish` scope in `authz.ts` and leave // `content:write` on saveDraft. Split by BLAST RADIUS — going live is a // bigger blast than saving a draft. guards: [{ scope: 'content:write' }], input: Schema.Struct({ type: Schema.NonEmptyString, id: Schema.NonEmptyString }), output: Schema.Union( Schema.Struct({ ok: Schema.Literal(true), id: Schema.String, status: Schema.String }), Schema.Struct({ ok: Schema.Literal(false) }), ), })