From fd8016514208e6d35ff0dcf332880ec2eb2586bc Mon Sep 17 00:00:00 2001 From: Joshua Date: Sun, 9 Aug 2026 22:20:37 +0100 Subject: [PATCH 01/13] docs: design guided inbox-first autopilot --- CONTEXT.md | 41 +++ ...026-08-09-guided-autopilot-inbox-design.md | 283 ++++++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 CONTEXT.md create mode 100644 docs/superpowers/specs/2026-08-09-guided-autopilot-inbox-design.md diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 000000000000..8d9563531ac5 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,41 @@ +# GITS Autonomy + +GITS autonomy turns operator-reviewed improvement ideas into safely queued work while keeping the operator-visible history distinct from execution state. + +## Language + +**Proposal**: +A candidate piece of work presented for operator review. It is not executable work until accepted. +_Avoid_: Goal, task, notification + +**Launch Configuration**: +The proposal-specific choices that define how accepted work should run, including its model and optional execution constraints. +_Avoid_: Global policy, settings form + +**Acceptance**: +The operator decision that approves a Proposal with its Launch Configuration and requests exactly one Goal. +_Avoid_: Dispatch, start, run + +**Goal**: +The durable execution record created from an accepted Proposal. A Goal may wait in the Queue before execution starts. +_Avoid_: Proposal, notification + +**Queue**: +The ordered collection of Goals eligible for future execution. Entering the Queue does not mean execution has started. +_Avoid_: Inbox, scheduler + +**Inbox Item**: +The durable operator-facing history of a Proposal and its resulting Goal. It is not execution truth. +_Avoid_: Goal, push notification + +**Notification**: +An optional delivery signal that points to an Inbox Item. Delivery failure does not remove or alter the Inbox Item. +_Avoid_: Inbox Item, Proposal + +**Pause**: +An operator state that prevents new proposals and future Goal starts without terminating work already running. +_Avoid_: Emergency Stop, kill + +**Emergency Stop**: +An operator action that terminates running autonomous work and prevents new Goal starts until autonomy is explicitly enabled again. +_Avoid_: Pause, reject diff --git a/docs/superpowers/specs/2026-08-09-guided-autopilot-inbox-design.md b/docs/superpowers/specs/2026-08-09-guided-autopilot-inbox-design.md new file mode 100644 index 000000000000..074eb8e9db57 --- /dev/null +++ b/docs/superpowers/specs/2026-08-09-guided-autopilot-inbox-design.md @@ -0,0 +1,283 @@ +# Guided Inbox-First Autopilot Design + +**Status:** Draft for operator review +**Date:** 2026-08-09 + +## Objective + +Redesign Autopilot around one understandable operator journey: receive a durable proposal in the Cockpit Inbox, open it from an Android PWA notification or the Inbox, validate the idea, choose a model from a dropdown, optionally adjust advanced proposal-specific settings, and accept it into the execution queue immediately. + +Keep the useful execution choices, but remove the current raw global-policy workflow. The normal cockpit must not require the operator to understand Automode modes, allowlist duplication, scheduler arming, approval flags, verification JSON, or notification-channel internals before accepting a proposal. + +Add notification diagnostics that send real Web Push notifications to the current device so Android PWA delivery and routing can be tested without creating production proposals or goals. + +## Domain Model + +The canonical terms are recorded in the repository `CONTEXT.md`. + +- A **Proposal** is a reviewable idea, not executable work. +- A **Launch Configuration** contains proposal-specific run choices. +- **Acceptance** validates a Proposal and requests exactly one **Goal**. +- A Goal enters the **Queue** immediately but may start later when execution gates permit. +- An **Inbox Item** is the durable operator-facing projection of the Proposal and Goal lifecycle. +- A **Notification** is an optional delivery signal pointing to that Inbox Item. + +These distinctions are load-bearing. The Inbox is not the Queue, accepting is not dispatching, and opening a notification is not acceptance. + +## Existing Modules Reused + +- `CockpitInbox` remains authoritative for unread state, pinning, retention, and the operator-visible timeline. +- `HermesProposalCard` remains authoritative for proposal content and editable launch inputs. +- `decideProposalWithAutomodeBridge` remains the deep module for decision, draft creation, idempotent Goal creation, scheduling, and Inbox projection. +- `AutomodeSupervisor` and its persisted Goals remain execution truth. +- `GitsSlotScheduler` remains the sole authority for whether queued work may start. +- `AutomodeNotifications`, `PushNotificationService`, and the existing service worker remain the Web Push delivery path. +- The existing server-side safety gates, isolated branches, verification floor, held-PR workflow, and boot disarm remain mandatory. + +The redesign deepens the existing proposal-acceptance seam rather than adding a parallel execution path. Inbox, PWA, and cockpit callers all cross the same acceptance interface, giving one place for validation, idempotency, recovery, and tests. + +## Chosen Interaction + +Use a three-step guided launch sheet opened from either an Inbox Item or its PWA notification. + +Two alternatives were rejected: + +- Keeping one dense editable card retains the current cognitive load and makes required choices unclear. +- One-click acceptance with entirely hidden defaults is simple but removes the model choice and operator confidence the redesign must preserve. + +The guided sheet keeps the common path short while placing rarely changed choices behind progressive disclosure. + +### Step 1: Validate Idea + +Show the Proposal's: + +- title and plain-language outcome; +- repository; +- summary and scope; +- evidence and verification plan; +- risk and action kind; and +- any blocking reason. + +The primary action is **Continue**. **Later** and **Reject** remain secondary actions. The operator does not edit execution settings on this step. + +### Step 2: Choose Model + +Show one prominent model dropdown. It is preselected from the Proposal's model when valid, otherwise from the server's current recommendation or default. Options come from the available, policy-supported model inventory; arbitrary model text is not accepted. + +Each option shows a short useful label, such as provider/model and recommendation status. If the selected model becomes unavailable before acceptance, the launch is blocked with a clear message and refreshed choices. + +An **Advanced details** disclosure contains the existing proposal-specific fields: + +- repository override; +- start time (`notBefore`); +- runtime limit; +- verification commands; and +- integration branch. + +Defaults come from the Proposal and current safe server policy. Advanced fields are validated with the existing proposal decision schemas and policy gates. The interface uses structured controls where the domain is enumerable; raw JSON is not shown in the common path. Verification commands use repeatable command rows, with label, argv, and optional timeout. + +### Step 3: Review and Launch + +Show a short plain-language summary: + +- what will run; +- where it will run; +- selected model; +- when it becomes eligible; +- runtime limit; +- verification expectation; and +- that successful work ends in a held PR rather than an automatic merge. + +The single primary action is **Accept & Queue**. The button remains disabled until required values are valid. While pending, it cannot be submitted again. Success shows the created Goal and its queue/schedule state, not a misleading claim that execution has started. + +The sheet keeps the current step in its URL-backed focused state so a PWA deep link, refresh, or existing cockpit window resolves to the same Proposal. Unsaved field edits remain local to the sheet and are discarded when it closes. + +## Simplified Autopilot Surface + +The normal Autopilot surface contains only: + +- **On / Paused** status and control; +- watched repositories; +- the Cockpit Inbox; +- a compact queue summary; and +- **Emergency Stop**. + +The following controls leave the normal surface: + +- manual/supervised/autonomous mode selection; +- manual Goal composer; +- max peer and budget fields; +- global model allowlist and arbitrary model text; +- peer-spawn, integrate, and destructive-action approval toggles; +- Motoko authority modes; +- scheduler enable, arm, disarm, nightly cap, and quota threshold controls; +- global verification-command JSON; +- global integration branch; +- duplicate GITS/Telegram notification toggles; and +- the separate goal-status filter toolbar. + +This is removal from the operator Interface, not deletion of safety behavior. Legacy persisted policy remains decodable for compatibility. Internal defaults and existing gates continue to constrain every Goal. + +### On, Pause, and Emergency Stop + +**On** enables proposal discovery for watched repositories and permits queued Goals to start through the existing scheduler and safety gates. Watched repositories are the execution allowlist and proposal-discovery list; the operator does not maintain two lists. + +**Pause** stops new proposal discovery and future Goal starts. It does not terminate a running Goal. Pausing also clears approval-based automatic arming so a restart or scheduler tick cannot silently undo the pause. + +**Emergency Stop** uses the existing stop-all path: it prevents new starts, disarms scheduling, and terminates currently running autonomous work. Re-enabling Autopilot is always explicit. + +One server operation owns each control transition so the web client does not coordinate several policy and scheduler RPCs. Internally, the implementation may reuse the current policy and scheduler stores, but partial failure must return a failed transition and leave future starts fail-closed. + +## Inbox and PWA Flow + +The normal path is: + +1. Proposal creation is durably persisted by Hermes. +2. `CockpitInbox` records `pending-review`. +3. Optional PWA delivery is attempted with a deep link to the Proposal launch sheet. +4. The operator opens the notification or Inbox Item. +5. The guided sheet loads the Proposal and current model choices. +6. **Accept & Queue** submits one proposal decision with its Launch Configuration. +7. The server creates or repairs exactly one Goal for the Proposal episode. +8. The server records `approved-queued` and schedules the Goal. +9. The sheet displays the resulting queue state. + +PWA delivery remains advisory. Notification failure never loses a Proposal because the Inbox transition is written first. Notification text stays concise and excludes full prompts, evidence, secrets, and repository contents. + +The notification itself does not mutate proposal or execution state. Tapping it opens the focused review sheet, and the explicit **Accept & Queue** action performs Acceptance. This avoids accidental launches and works consistently on Android browsers that do not expose notification action buttons uniformly. + +## Reliable Acceptance and Recovery + +Acceptance is a durable, idempotent operation keyed by Proposal episode ID. + +Its observable success condition is all of: + +1. the Proposal decision and edited Launch Configuration are persisted; +2. exactly one live Goal exists for the episode; +3. the Inbox Item references that Goal and displays `approved-queued`; and +4. scheduling has processed the Goal's eligibility time. + +The existing stores do not provide a cross-file transaction, so recovery uses ordered, replayable steps rather than pretending to be atomic. + +- If decision persistence succeeds but Goal creation fails, record `attention-required` when possible. Retry resumes Goal creation. +- If Goal creation succeeds but Inbox or scheduling fails, retry finds the existing live Goal and repairs the missing projection or schedule. It never creates a second live Goal. +- If the final response is lost, a repeated tap returns the same effective result. +- Multiple devices accepting concurrently converge on one live Goal for the episode. +- Reject and Later never create a Goal. + +The sheet remains open on failure, preserves entered choices, and shows one actionable error. An `attention-required` Inbox Item exposes **Retry launch** through the same guided sheet. + +## Android PWA Notification Diagnostics + +Settings → Notifications gains a compact **PWA diagnostics** section. It reports current-device prerequisites: + +- secure context; +- notification permission; +- service worker readiness; +- active Web Push subscription; and +- server VAPID availability. + +It provides two authenticated actions. + +### Send Delivery Test + +Send a real Web Push payload through the production server and push provider to the current browser subscription only. The request identifies the exact endpoint already registered by that browser. The server refuses an endpoint that is not in the authenticated installation's registered subscription set. + +The notification uses a unique tag so repeated tests are visible and do not collide with production notification deduplication. Its deep link opens the diagnostics result view and confirms that the notification click route reached the PWA. + +### Send Proposal Test + +Send a proposal-shaped notification to the current subscription. Tapping it opens the real guided launch sheet in explicit test mode with deterministic fixture content. + +Test mode exercises: + +- server dispatch; +- Android Web Push delivery; +- service-worker notification display; +- notification click and PWA focus/open behavior; +- deep-link routing; +- all three guided-sheet steps; and +- model-dropdown rendering. + +Its final button is **Complete Test**, not **Accept & Queue**. Test mode never writes a Proposal, Inbox Item, Goal, scheduler state, or notification delivery key used by production. The UI displays a permanent **Test notification** marker so fixture content cannot be mistaken for real work. + +The server reports only that the push provider accepted dispatch. The device confirms actual delivery by opening the notification. No false "delivered" claim is shown before that action. + +The test endpoint is owner-authenticated, accepts only the current registered endpoint and a fixed test kind, and accepts no caller-controlled title, body, or URL. This prevents it from becoming an arbitrary push or phishing endpoint. The UI disables repeated submission while a request is pending; no new configurable throttling system is introduced. + +## Data and Compatibility + +No second Proposal, Goal, Queue, or Inbox persistence model is added. + +The existing Proposal decision fields carry Launch Configuration. Existing Automode policy state remains readable. A migration normalizes duplicated watched/allowed repository lists into the watched-repository choice without discarding repositories already explicitly allowed. + +The normal web client stops sending broad policy edits. Narrow server operations expose Autopilot state transitions and watched-repository updates. Existing broad RPCs may remain temporarily for stored-state compatibility and internal tests, but they are no longer part of the cockpit Interface and receive no new features. + +Inbox retention remains unchanged: + +- prune unpinned terminal items 90 days after `terminalAt`; +- never prune pinned items; and +- never prune active items. + +## Error Handling + +- Inbox persistence failure prevents external proposal notification delivery and is surfaced to the initiating operation. +- External push failure is logged and does not alter Proposal, Inbox, or Goal state. +- Missing or stale Proposal deep links show a recovery state with Inbox refresh, not an empty launch form. +- Model inventory failure leaves the recommended value unavailable and blocks Acceptance until choices can be loaded. +- Server policy validation errors are attached to the affected launch field when possible. +- Acceptance partial failures use `attention-required` and the idempotent retry path. +- Corrupt existing Inbox or Automode state preserves the current fail-closed behavior; the redesign does not silently reset operator history. +- Test-push failure identifies the failing stage: prerequisites, subscription registration, server dispatch, or device-open confirmation. +- Pause or Emergency Stop partial failure must deny future starts. The UI never reports On until the server returns the reconciled state. + +## Testing + +Use focused checks at the existing seams. + +### Contracts and Server + +- schemas for narrow Autopilot controls and fixed notification-test input; +- watched repositories mapping to proposal discovery and execution allowlisting; +- On, Pause, and Emergency Stop semantics, including fail-closed partial failure; +- acceptance with edited model and advanced settings; +- exactly one live Goal for duplicate and concurrent Acceptance; +- recovery after failure at decision, Goal, Inbox, and scheduling stages; +- Inbox-first notification ordering and non-fatal push failure; +- current-subscription-only test delivery; +- rejection of unregistered endpoints and arbitrary test payloads; and +- proof that notification tests cannot mutate Proposal, Inbox, Goal, or scheduler state. + +### Web and Service Worker + +- guided-sheet step order and validation; +- recommended model selection and unavailable-model recovery; +- advanced details progressive disclosure and structured verification rows; +- Accept & Queue pending, success, error, and retry states; +- Inbox and notification deep links resolving the same Proposal; +- Android diagnostics prerequisite states; +- delivery and proposal test requests using the current subscription; +- unmistakable test-mode rendering and non-production final action; and +- notification click focusing a matching window or opening the correct URL. + +### Verification + +Run focused `bun run test` suites. Never use `bun test`. Repository completion additionally requires: + +- `bun fmt`; +- `bun lint`; and +- `bun typecheck`. + +After deployment, verify authenticated push configuration, current-device subscription registration, both notification-test dispatch paths, deep-link routing, and the production proposal → Inbox → guided Acceptance → queued Goal path. Physical Android receipt remains an operator-observed check because the server can prove push-provider acceptance but not notification display by the device. + +## Out of Scope + +- Direct queue mutation from a notification action button. +- Guaranteed delivery while Android or the browser suppresses notifications. +- Automatic merge or bypassing the held-PR review. +- A new scheduler, queue, or event store. +- Arbitrary notification payload composition. +- Persisted notification-test history or analytics. +- Per-user Inbox state for a multi-user product. +- Removing expert policy schemas before compatibility data has migrated. +- Redesigning Motoko chat or unrelated cockpit panels. From b9a3a461461dbe9ef74e2ba2a320999400de4568 Mon Sep 17 00:00:00 2001 From: Joshua Date: Sun, 9 Aug 2026 22:31:52 +0100 Subject: [PATCH 02/13] docs: plan guided inbox-first autopilot --- .../2026-08-09-guided-autopilot-inbox.md | 665 ++++++++++++++++++ 1 file changed, 665 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-09-guided-autopilot-inbox.md diff --git a/docs/superpowers/plans/2026-08-09-guided-autopilot-inbox.md b/docs/superpowers/plans/2026-08-09-guided-autopilot-inbox.md new file mode 100644 index 000000000000..097c10207d09 --- /dev/null +++ b/docs/superpowers/plans/2026-08-09-guided-autopilot-inbox.md @@ -0,0 +1,665 @@ +# Guided Inbox-First Autopilot Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the configuration-heavy Autopilot cockpit with a guided Inbox-first proposal launch flow and add current-device Android PWA notification diagnostics. + +**Architecture:** Keep the existing proposal bridge, durable Inbox, Automode Goal store, scheduler, and push sender as the deep execution module. Add one narrow Autopilot control operation, a three-step proposal launch sheet that submits the existing proposal decision contract, and a fixed authenticated push-test route that can target only an already registered subscription. + +**Tech Stack:** TypeScript, React 19, TanStack Query, Effect 4, Effect Schema/RPC, Vitest, `@effect/vitest`, Web Push, service workers, Base UI controls, Bun workspaces. + +## Global Constraints + +- Keep proposal choices, especially model selection, but make Model the only prominent run choice. +- Put repository override, start time, runtime, verification commands, and integration branch under Advanced details. +- A PWA notification opens review; it never mutates proposal or queue state itself. +- Acceptance creates exactly one live Goal and reports queued state, not started state. +- Test notifications target only the requesting browser's registered endpoint and cannot create Proposals, Inbox Items, Goals, scheduler state, or production delivery keys. +- Pause blocks new proposals and future starts without killing running work; Emergency Stop also kills running autonomous work. +- Preserve isolated branches, verification, held-PR review, quota/runtime gates, boot disarm, and Inbox retention. +- Terminal Inbox items are pruned after 90 days; pinned items and active items are never pruned. +- Add no dependencies and no new persistence store. +- Never run `bun test`; use `bun run test`. +- Before completion, `bun fmt`, `bun lint`, and `bun typecheck` must pass. + +--- + +## File Map + +- `packages/contracts/src/gits.ts`: narrow Autopilot configure input/result schemas. +- `packages/contracts/src/push.ts`: fixed push-test request/result schemas. +- `packages/contracts/src/rpc.ts`: Autopilot configure RPC registration. +- `packages/contracts/src/gits.test.ts`, `packages/contracts/src/push.test.ts`: schema coverage. +- `packages/client-runtime/src/wsRpcClient.ts`: typed `automode.configure` client method. +- `apps/server/src/gits/Layers/AutopilotControl.ts`: fail-closed On/Pause/Emergency Stop orchestration. +- `apps/server/src/gits/Layers/AutopilotControl.test.ts`: control semantics. +- `apps/server/src/gits/Layers/HermesAutomodeBridge.ts`: acceptance recovery projection. +- `apps/server/src/gits/Layers/HermesAutomodeBridge.test.ts`: partial-failure and retry coverage. +- `apps/server/src/push/Services/PushNotificationService.ts`: targeted delivery interface. +- `apps/server/src/push/Layers/PushNotificationService.ts`: registered-endpoint delivery. +- `apps/server/src/push/Layers/PushNotificationService.test.ts`: targeting and failure results. +- `apps/server/src/push/http.ts`: authenticated fixed test route. +- `apps/server/src/push/http.test.ts`: route security and payload coverage. +- `apps/server/src/server.ts`: push-test route registration. +- `apps/server/src/ws.ts`: Autopilot configure and stop handler wiring. +- `apps/server/src/server.test.ts`: RPC/route registration smoke coverage. +- `apps/web/src/lib/webPush.ts`: current-subscription diagnostics and test request helpers. +- `apps/web/src/lib/webPush.test.ts`: prerequisite and request behavior. +- `apps/web/src/service-worker.ts`: keep notification click routing generic and test it. +- `apps/web/src/components/settings/SettingsPanels.tsx`: PWA diagnostics UI. +- `apps/web/src/components/settings/SettingsPanels.browser.tsx`: diagnostics interaction coverage. +- `apps/web/src/components/gits/cockpit/proposal-launch/proposalLaunch.logic.ts`: launch defaults, model options, validation, edits. +- `apps/web/src/components/gits/cockpit/proposal-launch/proposalLaunch.logic.test.ts`: pure launch logic. +- `apps/web/src/components/gits/cockpit/ProposalLaunchSheet.tsx`: three-step real/test launch sheet. +- `apps/web/src/components/gits/cockpit/ProposalLaunchSheet.browser.tsx`: guided interaction coverage. +- `apps/web/src/components/gits/cockpit/AutopilotPanel.tsx`: simplified status/Inbox/queue surface. +- `apps/web/src/components/gits/cockpit/AutopilotPanel.browser.tsx`: reduced-surface coverage. +- `apps/web/src/components/gits/GitsCockpit.tsx`: remove obsolete form state/mutations and refresh all acceptance projections. +- `apps/web/src/components/gits/cockpit/autopilot/autopilot.logic.ts`: delete obsolete global policy-form logic. +- `apps/web/src/components/gits/cockpit/autopilot/autopilot.logic.test.ts`: delete obsolete tests. + +--- + +### Task 1: Add Narrow Contracts and Client Method + +**Files:** +- Modify: `packages/contracts/src/gits.ts` +- Modify: `packages/contracts/src/push.ts` +- Modify: `packages/contracts/src/rpc.ts` +- Modify: `packages/contracts/src/gits.test.ts` +- Create: `packages/contracts/src/push.test.ts` +- Modify: `packages/client-runtime/src/wsRpcClient.ts` + +**Interfaces:** +- Produces: `AutopilotConfigureInput`, `AutopilotControlSnapshot`, `WebPushTestKind`, `WebPushTestInput`, `WebPushTestResult`, and `WsGitsAutomodeConfigureRpc`. +- Produces client call: `client.gits.automode.configure(input)`. + +- [ ] **Step 1: Write failing contract tests** + +Add schema assertions equivalent to: + +```ts +const configure = Schema.decodeUnknownSync(AutopilotConfigureInput); +expect(configure({ enabled: true, repositories: ["/srv/repo"] })).toEqual({ + enabled: true, + repositories: ["/srv/repo"], +}); +expect(() => configure({ enabled: true, repositories: [] })).not.toThrow(); + +const pushTest = Schema.decodeUnknownSync(WebPushTestInput); +expect(pushTest({ endpoint: "https://push.example/device", kind: "proposal" })).toEqual({ + endpoint: "https://push.example/device", + kind: "proposal", +}); +expect(() => pushTest({ endpoint: "https://push.example/device", kind: "custom" })).toThrow(); +``` + +- [ ] **Step 2: Run the tests and verify they fail on missing exports** + +Run: `bun run test packages/contracts/src/gits.test.ts packages/contracts/src/push.test.ts` + +Expected: FAIL because the new schemas do not exist. + +- [ ] **Step 3: Add the schemas and RPC** + +Use these exact public shapes: + +```ts +export const AutopilotConfigureInput = Schema.Struct({ + enabled: Schema.Boolean, + repositories: Schema.Array(PathString), +}); + +export const AutopilotControlSnapshot = Schema.Struct({ + automode: AutomodeSnapshot, + scheduler: GitsSchedulerSnapshot, +}); + +export const WebPushTestKind = Schema.Literals(["delivery", "proposal"]); +export const WebPushTestInput = Schema.Struct({ + endpoint: PushEndpoint, + kind: WebPushTestKind, +}); +export const WebPushTestResult = Schema.Struct({ + accepted: Schema.Literal(true), + url: TrimmedNonEmptyString, +}); +``` + +Register `gits.automode.configure` with `AutopilotConfigureInput`, `AutopilotControlSnapshot`, and `AutomodeSupervisorError`; export it in `WsRpcSchema`. Add the corresponding client type and implementation beside `updatePolicy`. + +- [ ] **Step 4: Run focused tests and typecheck the touched packages** + +Run: `bun run test packages/contracts/src/gits.test.ts packages/contracts/src/push.test.ts && bun run build:contracts` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add packages/contracts/src/gits.ts packages/contracts/src/push.ts packages/contracts/src/rpc.ts packages/contracts/src/gits.test.ts packages/contracts/src/push.test.ts packages/client-runtime/src/wsRpcClient.ts +git commit -m "feat: add guided autopilot contracts" +``` + +### Task 2: Add Fail-Closed Autopilot Controls + +**Files:** +- Create: `apps/server/src/gits/Layers/AutopilotControl.ts` +- Create: `apps/server/src/gits/Layers/AutopilotControl.test.ts` +- Modify: `apps/server/src/ws.ts` +- Modify: `apps/server/src/server.test.ts` + +**Interfaces:** +- Consumes: `AutopilotConfigureInput`, `AutopilotControlSnapshot`. +- Produces: `configureAutopilot(dependencies, input)` and `emergencyStopAutopilot(dependencies)`. + +- [ ] **Step 1: Write failing control tests** + +Cover these observable calls: + +```ts +expect(onCalls).toEqual([ + ["scheduler.setConfig", { enabled: true }], + ["supervisor.updatePolicy", { + mode: "autonomous", + killSwitchEnabled: false, + maxActivePeers: 1, + allowedRepos: [repo], + proposalRepos: [repo], + nightlyProposalSweep: true, + sweepRequiresConfirmation: true, + autoEnqueueApprovedProposals: true, + gitsNotificationsEnabled: true, + }], +]); + +expect(pauseCalls).toEqual([ + ["scheduler.disarm", { reason: "Autopilot paused." }], + ["supervisor.updatePolicy", { + mode: "manual", + allowedRepos: [repo], + proposalRepos: [repo], + nightlyProposalSweep: false, + }], +]); +``` + +Also assert empty repositories reject enabling, a scheduler-enable failure never switches policy to autonomous, Pause disarms before updating policy, and Emergency Stop disarms before calling `stopAll`. These orderings ensure a later failure still denies future starts. + +- [ ] **Step 2: Run the test and verify it fails** + +Run: `bun run test apps/server/src/gits/Layers/AutopilotControl.test.ts` + +Expected: FAIL because `AutopilotControl.ts` does not exist. + +- [ ] **Step 3: Implement the two orchestration functions** + +Keep the seam dependency-based and small: + +```ts +export function configureAutopilot( + dependencies: { + supervisor: Pick; + scheduler: Pick; + }, + input: AutopilotConfigureInput, +): Effect.Effect; + +export function emergencyStopAutopilot( + dependencies: { + supervisor: Pick; + scheduler: Pick; + }, +): Effect.Effect; +``` + +Map scheduler errors into `AutomodeSupervisorError`. Do not arm on On; accepted work remains the authorization point through `scheduleApprovedGoal`. + +- [ ] **Step 4: Wire RPC handlers** + +Add `WS_METHODS.gitsAutomodeConfigure` to `ws.ts` using `configureAutopilot`. Replace the current stop-all handler effect with `emergencyStopAutopilot` so the existing UI and Telegram-compatible result remain unchanged. + +- [ ] **Step 5: Run focused tests** + +Run: `bun run test apps/server/src/gits/Layers/AutopilotControl.test.ts apps/server/src/server.test.ts` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add apps/server/src/gits/Layers/AutopilotControl.ts apps/server/src/gits/Layers/AutopilotControl.test.ts apps/server/src/ws.ts apps/server/src/server.test.ts +git commit -m "feat: add simple autopilot controls" +``` + +### Task 3: Make Acceptance Recovery Visible and Idempotent + +**Files:** +- Modify: `apps/server/src/gits/Layers/HermesAutomodeBridge.ts` +- Modify: `apps/server/src/gits/Layers/HermesAutomodeBridge.test.ts` + +**Interfaces:** +- Consumes: existing `decideProposalWithAutomodeBridge` dependencies. +- Produces: the same external result with a new recovery invariant: approval failure records `attention-required` when the Proposal is known. + +- [ ] **Step 1: Add failing partial-failure tests** + +Add scenarios for enqueue failure and scheduler failure. Assert the Inbox record includes: + +```ts +expect(events.at(-1)).toMatchObject({ + proposalId: proposal.id, + episodeId: proposal.episodeId, + goalId: null, + eventKey: `proposal:${proposal.id}:acceptance-failed`, + state: "attention-required", + reason: expect.stringContaining("Retry"), +}); +``` + +For scheduler failure after Goal creation, expect `goalId` to be populated. Retry the same approval and assert only one live Goal exists and the final Inbox state is `approved-queued` or `scheduled-tonight`. + +- [ ] **Step 2: Run the bridge test and verify failure** + +Run: `bun run test apps/server/src/gits/Layers/HermesAutomodeBridge.test.ts` + +Expected: FAIL because failure is not projected to the Inbox. + +- [ ] **Step 3: Implement one best-effort recovery projection** + +Track the known Proposal and current Goal inside the existing bridge operation. On an approval-path error, call `dependencies.inbox.record` with stable key `proposal::acceptance-failed`, state `attention-required`, the Goal ID when known, and the existing proposal deep link. Ignore only failure of this compensating Inbox write, then rethrow the original `HermesAdapterError`. + +Do not alter the successful ordering, episode deduplication, or decision contract. + +- [ ] **Step 4: Run the bridge and Inbox suites** + +Run: `bun run test apps/server/src/gits/Layers/HermesAutomodeBridge.test.ts apps/server/src/gits/Layers/CockpitInbox.test.ts` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add apps/server/src/gits/Layers/HermesAutomodeBridge.ts apps/server/src/gits/Layers/HermesAutomodeBridge.test.ts +git commit -m "fix: make proposal launch retries visible" +``` + +### Task 4: Add Current-Device Push Tests + +**Files:** +- Modify: `apps/server/src/push/Services/PushNotificationService.ts` +- Modify: `apps/server/src/push/Layers/PushNotificationService.ts` +- Modify: `apps/server/src/push/Layers/PushNotificationService.test.ts` +- Modify: `apps/server/src/push/http.ts` +- Create: `apps/server/src/push/http.test.ts` +- Modify: `apps/server/src/server.ts` + +**Interfaces:** +- Produces: `sendToEndpoint(endpoint, payload): Effect<"sent" | "disabled" | "not-found" | "failed">`. +- Produces: authenticated `POST /api/push/test` accepting only `WebPushTestInput`. + +- [ ] **Step 1: Write failing targeted-send tests** + +Create two registered subscriptions, call `sendToEndpoint` for one, and assert only that endpoint is sent. Cover disabled VAPID, missing endpoint, sender failure, and expired-subscription deletion. + +```ts +const result = yield* service.sendToEndpoint(active.endpoint, payload); +assert.strictEqual(result, "sent"); +assert.deepStrictEqual(sentEndpoints, [active.endpoint]); +``` + +- [ ] **Step 2: Run the push service test and verify failure** + +Run: `bun run test apps/server/src/push/Layers/PushNotificationService.test.ts` + +Expected: FAIL because `sendToEndpoint` is absent. + +- [ ] **Step 3: Implement targeted delivery by reusing the existing sender** + +List registered subscriptions, find an exact endpoint match, serialize the provided typed payload, and call `WebPushSender.send` once. Delete 404/410 endpoints. Return a closed result union instead of accepting caller-provided fallback behavior. + +- [ ] **Step 4: Write and implement route tests** + +The route builds fixed payload content only. It creates the tag server-side with `crypto.randomUUID()` so repeated Android tests remain visible without accepting caller text: + +```ts +const TEST_PAYLOADS = { + delivery: { + title: "GITS notification test", + body: "Android PWA delivery is working. Tap to confirm.", + url: "/gits?panel=autopilot¬ificationTest=delivery", + }, + proposal: { + title: "Test proposal ready", + body: "Tap to test the guided proposal launch.", + url: "/gits?panel=autopilot¬ificationTest=proposal", + }, +} satisfies Record>; + +const payload = { + ...TEST_PAYLOADS[input.kind], + tag: `gits:test:${input.kind}:${crypto.randomUUID()}`, +}; +``` + +Require owner authentication with the same helper as subscription routes. Return 200 plus `{ accepted: true, url }` only for `sent`; map disabled to 503, not-found to 404, and failed to 502. Tests must prove thread-scoped auth is rejected and request bodies cannot provide title/body/url. + +- [ ] **Step 5: Register the route and run tests** + +Run: `bun run test apps/server/src/push/Layers/PushNotificationService.test.ts apps/server/src/push/http.test.ts apps/server/src/server.test.ts` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add apps/server/src/push apps/server/src/server.ts +git commit -m "feat: add targeted PWA notification tests" +``` + +### Task 5: Build Guided Launch Logic Test-First + +**Files:** +- Create: `apps/web/src/components/gits/cockpit/proposal-launch/proposalLaunch.logic.ts` +- Create: `apps/web/src/components/gits/cockpit/proposal-launch/proposalLaunch.logic.test.ts` + +**Interfaces:** +- Produces: `LaunchStep`, `ProposalLaunchForm`, `launchModelOptions`, `initialProposalLaunchForm`, `validateProposalLaunchForm`, `proposalLaunchEdits`, and `TEST_PROPOSAL`. + +- [ ] **Step 1: Write failing pure tests** + +Cover proposal/default model precedence, allowed-model filtering, fallback to the three `CODEX_MODEL_TIERS`, invalid runtime, missing repository, ISO conversion for `notBefore`, structured verification rows, and deterministic test fixture behavior. + +```ts +expect(initialProposalLaunchForm(proposal, policy).model).toBe(proposal.model); +expect(launchModelOptions({ ...policy, allowedModels: [] }).map((item) => item.value)).toEqual([ + CODEX_MODEL_TIERS.light, + CODEX_MODEL_TIERS.medium, + CODEX_MODEL_TIERS.high, +]); +expect(validateProposalLaunchForm({ ...form, runtime: "-1" })).toMatchObject({ + ok: false, + field: "runtime", +}); +``` + +- [ ] **Step 2: Run and verify failure** + +Run: `bun run test apps/web/src/components/gits/cockpit/proposal-launch/proposalLaunch.logic.test.ts` + +Expected: FAIL because the module is absent. + +- [ ] **Step 3: Implement minimal pure logic** + +Use the existing `CODEX_MODEL_TIERS`, `HermesProposalCard`, `AutomodePolicy`, `GitsVerifyCommand`, and `MotokoProposalEdits` types. Model options are policy `allowedModels` when non-empty; otherwise use the three tier slugs. Include the Proposal model when valid and not duplicated. Do not add free-text custom models. + +Represent verification commands as `ReadonlyArray` and edit them with rows in the UI; never serialize them to editable JSON. + +- [ ] **Step 4: Run tests** + +Run: `bun run test apps/web/src/components/gits/cockpit/proposal-launch/proposalLaunch.logic.test.ts` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add apps/web/src/components/gits/cockpit/proposal-launch +git commit -m "feat: add guided proposal launch logic" +``` + +### Task 6: Build the Three-Step Launch Sheet + +**Files:** +- Create: `apps/web/src/components/gits/cockpit/ProposalLaunchSheet.tsx` +- Create: `apps/web/src/components/gits/cockpit/ProposalLaunchSheet.browser.tsx` +- Modify: `apps/web/src/components/gits/GitsCockpit.tsx` + +**Interfaces:** +- Consumes: Task 5 launch logic and existing `onDecision(proposal, decision, edits)` callback. +- Produces: `ProposalLaunchSheet` supporting real Proposal IDs and `testMode="proposal"`. +- Produces callback: `onDecision(...): Promise` so the sheet can render the actual queued Goal. + +- [ ] **Step 1: Write failing browser interactions** + +Render the sheet with a proposed card and assert: + +1. Step 1 shows outcome/scope/evidence and Continue. +2. Step 2 shows a model `