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
fix: restore offer-on-launch for Pi PR-boundary review (in-memory session baseline)
Bug A: the offer-once reconciliation derived inSessionContinuation from the
on-disk ack (isAncestor(lastAck, head)), which is true on every fresh pi launch
whose head descends from a prior ack, so reviewers auto-started on launch instead
of offering. Decide continuation from an in-memory per-session baseline instead:
reviewBaselineContinuation() autostarts only when the head advances beyond the
head first observed this session; an inherited head offers. Mirrored to ~/.pi.
Copy file name to clipboardExpand all lines: sdd/spec/agents.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1359,7 +1359,7 @@ None.
1359
1359
1360
1360
**Acceptance Criteria:**
1361
1361
1362
-
1. Lifecycle reconciliation recovers enforced open PRs with unacknowledged heads: in-session head advances auto-start, while inherited heads are offered once and remain merge-blocking until user choice. <!-- @impl: preseed/agents/pi/extensions/review-enforcement.ts::reconcileOpenPrReview --><!-- @impl: preseed/agents/pi/extensions/review-job-helpers.ts::shouldReconcileOpenPr --><!-- @impl: preseed/agents/pi/extensions/review-job-helpers.ts::reconcileBoundaryAction -->
1362
+
1. Lifecycle reconciliation recovers enforced open PRs with unacknowledged heads using an in-memory per-session baseline (the head first observed this session): a head that advances beyond the baseline auto-starts, while an inherited head (baseline unset or unchanged — fresh launch/clone/checkout) is offered once and remains merge-blocking until user choice. <!-- @impl: preseed/agents/pi/extensions/review-enforcement.ts::reconcileOpenPrReview --><!-- @impl: preseed/agents/pi/extensions/review-job-helpers.ts::shouldReconcileOpenPr --><!-- @impl: preseed/agents/pi/extensions/review-job-helpers.ts::reconcileBoundaryAction--><!-- @impl: preseed/agents/pi/extensions/review-job-helpers.ts::reviewBaselineContinuation --><!-- @test: src/__tests__/lib/review-state.test.ts (reviewBaselineContinuation: bare launch offers, only an in-session advance autostarts -> AC1)-->
1363
1363
2. Boundary-command and reconciliation paths call one shared routine, so reconciled windows match captured-command windows in lanes, review base, durable job, and audit trail. <!-- @impl: preseed/agents/pi/extensions/review-enforcement.ts::ensureReviewWindow -->
1364
1364
3. Head resolution reviews local HEAD during metadata lag only when it is on the PR branch, descends from GitHub's PR head, and the remote-tracking ref contains it. <!-- @impl: preseed/agents/pi/extensions/review-enforcement.ts::resolveEnforcedHead --><!-- @impl: preseed/agents/pi/extensions/review-helpers.ts::enforcedHeadDecision --><!-- @test: src/__tests__/lib/review-trigger.test.ts (enforcedHeadDecision pushed-vs-unpushed table -> AC3) -->
1365
1365
4. A boundary-shaped command that does not start a review appends a durable `boundary_candidate_ignored` audit event naming the gate reason, so a skipped review is always reconstructable from disk. <!-- @impl: preseed/agents/pi/extensions/review-enforcement.ts::onToolEnd --><!-- @impl: preseed/agents/pi/extensions/review-job-helpers.ts::shouldReconcileOpenPr --><!-- @impl: preseed/agents/pi/extensions/review-jobs.ts::appendReviewEvent --><!-- @test: src/__tests__/lib/review-state.test.ts (REQ-AGENT-058 AC4: every suppressed reconcile gate names a distinct non-empty reason to stamp into boundary_candidate_ignored -> AC4) -->
Copy file name to clipboardExpand all lines: sdd/spec/changes.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,12 @@
2
2
3
3
Semantic changes to the specification. Git history captures diffs; this file captures intent.
4
4
5
+
## 2026-06-10
6
+
7
+
- **Pi PR-boundary review OFFERS on a fresh launch again (bug-A regression fix)** ([REQ-AGENT-058](agents.md#req-agent-058-pr-boundary-review-reconciliation-and-missed-event-recovery) AC1 mechanism revised). The 2026-06-09 offer-once work derived `inSessionContinuation` from the on-disk ack (`isAncestor(lastAck, head)`), which is true on every fresh `pi` launch whose head descends from a prior ack — so reconciliation auto-started reviewers on launch instead of offering (user-reported: "offering worked yesterday you broke something today"; proven by `codeflare-review-events.jsonl` showing `trigger:"open-PR reconciliation (in-session continuation)"` at session start). Continuation is now decided from an **in-memory per-session baseline** (`reviewSessionBaselineHead`, the head first observed this session): the new pure helper `reviewBaselineContinuation(baseline, head, isAncestor)` returns true only when the head ADVANCED beyond the baseline (a genuine in-session push whose on-tool-end auto-start was dropped); a head present at launch (baseline unset or unchanged) offers. Process-scoped so a bare relaunch always re-baselines and offers. Mirrored into `~/.pi/agent/extensions/`. Tests: `review-state.test.ts` (`reviewBaselineContinuation`: bare launch offers, only an in-session advance autostarts). **Status:** REQ-AGENT-058 stays Implemented.
8
+
- **`gh pr edit --base main|master` now arms PR-boundary review across all enforcement surfaces** ([REQ-AGENT-058](agents.md#req-agent-058-pr-boundary-review-reconciliation-and-missed-event-recovery) AC3; [REQ-AGENT-061](agents.md#req-agent-061-pr-boundary-review-idle-durable-reaper) AC3/AC4 split). A PR opened against a non-protected base (or via the web UI) and later retargeted onto `main`/`master` with `gh pr edit --base` is the same boundary `gh pr create --base main` establishes, applied after creation — previously it armed no review until the durable reconciliation catch-up. The Pi extension (`prBoundaryCommandBase = prCreateBoundaryBase || prEditBoundaryBase`, `isBoundaryWords` recognizing `gh pr edit` with a protected `--base`) and both Claude hooks (`enforce-review-spawn.sh` awk across Bash/`ctx_execute`/`ctx_batch_execute`; `git-push-review-reminder.sh` `pr-retarget` trigger taking the base from the command itself, not a stale `gh view`) now detect it; metadata-only `gh pr edit` (title/body/labels) and non-protected `--base develop` remain ignored. REQ-AGENT-061 AC3 split so off-turn finalization's "summary deferred, marker left unclaimed for the on-turn emit" is its own AC4. Tests: `review-trigger.test.ts` (`prBoundaryCommandBase`), `agent-seed-manifest.test.ts` (`isPrBoundaryCommand`/`cwdFromBoundaryCommand` for `gh pr edit`), `enforce-review-spawn.test.js` + `git-push-review-reminder.test.js` (real-hook block/directive across `--base`/`--base=`/`-B` forms). **Status:** REQ-AGENT-058 stays Implemented; REQ-AGENT-061 stays Planned.
9
+
- **Reloaded enterprise config falls back to the first route as default** ([REQ-ENTERPRISE-012](enterprise-mode.md#req-enterprise-012-setup-configured-dynamic-route-catalog-and-access-group-list) AC6/AC7 split). On a setup re-run, `loadExistingConfig` left `defaultRouteName` empty when no default was stored, so a configured-but-default-less enterprise tenant reloaded with no default selected; it now resolves to `dynamicRoutes[0]` (reasoning off), matching the first-route-is-default rule. REQ-ENTERPRISE-012 AC6 split so the "Continue blocked until ≥1 route" UI gate (AC6) and the "`POST /api/setup/configure` rejects empty `dynamicRoutes` with 400" backend gate (AC7) are separate; [REQ-ENTERPRISE-006](enterprise-mode.md#req-enterprise-006-deploy-time-aig-secrets-and-enterprise_mode-var) AC7 added for the `enterprise`/`enterprise integration` deploy targets. Tests: `stores/setup.test.ts` (reload with `defaultRoute:null` → first route serialized as default). **Status:** REQ-ENTERPRISE-012 stays Implemented; REQ-ENTERPRISE-006 stays Planned.
10
+
5
11
## 2026-06-09
6
12
7
13
- **Enterprise dynamic routes are now required (≥1), the first route added is the default, and the group/route chips get the accent border** ([REQ-ENTERPRISE-012](enterprise-mode.md#req-enterprise-012-setup-configured-dynamic-route-catalog-and-access-group-list) AC2 revised + AC6 added). Acceptance testing on the live integration tenant surfaced two issues: (1) the access-group and dynamic-route chips rendered without the blue accent border the admin chips have — the accent CSS lived in a modifier (`email-tag--admin`) applied only to admin chips; (2) the "Default Route" selector offered a misleading "(no default)" option, implying a request could reach no destination, even though the interceptor and container already resolve an unset default to the first catalog route. Fix: the accent modifier is generalized to `email-tag--accent` and applied to the admin, access-group, and dynamic-route chips (non-enterprise regular-user chips stay plain to preserve the admin distinction); the wizard now requires ≥1 dynamic route in enterprise mode (`ConfigureStep` blocks Continue; `POST /api/setup/configure` rejects empty/absent `dynamicRoutes` with `400` before any KV write) and the store auto-assigns the first route added as the default (re-points to the new first route when the default is removed), dropping the "(no default)" option so routing always resolves to a gateway route. The interceptor's empty-catalog passthrough is retained as a defensive fallback, no longer a reachable enterprise configuration. Tests: `setup.test.ts` (enterprise empty-routes → `400`, no partial KV write), `stores/setup.test.ts` (first-route auto-default + remove-reassign + first route serialized as the default), `ConfigureStep.test.tsx` (Continue gate + route select offers no empty option). A gating-hardening pass (per acceptance-test feedback that every non-review change stay behind enterprise mode) confirmed no non-enterprise leak across the epic: the `/prefill` enterprise extras are now gated on `isEnterpriseMode` so a non-enterprise response is byte-identical (`handlers.test.ts` non-enterprise omission, AC5), the container-router `defaultRoute`/`defaultReasoning` writes are nested under catalog presence so they travel as a unit with the catalog (no stray empty-string write without a catalog), and removing the default route resets its reasoning grade to off. **Status:** REQ-ENTERPRISE-012 stays Implemented (UI gate + backend `400` + store auto-default + non-enterprise prefill omission unit-tested).
0 commit comments