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
43 changes: 30 additions & 13 deletions tools/workflow-engine/pr-review-lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
});
});

Expand Down
47 changes: 39 additions & 8 deletions tools/workflow-engine/pr-review-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -246,11 +253,35 @@ export const PR_REVIEW_LIFECYCLE_UNIVERSE: ReadonlyArray<PrReviewLifecycle> = [
];

/**
* 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";
}

/**
Expand Down
Loading