Home / Examples / A CI job on a hosted runner, under a service account
A CI job on a hosted runner, under a service account
The deployment shape: Actions runner (a hosted CI job), variant ci.
Persistence, and reach beyond the turn. A service account rather than a person, a push to a code host, and the irreversible class arriving in a deployment nobody thinks of as an agent.
Before you scroll
The label
One line, on the outside, for anybody. Two numbers matter: excess answers the question this document exists for, and unbounded excess is the only number on it that buying a control moves.
| Field | Value | Meaning |
|---|---|---|
| Shape | Actions runner (a hosted CI job), ci | The named deployment, in the product's published words |
| Grant | 8 of 23 primitives | Everything the agent can do |
| Mandate | 5 primitives | What the deployer authorised and expected |
| Excess | 4 | In the grant, not in the mandate. The finding |
| Unbounded excess | 3 | Excess whose barrier is not a control. The only number a control purchase moves |
| Irreversible | 3 | Granted capabilities with undo: no, as published |
| Widest reach | world | The furthest reach class in the grant |
| Measured | 8 of 8 rows | Rows seen directly against rows derived |
| As at | 11 September 2026, pack v0.8.0 | The date and the source version |
1. The shape
An ephemeral CI job with no agent, no hooks, and one platform-enforced grant: the workflow's permissions block. MEASURED on 26 August by measure.py inside the runner (the library's second entry), translated into findings on 5 September. Unrestricted egress; the token cannot write.
Tools in this shape: the job's shell. Profile version 2026-08-26, surface ci.
What the reach classes mean here, which is the profile's to say rather than the grammar's: host means the runner - destroyed after the job; not your machine, tenant means the repository, with the workflow's token, world means the internet, unrestricted.
What it cannot reach, and why. A grant is as much about the boundaries that hold as the ones that do not.
| What | Why | Source |
|---|---|---|
| your machine | a hosted runner | library entry 2 |
| the repository, for writing | the token is contents:read | evidence: ci.permissions-block |
2. The grant, measured
Everything the agent can do: 8 of 23 primitives. Ordered irreversible first, then weakest barrier first. Reversibility is a property of the action, not a severity, and stating it as the reason is what keeps the ordering descriptive.
| Capability | Undo | Barrier | Known by | The mandate | |
|---|---|---|---|---|---|
| ● | delete.file.host Delete files anywhere the account can reach | no | none (not a control) | observed | excess (unstated) |
| ● | read.file.host Read any file the account can reach | no | none (not a control) | observed | excess (unstated) |
| ● | send.endpoint.world Reach any host on the internet | no | none (not a control) | observed | authorised |
| ● | execute.process.host Run programs as the account | with-effort | none (not a control) | observed | authorised |
| ● | write.file.host Change any file the account can reach | with-effort | none (not a control) | observed | excess (unstated) |
| ● | write.file.project Change the project it is working on | with-effort | none (not a control) | observed | authorised |
| ○ | write.repository.project Commit to the repository it was pointed at | with-effort | boundary | observed | excess (unstated) |
| ● | read.file.project Read the project it is working on | yes | none (not a control) | observed | authorised |
| Barrier | What stands in the way | Is it a control | |
|---|---|---|---|
| ● | none | nothing in the way | no |
| ◉ | expectation | a rule in prose, enforced by nobody | no |
| ◐ | setting | a switch the agent's own account can flip | no |
| ○ | boundary | enforced above the grant, out of the agent's reach | yes |
3. The mandate, elicited
A CI job on a hosted runner. Check out the code, build it, run the tests, fetch what it needs, and - when a release is cut - push the tag. I did not want it reading credentials beyond its own token.
A mandate is elicited rather than measured, in minutes, because the deployer already knows it. This one was not: it is a first draft written to be argued with, authored 2026-09-09 by the site, as a starting point - not measured, not surveyed; the first thing to argue with. It authorises 5 primitives, refuses 1 and says nothing either way about 17. Propose a change to it.
| Capability | What the mandate says about it |
|---|---|
write.repository.tenant | deliberately in the mandate: most release workflows push a tag, and this profile's token is contents:read - so this row is a shortfall, and the kind the game calls 'a gap you were counting on' |
delete.file.host | unstated: the runner is destroyed after the job |
4. The delta, derived
/data/deltas/github__actions-runner__ci__ci-job.json with the version of both inputs pinned, the time it was computed and the version of the computation that produced it. The release gate recomputes it on every build and fails on a single row of disagreement, which is how a machine holds a rule that forbids the act rather than the artefact. Why this changed this morning.Excess: 4. In the grant and not in the mandate. That is the published definition and it is wider than the set the mandate refused outright: 0 were refused and 4 were never mentioned. A capability the mandate never mentioned was not authorised, and hiding the split would be the other kind of dishonesty.
Unbounded excess: 3. The excess whose barrier is one of the first three rows: nothing, a rule somebody wrote down, or a setting the agent's own account could change. None of those bounds anything.
The excess is listed as prohibitions below.
Shortfall: 1. Asked for and cannot: write.repository.tenant.
The same facts, as a figure
The table above is complete and it is the wrong shape for the one question this document exists to answer, which is how much of the right hand side has nothing on the left. A mark with no line reaching it is excess.
5. The prohibitions
The enforceable projection of the delta: one sentence per excess capability, each carrying the layer it would be enforced at and whether it is enforced today. 3 of 4 are not enforced today. They are sentences, not controls.
| Prohibition | Barrier today | Enforced today | Layer a control would sit at | |
|---|---|---|---|---|
| ● | The agent must not delete files anywhere the account can reach. delete.file.host | none | not enforced (a sentence, not a control) | boundary |
| ● | The agent must not read any file the account can reach. read.file.host | none | not enforced (a sentence, not a control) | boundary |
| ● | The agent must not change any file the account can reach. write.file.host | none | not enforced (a sentence, not a control) | boundary |
| ○ | The agent must not commit to the repository it was pointed at. write.repository.project | boundary | enforced | already enforced above the grant |
6. The provenance
sha256:d6d4ba40f1fb1f93f66. The source bytes.No row here was obtained by probing anybody's system. A row is measured only from a system we are entitled to run, or from the vendor's own published documentation. Causing a computer to output data intending unauthorised access is an offence with no damage requirement and no research defence.
7. What this is not
Three clocks, and only the first is ours. An ABP is exactly as fresh as the twin, and the twin is exactly as fresh as its connection to somebody else's systems. That is a parameter rather than a defect to hide, and the gap between the second clock and the third belongs to the risk layer, because how much it matters depends on the assets.
| Clock | What it measures | Who controls it |
|---|---|---|
| The ABP's clock | When the grant was last measured or calibrated | Us, and it can run on events |
| The twin's clock | When the twin last synchronised with the real environment | The customer's integration |
| Reality's clock | Never stops | Nobody |
Follow one capability through the model
The fifth graph rule says a path should read as a sentence in the reader's own language, and it is the acceptance test for this model:
agent github-actions-hosted-runner is-granted capability delete.file.host bounded-by barrier none which-exceeds mandate ci-job and-is undo no.
The four objects · The capability grammar · The barriers · This shape as JSON · This mandate as JSON