You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Goal: work starts from a ticket, and autonomy is chosen per ticket.
Part of the software factory epic. M2 is delivered and unblocks this — PR #119 (2026-09-04),
and the loop was proved end to end on a live pull request on 2026-09-12 (runs 3987682681:1 and 3987682176:1 against artyomsv/spire-test#31: /fix comment → dispatch →
sandboxed agent → push to the pull request's own source branch → next review round → thread
resolved on the forge → verdict written to review_finding).
Delivers
spire-worksource plus GitHub Issues, GitLab Issues and Jira arms, reusing the existing spire-context-* adapters' HTTP clients — same hosts, same registry, wider rights.
Tracker webhooks on the gateway's keyed registry edge.
work_item bookkeeping — NOT a mirror. No title, no body, no status of the ticket
itself. The tracker is the source of truth; owning a second one means owning sync, drift and
permissions.
Autonomy profiles, label mapping, the operator ceiling, and a per-work-source actor
allowlist.
Durable gates answerable from the dashboard, the tracker or a pull-request review, with
expiry. Human takeover.
The Work items and Approvals screens.
Added after the first draft — decisions taken 2026-09-08 and 2026-09-10
These four were agreed after this ticket was written (2026-09-04) and are recorded in docs/factory/ROADMAP.md § M3, docs/DECISIONS.md ("Deliberately deferred" under ADR-041) and docs/superpowers/specs/2026-09-10-accounts-scopes-and-roles-analysis.md §7 and §11. They are M3
scope, not follow-up work.
Authorise /fix on push rights, not on authorship. The first live /fix (2026-09-10) was
refused because the reviewer account's "May command this bot" list was empty and IntegrationSaga.requestFix denies by default. "The pull request's author may /fix" is
unsafe as written — on a public repository that is any stranger (AUTONOMY.md Rule 3's
drive-by contributor). The safe rule is the commenter can already push to this
repository: owners, admins, collaborators and same-repository authors. All three forges
expose a per-repository permission endpoint that answers it. Forks are already refused one
layer down by FixTargets.whyNotPushable. The hand-kept list stays as the override in both
directions — granting the command to somebody without write access, and taking it from
somebody who has it.
Type a handle, store an id, show the handle./fix matches on the stable provider user
id on purpose, because a handle can change hands. But the form asks the operator to type 3218389 and the Policy cell renders 1. Typing @artyomsv must resolve through the
account's own credential, store the id, and render the handle back.
Workspace moves from the forge account to the repository. Today the account row carries
the workspace as part of the lookup key (scm_provider UNIQUE (type, workspace, role)).
That is the thing that changes. Assume one workspace per repository for now; a repository may
later relate to more than one. Deferred here explicitly by ADR-041. This changes the
registry key, so it needs its own ADR.
The repository becomes the centre of its own screen. Register a repository, list the
accounts that may act on it, and list its webhooks — one per event kind (reviewer events,
factory events, later issue events for a product-owner role). The Accounts form asking for a
workspace goes away with the key change above.
Two things the first draft assumed and had to be designed instead
labelEvents carrying the actor, not a bare set of label strings. A set has no author, and
the allowlist rule needs one. Where neither a webhook nor a tracker audit trail can attribute a
label it is UNATTRIBUTED and selects nothing — the alternative is a rule that quietly
applies only to labels a webhook happened to witness.
Policy re-resolved at every phase transition (FR-F30), with the profile version pinned at
admission, lowest-label-wins, and retirement when a tracker issue moves repository.
Likely to need deciding here
M2's plan reconciliation (PR #110) left one question open that lands in this milestone: whether a
run aggregate exists at all.DomainEvent carries no run member today — factory_run is
projected straight from cs.run-results, and the durable record of a run is factory_run + llm_charge, not the event store (ADR-034). The work-item vocabulary sketched in ARCHITECTURE.md §6 (WorkItemAdmitted, GateOpened, GateResolved, …) is the first thing that
would need one.
Exit criteria
A ticket labelled at each of three profiles produces three visibly different journeys.
A label naming a profile above the ceiling is clamped and says so.
A label applied by an actor outside the allowlist is ignored — and so is one whose applier
cannot be determined.
Lowering the ceiling stops an in-flight item at its next phase.
A commenter with push access to the repository can /fixwithout appearing on any list,
and a commenter without push access is refused — with the hand-kept list proved to override
in both directions.
The allowlist form accepts @handle, stores the provider user id, and renders the handle
back; a handle that cannot be resolved is refused at entry rather than stored as text.
A repository screen names its workspace, the accounts that may act on it, and one webhook per
event kind — with no workspace field left on the account form.
Goal: work starts from a ticket, and autonomy is chosen per ticket.
Part of the software factory epic. M2 is delivered and unblocks this — PR #119 (2026-09-04),
and the loop was proved end to end on a live pull request on 2026-09-12 (runs
3987682681:1and3987682176:1againstartyomsv/spire-test#31:/fixcomment → dispatch →sandboxed agent → push to the pull request's own source branch → next review round → thread
resolved on the forge → verdict written to
review_finding).Delivers
spire-worksourceplus GitHub Issues, GitLab Issues and Jira arms, reusing the existingspire-context-*adapters' HTTP clients — same hosts, same registry, wider rights.work_itembookkeeping — NOT a mirror. No title, no body, no status of the ticketitself. The tracker is the source of truth; owning a second one means owning sync, drift and
permissions.
allowlist.
expiry. Human takeover.
Added after the first draft — decisions taken 2026-09-08 and 2026-09-10
These four were agreed after this ticket was written (2026-09-04) and are recorded in
docs/factory/ROADMAP.md§ M3,docs/DECISIONS.md("Deliberately deferred" under ADR-041) anddocs/superpowers/specs/2026-09-10-accounts-scopes-and-roles-analysis.md§7 and §11. They are M3scope, not follow-up work.
/fixon push rights, not on authorship. The first live/fix(2026-09-10) wasrefused because the reviewer account's "May command this bot" list was empty and
IntegrationSaga.requestFixdenies by default. "The pull request's author may/fix" isunsafe as written — on a public repository that is any stranger (AUTONOMY.md Rule 3's
drive-by contributor). The safe rule is the commenter can already push to this
repository: owners, admins, collaborators and same-repository authors. All three forges
expose a per-repository permission endpoint that answers it. Forks are already refused one
layer down by
FixTargets.whyNotPushable. The hand-kept list stays as the override in bothdirections — granting the command to somebody without write access, and taking it from
somebody who has it.
/fixmatches on the stable provider userid on purpose, because a handle can change hands. But the form asks the operator to type
3218389and the Policy cell renders1. Typing@artyomsvmust resolve through theaccount's own credential, store the id, and render the handle back.
the workspace as part of the lookup key (
scm_providerUNIQUE(type, workspace, role)).That is the thing that changes. Assume one workspace per repository for now; a repository may
later relate to more than one. Deferred here explicitly by ADR-041. This changes the
registry key, so it needs its own ADR.
accounts that may act on it, and list its webhooks — one per event kind (reviewer events,
factory events, later issue events for a product-owner role). The Accounts form asking for a
workspace goes away with the key change above.
Two things the first draft assumed and had to be designed instead
labelEventscarrying the actor, not a bare set of label strings. A set has no author, andthe allowlist rule needs one. Where neither a webhook nor a tracker audit trail can attribute a
label it is
UNATTRIBUTEDand selects nothing — the alternative is a rule that quietlyapplies only to labels a webhook happened to witness.
admission, lowest-label-wins, and retirement when a tracker issue moves repository.
Likely to need deciding here
M2's plan reconciliation (PR #110) left one question open that lands in this milestone: whether a
run aggregate exists at all.
DomainEventcarries no run member today —factory_runisprojected straight from
cs.run-results, and the durable record of a run isfactory_run+llm_charge, not the event store (ADR-034). The work-item vocabulary sketched inARCHITECTURE.md§6 (WorkItemAdmitted,GateOpened,GateResolved, …) is the first thing thatwould need one.
Exit criteria
cannot be determined.
/fixwithout appearing on any list,and a commenter without push access is refused — with the hand-kept list proved to override
in both directions.
@handle, stores the provider user id, and renders the handleback; a handle that cannot be resolved is refused at entry rather than stored as text.
event kind — with no workspace field left on the account form.
FRs: FR-F16, F16a, F17, F22..F25, F30. Docs:
docs/factory/ROADMAP.md§ M3,docs/factory/AUTONOMY.md,docs/DECISIONS.md(ADR-041 deferrals).