Functions & access
Typed functions own operational data. Mutations deny anonymous callers by default.
Operational data (submissions, orders, comments) is written only through
functions defined in config or under graft/:
export const submitContact = defineFunction({
name: "submitContact",
kind: "mutation",
public: true, // greppable opt-out of the anonymous deny default
rateLimit: { limit: 5, windowSeconds: 60 },
input: submissions.fields,
handler: async (ctx) => {
const record = await insertRecord(ctx, submissions, ctx.input);
return { id: record.id, receivedAt: record.createdAt.toISOString() };
},
});
Serve at POST /api/fn/<name> (JSON object body) via a framework mount or
graft serve. Success returns { data }. Failure returns a GraftError with
fix. Every response carries x-graft-correlation-id.
Secure by default
- Mutations reject anonymous callers unless
public: true access: requireScopes("submissions:read")replaces the default with a scope rulerateLimitcounts against the audit log — no separate counter service- Every invocation writes an
audit_logrow (actor, correlation id, git SHA)
Context
Handlers receive FunctionContext: { input, db, actor, branch, request, correlationId }.
Use core helpers (insertRecord, listRecords, updateRecord, deleteRecord,
searchRecords) so authority and Zod validation stay enforced.
Destructive operations
Mark destructive: true for irreversible work. First call files an approval and
returns 403 with the id. A human runs graft approve <id>. The caller retries
with x-graft-approval (HTTP) or approval (MCP). Approvals are one-shot and
input-bound. See Audit & approvals.
Identity
@usegraft/auth verifies bearers. Graft does not mint identities. See
Auth.