Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 37 additions & 15 deletions .github/workflows/persona-runner-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,29 @@
# Posting identity (per persona, from the manifest — NOT a repo-wide default):
# Which account a persona's advisory posts as is read from that persona's
# personas/<id>/persona.yml runtime.identity (account + credential), the
# #1316/#1317 pattern. Advisory personas post as the machine user donpetry-bot
# (DON_PETRY_BOT_GH_PAT), never a human maintainer — a human byline halves the
# router's recursion guard and carries owner author_association
# (.github-private#1650). The runner holds only the PATs for the sanctioned
# posting identities and fails loudly on any other declared credential.
# #1316/#1317 pattern. Advisory personas post as the OWNER account don-petry
# (GH_PAT_DON_PETRY) — the identity ADR-0005 already sanctioned and
# docs/architecture/adr/0008-personas-post-as-the-owner-account.md restores
# fleet-wide, reversing #1650. GH_PAT_DON_PETRY is known-good (dev-lead posts
# with it continuously); donpetry-bot's PAT lacked comment-write here and
# silently discarded advisories (#1734). Trade-off ADR-0008 records honestly: a
# don-petry byline is a human account the router's ACTOR axis cannot exclude
# (it must stay invocable by the maintainer who invokes it most), so the
# `<!-- persona: -->` MARKER axis becomes the SOLE mechanical recursion guard —
# acceptable because that marker is workflow-enforced in the post step below
# (pr_ensure_marker), not prompt-enforced (the #860 failure mode). The runner
# holds only the PATs for the sanctioned posting identities and fails loudly on
# any other declared credential.
#
# Requires:
# CLAUDE_CODE_OAUTH_TOKEN — org secret, the engine credential
# DON_PETRY_BOT_GH_PAT — org PAT: the WORKFLOW uses it to post the comment as
# donpetry-bot on the cross-repo item. The agent never
# sees it. (dev-lead advisories still post via
# GH_PAT_DON_PETRY pending its identity split.)
# GH_PAT_DON_PETRY — org PAT: the WORKFLOW uses it to post the comment as
# the owner don-petry on the cross-repo item. The
# agent never sees it. (ADR-0008)
# DON_PETRY_BOT_GH_PAT — org PAT for donpetry-bot, retained: other paths
# still post as the bot (pr-review's approver identity),
# and the resolution logic accepting both is what makes
# the ADR-0008 change a one-line manifest edit.
name: Persona Runtime — Reusable

on:
Expand Down Expand Up @@ -66,16 +77,19 @@
CLAUDE_CODE_OAUTH_TOKEN:
required: true
DON_PETRY_BOT_GH_PAT:
# donpetry-bot's PAT — the machine account advisory personas post as. Which
# PAT is used is chosen per persona from runtime.identity.credential, not a
# repo-wide default (.github-private#1650). Grandfathered credential name.
# donpetry-bot's PAT — retained though advisory personas no longer post as
# the bot (ADR-0008). Still referenced by other paths (pr-review's approver
# identity), and keeping the resolution logic accepting both credentials is
# what makes the ADR-0008 change a one-line manifest edit. Which PAT is used
# is chosen per persona from runtime.identity.credential, not a repo-wide
# default (.github-private#1650). Grandfathered credential name.
required: false
GH_PAT_DON_PETRY:
# don-petry's PAT — used only by a persona that still declares it as its
# posting identity (currently dev-lead, whose commit-vs-advisory identity
# split is tracked separately). Advisory-only personas post as donpetry-bot.
# don-petry's PAT — the owner account advisory personas post as (ADR-0008,
# restoring the ADR-0005 position; reverses #1650, fixes #1734). Chosen per
# persona from runtime.identity.credential, never a repo-wide default.
required: false
# GH_PAT_WORKFLOWS removed (#1650): the broadly-scoped workflows PAT is no

Check warning on line 92 in .github/workflows/persona-runner-reusable.yml

View workflow job for this annotation

GitHub Actions / Lint

92:7 [comments-indentation] comment not indented like content
# longer a posting fallback here — least privilege, and posting under it
# attributed advisories to a human owner account.

Expand All @@ -94,7 +108,7 @@
timeout-minutes: 15
permissions:
contents: read # checkout THIS repo's tooling + prompts. The agent reads
# the public source repo with github.token; the comment is

Check warning on line 111 in .github/workflows/persona-runner-reusable.yml

View workflow job for this annotation

GitHub Actions / Lint

111:24 [comments-indentation] comment not indented like content
# posted by the workflow with the persona's manifest PAT.
steps:
- name: Checkout persona tooling + prompts
Expand Down Expand Up @@ -280,6 +294,14 @@

# Mechanically guarantee the recursion marker — the agent could not post
# unmarked (no write token), and now cannot post unmarked via us either.
# LOAD-BEARING under ADR-0008: personas now post as the owner account
# don-petry, which the router's actor axis cannot exclude, so THIS marker
# is the SOLE mechanical recursion guard for persona output. Enforcing it
# here in the post step — the only writer — is what keeps it workflow-
# enforced, not prompt-enforced (the #860 runaway failure mode). The
# forgot-the-marker path is covered by tests/persona_runner.bats
# ("pr_ensure_marker PREPENDS the marker when the agent forgot it"); keep
# that coverage green — do not weaken this call.
body="$(pr_ensure_marker "$PERSONA" "$body")"

if [ -z "${ITEM_NUMBER}" ]; then
Expand Down
132 changes: 132 additions & 0 deletions docs/architecture/adr/0008-personas-post-as-the-owner-account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# 0008. Personas post advisory comments as the owner account

## Status

accepted

## Context

Personas post advisory comments through the shared persona runner
(`persona-runner-reusable.yml`), which resolves *which account* a persona posts
as from that persona's `personas/<id>/persona.yml` `runtime.identity` —
following the manifest-read identity pattern of #1316/#1317, never a shared
default.

Three facts, all verifiable on `main`, decide this record:

1. **ADR-0005 already sanctions posting as a real account.** Its § on
write-identity says: *"'Write runtime identity' here means code/branch write
capability (commit, push, label, close), **not** the account a persona posts
advisory comments as: an advisory-only persona may still run under a real
account — `security-lead`, for instance, posts as the owner account
`don-petry`."* Posting advisory comments as `don-petry` is therefore the
accepted position, not a new direction.

2. **#1650 (PR #1694) contradicted that accepted record without superseding it.**
#1650 switched eight persona manifests to `account: donpetry-bot` /
`credential: DON_PETRY_BOT_GH_PAT` on the rationale that an advisory should be
identifiably a bot and that a bot byline preserved both axes of the router's
recursion guard. ADR-0000 makes an accepted ADR immutable — a change of
direction is a NEW ADR, never an in-place edit — but #1650 changed the code
without writing one, so since 2026-09-08 the accepted record (ADR-0005) and
the manifests have disagreed. `dev-lead` was never switched and still posts as
`don-petry`, so the fleet was also internally inconsistent.

3. **The #1650 identity is a live regression (#1734).** `DON_PETRY_BOT_GH_PAT`
authenticates as `donpetry-bot`, which lacks permission to write issue
comments in this repo: the runner's post step 403s and the advisory is
**discarded** — persisted nowhere. A real qa-lead invocation on epic #1723
was lost this way, twelve minutes after #1694 merged. `GH_PAT_DON_PETRY` is
known-good: `dev-lead` posts advisories with it continuously, so restoring it
resolves #1734 immediately, with no new secret to provision.

This ADR **aligns with** ADR-0005 rather than reversing it; it reverses #1650,
which was code that never had an ADR. No accepted ADR is being superseded, so no
`superseded-by` link is warranted — ADR-0005 remains `accepted` and unedited,
and this note exists so a future reader does not assume an unrecorded conflict
between the two records.

`pr-review` is deliberately out of scope. Its manifest identity governs the
account it **approves** pull requests as (`pr-review.yml` resolves `BOT_USER`
from it), not an advisory comment. It must remain the review-only machine user
`donpetry-bot`: `dev-lead` authors PRs as `don-petry`, and an approval from
`don-petry` on `don-petry`'s own PR is rejected by GitHub and would fail the
last-push-approval / CODEOWNERS gate. ADR-0005's carve-out is about *advisory
comments*, which is exactly the boundary that keeps `pr-review` untouched here.

## Decision

We will make advisory personas post their advisory comments as the **owner
account `don-petry`**, using the credential `GH_PAT_DON_PETRY`, restoring the
ADR-0005 position across the fleet:

- The eight advisory personas (`business-analyst`, `devops-lead`, `qa-lead`,
`scrum-master`, `security-lead`, `solution-architect`, `sre-lead`, and
`dev-lead` — already correct) declare `runtime.identity.account: don-petry`
and `runtime.identity.credential: GH_PAT_DON_PETRY`.
- `pr-review` is **not** an advisory-comment poster in this sense and keeps
`donpetry-bot` / `DON_PETRY_BOT_GH_PAT` — it is the PR-review approver and must
be a distinct account from the code author.
- Identity is still read from the manifest, never a shared default (#1317
preserved). The runner continues to accept both `GH_PAT_DON_PETRY` and the
grandfathered `DON_PETRY_BOT_GH_PAT`; the change is one line of manifest per
persona, not a workflow behaviour change.

## Consequences

- **#1734 is fixed:** advisories post with a known-good credential and land
instead of 403ing into the void. Intent (ADR-0005) and code agree again, and
the fleet is internally consistent (every advisory persona posts as
`don-petry`).

- **The recursion guard drops to a single axis — this is the real cost, stated
plainly.** The router's pre-filter
(`persona-mention-reusable.yml`) carries two independent recursion axes: an
**actor axis** (`comment.user.login != 'donpetry-bot' && != 'github-actions[bot]'`)
and a **marker axis** (`!contains(body, '<!-- persona:')`). With personas
posting as `don-petry`, the actor axis no longer covers persona output — and
`don-petry` **cannot** be added to that exclusion list, because it is the human
maintainer who invokes the personas most; excluding it would make them
un-invocable. So the **marker axis becomes the sole mechanical guard** for
persona output.

- **Why that single axis is acceptable:**
1. The marker is **workflow-enforced, not prompt-enforced.**
`persona-runner-reusable.yml` applies `pr_ensure_marker` in the *post* step
— the only writer — so an agent that forgets the marker cannot post an
unmarked comment because it cannot post at all. Prompt-only marker
enforcement is precisely how #860 produced 1,481 acks in 4.5h
(`docs/postmortems/2026-06-pr-860-runaway.md`); this design does not rely on
it.
2. It has been **proven live:** the #1300 soak posted a marked advisory, a
router run picked it up and returned `skipped` — the guard caught its own
marker.
3. A **third, weaker protection remains:** `prompts/<id>/advisory.md` forbids
writing a literal `@petry-projects/<role>` in the body, so an advisory
should carry no routable handle at all.

- **Residual risk (do not bury it):** the guard is now single-axis. If the marker
enforcement in the post step is ever weakened or bypassed, nothing else stops a
persona comment from re-routing into an unbounded loop. That makes
`pr_ensure_marker` and its bats coverage (`tests/persona_runner.bats`,
the *forgot-the-marker* path) **load-bearing** in a way they were not before —
a change to the post step must treat them as such.

- **`donpetry-bot` is still used elsewhere** — as `pr-review`'s approver identity
and `dev-lead`'s review identity — so it stays in the router's actor-exclusion
list; this decision does not touch that list.

## References

- ADR-0005 (`docs/architecture/adr/0005-advisory-by-default-personas.md`) — the
accepted record this aligns with; already permits posting as a real account.
Not superseded.
- ADR-0000 (`docs/architecture/adr/0000-adr-process.md`) — ADR process,
immutability, and the supersede-not-edit rule this record follows.
- #1650 / PR #1694 — the (ADR-less) code change this reverses; its unperformed
AC #6 (live verification) is repeated here as the acceptance bar.
- #1734 — the live regression this fixes.
- #1317 — identity resolved from the manifest, never a shared default
(preserved).
- `docs/postmortems/2026-06-pr-860-runaway.md` — why marker enforcement lives in
the workflow, not the prompt.
12 changes: 7 additions & 5 deletions personas/business-analyst/persona.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ trust:

# --- Runtime identity (§5.1) ------------------------------------------------
# Advisory-only (producers under prompts/incubation, consumed by path and the
# persona runner). On the comment path it posts as the machine user
# `donpetry-bot`, NOT the human owner — an advisory must be identifiably a bot in
# the audit trail (.github-private#1650). Shared runtime (see ADR-0006).
# persona runner). On the comment path it posts as the owner account `don-petry`
# (GH_PAT_DON_PETRY) — the identity ADR-0005 already sanctioned and ADR-0008
# restores fleet-wide, reversing #1650. GH_PAT_DON_PETRY is known-good; the bot
# PAT lacked comment-write here and silently discarded advisories (#1734). Shared
# runtime (see ADR-0006).
runtime:
identity:
account: donpetry-bot
credential: DON_PETRY_BOT_GH_PAT
account: don-petry
credential: GH_PAT_DON_PETRY

evals:
required_before: stable
Expand Down
10 changes: 6 additions & 4 deletions personas/devops-lead/persona.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ trust:
# --- Runtime identity (§5.1) ------------------------------------------------
# Advisory-only: devops-lead ships no dedicated reusable/stub in this pilot.
# It is consumed by path and is human-invocable. On the comment path it posts as
# the machine user `donpetry-bot`, NOT the human owner — an advisory must be
# identifiably a bot in the audit trail (.github-private#1650).
# the owner account `don-petry` (GH_PAT_DON_PETRY) — the identity ADR-0005 already
# sanctioned and ADR-0008 restores fleet-wide, reversing #1650. GH_PAT_DON_PETRY
# is known-good; the bot PAT lacked comment-write here and silently discarded
# advisories (#1734).
runtime:
identity:
account: donpetry-bot
credential: DON_PETRY_BOT_GH_PAT
account: don-petry
credential: GH_PAT_DON_PETRY

evals:
required_before: stable
Expand Down
23 changes: 13 additions & 10 deletions personas/qa-lead/persona.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,21 @@ trust:
# qa-lead ships no dedicated reusable/stub in this pilot — it is consumed by
# path (e.g. by the initiative-planner) and is human-invocable via the persona
# runner. It declares only `runtime.identity`: on the advisory/comment path the
# persona runner authenticates with this account's PAT. It is the machine user
# `donpetry-bot`, NOT the human owner `don-petry` — a persona advisory must be
# identifiably a bot in the audit trail, and posting under a human account halved
# the router's recursion guard (only the marker axis protected it) and carried
# owner `author_association` (.github-private#1650). `donpetry-bot` is already in
# the router's bot-actor exclusion, so posting as it restores two-axis protection.
# Add the rest of `runtime` + a canary-rings.json entry when a dedicated advisory
# workflow is wired.
# persona runner authenticates with this account's PAT. It is the owner account
# `don-petry` (GH_PAT_DON_PETRY) — the identity ADR-0005 already sanctioned and
# ADR-0008 restores fleet-wide, reversing the #1650 switch to `donpetry-bot`.
# Trade-off ADR-0008 records honestly: a `don-petry` byline is a human account the
# router's actor axis cannot exclude (it must stay invocable by the maintainer who
# invokes it most), so the `<!-- persona: -->` marker axis becomes the sole
# mechanical recursion guard — acceptable because that marker is workflow-enforced
# in the post step (persona-runner-reusable.yml), not prompt-enforced. GH_PAT_DON_PETRY
# is known-good (dev-lead posts with it continuously); the bot PAT lacked
# comment-write here and silently discarded advisories (#1734). Add the rest of
# `runtime` + a canary-rings.json entry when a dedicated advisory workflow is wired.
runtime:
identity:
account: donpetry-bot
credential: DON_PETRY_BOT_GH_PAT
account: don-petry
credential: GH_PAT_DON_PETRY

evals:
required_before: stable
Expand Down
12 changes: 7 additions & 5 deletions personas/scrum-master/persona.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,15 @@ trust:

# --- Runtime identity (§5.1) ------------------------------------------------
# Advisory-only (consumed by the initiative-planner and the persona runner). On
# the comment path it posts as the machine user `donpetry-bot`, NOT the human
# owner — an advisory must be identifiably a bot in the audit trail
# (.github-private#1650). No dedicated reusable yet.
# the comment path it posts as the owner account `don-petry` (GH_PAT_DON_PETRY) —
# the identity ADR-0005 already sanctioned and ADR-0008 restores fleet-wide,
# reversing #1650. GH_PAT_DON_PETRY is known-good; the bot PAT lacked
# comment-write here and silently discarded advisories (#1734). No dedicated
# reusable yet.
runtime:
identity:
account: donpetry-bot
credential: DON_PETRY_BOT_GH_PAT
account: don-petry
credential: GH_PAT_DON_PETRY

evals:
required_before: stable
Expand Down
10 changes: 6 additions & 4 deletions personas/security-lead/persona.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,14 @@ trust:
# --- Runtime identity (§5.1) ------------------------------------------------
# Advisory-only: security-lead ships no dedicated reusable/stub in this pilot.
# It is consumed by path and is human-invocable. On the comment path it posts as
# the machine user `donpetry-bot`, NOT the human owner — an advisory must be
# identifiably a bot in the audit trail (.github-private#1650).
# the owner account `don-petry` (GH_PAT_DON_PETRY) — the very example ADR-0005
# gives ("security-lead ... posts as the owner account don-petry"), which ADR-0008
# restores fleet-wide, reversing #1650. GH_PAT_DON_PETRY is known-good; the bot
# PAT lacked comment-write here and silently discarded advisories (#1734).
runtime:
identity:
account: donpetry-bot
credential: DON_PETRY_BOT_GH_PAT
account: don-petry
credential: GH_PAT_DON_PETRY

evals:
required_before: stable
Expand Down
11 changes: 6 additions & 5 deletions personas/solution-architect/persona.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,14 @@ trust:
# --- Runtime identity (§5.1) ------------------------------------------------
# Present from birth. Advisory-only: solution-architect ships no dedicated
# reusable/stub in this pilot. It is consumed by path and is human-invocable. On
# the comment path it posts as the machine user `donpetry-bot`, NOT the human
# owner — an advisory must be identifiably a bot in the audit trail
# (.github-private#1650).
# the comment path it posts as the owner account `don-petry` (GH_PAT_DON_PETRY) —
# the identity ADR-0005 already sanctioned and ADR-0008 restores fleet-wide,
# reversing #1650. GH_PAT_DON_PETRY is known-good; the bot PAT lacked
# comment-write here and silently discarded advisories (#1734).
runtime:
identity:
account: donpetry-bot
credential: DON_PETRY_BOT_GH_PAT
account: don-petry
credential: GH_PAT_DON_PETRY

evals:
required_before: stable
Expand Down
10 changes: 6 additions & 4 deletions personas/sre-lead/persona.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,14 @@ trust:
# --- Runtime identity (§5.1) ------------------------------------------------
# Advisory-only: sre-lead ships no dedicated reusable/stub in this pilot.
# It is consumed by path and is human-invocable. On the comment path it posts as
# the machine user `donpetry-bot`, NOT the human owner — an advisory must be
# identifiably a bot in the audit trail (.github-private#1650).
# the owner account `don-petry` (GH_PAT_DON_PETRY) — the identity ADR-0005 already
# sanctioned and ADR-0008 restores fleet-wide, reversing #1650. GH_PAT_DON_PETRY
# is known-good; the bot PAT lacked comment-write here and silently discarded
# advisories (#1734).
runtime:
identity:
account: donpetry-bot
credential: DON_PETRY_BOT_GH_PAT
account: don-petry
credential: GH_PAT_DON_PETRY

evals:
required_before: stable
Expand Down
Loading
Loading