docs: LLM-Agent-SHA opaque attribution convention, Phase 0 (#86)

Implements the Phase 0 owner decision on #86 (issuecomment-1354):

- docs/llm-agent-sha.md: format llm-<12 lowercase hex> (^llm-[0-9a-f]{12}$),
  generation rules, per-PR/workstream lifetime, visible markdown metadata
  blocks, no SHA in branch/worktree names, same-SHA vs same-user vs
  same-profile distinction. Attribution only — never an eligibility input.
- docs/llm-workflow-runbooks.md: attribution subsection + handoff/review
  runbook pointers.
- templates start-issue.md / review-pr.md: handoff and review metadata
  blocks; reviewer rule that a different SHA is not a different actor.
- tests/test_llm_agent_sha.py: negative tests — same Gitea user with a
  different LLM-Agent-SHA still fails self-review and self-merge; eligibility
  results are identical with/without/across SHA env values; no gate accepts
  or reads any agent-SHA input.

No launcher/env handling, no gitea_whoami fields, no PR auto-injection, no
audit schema changes. No eligibility behavior changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 14:33:02 -04:00
parent 53e061bafd
commit 86141bfa0f
5 changed files with 374 additions and 2 deletions
+138
View File
@@ -0,0 +1,138 @@
# LLM-Agent-SHA — Opaque Agent Attribution (Phase 0)
Convention for attributing work to a specific LLM session/workstream across
issues, branches, PRs, and review handoffs, without exposing a human or model
identity. Approved by the owner decision on issue #86
(`#issuecomment-1354`); this document implements **Phase 0 only**.
## The one rule that matters
`LLM-Agent-SHA` is **informational attribution metadata only**. It must never
be used for authentication, authorization, review eligibility, merge
eligibility, profile permissions, or any other security decision.
The security gates remain, unchanged:
- the **authenticated Gitea user** (self-review/self-merge protection),
- the **active MCP profile** and its `allowed_operations`
(see [`gitea-execution-profiles.md`](gitea-execution-profiles.md)),
- the fail-closed eligibility checks in `gitea_check_pr_eligibility`.
Two sessions with different `LLM-Agent-SHA` values that authenticate as the
same Gitea user are **the same actor** for review/merge safety. A different
SHA never unlocks self-review or self-merge. `tests/test_llm_agent_sha.py`
proves the eligibility logic never consults the SHA.
## Format
```text
LLM-Agent-SHA: llm-<12 lowercase hex chars>
```
Validation regex:
```text
^llm-[0-9a-f]{12}$
```
Examples: `llm-8f3a9c2d6b41`, `llm-41d0e7aa9f2c`, `llm-b7c93d441a08`.
### Generation
Generate 48 random bits, e.g. `python3 -c "import secrets; print('llm-' +
secrets.token_hex(6))"`, or hash a non-secret session UUID. An
operator-provided opaque ID is also fine.
Do **not** derive the value from any of:
- a Gitea token or other secret,
- an email address or username,
- a machine hostname or private filesystem path,
- a model or provider name,
- conversation contents.
The SHA must contain no model name, provider name, human name, email,
hostname, token, private path, or conversation-derived content. It is safe to
include in PR bodies, issue comments, and audit logs — and only there.
## Lifetime
Canonical lifetime is **per PR/workstream**: pick one SHA when starting an
issue and keep it through the branch, PR, and handoff for that workstream. A
per-session SHA is acceptable when the session maps cleanly to one
workstream. Do not reuse a SHA across unrelated workstreams.
## Placement
Phase 0 uses **visible markdown metadata blocks** (not hidden HTML
comments). Include the block in PR bodies and review handoffs; keep it out of
ordinary comments unless attribution is genuinely useful there.
**Never put the SHA in branch or worktree names.** Branches stay
issue-linked and human-readable (`docs/issue-86-llm-agent-sha-phase0`), per
the branch standard.
### Handoff metadata block (implementer → PR body / handoff report)
```markdown
LLM Handoff Metadata:
- LLM-Agent-SHA: llm-8f3a9c2d6b41
- LLM-Role: implementer
- Authenticated-Gitea-User: jcwalker3
- MCP-Profile: gitea-default
- Branch: docs/example-branch
- Worktree: branches/docs-example-branch
- Self-review allowed: no
```
### Review metadata block (reviewer → review comment)
```markdown
Review Metadata:
- LLM-Agent-SHA: llm-41d0e7aa9f2c
- LLM-Role: reviewer
- Authenticated-Gitea-User: sysadmin
- MCP-Profile: prgs-reviewer
- Eligibility: passed
```
## Same SHA vs same user vs same profile
Reviewers and operators must keep three distinct identities straight:
| Comparison | Meaning | Effect on eligibility |
|---|---|---|
| same `LLM-Agent-SHA` | same LLM session/workstream wrote both artifacts | **none — attribution only** |
| same authenticated Gitea user | same Gitea actor | **blocks** self-review / self-merge, regardless of SHA |
| same MCP profile | same capability set | governs `allowed_operations` (what actions are permitted at all) |
Concretely: an implementer session (`llm-8f3a…`, user `jcwalker3`) and a
would-be reviewer session (`llm-41d0…`, also user `jcwalker3`) have different
SHAs but the **same Gitea user** — the reviewer session is still the PR
author to Gitea and must not review, approve, or merge. Review handoffs
require a genuinely different authenticated user (e.g. `sysadmin` /
`prgs-reviewer`).
## Phase 0 scope (and what is deferred)
Phase 0 is documentation, handoff/review templates, and negative tests only.
Deferred to later owner-approved phases; none of this exists yet:
- launcher-enforced SHA generation,
- `LLM_AGENT_SHA` / `LLM_AGENT_ROLE` environment injection,
- `gitea_whoami` returning SHA/role,
- automatic PR body injection by MCP tools,
- audit schema changes requiring the SHA,
- release/orchestrator lineage tracking.
MCP tools neither read nor emit the SHA. Setting an `LLM_AGENT_SHA`
environment variable has no effect on any tool; the negative tests assert
eligibility results are byte-identical with and without it.
## Related documents
- [`llm-workflow-runbooks.md`](llm-workflow-runbooks.md) — the runbooks whose
handoffs carry these blocks
- [`gitea-execution-profiles.md`](gitea-execution-profiles.md) — profiles and
`allowed_operations` (the real permission gate)
- [`safety-model.md`](safety-model.md) — audit, redaction, confirmation gates
+20 -2
View File
@@ -45,6 +45,18 @@ Use any eligible reviewer profile to review PR #N.
Use any eligible merger profile to merge PR #N if checks pass.
```
### Attribution: `LLM-Agent-SHA` (metadata only)
Sessions may attribute their work with an opaque `LLM-Agent-SHA`
(`llm-<12 lowercase hex>`, e.g. `llm-8f3a9c2d6b41`) in PR-body and
review-handoff metadata blocks — see
[`llm-agent-sha.md`](llm-agent-sha.md) for the full convention. It is
**attribution only**: eligibility is decided solely by the authenticated
Gitea user and the profile's allowed operations. Two sessions with different
SHAs under the same Gitea user are the same actor — a different SHA never
permits self-review or self-merge. Keep the SHA out of branch and worktree
names.
## Prerequisites: canonical config + thin launchers
Runtime profiles live in **one canonical JSON file**, referenced by every LLM
@@ -274,7 +286,8 @@ touching anything.
`fix/...` / `docs/...`); `cd` into that worktree; implement narrowly; add or
update tests if behavior changes; run the full suite; commit with an
issue-linked message; open a PR to `master`. **Do not** review or merge your
own PR.
own PR. Include an `LLM Handoff Metadata` block (with `LLM-Agent-SHA`) in
the PR body — see [`llm-agent-sha.md`](llm-agent-sha.md).
- **Prompt:** `Use an author profile to implement issue #N and open a PR to
master. Do not self-review or self-merge.`
@@ -285,7 +298,11 @@ touching anything.
- **Steps:** confirm identity + eligibility (menu eligibility check or
`gitea_check_pr_eligibility`); read the diff; confirm scope matches the linked
issue; post the review (`comment` / `request_changes` / `approve`) via the
gated review tool. Pin the reviewed head SHA where supported.
gated review tool. Pin the reviewed head SHA where supported. Include a
`Review Metadata` block (with your own `LLM-Agent-SHA`) in the review —
and remember: a different `LLM-Agent-SHA` does **not** make you a different
actor; only a different authenticated Gitea user does
([`llm-agent-sha.md`](llm-agent-sha.md)).
- **Prompt:** `Use any eligible reviewer profile to review PR #N. Approve only
if scope matches issue #M and checks pass; otherwise request changes.`
@@ -391,6 +408,7 @@ scripts/release-tag v0.4.0 --notes-file /tmp/release-notes.md --push
- [`../skills/llm-project-workflow/SKILL.md`](../skills/llm-project-workflow/SKILL.md) — portable cross-project LLM workflow skill.
- [`gitea-execution-profiles.md`](gitea-execution-profiles.md) — the profile model.
- [`llm-agent-sha.md`](llm-agent-sha.md) — opaque agent attribution metadata (never an eligibility input).
- [`safety-model.md`](safety-model.md) — trust boundaries and audit logging.
- [`tool-boundaries.md`](tool-boundaries.md) — per-tool allowed operations.
- [`credential-isolation.md`](credential-isolation.md) — credential handling.