Attribution: every effect is traceable to the principal who requested it #151

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

Outcome

Reading the record of any effect the harness caused answers "who asked for this" without inference.

Why

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

Authority and attribution are routinely treated as one thing and are not. Authority is enforcement — what a principal may cause. Attribution is evidence — what the record says happened and on whose behalf. A system can have either without the other, and the failure modes differ: missing authority lets the wrong thing happen, missing attribution means nobody can find out that it did.

#145 named the current state exactly:

Nothing in the audit trail distinguishes them.

That is tolerable while the requester set is one account. #135 and #136 end that condition — a guild channel with a trusted-user list means several people can cause effects through one workload.

There is a second reason this one is worth doing properly. The August 19 stream asks how permissions work when someone borrows an agent, and #122 exists because Deep is the agent on that panel whose authority is declarative and reviewable. An agent that can describe its own boundaries but cannot say who asked it to do something is answering half the question.

Scope

  • The requesting principal is carried from admission through every span, log, and job state transition the request produces.
  • Effects on external systems are attributable: a Forgejo write caused by a job can be traced back to the principal who submitted it.
  • Jobs are listable by requesting principal, which #143 already requires — this issue makes that listing trustworthy rather than merely present.
  • Attribution survives the job. A terminal job's record still names its requester.

Explicitly out of scope

  • Items 7 and 8 — authorization beyond admission, and approval gates. Still not approved. Kai un-deferred items 5 and 6 only.
  • Enforcement. This issue records who asked. It does not decide what they may do — that is the per-requester authority issue, filed alongside this one.
  • Telemetry body-safety. Issue #55's contract is unaffected. A principal identifier is not message content, and nothing here proposes putting bodies in spans.

Design questions to settle

1. What identifies a principal in the record? A Discord user ID is stable and opaque, which is the right property for a durable record; a handle is readable and mutable. The existing SIRENS_ECHO_PRINCIPAL_HANDLE / SIRENS_ECHO_PRINCIPAL_USER_ID pair suggests both are already thought of together. Recommend the ID as the recorded value, with any handle treated as display only.

2. Does attribution reach telemetry as a span attribute? #146 is adding a job id as a promoted attribute on spans and logs and has already solved this shape. If a job id is queryable, and a job id resolves to a principal, that may be sufficient indirection — and it keeps a user identifier out of the telemetry store. Worth deciding deliberately rather than attaching the principal everywhere by reflex.

3. Retention. A durable record of who asked for what is exactly what makes attribution useful and exactly what makes it a data question. Not blocking, but it should be answered before the guild widens beyond a two-party channel.

Acceptance

  • Given any job, its requesting principal is retrievable from the record without inference.
  • Given a principal, their jobs are listable.
  • An effect on an external system can be traced to the principal that caused it.
  • Attribution is present on jobs that failed or were cancelled, not only on successful ones.

Depends on

  • #143 — the job record already carries the requesting principal as a field, kept deliberately when this item was deferred.

Blocks

  • #145 — Ward-backed execution.

Sequencing note

This is the lighter of the two un-deferred items and has no hard dependency on the other. Attribution needs the principal field, which exists; per-requester authority needs a grant model, which does not. This one can land first, and doing so makes the authority work easier to verify — you can see who asked before you start denying them.

Next owner

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

## Outcome Reading the record of any effect the harness caused answers "who asked for this" without inference. ## Why Item 6 of the sixteen-gap comparison, deferred in the original batch and **approved as scope by Kai on 2026-08-12**. Authority and attribution are routinely treated as one thing and are not. Authority is enforcement — what a principal may cause. Attribution is evidence — what the record says happened and on whose behalf. A system can have either without the other, and the failure modes differ: missing authority lets the wrong thing happen, missing attribution means nobody can find out that it did. https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/145 named the current state exactly: > Nothing in the audit trail distinguishes them. That is tolerable while the requester set is one account. https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/135 and https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/136 end that condition — a guild channel with a trusted-user list means several people can cause effects through one workload. There is a second reason this one is worth doing properly. The August 19 stream asks how permissions work when someone borrows an agent, and https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/122 exists because Deep is the agent on that panel whose authority is declarative and reviewable. An agent that can describe its own boundaries but cannot say who asked it to do something is answering half the question. ## Scope * The requesting principal is carried from admission through every span, log, and job state transition the request produces. * Effects on external systems are attributable: a Forgejo write caused by a job can be traced back to the principal who submitted it. * Jobs are listable by requesting principal, which https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/143 already requires — this issue makes that listing trustworthy rather than merely present. * Attribution survives the job. A terminal job's record still names its requester. ## Explicitly out of scope * **Items 7 and 8** — authorization beyond admission, and approval gates. Still **not approved**. Kai un-deferred items 5 and 6 only. * **Enforcement.** This issue records who asked. It does not decide what they may do — that is the per-requester authority issue, filed alongside this one. * **Telemetry body-safety.** Issue #55's contract is unaffected. A principal identifier is not message content, and nothing here proposes putting bodies in spans. ## Design questions to settle **1. What identifies a principal in the record?** A Discord user ID is stable and opaque, which is the right property for a durable record; a handle is readable and mutable. The existing `SIRENS_ECHO_PRINCIPAL_HANDLE` / `SIRENS_ECHO_PRINCIPAL_USER_ID` pair suggests both are already thought of together. Recommend the ID as the recorded value, with any handle treated as display only. **2. Does attribution reach telemetry as a span attribute?** https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/146 is adding a job id as a promoted attribute on spans and logs and has already solved this shape. If a job id is queryable, and a job id resolves to a principal, that may be sufficient indirection — and it keeps a user identifier out of the telemetry store. Worth deciding deliberately rather than attaching the principal everywhere by reflex. **3. Retention.** A durable record of who asked for what is exactly what makes attribution useful and exactly what makes it a data question. Not blocking, but it should be answered before the guild widens beyond a two-party channel. ## Acceptance * Given any job, its requesting principal is retrievable from the record without inference. * Given a principal, their jobs are listable. * An effect on an external system can be traced to the principal that caused it. * Attribution is present on jobs that failed or were cancelled, not only on successful ones. ## Depends on * https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/143 — the job record already carries the requesting principal as a field, kept deliberately when this item was deferred. ## Blocks * https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/145 — Ward-backed execution. ## Sequencing note This is the lighter of the two un-deferred items and has no hard dependency on the other. Attribution needs the principal field, which exists; per-requester authority needs a grant model, which does not. **This one can land first**, and doing so makes the authority work easier to verify — you can see who asked before you start denying them. ## Next owner Engineer. Starts after August 19; the demo track owns the week.
Author
Member

Design question 2 resolved: the principal goes into the span

Direction from Kai, 2026-08-12 session.

The issue offered two shapes — the principal as a span attribute in its own right, or job-id indirection where a span carries a job id and the job record resolves to a principal.

Decision: put it in the span. Attribution is a first-class span attribute, not something recovered by joining through the job store.

Why this is the stronger choice

Indirection only answers attribution questions for work that produced a job record. A turn that fails before a job exists, an admission denial, a validation rejection — none of those have a job id to resolve through, and all of them are exactly the moments where "who was this" matters. A span attribute covers the whole request path rather than the durable-work subset of it.

It also makes the query direct. The existing structured logging already promotes trace_id and span_id to row fields, so a principal attribute joins a mechanism that works today rather than requiring a lookup against Postgres at analysis time.

What goes in the attribute

The Discord user ID, not the handle. Design question 1 recommended this and the span decision makes it firmer: a handle is mutable and a telemetry store is durable, so recording a handle produces records that quietly become wrong. Any handle stays display-only, resolved at read time.

Kai holds the override if she wants the handle carried alongside.

Consequence, stated rather than discovered

A Discord user ID now lands in the telemetry store for every request, not only for requests that became jobs.

That does not touch issue #55's body-safety contract — a principal identifier is not message content, and nothing here proposes putting bodies in spans. But it does make design question 3, retention, a live question rather than a deferred one, and it should be answered before the guild channel gains members beyond the current two-party set. Recording that here so it is a known open item rather than a surprise.

Coupling with #146

#146 is adding a job id as a promoted attribute on spans and logs. The principal is now a sibling attribute on the same spans, promoted the same way. Build them together — it is one instrumentation pass, not two.

## Design question 2 resolved: the principal goes into the span Direction from Kai, 2026-08-12 session. The issue offered two shapes — the principal as a span attribute in its own right, or job-id indirection where a span carries a job id and the job record resolves to a principal. **Decision: put it in the span.** Attribution is a first-class span attribute, not something recovered by joining through the job store. ## Why this is the stronger choice Indirection only answers attribution questions for work that produced a job record. A turn that fails before a job exists, an admission denial, a validation rejection — none of those have a job id to resolve through, and all of them are exactly the moments where "who was this" matters. A span attribute covers the whole request path rather than the durable-work subset of it. It also makes the query direct. The existing structured logging already promotes `trace_id` and `span_id` to row fields, so a principal attribute joins a mechanism that works today rather than requiring a lookup against Postgres at analysis time. ## What goes in the attribute The **Discord user ID**, not the handle. Design question 1 recommended this and the span decision makes it firmer: a handle is mutable and a telemetry store is durable, so recording a handle produces records that quietly become wrong. Any handle stays display-only, resolved at read time. Kai holds the override if she wants the handle carried alongside. ## Consequence, stated rather than discovered A Discord user ID now lands in the telemetry store for every request, not only for requests that became jobs. That does **not** touch issue #55's body-safety contract — a principal identifier is not message content, and nothing here proposes putting bodies in spans. But it does make design question 3, retention, a live question rather than a deferred one, and it should be answered before the guild channel gains members beyond the current two-party set. Recording that here so it is a known open item rather than a surprise. ## Coupling with #146 #146 is adding a job id as a promoted attribute on spans and logs. The principal is now a sibling attribute on the same spans, promoted the same way. Build them together — it is one instrumentation pass, not two.
Author
Member

One acceptance criterion needs qualifying, following the #150 decision

#150 settled its design question 1: filtered grants over a single credential, not per-principal credentials.

That has a consequence for this issue's third acceptance criterion, which currently reads:

An effect on an external system can be traced to the principal that caused it.

With one shared credential, a Forgejo write is performed as the pod's identity regardless of who requested it. Forgejo's own audit trail will not distinguish requesters. The traceability this criterion asks for therefore lives entirely in this harness's record — the span attribute decided above, plus the job record.

The criterion is still satisfiable and still worth keeping. It just means something more precise than it appears to:

Given an effect on an external system, this harness's record identifies the principal that caused it.

Read the strong version instead and the criterion is unachievable without credential brokering, which is deferred item 11 and deliberately out of scope for both issues.

Why this matters beyond wording

It makes attribution load-bearing rather than convenient. Under per-principal credentials the downstream system would carry its own copy of who did what, and this harness's record would be a second source. Under filtered grants it is the only source. If the span attribute is missing or the job record is lost, the information does not exist anywhere else.

That is an argument for building this issue carefully and for answering the retention question sooner rather than later.

## One acceptance criterion needs qualifying, following the #150 decision #150 settled its design question 1: **filtered grants over a single credential**, not per-principal credentials. That has a consequence for this issue's third acceptance criterion, which currently reads: > An effect on an external system can be traced to the principal that caused it. With one shared credential, a Forgejo write is performed as the pod's identity regardless of who requested it. **Forgejo's own audit trail will not distinguish requesters.** The traceability this criterion asks for therefore lives entirely in this harness's record — the span attribute decided above, plus the job record. The criterion is still satisfiable and still worth keeping. It just means something more precise than it appears to: > Given an effect on an external system, **this harness's record** identifies the principal that caused it. Read the strong version instead and the criterion is unachievable without credential brokering, which is deferred item 11 and deliberately out of scope for both issues. ## Why this matters beyond wording It makes attribution load-bearing rather than convenient. Under per-principal credentials the downstream system would carry its own copy of who did what, and this harness's record would be a second source. Under filtered grants it is the **only** source. If the span attribute is missing or the job record is lost, the information does not exist anywhere else. That is an argument for building this issue carefully and for answering the retention question sooner rather than later.
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#151
No description provided.