Review workflows.
Last updated
Review plans
What it is. The approval policy attached to a datasource — it decides how a submitted query gets from "submitted" to "executed". Use it to require AI scoring, one or more human sign-offs, or both, and to auto-approve low-risk reads. Each plan is a sequence of stages; each stage names approvers (by role or specific user) and a minimum number of approvals before the query advances.
Configure it. Build plans at /admin/review-plans — start
from a built-in template or from scratch. A rule that names a role covers every holder of
that role, service accounts included. When a rule's role is held by active service
accounts, the editor warns under that rule and shows how many could approve. The warning
is advisory: it does not stop you saving the plan.
/admin/review-plans → caret next to Add review plan exposes four built-in templates that prefill the create modal.
/admin/review-plans → Add review plan. Stack approver rows to build multi-stage chains.- Open
/admin/review-plansand click Add review plan, or pick a built-in template from the dropdown caret next to it (Strict — writes need 2 approvals, Lenient — reads auto-approved, AI-only — no human approval, Standard — AI + 1 reviewer) to prefill the modal with sensible defaults. - Name and describe the policy — e.g. "Production writes — two reviewers".
- Pick the gates. Toggle Require AI review to score every query before it queues for humans; toggle Require human approval to demand at least one reviewer sign-off. Auto-approve LOW-risk reads lets
SELECTs skip humans entirely when AI risk is below the threshold. - Set thresholds. Minimum approvals is the number of distinct reviewers needed before the query advances; Approval timeout (hours) is when AccessFlow auto-rejects an idle
PENDING_REVIEWquery (it scans on a cadence set byACCESSFLOW_WORKFLOW_TIMEOUT_POLL_INTERVAL, defaultPT5M). - Build the approver chain. Click Add approver for each stage; each row names a role (or a specific user) and a stage number. Stages advance sequentially, and a single REJECTED decision at any stage terminates the query.
What can a review plan control?
A review plan is six settings plus an approver chain. Together they decide whether a query needs AI scoring, how many humans must sign off, which of them, and what happens if nobody does. Every setting below is per-plan, and a plan attaches to a datasource.
| Setting | What it does | When disabled / unset |
|---|---|---|
| Require AI review | Every query is scored for risk before it queues for humans. | The query skips PENDING_AI and goes straight to review. |
| Require human approval | At least one reviewer must sign off before execution. | The query is approved without a human — AI-only gating. |
| Auto-approve LOW-risk reads | SELECTs below the AI risk threshold skip human approval. |
Reads queue for review like any other statement. |
| Minimum approvals | How many distinct reviewers must approve before the query advances. | Defaults to one approval per stage. |
| Approval timeout (hours) | How long an idle PENDING_REVIEW query waits before auto-rejection. |
The query waits indefinitely for a decision. |
| Approver chain | Ordered stages, each naming a role or a specific user as approver. | No chain means no staged escalation — one flat approval step. |
Stages advance sequentially and a single Reject at any stage terminates the query. A user can never approve their own query, whatever their role.
Reviewer decisions. Reviewers can Approve, Reject, or
Request changes. Reject and Request changes both require a
non-empty comment — the server enforces this (HTTP 400 VALIDATION_ERROR),
and the UI disables the confirm button until the textarea is populated. The comment is
persisted on the decision row, rendered on the rejected stage of the timeline on
/queries/<id>, and surfaced to the submitter as a "Changes requested"
alert whenever the latest decision is REQUESTED_CHANGES and the query is
still PENDING_REVIEW. Approve still treats the comment as
optional.
Query status transitions. A query moves through these states:
PENDING_AI → PENDING_REVIEW → APPROVED → EXECUTED
↘ REJECTED (manual reviewer rejection)
↘ TIMED_OUT (approval-timeout auto-reject)
PENDING_REVIEW → CANCELLED (submitter only)
APPROVED → FAILED (execution error)
A single REJECTED decision at any stage terminates the query. If a query
sits in PENDING_REVIEW past the plan's approval timeout, AccessFlow
auto-rejects it (it scans on a cadence set by
ACCESSFLOW_WORKFLOW_TIMEOUT_POLL_INTERVAL, default PT5M).
Auto-approve reads lets SELECTs skip human approval entirely;
Require AI review still scores the read but won't block on a human.
Escalation & reminders
What it is. Between a request arriving and the approval timeout auto-rejecting it, nothing used to happen — a stalled chain was silent until the submitter found out by being rejected. Two optional settings on a review plan add the warning shots.
Where. Admin → Review plans, alongside Approval timeout: Escalate after (hours) and Nudge every (hours). Both are blank by default, and blank means off — an existing plan behaves exactly as it did before you set them.
-
Escalate after — when nobody has decided within this window, the
request is raised to the reviewers it is waiting on and your organization's admins,
once. It appears with an escalation notice on the request page and goes out over the
plan's notification channels; PagerDuty pages for it on channels that enable the
REVIEW_STALLEDtrigger. It must be shorter than the approval timeout — a longer window could never fire, so AccessFlow rejects it rather than letting you save a setting that quietly does nothing. - Nudge every — re-notifies the reviewers who still have not decided, on this cadence. It reaches only the people already on the hook, never admins, and never pages: a reminder is not an incident.
What it does not do. Escalation is notify-only. It never changes who is allowed to approve, and it never approves, rejects, or times out anything — waiting must not become a way around the approvers you configured. The request stays exactly where it was, with the same people able to act on it.
Grouped requests have no plan of their own, so a bundle uses the shortest escalation window among its members' plans. A member whose plan has escalation switched off simply does not contribute one. For bundles the escalation is recorded on the request group and nothing more — no notification and no queue badge yet, since grouped requests have no notification path at all.
Out-of-office delegation
What it is. An approval chain moves at the speed of its slowest human.
When a named reviewer goes on holiday, a request sits in PENDING_REVIEW
until the approval timeout auto-rejects it. Delegation lets a reviewer hand their review
duty to a named colleague for a set window instead.
Where. Any reviewer sets this themselves on Profile settings → Out-of-office delegation. No admin involvement, and no permission is required to delegate — a delegation from someone with no review rights simply confers nothing.
How to set one up. Choose a colleague, optionally narrow the delegation to a single datasource, pick a start and end time, and save. During the window the delegate becomes an eligible approver everywhere you were — query review, governed API requests, and grouped requests — and the requests show up in their review queue with a Delegated tag. Revoking is immediate and takes effect on the next decision; the record itself is kept as evidence for anything already approved under it.
What it cannot do. These limits are enforced by the server, not the interface:
- A delegation never grants a permission. The delegate still needs review rights of their own; delegation only widens which requests they may act on.
- The delegate can never act on a request the delegator submitted — the no-self-approval rule follows the borrowed identity, not just the person clicking.
- Delegation does not chain. If A delegates to B and B delegates to C, C gains nothing from A.
- One human still gets one vote. Covering for two absent approvers at once does not let someone satisfy a two-approval requirement alone.
- It stops the moment either party is deactivated, including via SCIM deprovisioning.
Audit. Every decision made under a delegation records both people — who clicked approve, and whose authority they used — so the trail never implies the absent reviewer acted. Admins can see every delegation in the organization under Admin, which is what makes an “on behalf of” entry interpretable months later.
Routing policies
What it is. Policy-as-code that decides a query's path automatically, after AI analysis and before reviewers see it. Use it to auto-approve routine reads, hard-block dangerous patterns, or escalate sensitive ones — instead of sending everything through the same review plan. Policies run in ascending priority and the first enabled one whose condition matches wins; anything unmatched falls through to the datasource's review plan exactly as before.
Configure it. Manage them at /admin/routing-policies (the
Routing policies entry in the Security nav group):
- Open
/admin/routing-policies(the Routing policies entry in the Security nav group, next to Review plans) and click Add policy. - Name the policy and optionally scope it to one datasource — leave the datasource blank for an org-wide rule. Set its priority (unique per organisation; lower runs first) and the enabled toggle.
- Build the condition with the guided builder: pick match ALL (AND) or match ANY (OR), then add leaf conditions — each can be negated (NOT). Operands include query type, referenced tables (glob, e.g.
payroll.*), AI risk level, AI risk score (with a comparison operator), requester role, requester group, time-of-day window, day-of-week, presence of aWHEREclause, presence of aLIMITclause, query shape (the query contains a join, a set operation such asUNION, a subquery, aWITHclause,GROUP BY,HAVING, an aggregate function or a window function — anywhere, including inside subqueries), the transactional (BEGIN…COMMIT) flag, and the pre-flight cost estimate — estimated rows (comparison against the engine's own EXPLAIN estimate, or the exact affected-row count for UPDATE/DELETE) and scan type (glob match on the plan's root operation, e.g.Seq*) — so a policy can route a 10-million-row sequential-scan DELETE differently from a 10-row indexed one. There is also estimated bytes scanned (entered in MB, GB, TB or PB), the amount of data the warehouse expects the query to read, so you can send, say, every query over 1 TB to a second reviewer. Only BigQuery, Snowflake and Databricks report this estimate; on every other datasource the condition never matches — so a negated one (NOT) matches every query there, and it is best scoped to a warehouse datasource. To refuse such queries outright, set a bytes-scanned cap on the datasource instead. And there is data budget used (%): how much of the person's own data budget on the datasource they have already read, so you can send a heavy reader's queries to a second reviewer at, say, 80 % — before the budget itself stops them. It only applies toSELECTqueries by someone a budget covers; for anyone else it never matches. - Choose the action. Auto-approve (skip human review), Auto-reject (block the query), Require approvals (force human review with an absolute minimum number of approvers), or Escalate (force human review, adding a delta on top of the review plan's minimum). The approver count applies only to the last two actions.
- Reorder policies any time with the per-row up/down controls — the order is the evaluation order.
QUERY_APPROVED /
QUERY_REJECTED with source: "ROUTING_POLICY"), and the query detail page
shows which policy matched. Routing policies are managed via the ADMIN-only
/api/v1/admin/routing-policies CRUD and /reorder endpoints.
/admin/routing-policies — ordered, attribute-based auto-decision rules; first match by priority wins, unmatched falls through to the review plan.Simulate it before you save it. A routing rule is easy to write and hard to predict — the same condition that blocks one dangerous DELETE can quietly block a nightly job nobody remembered. Every policy form has a Simulate button that dry-runs the draft against your own past queries and shows the blast radius first. Pick a date range (up to 90 days) and AccessFlow replays that traffic twice — once against the policies you have today, once with the draft added or replacing the one you are editing — then reports the difference between those two runs: how many past queries would take a different path, which of them, and which people would feel it. The same button sits on the masking and row security forms, so you can preview those before saving too.
External decision hook
What it is. A way to plug your own decision logic into routing when a rule does not fit the routing-policy builder. You run a small web service — an Open Policy Agent sidecar or an in-house policy service — and AccessFlow asks it about every query that no routing policy matched (a query already rejected by a cost or data limit, or whose AI analysis failed, goes to review without asking it), just before a standing access grant or the review plan gets to approve it. A policy that matched always wins, so the hook can never overrule a rule you wrote in AccessFlow.
What it can answer. Allow (no objection — the grant and the review plan decide as usual), Escalate (send the query to reviewers and add approvals on top of the plan's minimum), Require approvals (send it to reviewers with at least this many approvals — never fewer than the plan already asks for) or Reject. There is deliberately no approve: a hook can add checks, never remove them, so a broken or compromised service cannot wave queries through.
Configure it. Open /admin/decision-hooks (the Decision
hooks entry in the Security nav group, next to Routing policies) and
click Add hook:
- Name it and choose where it applies — the whole organisation, or one datasource. A datasource's own hook replaces the organisation-wide one for that datasource; switching it off means that datasource has no hook at all.
- Enter the endpoint URL. It must be
https://. Addresses inside your own network —localhost, private ranges, the cloud metadata address — are refused, because the URL is typed by an admin and called from inside your network. If your policy service runs next to AccessFlow (an OPA sidecar onhttp://localhost:8181, say), setACCESSFLOW_WORKFLOW_DECISION_HOOK_ALLOW_PRIVATE_NETWORK=trueon the backend; that also allows plainhttp://. - Set the timeout (100–10,000 ms, default 2,000). A slower answer counts as a failure.
- Set a signing secret of at least 32 characters. AccessFlow signs every
request with it exactly like webhook
notifications (
X-AccessFlow-Signature: sha256=…), and your service must sign its answer the same way. The secret is stored encrypted and never shown again; leave it empty when editing to keep it. - Decide whether to send the SQL. By default the request carries who is asking, the datasource, the query type, the tables it touches, the AI risk verdict, the cost estimate and where the request came from — but not the SQL text, which can contain real values such as names or account numbers. Turn on Send the SQL text only if your service may see them.
- Switch Enabled off for the first save, then use the Send a test request button in the hook's row. It sends a signed test request and shows what your service answered, or why it failed. A switched-off hook is never asked about real queries, so switch it on only once the test comes back the way you expect.
ACCESSFLOW_WORKFLOW_DECISION_HOOK_CIRCUIT_FAILURE_THRESHOLD,
ACCESSFLOW_WORKFLOW_DECISION_HOOK_CIRCUIT_OPEN_DURATION). Every answer is shown on
the query's detail page and recorded in the
audit log
(QUERY_DECISION_HOOK_EVALUATED). The
effective-access explainer names the hook that would
apply but never calls it. You can also manage hooks with the
Terraform provider (accessflow_decision_hook).
SQL review rules
What it is. A catalog of named, deterministic checks that every SQL query
is measured against — the third opinion next to the
AI analysis and
routing policies. Where the AI gives a judgement and a
routing policy gives a single verdict, a rule gives a named, repeatable finding:
"this DELETE has no WHERE clause", "this touches a protected
table", "this pattern starts with a wildcard". The same query always produces the same
findings, they cost nothing to compute, and they keep working with AI analysis switched off.
Authors see them in the editor as they type, before anything is submitted.
Eighteen built-in rules — missing WHERE on
UPDATE or DELETE, an always-true WHERE such as
1 = 1 that would otherwise defeat those two, SELECT *, a
SELECT with no row limit, ORDER BY without a limit, cross joins,
LIKE '%…', DROP, TRUNCATE, any DDL, a call to a banned
function (pg_sleep, sleep, benchmark,
load_file out of the box), any statement touching a protected
table you name by pattern (payroll.*, *.audit_log), and a
data change submitted outside a BEGIN … COMMIT transaction, and four
that catch risky migrations: a NOT NULL column added without a
DEFAULT (it fails, or rewrites the table, once the table has rows), a
PostgreSQL CREATE INDEX without CONCURRENTLY (it blocks writes
until the index is built — this one is checked on PostgreSQL datasources only), an
in-place column type change (it can rewrite the table and cut or reject existing values),
and SET NOT NULL on an existing column (it scans the whole table under a
lock). Every rule is
derived from the parsed statement alone — AccessFlow never connects to your database to
evaluate one.
Three severities, per rule. Each rule runs at one of
Off (not checked), Warn (the finding is recorded and shown to the author
and the reviewers, and changes nothing about the approval path), or Block (the
finding is recorded and the query can no longer be approved automatically —
a person must look at it). Out of the box the dangerous ones block — missing
WHERE, always-true WHERE, DROP,
TRUNCATE, banned functions, protected tables — and the rest warn: the
performance hints, any other DDL, a data change outside a transaction, and the four
migration checks, which are safe on an empty table and so start as warnings. Think twice
before blocking them: a schema change set is checked against the rules of every
environment its pipeline deploys to, and a blocking finding there stops the change set
from being saved at all, rather than sending it to a reviewer.
Configure it. Rulesets live at /admin/sql-review (the
SQL review entry in the Security nav group, next to Routing
policies; needs the Manage SQL review rules permission, which the built-in
Admin role holds):
- Give every datasource an environment. On the datasource's create wizard or settings page, set Environment to Development, Test, Staging or Production — or leave it unset. See Datasources.
- Open
/admin/sql-reviewand click Add ruleset. Name it, and bind it to one environment — or pick Organization default, the ruleset that applies to every datasource with no environment set (and to any environment that has no ruleset of its own). One ruleset per environment, one organization default. - Set the severities. The rules table lists every built-in rule with its default severity underneath; change only the ones you want to. For Protected table add the table patterns; for Disallowed function add or replace the banned names. Rules left at their default are not stored, so a ruleset stays a short list of what you changed.
- Enable it. A ruleset can be switched off with the Enabled toggle — note that a disabled ruleset bound to an environment evaluates no rules and does not fall back to the organization default, so switching production off never quietly re-enables the default there.
Access recertification campaigns
What it is. Recurring attestation campaigns that make someone periodically
re-confirm who still needs standing datasource access — the review control SOC 2 and
ISO 27001 auditors ask for. An admin schedules an org- or datasource-scoped campaign;
when it opens it snapshots the current standing grants into one item per
grant and notifies the eligible reviewers (multi-channel, plus an
attestation.campaign_opened WebSocket event). Reviewers work a
certify / revoke worklist that reuses the review-queue patterns
(self-review blocked, bulk-certify); a revoke routes through the normal
permission-revoke path, so access is actually removed.
The row limit that really applies. A grant's own row limit is not always what its holder gets: a lower limit on one of their groups, the datasource's own limit, or the system-wide ceiling can all cut it down. Each item therefore records both the limit configured on the grant under review and the limit that actually applies, with what set it (this grant, a named group, the datasource's own limit or the system-wide ceiling). When the two differ, the worklist shows it — for example Configured 5000 · applies 1000 (datasource cap) — so nobody certifies a limit that isn't in force.
Configure it. Manage campaigns from /admin/attestation and
certify items from the reviewer worklist at /reviews/attestations. Two
clustered-safe jobs run the lifecycle: one opens SCHEDULED campaigns at their
scheduled_open_at, the other closes OPEN campaigns at their
due_at and applies each campaign's pending-default (KEEP or
REVOKE) to anything a reviewer never got to. A completed campaign exports as a
CSV evidence file (who reviewed what, decisions, timestamps, and the
configured and applied row limit), and every
transition — ATTESTATION_CAMPAIGN_OPENED/CLOSED,
ATTESTATION_ITEM_CERTIFIED/REVOKED — lands in the tamper-evident
audit log.
Tune it. ACCESSFLOW_ATTESTATION_OPEN_POLL_INTERVAL and
ACCESSFLOW_ATTESTATION_CLOSE_POLL_INTERVAL (open / close scan cadence, both
default PT5M) and ACCESSFLOW_ATTESTATION_MAX_EVIDENCE_ROWS (row
cap before an evidence CSV is marked truncated, default 50000).
/admin/attestation — admins schedule recurring access-recertification campaigns; reviewers certify or revoke each snapshotted grant.
Unused-grant analytics. Certifying access is easier when you know which
grants nobody is using, so a clustered-safe background job folds the audit log's executed
queries and executed API calls into a per-grant usage summary — last used, how often, and
which of the granted tables (datasource grants) or operations (API-connector grants) were
ever actually exercised — and labels each grant NEVER_USED,
STALE, OVER_SCOPED, ACTIVE, or
INSUFFICIENT_DATA. The label shows on every attestation item and sorts the
reviewer worklist staleness-first, so never-used grants land on page one; admins and
auditors also get the standing report at /admin/over-provisioned-access
(filter by resource, holder, or recommendation, and export as CSV — gated on the
ACCESS_USAGE_REPORT_VIEW permission, held by ADMIN and
AUDITOR), and an opt-out notification nudges org admins once per cooldown
when a grant crosses the staleness threshold. It is advisory only:
nothing is ever revoked automatically, and a recommendation is never an input to routing,
grant coverage, or any other decision path. Grants inherited from a user group are not
covered.
Tune it. Grant-usage analytics reads nothing but the audit log, so the knobs are all about how far back it looks and how patient it is (defaults shown):
- Cadence & window:
ACCESSFLOW_ACCESS_USAGE_AGGREGATION_POLL_INTERVAL(PT1H— how often the job folds new audit events),ACCESSFLOW_ACCESS_USAGE_BACKFILL_WINDOW(P90D— how far back a newly-summarised grant starts observing). - Verdict thresholds:
ACCESSFLOW_ACCESS_USAGE_STALENESS_THRESHOLD(P60D— unused for longer than this isSTALE),ACCESSFLOW_ACCESS_USAGE_MIN_OBSERVATION_WINDOW(P14D— below this a grant isINSUFFICIENT_DATArather thanNEVER_USED),ACCESSFLOW_ACCESS_USAGE_OVER_SCOPED_THRESHOLD(0.5— exercised/granted ratio below which an active grant isOVER_SCOPED). - Caps:
ACCESSFLOW_ACCESS_USAGE_MAX_ROWS_PER_TICK(50000— audit events folded per organization per run; the remainder is picked up next run),ACCESSFLOW_ACCESS_USAGE_MAX_TRACKED_TARGETS(200— distinct exercised tables/operations retained per grant),ACCESSFLOW_ACCESS_USAGE_MAX_REPORT_ROWS(50000— row cap before a report CSV is marked truncated). - Staleness nudge:
ACCESSFLOW_ACCESS_USAGE_NUDGE_ENABLED(true— setfalseto switch the notification off),ACCESSFLOW_ACCESS_USAGE_NUDGE_COOLDOWN(P30D— minimum time between nudges for the same grant).
Privileged access. The unused-grant report can only see access that has
a grant to report on. Its counterpart at /admin/privileged-access lists the
access that never appears in a permission table at all: every user whose role carries
QUERY_ADMIN — the built-in ADMIN role or a custom role that
includes it — who can therefore submit against any datasource with no permission row,
and every holder of an unexpired break-glass grant, direct or inherited through a group.
One row per person, with the role that carries the bypass, each break-glass datasource
and when it expires, and how many queries — and how many break-glass runs — that person
has actually submitted and when last. Readable by ADMIN and
AUDITOR, recorded in the audit log on every read, and advisory in exactly
the same way: nothing here revokes anything.
Effective-access explainer
What it is. The answer to why — why would this query be auto-approved, why did that one go to a reviewer, who could approve it — without submitting a probe query against production to find out. An admin describes one hypothetical request (a user, a datasource, the SQL) and AccessFlow replays it through the very evaluators that decide real ones: the datasource and quota gates, the merged effective permission with each contributing grant named, every routing policy in priority order including the ones that nearly matched, the grant-covered fast path, the review plan, who could review it, and the row-security and masking policies that would apply. The result is an ordered, per-stage trace ending in the status the request would reach.
What it is not. A simulation writes nothing, executes nothing, publishes no event and never calls an AI provider — the AI verdict is the one stage a hypothetical request cannot have, so the trace says so rather than guessing. Every other approximation a hypothetical request cannot avoid is named in the response instead of smoothed over. It is a read-only trace of the decision, not a dry run of the query — the editor's Dry run action is for the latter.
Who can write to this table? The reverse question, which no permission
screen could answer: for one datasource and one table, every user who could submit a
read, a write or a DDL statement against it, one row per person, naming each source that
grants it — the direct permission row, each inherited group permission, an active
just-in-time grant (and whether it also skips review), break-glass, and the
QUERY_ADMIN holders who bypass the per-datasource gate entirely and would
otherwise appear nowhere. Table matching delegates to the enforcement gate's own rules,
so the report cannot drift from what actually happens at submission.
Where. In the app, under Access control → Access simulation
(/admin/access-simulations): a Query trace tab that lays the trace
out stage by stage — skipped stages included, and every routing policy in a table with
the one that decided it highlighted — and a Who has access tab for the reverse
lookup, with break-glass shown in a column of its own. Over the API they are
POST /api/v1/admin/access-simulations and
GET /api/v1/admin/effective-access. Both are gated by the permission that
already governs datasource grants; the who-can-write lookup is also open to the
AUDITOR role, which reads exactly this from the attestation and usage
reports already, so an auditor sees that tab alone. The same trace exists for
governed API calls and for
deployments,
where it can also answer "would a release at this moment be held by a freeze window?".
Every run is recorded in the audit log.
Deployment pipelines
What it is. The same review machinery, applied to CI/CD
deployments. A deployment pipeline names a provider (GitHub
Actions, GitLab CI, Azure Pipelines, Jenkins, CircleCI, Bitbucket Pipelines, or generic)
and holds ordered environments — dev,
staging, production — each of which can require review, set its
own approval count, override the review plan, opt in
to break-glass, and optionally name the database that backs it. A CI job asks AccessFlow for permission to release, the request flows
through AI release-risk analysis, routing policies and
human approval, and the job blocks on a fail-closed gate until every
required approval is granted. The submitter — for a pipeline, the user who owns the API
key — can never approve their own deployment.
/deployments — every deployment you submitted, with its status, target environment and release version.
/deployments/<id> — release metadata, the approval timeline, and the approve and reject actions for whoever may decide this one. Once it is approved, the same page carries the fail-closed releasability banner.
/reviews?tab=deployments). A deployment never appears here for its own submitter.
Configure it. Create pipelines at
/admin/deployment-pipelines (requires the
DEPLOYMENT_PIPELINE_MANAGE permission, held by ADMIN). Each
pipeline's settings page carries seven tabs. Alongside General and the
CI setup snippet sit Environments (promotion
order, tags, required approvals, review-plan override, break-glass opt-in),
Versions (which version each environment is running — see below),
Permissions (per-user and per-group can_trigger /
can_break_glass grants with an optional expiry — a user's effective grant is
the most-permissive union of their direct grant and every unexpired group grant),
Freeze windows, and Routing policies. The CI setup
tab carries copy-paste snippets for GitHub Actions, GitLab CI, Azure Pipelines and
curl, chosen from a toggle rather than preselected from the pipeline's provider; a
pipeline set to Jenkins, CircleCI, Bitbucket Pipelines or generic uses the curl one. Reviewers work
deployments on the Deployments tab of the review queue
(/reviews?tab=deployments, requires DEPLOYMENT_REVIEW,
held by ADMIN and REVIEWER) — the same page that holds
query and API-request reviews, with one tab per kind of request you may review and a
pending count on each. Opening a deployment from that queue does not cost you the
decision: approve and reject sit on the deployment's own page too, for anyone eligible
to decide it. Everyone sees their own at /deployments.
Where the pipeline id is. A CI job names its pipeline by id, never by name, because a trigger-only API key is not allowed to list pipelines and so has nothing to resolve a name against. You can copy that id from either place you meet a pipeline: the list's ID column, which shows a shortened form, and the pipeline's settings page, which shows it in full beneath the name. Both copy the whole UUID, not the shortened form.
/admin/deployment-pipelines — one row per pipeline, with its copyable id and CI provider.
Environment tags. Each environment can carry short free-form tags — at most ten, each up to 32 characters — with no fixed meaning: a customer, a region, a tier, whatever you group by. They exist so one application deployed for many customers can be modelled as one environment per customer, all tagged alike, and then read back as a single view. Add them in the Tags field on the Environments tab and filter on them in the version matrix below.
Which version is where. Once releases have flowed through the gate,
/deployment-versions shows one row per environment that has been deployed
to: its tags, the version it is running now, the version it was running before, when
it got there, and how the last deploy ended. Filter by pipeline, tag, environment
name, or narrow to just the environments that have fallen behind. The page is open to
pipeline admins, deployment reviewers and admins; someone who can only trigger a
pipeline reaches that pipeline's own matrix by picking it in the pipeline filter on
/deployments and choosing Version matrix. The pipeline's
own Versions tab shows the same rows, and additionally lists
environments nothing has been deployed to yet.
Each row says how far behind it is: how many newer releases have gone out since it last moved, and how many days passed between its deploy and the newest one. "Behind" is a plain comparison against the newest release that succeeded on the pipeline — version strings are yours, so nothing is parsed or ordered numerically. An environment whose last deploy failed or was rolled back shows the version it reverted to, or says the version is unknown when two rollbacks in a row left no earlier record to fall back on. Every environment also keeps a history, newest first, of every deployment aimed at it.
Freeze windows. A freeze window suspends releases for the whole
organization, one pipeline, or a single environment. It is either one-off
(a start and end instant) or recurring weekly (days of the week plus a
start and end time in a named IANA timezone — an end time before the start time spans
midnight). HOLD lets a deployment be submitted, analyzed and approved but
withholds releasability until the window closes, so the release proceeds by itself
afterwards with no re-approval. REJECT auto-rejects at submission instead.
Where several windows are active at once the most specific scope wins, and
REJECT beats HOLD. A window whose definition cannot be evaluated
— a bad timezone, say — counts as an active HOLD, never a
REJECT: a broken rule can stall a release but can never destroy a request.
Break-glass and rollbacks. An emergency deploy needs
both a can_break_glass grant on the pipeline and
allow_break_glass on the target environment — for everyone, with no admin
bypass. It skips AI analysis, routing and review, and pays for that with an instant
notification to every org admin, a prominent audit row naming any freeze window it
bypassed, and a mandatory retro-review an admin who is never the submitter must
acknowledge. After a deploy the pipeline reports its outcome; a
rollback on a review-required environment opens a follow-up review in the
reviewer queue that the submitter can never close.
/reviews?tab=rollbacks) — the follow-up worklist a rolled-back deployment opens.Wire up CI. See the deployment gate in CI for the GitHub Action, the GitLab CI template, the Azure Pipelines step template, and the plain-curl path, plus how to issue the service-account API key a pipeline authenticates with.
Tune it. ACCESSFLOW_DEPLOYGOV_TIMEOUT_CHECK (how often
deployments awaiting review are checked against their review plan's approval timeout,
default PT5M) and ACCESSFLOW_DEPLOYGOV_RELEASE_CHECK (how often
approved deployments are scanned to announce that they have become releasable, default
PT1M). Every transition lands in the
audit log.
Schema change sets
What it is. A governed way to change a database's structure —
create a table, add a column, drop an index — across your environments in order. A
schema change set is an ordered list of schema statements written once,
under a deployment pipeline, and then
promoted one environment at a time: dev, then
staging, then production. An environment only accepts a change
set that has already applied successfully on every lower environment that has a
database behind it, so production can only ever receive a change that worked in staging.
What is refused before it is saved. Every statement is checked with the
target database's own parser when you save. Statements that read or change data —
SELECT, INSERT, UPDATE, DELETE — are
refused with a message pinned to the statement, and so are a
BEGIN … COMMIT wrapper and several statements pasted into one box. If a
SQL review rule set to block matches, the save is
refused too; a rule set to warn shows its warning under the statement and lets
the save through. The first promotion freezes the list: from then on
the statements cannot be edited, so the text that reaches production is exactly the text
that ran in staging.
What it will not do. There is no rollback. Statements run one after another, each on its own, so if statement 7 of 12 fails, the first six stay applied and the promotion reads partially applied. Fix what is left with a new change set.
Configure it. First give each environment a database: on the pipeline's
Environments tab at /admin/deployment-pipelines, pick the
datasource that environment's schema changes should land on. Environments without one
are skipped by the ladder. Then open Schema changes → Change sets
(/schema-change-sets; requires the SCHEMA_CHANGE_MANAGE
permission, held by ADMIN), create a change set for the pipeline, and add
its statements. The detail page shows the promotion ladder: one step
per environment, each saying whether it is ready to promote, in progress, applied, or
blocked — and, when blocked, why (a lower environment not yet applied, an active freeze
window, no database bound).
Promote it. Promoting needs the DDL grant on the target datasource for the person clicking Promote, admins included. A promotion is refused inside a freeze window. It then goes through review as a request group — approvers come from the target datasource's review plan — the environment's own approval count and review-plan override do not apply to schema changes — and an environment that requires review is refused outright if that plan would not ask a person. Once approved, the statements run in order within about a minute, and the ladder moves on; from that point the promotion can no longer be cancelled. Reviewers are notified when a promotion needs them; the promoter is notified when it applies or fails.
Watch for drift. Schema changes → Schema drift
(/schema-drift) compares each environment's live structure against the
environment below it and lists the differences — a table missing, a column with a
different type — for someone to acknowledge. Scan now checks
one environment on demand. Scheduled scans are off until you turn them on per pipeline
with PUT /api/v1/schema-drift/configs/{pipelineId}, which also chooses what
to compare against: the environment below (the default), one reference environment, or
the structure recorded right after that environment's last successful promotion — the
one that catches changes made outside AccessFlow. Drift compares tables, columns and
foreign keys only; indexes, constraints, defaults, views and column lengths are
invisible to it, so "no drift" means nothing changed in what it can see. It never fixes
anything itself.
Tune it. ACCESSFLOW_SCHEMACHANGE_MAX_STATEMENTS (statements
per change set, default 50) and the
ACCESSFLOW_SCHEMACHANGE_DRIFT_* settings for the scan cadence, time budget
and caps — see the
deployment reference.
Every change set edit, promotion step, scan and acknowledgement lands in the
audit log.