mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-07 03:00:29 +00:00
parent
8d3d39889e
commit
6a5a47326a
6 changed files with 1028 additions and 20 deletions
751
run.json
751
run.json
File diff suppressed because one or more lines are too long
1
stages/004-preflight_lint@1/output.log
Normal file
1
stages/004-preflight_lint@1/output.log
Normal file
|
|
@ -0,0 +1 @@
|
|||
blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126
|
||||
8
stages/004-preflight_lint@1/script_timing.json
Normal file
8
stages/004-preflight_lint@1/script_timing.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",
|
||||
"exit_code": 0,
|
||||
"duration_ms": 136041,
|
||||
"termination": "exited",
|
||||
"output_bytes": 0,
|
||||
"live_streaming": false
|
||||
}
|
||||
6
stages/004-preflight_lint@1/status.json
Normal file
6
stages/004-preflight_lint@1/status.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"outcome": "succeeded",
|
||||
"notes": "Script completed: cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1",
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-05-24T01:47:11.101490Z"
|
||||
}
|
||||
276
stages/005-implement@1/prompt.md
Normal file
276
stages/005-implement@1/prompt.md
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
Goal: ---
|
||||
title: feat: Batch run archive actions
|
||||
type: feat
|
||||
status: active
|
||||
date: 2026-05-24
|
||||
---
|
||||
|
||||
# feat: Batch Run Archive Actions
|
||||
|
||||
## Overview
|
||||
|
||||
Add API support for archiving and unarchiving multiple runs in one request, then update the web list and board multi-run actions to use the new batch endpoints. The existing single-run archive and unarchive endpoints remain unchanged for run-scoped callers and direct lifecycle actions.
|
||||
|
||||
## Problem Frame
|
||||
|
||||
The web UI currently performs multi-run archive/unarchive actions by issuing one lifecycle request per selected run. That works, but it puts batch orchestration in the browser, repeats request overhead, and leaves API/CLI/MCP consumers without a first-class batch contract. A bounded fail-soft batch endpoint gives the server ownership of the multi-run operation while preserving the independent event stream semantics of each run.
|
||||
|
||||
## Requirements Trace
|
||||
|
||||
- R1. Provide public API endpoints that archive and unarchive many runs in one request.
|
||||
- R2. Preserve existing single-run archive/unarchive behavior, including eligibility, idempotency, and emitted events.
|
||||
- R3. Return per-run results so mixed-success batches can be reported without rolling back successful items.
|
||||
- R4. Update web bulk actions to make one request per batch action instead of one request per run.
|
||||
- R5. Keep cache invalidation and toast behavior equivalent to the current UI.
|
||||
|
||||
## Scope Boundaries
|
||||
|
||||
- Do not make batch archive/unarchive transactional; each run remains an independent event stream.
|
||||
- Do not add new workflow event variants; successful batch items emit the same run archive/unarchive events as single-run actions.
|
||||
- Do not change the existing `POST /api/v1/runs/{id}/archive` or `POST /api/v1/runs/{id}/unarchive` contracts.
|
||||
- Do not add CLI commands in this change; this plan is limited to HTTP API and web UI usage.
|
||||
|
||||
## Context & Research
|
||||
|
||||
### Relevant Code and Patterns
|
||||
|
||||
- OpenAPI is the source of truth for HTTP contracts in `docs/public/api-reference/fabro-api.yaml`.
|
||||
- Server lifecycle routes live in `lib/crates/fabro-server/src/server/handler/lifecycle.rs`; single-run archive/unarchive already funnel through `operations::archive` and `operations::unarchive`.
|
||||
- Batch routes that are not tied to one path run ID should use `RequiredUser`, not `RequireRunScopedOrRunTools`, because a run-scoped worker token cannot safely authorize mutation of arbitrary run IDs from a request body.
|
||||
- Web lifecycle helpers live in `apps/fabro-web/app/lib/run-actions.ts`; list and board multi-run archive behavior lives in `apps/fabro-web/app/routes/runs.tsx`.
|
||||
- Run-list cache invalidation already uses `mutateRunListCaches` in `apps/fabro-web/app/lib/board-cache.ts`.
|
||||
|
||||
### Strategy Docs
|
||||
|
||||
- `docs/internal/testing-strategy.md`: server tests should assert public HTTP contracts and prefer structured assertions.
|
||||
- `docs/internal/error-handling-strategy.md`: preserve structured errors internally and return curated API messages at HTTP boundaries.
|
||||
- `docs/internal/events-strategy.md`: no new events are needed because existing per-run lifecycle events already represent the durable state transition.
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
- Add collection action endpoints `POST /api/v1/runs/archive` and `POST /api/v1/runs/unarchive`. These avoid changing single-run URLs and keep generated client methods clear (`batchArchiveRuns`, `batchUnarchiveRuns`).
|
||||
- Use fail-soft HTTP `200` responses for valid batch requests, even when individual items fail. Per-item failures carry structured result entries; request-level validation failures still return normal `400` errors.
|
||||
- Validate `run_ids` at the request boundary: non-empty, maximum 250 IDs, no duplicates, and every value parseable as a `RunId`. Invalid request bodies must not mutate any runs.
|
||||
- Process eligible IDs sequentially in server code. The batch is bounded, lifecycle operations append events, and sequential processing avoids adding lock-order or concurrency behavior that the feature does not need.
|
||||
- Treat idempotent single-run outcomes as successful batch outcomes: already archived counts as success for archive; terminal not-archived counts as success for unarchive.
|
||||
|
||||
## API Contract
|
||||
|
||||
Add these OpenAPI operations:
|
||||
|
||||
- `POST /api/v1/runs/archive`
|
||||
- operationId: `batchArchiveRuns`
|
||||
- request: `BatchRunLifecycleRequest`
|
||||
- response: `BatchRunLifecycleResponse`
|
||||
- `POST /api/v1/runs/unarchive`
|
||||
- operationId: `batchUnarchiveRuns`
|
||||
- request: `BatchRunLifecycleRequest`
|
||||
- response: `BatchRunLifecycleResponse`
|
||||
|
||||
Add schemas:
|
||||
|
||||
- `BatchRunLifecycleRequest`
|
||||
- required `run_ids`
|
||||
- `run_ids`: array of strings, `minItems: 1`, `maxItems: 250`, `uniqueItems: true`
|
||||
- `BatchRunLifecycleResponse`
|
||||
- required `results`, `summary`
|
||||
- `results`: array of `BatchRunLifecycleResult`, ordered exactly like the request `run_ids`
|
||||
- `summary`: `BatchRunLifecycleSummary`
|
||||
- `BatchRunLifecycleResult`
|
||||
- required `run_id`, `ok`, `outcome`
|
||||
- `run_id`: string
|
||||
- `ok`: boolean
|
||||
- `outcome`: enum covering `archived`, `already_archived`, `unarchived`, `not_archived`, `not_found`, `conflict`, `error`
|
||||
- optional `run`: `Run`, present for successful items when a decorated summary can be loaded
|
||||
- optional `error`: `ErrorResponseEntry`, present for failed items
|
||||
- `BatchRunLifecycleSummary`
|
||||
- required `requested`, `succeeded`, `failed`
|
||||
- all integer counts
|
||||
|
||||
## Implementation Units
|
||||
|
||||
- [ ] **Unit 1: OpenAPI batch lifecycle contract**
|
||||
|
||||
**Goal:** Add the public API contract and generated clients for batch archive/unarchive.
|
||||
|
||||
**Requirements:** R1, R3
|
||||
|
||||
**Dependencies:** None
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/public/api-reference/fabro-api.yaml`
|
||||
- Generated by build/codegen: `lib/crates/fabro-api/src/generated.rs`
|
||||
- Generated by codegen: `lib/packages/fabro-api-client/src/api/runs-api.ts`
|
||||
- Generated by codegen: `lib/packages/fabro-api-client/src/models/*`
|
||||
|
||||
**Approach:**
|
||||
- Add the two collection action paths and the four batch lifecycle schemas described in the API Contract section.
|
||||
- Keep response status simple: `200` for a valid processed batch; `400`, `401`, and `500` for request-level failures.
|
||||
- Run Rust generation through `cargo build -p fabro-api`.
|
||||
- Run TypeScript generation through `cd lib/packages/fabro-api-client && bun run generate`.
|
||||
|
||||
**Patterns to follow:**
|
||||
- Existing single-run archive/unarchive path docs in the same OpenAPI file.
|
||||
- Existing generated client workflow described in `AGENTS.md`.
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: generated Rust and TypeScript clients expose `batchArchiveRuns` and `batchUnarchiveRuns`.
|
||||
- Contract: request schema enforces `run_ids` as the only required input.
|
||||
- Contract: result entries can represent both a successful decorated `Run` and a failed `ErrorResponseEntry`.
|
||||
|
||||
**Verification:**
|
||||
- API generation completes without hand-edited generated files.
|
||||
|
||||
- [ ] **Unit 2: Server batch lifecycle handlers**
|
||||
|
||||
**Goal:** Implement the two new endpoints in the server using existing archive/unarchive operations.
|
||||
|
||||
**Requirements:** R1, R2, R3
|
||||
|
||||
**Dependencies:** Unit 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-server/src/server/handler/lifecycle.rs`
|
||||
- Test: `lib/crates/fabro-server/src/server/tests.rs`
|
||||
|
||||
**Approach:**
|
||||
- Register `POST /runs/archive` and `POST /runs/unarchive` in the lifecycle route set.
|
||||
- Use `RequiredUser` for both handlers and convert the user into `Principal::User` for per-run operation calls.
|
||||
- Add a small shared batch helper that accepts the parsed request, the action, and the actor, then returns `BatchRunLifecycleResponse`.
|
||||
- Before processing any item, validate the entire request for empty list, over-limit list, duplicate IDs, and invalid IDs. Return a normal `400` API error if validation fails.
|
||||
- For each valid ID, call `operations::archive` or `operations::unarchive`; map success outcomes to item-level success results and map `RunNotFound`/`Precondition` to item-level `not_found`/`conflict` failures.
|
||||
- For successful items, load and decorate the current run summary the same way single-run lifecycle responses do. If summary loading fails after the operation succeeds, record that item as `error` rather than hiding the failure.
|
||||
|
||||
**Patterns to follow:**
|
||||
- `run_archive_action` for operation mapping and existing error semantics.
|
||||
- `run_response` / `state.decorate_run_summary` for response shape.
|
||||
- Existing server tests around `archive_and_unarchive_updates_listing_visibility`.
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: two terminal runs archived in one request return two successful result entries, summary `requested=2/succeeded=2/failed=0`, and both runs are hidden from default `GET /api/v1/runs`.
|
||||
- Happy path: two archived runs unarchived in one request return successful entries and both runs reappear in default listing.
|
||||
- Idempotency: archiving an already archived run returns `ok=true` with `already_archived`; unarchiving a terminal non-archived run returns `ok=true` with `not_archived`.
|
||||
- Mixed result: a batch containing one terminal run, one running run, and one missing run returns ordered results with one success, one `conflict`, and one `not_found`; the terminal run is still archived.
|
||||
- Error path: empty `run_ids`, duplicate IDs, invalid IDs, and more than 250 IDs return request-level `400` and mutate no runs.
|
||||
- Auth path: unauthenticated requests are rejected; run-scoped worker authentication is not accepted for batch endpoints.
|
||||
|
||||
**Verification:**
|
||||
- Existing single-run archive/unarchive tests pass unchanged.
|
||||
- New endpoint tests prove both API contract and run-list visibility effects.
|
||||
|
||||
- [ ] **Unit 3: Frontend lifecycle helpers**
|
||||
|
||||
**Goal:** Add typed web helpers for batch archive/unarchive.
|
||||
|
||||
**Requirements:** R3, R4, R5
|
||||
|
||||
**Dependencies:** Unit 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `apps/fabro-web/app/lib/run-actions.ts`
|
||||
- Test: `apps/fabro-web/app/lib/run-actions.test.ts`
|
||||
|
||||
**Approach:**
|
||||
- Add `archiveRuns(runIds: string[])` and `unarchiveRuns(runIds: string[])` wrappers around the generated client methods.
|
||||
- Keep existing `archiveRun` and `unarchiveRun` helpers unchanged.
|
||||
- Preserve existing `LifecycleActionError` behavior for single-run actions. Batch helpers should return the generated batch response for valid mixed results and throw only for request-level API failures.
|
||||
|
||||
**Patterns to follow:**
|
||||
- Existing lifecycle action helpers in `run-actions.ts`.
|
||||
- Axios adapter tests in `run-actions.test.ts`.
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: `archiveRuns(["run-1", "run-2"])` sends one generated-client request and returns parsed batch results.
|
||||
- Mixed result: helper resolves a response containing one success and one per-item failure without throwing.
|
||||
- Request error: helper throws parsed `ApiError`/lifecycle-style data when the server returns a request-level `400`.
|
||||
- Regression: existing `archiveRun`, `unarchiveRun`, `canArchive`, and `canUnarchive` tests continue to pass.
|
||||
|
||||
**Verification:**
|
||||
- Web unit tests cover the new helper contract without changing single-run behavior.
|
||||
|
||||
- [ ] **Unit 4: Web bulk-action integration**
|
||||
|
||||
**Goal:** Replace multi-request UI orchestration with one batch request per bulk action.
|
||||
|
||||
**Requirements:** R4, R5
|
||||
|
||||
**Dependencies:** Units 1 and 3
|
||||
|
||||
**Files:**
|
||||
- Modify: `apps/fabro-web/app/routes/runs.tsx`
|
||||
- Test: `apps/fabro-web/app/routes/runs.test.tsx`
|
||||
|
||||
**Approach:**
|
||||
- Update `BulkActionToolbar` to call `archiveRuns` or `unarchiveRuns` once with eligible selected IDs.
|
||||
- Add a small pure batch-summary helper in `runs.tsx`, export it for route tests, and use it to compute toast messages from the batch response summary rather than `Promise.allSettled`.
|
||||
- Keep current eligibility filtering: archive only selected runs whose lifecycle status is archivable, unarchive only selected archived runs.
|
||||
- Clear selection only when all eligible items succeed, matching the current all-success behavior.
|
||||
- Call `mutateRunListCaches` once after the batch settles.
|
||||
- Update the kanban column archive-all action to call `archiveRuns` once with the column’s eligible IDs and reuse the same success/partial/failure toast behavior where practical.
|
||||
|
||||
**Patterns to follow:**
|
||||
- Current `BulkActionToolbar` pending state, clear-selection behavior, and toast wording.
|
||||
- Current `ColumnActionsMenu` archive-all action and cache invalidation.
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: selecting multiple archived rows and clicking Unarchive calls the batch helper once and clears selection after all items succeed.
|
||||
- Happy path: selecting multiple terminal rows and clicking Archive calls the batch helper once and invalidates run-list caches once.
|
||||
- Mixed result: partial failure keeps selection and shows an error-tone partial-success toast with succeeded/failed counts.
|
||||
- Ineligible selection: selecting only non-archivable runs still shows the existing “No selected runs can be archived” style error without making an API request.
|
||||
- Board action: archive-all for a column calls the batch helper once with all eligible IDs.
|
||||
|
||||
**Verification:**
|
||||
- UI behavior remains equivalent from the user’s perspective, but network behavior becomes one request per bulk action.
|
||||
|
||||
## System-Wide Impact
|
||||
|
||||
- **Auth:** Batch endpoints are user-only. This intentionally avoids giving a worker token with one run scope the ability to mutate arbitrary run IDs from a request body.
|
||||
- **Events:** No new event names or payloads. Each successful item appends the existing per-run archive/unarchive event.
|
||||
- **Caching:** Frontend run-list caches are still invalidated after lifecycle changes. The batch path should reduce invalidation churn from once per selected run to once per user action.
|
||||
- **Generated clients:** Both Rust and TypeScript generated clients change because OpenAPI is the source of truth.
|
||||
- **Compatibility:** Single-run endpoints and existing generated methods remain available and unchanged.
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Route ambiguity with `/runs/{id}` paths | Use static collection action paths and add server tests that call the exact batch routes. |
|
||||
| Partial mutation surprises | Use explicit fail-soft result entries and summarize succeeded/failed counts. |
|
||||
| Worker token privilege expansion | Require `RequiredUser` for batch endpoints. |
|
||||
| Duplicate IDs producing confusing results | Reject duplicates at request validation before any mutation. |
|
||||
| UI toast regressions | Cover all-success, all-failure, partial-failure, and ineligible-selection behavior in frontend tests. |
|
||||
|
||||
## Documentation / Operational Notes
|
||||
|
||||
- The OpenAPI descriptions should explicitly state that batch actions are fail-soft and not transactional.
|
||||
- No migration, feature flag, or rollout sequencing is required.
|
||||
- No new public docs are required unless API reference publishing is part of the release process.
|
||||
|
||||
## Sources & References
|
||||
|
||||
- OpenAPI source: `docs/public/api-reference/fabro-api.yaml`
|
||||
- Server lifecycle handlers: `lib/crates/fabro-server/src/server/handler/lifecycle.rs`
|
||||
- Workflow archive operations: `lib/crates/fabro-workflow/src/operations/archive.rs`
|
||||
- Web lifecycle helpers: `apps/fabro-web/app/lib/run-actions.ts`
|
||||
- Web runs route: `apps/fabro-web/app/routes/runs.tsx`
|
||||
- Testing guidance: `docs/internal/testing-strategy.md`
|
||||
- Error handling guidance: `docs/internal/error-handling-strategy.md`
|
||||
- Event guidance: `docs/internal/events-strategy.md`
|
||||
|
||||
|
||||
## Completed stages
|
||||
- **toolchain**: succeeded
|
||||
- Script: `command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1`
|
||||
- Output:
|
||||
```
|
||||
cargo 1.95.0 (f2d3ce0bd 2026-03-21)
|
||||
```
|
||||
- **preflight_compile**: succeeded
|
||||
- Script: `cargo check -q --workspace 2>&1`
|
||||
- Output: (empty)
|
||||
- **preflight_lint**: succeeded
|
||||
- Script: `cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1`
|
||||
- Output: (empty)
|
||||
|
||||
|
||||
Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.
|
||||
6
stages/005-implement@1/provider_used.json
Normal file
6
stages/005-implement@1/provider_used.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"mode": "agent",
|
||||
"provider": "openai",
|
||||
"model": "gpt-5.5",
|
||||
"reasoning_effort": "xhigh"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue