Skip to content

Commit aa9cbdd

Browse files
committed
Tighten Linear review gate and usage startup
1 parent a22cfe5 commit aa9cbdd

5 files changed

Lines changed: 14 additions & 7 deletions

File tree

apps/ade-cli/src/bootstrap.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,6 @@ export async function createAdeRuntime(args: {
978978
pollIntervalMs: 120_000,
979979
onUpdate: (snapshot) => pushEvent("runtime", { type: "usage", snapshot }),
980980
});
981-
usageTrackingService.start();
982981
const budgetCapService = createBudgetCapService({
983982
db,
984983
logger,
@@ -1203,6 +1202,7 @@ export async function createAdeRuntime(args: {
12031202
};
12041203
automationService.bindAdeActionRegistry(adeActionLookup);
12051204

1205+
usageTrackingService.start();
12061206
runtimeCreated = true;
12071207
return runtime;
12081208
} finally {

apps/desktop/src/main/services/cto/linearDispatcherService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2436,7 +2436,7 @@ export function createLinearDispatcherService(args: {
24362436
if (run.status !== "awaiting_human_review") {
24372437
throw new Error("This workflow run is not awaiting supervisor review.");
24382438
}
2439-
if (!currentStep || currentStep.type !== "request_human_review" || !reviewContext) {
2439+
if (!currentStep || currentStep.type !== "request_human_review" || !currentStepRow || !reviewContext) {
24402440
throw new Error("This workflow run is not on a human review step.");
24412441
}
24422442
updateRun(run.id, {

apps/desktop/src/main/services/cto/linearSync.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2263,8 +2263,8 @@ describe("linearDispatcherService (file group)", () => {
22632263
db.close();
22642264
});
22652265

2266-
it("rejects early approve before the supervisor review gate is active", async () => {
2267-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "ade-linear-dispatcher-early-approve-"));
2266+
it.each(["approve", "reject"] as const)("rejects early %s before the supervisor review gate is active", async (action) => {
2267+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "ade-linear-dispatcher-early-review-action-"));
22682268
const db = await openKvDb(path.join(root, "ade.db"), { debug() {}, info() {}, warn() {}, error() {} } as any);
22692269
const policy = buildSupervisedWorkerPolicy();
22702270

@@ -2312,8 +2312,11 @@ describe("linearDispatcherService (file group)", () => {
23122312
expect(detailBeforeReview?.run.status).not.toBe("awaiting_human_review");
23132313

23142314
await expect(
2315-
dispatcher.resolveRunAction(run.id, "approve", "Pre-approved.", policy),
2315+
dispatcher.resolveRunAction(run.id, action, "Pre-resolved.", policy),
23162316
).rejects.toThrow("not awaiting supervisor review");
2317+
const detailAfterRejectedAction = await dispatcher.getRunDetail(run.id, policy);
2318+
expect(detailAfterRejectedAction?.run.status).toBe(detailBeforeReview?.run.status);
2319+
expect(detailAfterRejectedAction?.run.reviewState).not.toBe(action === "approve" ? "approved" : "rejected");
23172320

23182321
const awaitingReview = await dispatcher.advanceRun(run.id, policy);
23192322
expect(awaitingReview?.status).toBe("awaiting_human_review");

docs/features/linear-integration/dispatch-and-sync.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,11 @@ A run's steps advance via `currentStepIndex`. For each step type:
205205
on timeout the step is marked failed with `review_timeout` and the
206206
run advances rather than stalling. `rejectAction` drives the
207207
rejection path (`cancel`, `reopen_issue`, or `loop_back` which
208-
resets `currentStepIndex` to `loopToStepId ?? "launch"`).
208+
resets `currentStepIndex` to `loopToStepId ?? "launch"`). Approve
209+
and reject actions are only valid while the run status is
210+
`awaiting_human_review` and the current step row is still a
211+
`request_human_review` step; early or stale resolutions throw
212+
without mutating `reviewState`.
209213
- `emit_app_notification` — broadcasts a
210214
`linear-workflow-notification` event via
211215
`ctoLinearWorkflowEvent` IPC. The renderer listens in `CtoPage` and

docs/features/remote-runtime/internal-architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Runtime event streaming uses `ade/actions/call` with `name: "stream_events"` for
3737

3838
Each `stream_events` response carries a per-runtime `eventEpoch` UUID minted when the daemon's `eventBuffer` is constructed. The preload event pump compares it against the last seen epoch for the active binding; if it changes (daemon restart, ssh reconnect to a fresh process) the cursor and dedup set reset and the next poll starts from `cursor=0`. The `startedAtMs` "drop events older than the pump start" filter is only applied to **local** bindings — remote pumps rely on the epoch reset instead, so older events backfilled after a reconnect are still delivered.
3939

40-
The remote event allowlist (`toRemoteRuntimeBufferedEvent`) accepts the runtime event categories desktop renders today: `category in {agent_chat, terminal, lane, pr, file_watch, process, test, project_state, orchestrator, dag_mutation, runtime, pty}`. The runtime additionally emits source-tagged events that preload routes to dedicated remote subscribers: `usage`, `usage_threshold`, `automation_event`, `conflict_event`, `github_status_changed`, `linear_workflow_event`, `feedback_submission_event`, `computer_use_event`, `ios_simulator_event`, `app_control_event`, and `macos_vm` (re-keyed to its `eventType`). ade-cli wires these into the runtime event buffer in `bootstrap.ts` so a remote-bound window sees the same usage, automation, conflict, GitHub, Linear, feedback, Computer Use, iOS Simulator, App Control, and macOS VM events as the local host.
40+
The remote event allowlist (`toRemoteRuntimeBufferedEvent`) accepts the runtime event categories desktop renders today: `category in {agent_chat, terminal, lane, pr, file_watch, process, test, project_state, orchestrator, dag_mutation, runtime, pty}`. The runtime additionally emits source-tagged events that preload routes to dedicated remote subscribers: `usage`, `usage_threshold`, `automation_event`, `conflict_event`, `github_status_changed`, `linear_workflow_event`, `feedback_submission_event`, `computer_use_event`, `ios_simulator_event`, `app_control_event`, and `macos_vm` (re-keyed to its `eventType`). ade-cli wires these into the runtime event buffer in `bootstrap.ts` so a remote-bound window sees the same usage, automation, conflict, GitHub, Linear, feedback, Computer Use, iOS Simulator, App Control, and macOS VM events as the local host. Headless runtimes start `usageTrackingService` during `createAdeRuntime()` after the ADE action registry is bound, so the usage poller and threshold events run only once the runtime can answer the matching usage/budget actions.
4141

4242
## SSH transport
4343

0 commit comments

Comments
 (0)