Skip to content
Menu

Approvals

View .md

The approval workflow makes a second person review every change that decides what SQL runs or where data goes before it takes effect. It is off by default. A host cannot tell a new installation from an existing one, so turning it on is the host’s decision, and Beacon logs a startup note while it is off.

builder.Services.AddBeaconServices(builder.Configuration, options =>
{
options.EnableApprovalWorkflow(x =>
{
x.RequireApprovedVersionToRun = true; // strict mode, recommended for regulated deployments
});
});
OptionDefaultWhat it does
Enabled (set by EnableApprovalWorkflow)falseTurns the workflow on.
AllowSelfApprovalfalseLet the requester (and others who shaped a change) approve it themselves. Only for a deployment with a single possible approver; Beacon warns at startup while it is on, and warns when fewer than two users can approve while it is off.
RequireApprovedVersionToRunfalseStrict mode: a subscription (and a saved-query MCP tool) runs only a version that carries an approval for exactly its content.
CoversMigrationJobstrueNew and changed migration job definitions need approval before any run.
CoversCustomSqlRulestrueNew and changed Custom SQL rules of data contracts need approval before any evaluation.
CoversRecipientDestinationstrueNew recipients and changed destinations (type, address, headers) receive nothing until approved.

The per-kind switches apply only while the workflow is enabled. Configuration is in code; there are no appsettings keys for it.

Decisions need the Approver functional permission. Admins hold it too.

  • The requester never approves their own change, however either of them signed in, and neither does anyone else who shaped it (for an AI actor’s change: the plan’s reviewer and whoever asked for its revisions). Admins are no exception.
  • Four eyes binds per Beacon user. Do not give one person two accounts that can approve.
  • API keys hold no functional permission and MCP has no approval tool, so neither can approve anything.
  • Approvers see every request on Approvals (/beacon/approvals), including requests for queries they otherwise cannot read; other users see the requests of resources they can read.

What counts as a change: the query’s executable content: its steps (added, removed, reordered), each step’s name, data source, SQL and parameters, and the final query. Every character counts, whitespace and line endings included. A change to only the query’s name or description goes live directly.

Submitting. Saving a change files a PendingApproval version with exactly the submitted content and its SHA-256 content hash, and an approval request naming the requester and the active version the change was made on. The live query is not touched.

  • A query has one pending request. A new submission by the same person supersedes their earlier request; while another person’s request waits, every submission of the query (a save, a restore, an AI actor’s refinement) is refused with 409 until a reviewer decides.
  • A new query goes live only once approved; until then it has no steps and runs nothing.
  • A version restore is submitted like a save.
  • Saving a query whose stored SQL differs from its active version (changed outside a versioned save) brings that SQL under review. Whoever saves it becomes its requester, so another Approver should review it.

Reviewing. The request detail shows the proposed version, the active version and a step-by-step diff (SQL, data source and parameters per step), the proposal’s content hash, and whether the caller may approve and why not.

Deciding. Approving sends back the content hash the reviewer saw (expectedContentHash, required). The approval applies only if:

  • the request is still pending (a second decision answers 409 and never overwrites the first);
  • no other version went live since the change was made (otherwise it is stale; submit again);
  • the proposal still holds the submitted content, and that content is what the reviewer was shown;
  • every step passes the save checks again (the execution gate and the data source’s metadata).

The decision, the move of the query to the new version and the version statuses commit in one transaction. Rejecting marks the proposal Rejected; a live version is never marked Rejected.

With the workflow on, runs that happen without the person who wrote the SQL (scheduled subscription runs, test deliveries, subscriptions an AI actor creates, and saved-query MCP tools) execute only reviewed content:

  • A subscription run executes the stored steps only when they are the content of the query’s active version. Otherwise the run is recorded as Skipped (“query change not approved”), the subscription and its Control Tower row show Not approved, and a test delivery answers 409.
  • A query stored before the workflow was on and never versioned since runs as stored outside strict mode, logging a warning each time; Beacon counts such queries at startup. Save the query to submit it.
  • In strict mode a run also needs an approval for exactly the active version’s content hash, so versions that went live while the workflow was off, or were approved before Beacon recorded hashes, do not run until approved.

Interactive previews run the caller’s own SQL under the caller’s permissions and are not compared.

With the workflow on, an actor’s new query or refinement is validated without running it (the save checks plus the engine’s non-executing validation: EXPLAIN without ANALYZE on PostgreSQL, MySQL and Snowflake, sp_describe_first_result_set on SQL Server and Synapse) and then submitted with the actor’s creator as the requester. The creator cannot approve it. See AI actors.

Migration jobs, Custom SQL rules and recipients

Section titled “Migration jobs, Custom SQL rules and recipients”

These are reviewed as change requests with the same rules: four eyes, one decision while pending, and an approval bound to what the reviewer was shown (expectedPayloadHash) and to the definition the resource still holds.

KindReviewedUntil approved
Migration jobNew job, or a changed source, steps, destination, mode or transformationEvery run, manual or scheduled, is refused and recorded as failed.
Custom SQL ruleNew rule, or changed SQL or data sourceThe rule is left out of every evaluation.
Recipient destinationNew recipient, or changed type, destination or headersDeliveries skip the recipient; each skip is logged. Attaching an existing recipient to a subscription is not reviewed.

When a change lies only in a part the review shows masked (a token, a header value), the approval must carry a comment saying how it was checked.

Resources stored before Beacon reviewed their kind, or last saved while the switch was off, keep working as before.

While the workflow is on, whoever requested or approved the version a saved-query tool runs cannot be the one who enables the tool (unless AllowSelfApproval). See Saved query tools.

Method and routeWhoWhat
GET /beacon/api/approvals/pendingApprover, or readers of the queryPending query approvals (paged).
GET /beacon/api/approvals/{id}Approver, or readers of the queryRequest detail: proposal, active version, diff, contentHash, mayApprove.
POST /beacon/api/approvals/{id}/approveApproverBody { "comment": "…", "expectedContentHash": "…" }.
POST /beacon/api/approvals/{id}/rejectApproverBody { "comment": "…" }.
GET /beacon/api/approvals/changesApprover, or readers of the resourceChange requests: migration jobs, Custom SQL rules, recipients.
GET /beacon/api/approvals/changes/{id}Approver, or readers of the resourceChange request detail with its payload hash.
POST /beacon/api/approvals/changes/{id}/approveApproverBody { "comment": "…", "expectedPayloadHash": "…" }.
POST /beacon/api/approvals/changes/{id}/rejectApproverBody { "comment": "…" }.

The reviewer is always the signed-in caller, never an id in the request. Decisions push an ApprovalUpdated event to the requester and reviewer over the realtime hub.

Every submission and decision is recorded in the security audit trail: approvals.query_change_* for query versions, approvals.change_* for change requests (with the kind, the resource id and the requests a submission supersedes) and approvals.ai_plan_* for AI actor plans. The reviewer is the event’s actor, and a decision commits together with its event; a decision on a query change or a change request also names its requester by user id. Neither the proposed content nor a comment is stored in the event.

  1. Make sure at least two people hold Approver.
  2. Enable the workflow; leave strict mode off for the first deployment.
  3. Check the startup log: Beacon counts queries whose stored SQL is not versioned or approved.
  4. Have owners save those queries (submitting them), approve them, then turn on RequireApprovedVersionToRun.