Per-requester authority: a job acts as the principal who asked for it #150

Closed
opened 2026-08-12 11:58:10 +00:00 by coilyco-ops · 2 comments
Member

Outcome

What a job may do is determined by who requested it, not by which pod is running it.

Why

Item 5 of the sixteen-gap comparison, deferred in the original batch and approved as scope by Kai on 2026-08-12.

The batch was cut on the reasoning that pod-level authority is acceptable while admission is a direct-message allowlist of one account, because the requester set and the trusted principal are the same person. That condition is expiring. #135 opens Sirens Deep to guild channel 1537024102886277210 for the August 19 demo, and #136 adds per-channel addressing with a trusted-user list. Once more than one person can summon Deep, "every job acts as the same identity with the same grants" stops being a defensible simplification and becomes a real hole.

#145 stated this plainly and recommended the sequencing that produced this issue:

Execution will run under pod-level authority with no per-requester attribution. Every job, whoever asked for it, acts as the same identity with the same grants. Nothing in the audit trail distinguishes them.

Recommendation: do not land this unit and a widened admission surface in the same window. If the guild opens first, items 5 and 6 should come before this unit rather than after it.

The guild opens first. So this comes first.

Scope

  • Grants resolve against the requesting principal carried on the job record, rather than against a single ambient identity.
  • A principal with no grant for an action cannot cause a job to perform it, regardless of what the model proposes.
  • The grant model is declarative and reviewable, consistent with how authority is already expressed in this portfolio — see the guardfile layer described in #122.
  • Denial is a defined job outcome with a stated reason, not a crash and not a silent no-op.

Explicitly out of scope

Items 7 and 8 — authorization beyond admission and approval gates — remain not approved. Kai un-deferred items 5 and 6 only.

The boundary matters and is easy to blur: this issue decides what a principal may cause a job to do. It does not add a second authorization checkpoint beyond admission, and it does not introduce a human-in-the-loop approval step before a job proceeds. If the design starts needing either, that is a signal to stop and get them approved rather than to absorb them quietly.

Design questions to settle before building

1. What does "acts as the principal" mean concretely? Two materially different readings:

  • Filtered grants, one credential. The pod keeps its single set of credentials; the harness narrows which tools and which arguments a given principal may reach. Simpler, no credential brokering, and the audit trail is honest about the fact that one identity performed the effect on someone's behalf.
  • Per-principal credentials. Each requester's jobs act with credentials belonging to that requester. Stronger, and it makes the downstream system's own audit trail correct — but it is credential brokering, which is adjacent to deferred item 11 (per-task secret brokering) and should not be entered accidentally.

The first is almost certainly right for this window. Stating both so the choice is made rather than defaulted.

2. Where does the grant table live? The access policy ConfigMap already models per-guild users and roles and is gaining per-channel structure in issue 136. A per-principal grant set is a different kind of statement from an admission gate, and the same coupling concern raised in 136 applies here.

3. What is the default for a principal with no entry? Recommend deny — consistent with Evaluate already failing closed on an unlisted guild.

Acceptance

  • A job's permitted actions are derived from its requesting principal.
  • Two principals with different grants submitting the same request produce different outcomes, and the denied one is told why.
  • A principal absent from the grant model is denied by default.
  • The grant model is reviewable as a document rather than inferred from code.

Depends on

  • #143 — the job record, which already carries the requesting principal as a field. That field was deliberately kept when this item was deferred, precisely so this issue would not need a retrofit.

Blocks

  • #145 — Ward-backed execution. That unit must not land before this one now that the guild is open.

Next owner

Engineer. Starts after August 19; the demo track owns the week.

## Outcome What a job may do is determined by who requested it, not by which pod is running it. ## Why Item 5 of the sixteen-gap comparison, deferred in the original batch and **approved as scope by Kai on 2026-08-12**. The batch was cut on the reasoning that pod-level authority is acceptable while admission is a direct-message allowlist of one account, because the requester set and the trusted principal are the same person. That condition is expiring. https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/135 opens Sirens Deep to guild channel `1537024102886277210` for the August 19 demo, and https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/136 adds per-channel addressing with a trusted-user *list*. Once more than one person can summon Deep, "every job acts as the same identity with the same grants" stops being a defensible simplification and becomes a real hole. https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/145 stated this plainly and recommended the sequencing that produced this issue: > Execution will run under pod-level authority with no per-requester attribution. Every job, whoever asked for it, acts as the same identity with the same grants. Nothing in the audit trail distinguishes them. > **Recommendation: do not land this unit and a widened admission surface in the same window.** If the guild opens first, items 5 and 6 should come before this unit rather than after it. The guild opens first. So this comes first. ## Scope * Grants resolve against the requesting principal carried on the job record, rather than against a single ambient identity. * A principal with no grant for an action cannot cause a job to perform it, regardless of what the model proposes. * The grant model is declarative and reviewable, consistent with how authority is already expressed in this portfolio — see the guardfile layer described in https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/122. * Denial is a defined job outcome with a stated reason, not a crash and not a silent no-op. ## Explicitly out of scope Items 7 and 8 — **authorization beyond admission** and **approval gates** — remain **not approved**. Kai un-deferred items 5 and 6 only. The boundary matters and is easy to blur: this issue decides *what a principal may cause a job to do*. It does not add a second authorization checkpoint beyond admission, and it does not introduce a human-in-the-loop approval step before a job proceeds. If the design starts needing either, that is a signal to stop and get them approved rather than to absorb them quietly. ## Design questions to settle before building **1. What does "acts as the principal" mean concretely?** Two materially different readings: * **Filtered grants, one credential.** The pod keeps its single set of credentials; the harness narrows which tools and which arguments a given principal may reach. Simpler, no credential brokering, and the audit trail is honest about the fact that one identity performed the effect on someone's behalf. * **Per-principal credentials.** Each requester's jobs act with credentials belonging to that requester. Stronger, and it makes the downstream system's own audit trail correct — but it is credential brokering, which is adjacent to deferred item 11 (per-task secret brokering) and should not be entered accidentally. The first is almost certainly right for this window. Stating both so the choice is made rather than defaulted. **2. Where does the grant table live?** The access policy ConfigMap already models per-guild users and roles and is gaining per-channel structure in issue 136. A per-principal grant set is a different kind of statement from an admission gate, and the same coupling concern raised in 136 applies here. **3. What is the default for a principal with no entry?** Recommend deny — consistent with `Evaluate` already failing closed on an unlisted guild. ## Acceptance * A job's permitted actions are derived from its requesting principal. * Two principals with different grants submitting the same request produce different outcomes, and the denied one is told why. * A principal absent from the grant model is denied by default. * The grant model is reviewable as a document rather than inferred from code. ## Depends on * https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/143 — the job record, which already carries the requesting principal as a field. That field was deliberately kept when this item was deferred, precisely so this issue would not need a retrofit. ## Blocks * https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/145 — Ward-backed execution. That unit must not land before this one now that the guild is open. ## Next owner Engineer. Starts after August 19; the demo track owns the week.
Author
Member

Design question 1 resolved: filtered grants over one credential

Direction from Kai, 2026-08-12 session.

Of the two readings this issue put forward, the choice is filtered grants over a single credential. The pod keeps its own credentials; the harness narrows which tools and which arguments a given principal may reach.

Per-principal credentials are not being built. That path is credential brokering, which is deferred item 11, and this issue explicitly does not enter it.

What this makes true, and what it does not

The enforcement boundary is inside this harness. A principal without a grant cannot cause the job to invoke the tool, so the effect never happens — that is real enforcement, not advisory.

What it does not do is make the downstream system's own audit trail correct. A Forgejo write caused by any principal is performed with the pod's credential, so Forgejo records the pod identity. Only this harness's record knows who asked.

That is an acceptable trade at this scale and it needs saying out loud, because it qualifies an acceptance criterion on the sibling issue — see below.

The hard part is arguments, not names

This is the bullet to plan around. #147 recorded it while discussing structured commands:

Two issues in the deploy repository — coilyco-bridge/deploy#358 and #359 — both failed for the same reason: the surrounding tooling bounds tool names but not tool arguments.

Filtered grants inherit that gap directly. "Principal A may call forgejo__create_issue" is a name bound and the existing tooling can express it. "Principal A may create issues in this repository only" is an argument bound, and that is the thing the tooling demonstrably does not do today.

A grant model that only filters names is close to no filtering at all once a tool takes an owner and repo argument. Assume argument bounding is in scope for this issue rather than discovering it partway through, and expect it to be the bulk of the work.

Express it in the guardfile vocabulary

The scope bullet asks for a declarative and reviewable grant model. The portfolio already has one — the ward KDL guardfiles described in #122, where paths are fixed to one repository with no owner or repo argument to redirect, and edits and deletes are denied by absence.

That is exactly the shape this issue needs, and it already solves argument bounding for the single-identity case. Extending it to resolve per principal is a smaller and more reviewable change than inventing a second authority vocabulary next to it. Worth evaluating first before designing anything new.

It also has a pleasant property for August 19: #122 teaches Deep to describe that vocabulary accurately. A grant model expressed in the same terms is one the agent can already explain.

Unchanged

Design questions 2 (where the grant table lives) and 3 (default for an unknown principal, recommended deny) remain open. Items 7 and 8 remain not approved.

## Design question 1 resolved: filtered grants over one credential Direction from Kai, 2026-08-12 session. Of the two readings this issue put forward, the choice is **filtered grants over a single credential**. The pod keeps its own credentials; the harness narrows which tools and which arguments a given principal may reach. **Per-principal credentials are not being built.** That path is credential brokering, which is deferred item 11, and this issue explicitly does not enter it. ## What this makes true, and what it does not The enforcement boundary is inside this harness. A principal without a grant cannot cause the job to invoke the tool, so the effect never happens — that is real enforcement, not advisory. What it does **not** do is make the downstream system's own audit trail correct. A Forgejo write caused by any principal is performed with the pod's credential, so Forgejo records the pod identity. Only this harness's record knows who asked. That is an acceptable trade at this scale and it needs saying out loud, because it qualifies an acceptance criterion on the sibling issue — see below. ## The hard part is arguments, not names This is the bullet to plan around. #147 recorded it while discussing structured commands: > Two issues in the deploy repository — `coilyco-bridge/deploy#358` and `#359` — both failed for the same reason: the surrounding tooling bounds tool *names* but not tool *arguments*. Filtered grants inherit that gap directly. "Principal A may call `forgejo__create_issue`" is a name bound and the existing tooling can express it. "Principal A may create issues **in this repository only**" is an argument bound, and that is the thing the tooling demonstrably does not do today. A grant model that only filters names is close to no filtering at all once a tool takes an owner and repo argument. **Assume argument bounding is in scope for this issue** rather than discovering it partway through, and expect it to be the bulk of the work. ## Express it in the guardfile vocabulary The scope bullet asks for a declarative and reviewable grant model. The portfolio already has one — the ward KDL guardfiles described in #122, where paths are fixed to one repository with no owner or repo argument to redirect, and edits and deletes are denied by absence. That is exactly the shape this issue needs, and it already solves argument bounding for the single-identity case. Extending it to resolve per principal is a smaller and more reviewable change than inventing a second authority vocabulary next to it. Worth evaluating first before designing anything new. It also has a pleasant property for August 19: #122 teaches Deep to describe that vocabulary accurately. A grant model expressed in the same terms is one the agent can already explain. ## Unchanged Design questions 2 (where the grant table lives) and 3 (default for an unknown principal, recommended deny) remain open. Items 7 and 8 remain not approved.
Author
Member

Refinement: the vocabulary is right, the binding point is not

My comment above recommended evaluating the ward KDL guardfile as the grant vocabulary before designing anything new. Having now read services/sirens-echo/forgejo-mcp.mcp.kdl rather than reasoning from #122's description, that recommendation holds and the reason is stronger than I stated — but it comes with a constraint I got wrong.

The vocabulary already does argument bounding, with constraints

Not just path fixing. Typed fields with bounds:

can list issue {
    path "/repos/coilyco-gaming/sirens-echo/issues"
    query {
        field "state" type="string"
        field "limit" type="integer" minimum=1 maximum=3 required=#true
        ...
    }
}

So the "bounds names but not arguments" gap that killed deploy#358 and #359 is already solved in this vocabulary. That is a significant head start — I had assumed argument bounding would be the bulk of the work, and the expressive half of it exists.

But the binding point is per-release, not per-principal

The file's own header:

Editing this changes Echo AND Deep: both MCP releases mount this one file.

One guardfile, mounted by both lanes, static for the lifetime of the release. There is no principal in the model at all — the boundary is a property of the deployment, not of the requester.

So "extend the vocabulary to resolve per principal," as I put it, understates the change. The vocabulary needs no extension. What needs designing is where a per-principal boundary binds, and the options are materially different:

  • Several guardfiles, selected per principal class. The MCP release mounts a set rather than one file, and the harness picks by principal. Keeps the guardfile as the single source of authority. Costs a file per class and a selection mechanism, and the "editing this changes both lanes" property gets more complicated, not less.
  • A harness-side filter above the guardfile. The guardfile stays the outer bound for everyone; the harness narrows further per principal before a call is made. Simpler to build, but authority now lives in two places and only one of them is the reviewable document — which is exactly the property #122 says is worth having.
  • Principal reaches the guardfile layer as an input. Most faithful, and the largest change, since it means ward-mcp itself grows a notion of who is asking. That is upstream work in coilyco-flight-deck/ward, not work in this repository.

The third is out of scope to build here but should be named, because choosing either of the first two forecloses on it quietly.

What does not change

The recommendation to start from this vocabulary rather than invent one. Whatever binds, it should be expressed in terms Deep can already describe — see #122, which teaches the agent exactly this model.

Design questions 2 and 3 remain open, and question 2 (where the grant table lives) is now partly the same question as this one.

## Refinement: the vocabulary is right, the binding point is not My comment above recommended evaluating the ward KDL guardfile as the grant vocabulary before designing anything new. Having now read `services/sirens-echo/forgejo-mcp.mcp.kdl` rather than reasoning from #122's description, that recommendation holds and the reason is stronger than I stated — but it comes with a constraint I got wrong. ## The vocabulary already does argument bounding, with constraints Not just path fixing. Typed fields with bounds: ```kdl can list issue { path "/repos/coilyco-gaming/sirens-echo/issues" query { field "state" type="string" field "limit" type="integer" minimum=1 maximum=3 required=#true ... } } ``` So the "bounds names but not arguments" gap that killed `deploy#358` and `#359` is **already solved** in this vocabulary. That is a significant head start — I had assumed argument bounding would be the bulk of the work, and the expressive half of it exists. ## But the binding point is per-release, not per-principal The file's own header: > **Editing this changes Echo AND Deep: both MCP releases mount this one file.** One guardfile, mounted by both lanes, static for the lifetime of the release. There is no principal in the model at all — the boundary is a property of the deployment, not of the requester. So "extend the vocabulary to resolve per principal," as I put it, understates the change. The vocabulary needs no extension. **What needs designing is where a per-principal boundary binds**, and the options are materially different: * **Several guardfiles, selected per principal class.** The MCP release mounts a set rather than one file, and the harness picks by principal. Keeps the guardfile as the single source of authority. Costs a file per class and a selection mechanism, and the "editing this changes both lanes" property gets more complicated, not less. * **A harness-side filter above the guardfile.** The guardfile stays the outer bound for everyone; the harness narrows further per principal before a call is made. Simpler to build, but authority now lives in two places and only one of them is the reviewable document — which is exactly the property #122 says is worth having. * **Principal reaches the guardfile layer as an input.** Most faithful, and the largest change, since it means ward-mcp itself grows a notion of who is asking. That is upstream work in `coilyco-flight-deck/ward`, not work in this repository. The third is out of scope to build here but should be named, because choosing either of the first two forecloses on it quietly. ## What does not change The recommendation to start from this vocabulary rather than invent one. Whatever binds, it should be expressed in terms Deep can already describe — see #122, which teaches the agent exactly this model. Design questions 2 and 3 remain open, and question 2 (where the grant table lives) is now partly the same question as this one.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo#150
No description provided.