diff --git a/tools/workflow-engine/pr-review-lifecycle.test.ts b/tools/workflow-engine/pr-review-lifecycle.test.ts index 8d785edad2..6e77e5424b 100644 --- a/tools/workflow-engine/pr-review-lifecycle.test.ts +++ b/tools/workflow-engine/pr-review-lifecycle.test.ts @@ -4,8 +4,10 @@ import { describe, expect, test } from "bun:test"; import { PR_REVIEW_LIFECYCLE_UNIVERSE, dispatchPrReviewTransition, - isPeerAgentTerritory, + isHumanOperator, + isPeerAgent, newReviewContext, + requiresCoordinationLane, type PrReviewLifecycle, type ReviewFinding, } from "./pr-review-lifecycle.js"; @@ -137,23 +139,38 @@ describe("ReviewFindingKind taxonomy", () => { }); }); -describe("isPeerAgentTerritory discriminator", () => { - test("self → false (my own work)", () => { - expect(isPeerAgentTerritory("self")).toBe(false); +describe("requiresCoordinationLane discriminator", () => { + test("self → false (my own work; no coordination needed)", () => { + expect(requiresCoordinationLane("self")).toBe(false); }); - test("peer-otto → true", () => { - expect(isPeerAgentTerritory("peer-otto")).toBe(true); + test("peer-otto → true (peer-agent territory)", () => { + expect(requiresCoordinationLane("peer-otto")).toBe(true); }); - test("peer-codex / peer-lior / peer-alexa → true", () => { - expect(isPeerAgentTerritory("peer-codex")).toBe(true); - expect(isPeerAgentTerritory("peer-lior")).toBe(true); - expect(isPeerAgentTerritory("peer-alexa")).toBe(true); + test("peer-codex / peer-lior / peer-alexa → true (peer-agent territory)", () => { + expect(requiresCoordinationLane("peer-codex")).toBe(true); + expect(requiresCoordinationLane("peer-lior")).toBe(true); + expect(requiresCoordinationLane("peer-alexa")).toBe(true); }); - test("human-aaron → true (substantive engagement; don't touch substrate)", () => { - expect(isPeerAgentTerritory("human-operator")).toBe(true); + test("human-operator → true (distinct lane; coordination required)", () => { + expect(requiresCoordinationLane("human-operator")).toBe(true); }); test("unknown → false (default; treat as substrate-honest mine)", () => { - expect(isPeerAgentTerritory("unknown")).toBe(false); + expect(requiresCoordinationLane("unknown")).toBe(false); + }); +}); + +describe("isPeerAgent / isHumanOperator distinguish lanes", () => { + test("peer-otto is peer-agent, not human-operator", () => { + expect(isPeerAgent("peer-otto")).toBe(true); + expect(isHumanOperator("peer-otto")).toBe(false); + }); + test("human-operator is human-operator, not peer-agent", () => { + expect(isPeerAgent("human-operator")).toBe(false); + expect(isHumanOperator("human-operator")).toBe(true); + }); + test("self is neither peer-agent nor human-operator", () => { + expect(isPeerAgent("self")).toBe(false); + expect(isHumanOperator("self")).toBe(false); }); }); diff --git a/tools/workflow-engine/pr-review-lifecycle.ts b/tools/workflow-engine/pr-review-lifecycle.ts index c0bfc418a2..5d866e6f37 100644 --- a/tools/workflow-engine/pr-review-lifecycle.ts +++ b/tools/workflow-engine/pr-review-lifecycle.ts @@ -5,9 +5,9 @@ // reviewer-feedback gate-state). // // Per the human maintainer (2026-05-28): "also does it give you time to look at prs and -// put comments?" — substrate-engineering substrate-engineering substrate -// gap: AutoLoopLifetime (PR #5805) only models SHIP work, not REVIEW -// work. This DU makes producing-side review-substrate explicit. +// put comments?" — substrate-engineering gap: AutoLoopLifetime (PR #5805) +// only models SHIP work, not REVIEW work. This DU makes producing-side +// review-substrate explicit. // // Composes with: // - .claude/rules/fighting-past-self-vs-peer-agent-distinguisher-fix-your-own-coordinate-on-peers-dont-punt-by-default.md @@ -21,7 +21,14 @@ // - AutoLoopLifetime (PR #5805) — will compose; producing-side review // work becomes additional state-transition in loop substrate -import { type LifetimeState } from "./world"; +// LifetimeState — minimal base marker for substrate-engineering lifetime DUs. +// Inlined here (rather than imported from a non-existent "./world" module) so +// this PoC compiles standalone and doesn't break repo-wide `tsc --noEmit` or +// `bun test`. Sibling lifecycle DUs (e.g. B-0867.20 ReviewLifetime) carry +// their own base marker until a shared `world.ts` module lands. +export interface LifetimeState { + readonly kind: string; +} // ───────────────────────────────────────────────────────────────────── // PrReviewLifecycle — producing-side state machine @@ -246,11 +253,35 @@ export const PR_REVIEW_LIFECYCLE_UNIVERSE: ReadonlyArray = [ ]; /** - * Determines if a PR is in peer-agent territory (don't touch commits but - * review-allowed per fighting-past-self-vs-peer-agent-distinguisher rule). + * Returns true when the author lane requires coordination before touching + * commits (peer-agent territory OR human-operator territory). Both lanes + * are non-self-authored; both require coordination per + * fighting-past-self-vs-peer-agent-distinguisher rule — but the lanes are + * substantively DISTINCT and should NOT be conflated for any purpose + * beyond "this is not my own commit-substrate." Callers that need to + * distinguish should use `isPeerAgent` or `isHumanOperator` directly. + */ +export function requiresCoordinationLane(authorLane: ReviewContext["authorLane"]): boolean { + return isPeerAgent(authorLane) || isHumanOperator(authorLane); +} + +/** + * True iff the author lane is a peer-agent surface (otto-* / codex / lior / + * alexa / vera / riven / amara / kestrel / prism / mika). Distinct from + * human-operator per the fighting-past-self discriminator table. + */ +export function isPeerAgent(authorLane: ReviewContext["authorLane"]): boolean { + return authorLane.startsWith("peer-"); +} + +/** + * True iff the author lane is the human operator. Distinct from peer-agent + * per the fighting-past-self discriminator table; coordination shape + * differs (peer-agents coordinate via bus / peer-call; human operator + * coordinates via conversation / explicit authorization). */ -export function isPeerAgentTerritory(authorLane: ReviewContext["authorLane"]): boolean { - return authorLane.startsWith("peer-") || authorLane === "human-operator"; +export function isHumanOperator(authorLane: ReviewContext["authorLane"]): boolean { + return authorLane === "human-operator"; } /**