import { HookContextSchema, HookEvent } from '@objectstack/spec/data';import type { HookContext } from '@objectstack/spec/data';// Validate dataconst result = HookContextSchema.parse(data);
Service-principal label for audit attribution when the caller is not a real user (e.g. svc:flow:<flowName>)
organizationId
string
optional
Active organization ID (blessed developer-facing name)
accessToken
string
optional
isSystem
boolean
optional
True when the call was made with an elevated system context (engine self-writes)
skipTriggers
boolean
optional
True when record-change automation (flow triggers) must be suppressed for this write — e.g. package seed replay. Lifecycle hooks still run.
skipAutomations
boolean
optional
True when metadata-bound automation hooks must be suppressed for this write — e.g. data import with "run automations" unchecked, or import undo. Implies skipTriggers; code-registered system hooks (audit, security) still run.
positions
string[]
optional
Position names held by the caller (ADR-0090 D3; formerly roles), copied from ExecutionContext.positions. For hook READS only — e.g. tailoring a message, or branching a business rule the hook runs through its own ctx.api channel. Authorization is decided by the security service on the ExecutionContext (permissions / positions / derived posture); this is NOT an authorization input and a hook must not gate a write by testing it. A hook context carries no services key, so the sharing service cannot be called from one either — the sharing gates already ran inside the engine before the hook chain.
preserveAudit
boolean
optional
True when this write is a historical import that must KEEP its caller-supplied updated_at/updated_by (and the readonly audit family) instead of being stamped with the import instant. Server-set, opt-in, absent on normal writes; read by the built-in audit hook. A stamping policy, not an authorization input.
roles
never
optional
[REMOVED] HookContext.session.roles was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — it was declared, read by two dead exemption branches, and never produced: ObjectQL's buildSession() builds the session field by field and has never written roles, so every read resolved undefined and a guard keyed on it was dead code that merely LOOKED like an authorization decision. Delete the key. To gate a hook on the caller, read ctx.session.userId / ctx.session.isSystem; to judge PRIVILEGE, ask the security service, which evaluates the ADR-0095 vocabulary on the execution context — capability grants (permissions), placements (positions) and the derived posture — never a role-name string comparison (ADR-0090 D3 bans the role spelling outright). Nothing to migrate: a HookContext is built per operation by the engine and never stored, so no metadata source carries this key. NOTE an ACTION body's ctx.session is a different object and still carries its own roles array today; that surface is tracked separately and is not what this key was.
Id of the automation flow run performing this write, when it originates from a flow data node. Lets a hook recognize the run that OWNS state that run itself opened — the approvals record lock exempts the run holding the pending request.
attributedUserId
string
optional
The real human credited for a write whose authorization subject was the SYSTEM — e.g. the admin whose better-auth update-member-role call the identity adapter executes as isSystem. ATTRIBUTION ONLY: the audit writer records it as sys_audit_log.user_id; no security middleware reads it, and it never becomes the subject the write is authorized as.