Confirmation¶
Some operations are allowed and still deserve a question first.
Gantry Confirmation v0 is not an authentication or authorization mechanism.
Policy handles authorization. Confirmation records that the host application
indicated user consent before execution — nothing more. Gantry never claims that
a particular authenticated person approved anything, which is why there is no
approved_by field anywhere in this model.
policy = gantry.Policy(
name="prod-data-agents",
rules=[
gantry.allow.materialize(
sources=["raw.*"],
destinations=["prod.*"],
require_confirmation=True,
confirmation_code="PRODUCTION_WRITE",
confirmation_message="This will write to production data.",
),
],
)
run = await materialize(sql)
if run.status is gantry.RunStatus.AWAITING_CONFIRMATION:
print(run.confirmation.message)
run = await gantry.runs.confirm(run.id) # or gantry.runs.decline(run.id)
Nothing has happened while a run is parked
AWAITING_CONFIRMATION is neither a refusal nor an execution. The run is
durable, the policy decision is recorded, and no engine has been asked to do
anything. It resumes as the same run — the same immutable proposal — if the
host confirms.
Host-side control¶
gantry.runs.confirm
async
¶
confirm(
run_id: str,
*,
metadata: Mapping[str, object] | None = None,
) -> Run
The host says the user agreed. Resume the run and return what it became.
Idempotent and single-shot: the transition out of AWAITING_CONFIRMATION is
a compare-and-set in the store, so of any number of concurrent or repeated
confirmations exactly one releases the work and the rest return the run as
it now is.
This records that the host supplied confirmation. It does not record that an authenticated person approved anything — Gantry cannot know that, and v0 does not claim it.
Source code in gantry/runs/service.py
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
gantry.runs.decline
async
¶
decline(
run_id: str,
*,
metadata: Mapping[str, object] | None = None,
) -> Run
The host says no. The run becomes terminal and nothing external runs.
Source code in gantry/runs/service.py
203 204 205 206 207 208 209 210 211 212 213 214 | |
gantry.runs.awaiting
¶
awaiting() -> tuple[str, ...]
Run ids parked in this process, for a host that wants to list them.
Source code in gantry/runs/service.py
217 218 219 220 | |
gantry.runs.service.ConfirmationError
¶
Bases: RuntimeError
A confirmation that cannot be honoured, refused rather than approximated.
Source code in gantry/runs/service.py
44 45 | |
What was asked¶
gantry.ConfirmationRequirement
dataclass
¶
Whether to ask, and everything to say when asking.
Several rules can each want confirmation; they collapse into one requirement carrying several reasons rather than into several prompts. Being asked twice about one operation teaches a user to click through.
Source code in gantry/confirmation/model.py
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
gantry.ConfirmationReason
dataclass
¶
One reason the host should ask before this runs.
Source code in gantry/confirmation/model.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | |
gantry.ConfirmationReasonCode
¶
Bases: StrEnum
Why the host should ask, in machine-readable form.
Small on purpose. A code is for the host to decide how loudly to ask — a production write may warrant a different prompt from an expensive query — and a vocabulary nobody can enumerate cannot be used for that.
Source code in gantry/confirmation/status.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
What was answered¶
gantry.ConfirmationRecord
dataclass
¶
What was asked, what was answered, and which proposal it was about.
proposal_hash is the binding that makes the record mean anything: a
confirmation is for one immutable proposal, so confirming one statement can
never license running a different one. A run whose proposal changes needs a
new run and a fresh confirmation.
Source code in gantry/confirmation/model.py
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | |
gantry.ConfirmationStatus
¶
Bases: StrEnum
Whether a run needed confirmation, and what the host said.
NOT_REQUIRED is a real answer rather than an absence: a run that never
needed asking about reads differently from one still waiting, and both have
to be distinguishable months later.
Source code in gantry/confirmation/status.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
NOT_REQUIRED
class-attribute
instance-attribute
¶
NOT_REQUIRED = 'not_required'
No rule asked for confirmation. Execution proceeded on policy alone.
REQUIRED
class-attribute
instance-attribute
¶
REQUIRED = 'required'
Policy allowed the operation and asked that the user be told first.
CONFIRMED
class-attribute
instance-attribute
¶
CONFIRMED = 'confirmed'
The host application said the user confirmed. Not an authenticated approval.
DECLINED
class-attribute
instance-attribute
¶
DECLINED = 'declined'
The host said no. Terminal, and nothing external ran.
The transition¶
gantry.runs.compare_and_set
¶
Transition a run if it is still in expected, atomically.
Used by confirmation, where only one caller may be the one that lets work start. A store that predates this method cannot make the guarantee, so the failure is explicit rather than a silently weaker transition.
Source code in gantry/runs/store.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | |