mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
parent
f274126904
commit
eb0beee569
7 changed files with 480 additions and 12 deletions
246
run.json
246
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/a15771d4596aecf3e872f544170dbf3f374a64bc9b2c4df4fc9b3b270f5c9822
|
||||
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/a15771d4596aecf3e872f544170dbf3f374a64bc9b2c4df4fc9b3b270f5c9822",
|
||||
"exit_code": 101,
|
||||
"duration_ms": 100201,
|
||||
"termination": "exited",
|
||||
"output_bytes": 679,
|
||||
"live_streaming": true
|
||||
}
|
||||
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": "failed",
|
||||
"notes": null,
|
||||
"failure_reason": "Script failed with exit code: 101\n\n## output\nerror[E0063]: missing field `expected_secret_name` in initializer of `fabro_model::Provider`\n --> lib/crates/fabro-api/tests/provider_round_trip.rs:14:20\n |\n14 | let provider = Provider {\n | ^^^^^^^^ missing `expected_secret_name`\n\nerror[E0063]: missing field `expected_secret_name` in initializer of `fabro_model::Provider`\n --> lib/crates/fabro-api/tests/provider_round_trip.rs:51:20\n |\n51 | let provider = Provider {\n | ^^^^^^^^ missing `expected_secret_name`\n\nFor more information about this error, try `rustc --explain E0063`.\nerror: could not compile `fabro-api` (test \"provider_round_trip\") due to 2 previous errors\n",
|
||||
"timestamp": "2026-05-24T15:44:24.017812Z"
|
||||
}
|
||||
218
stages/005-fix_lints@1/prompt.md
Normal file
218
stages/005-fix_lints@1/prompt.md
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
Goal: ---
|
||||
title: fix: Prefer root stage TODO projection
|
||||
type: fix
|
||||
status: active
|
||||
date: 2026-05-24
|
||||
---
|
||||
|
||||
# fix: Prefer Root Stage TODO Projection
|
||||
|
||||
## Overview
|
||||
|
||||
Fix stage TODO projection so `StageProjection.todos` represents the selected
|
||||
stage agent's root session plan. Today a child OpenAI session can emit its own
|
||||
`todo.created` events on the same `stage_id`, replacing the root list and
|
||||
causing later root `todo.updated` completions to be ignored. The visible
|
||||
symptom is an agent sidebar showing a stale child TODO list such as `0/3`
|
||||
completed even though the root stage plan completed.
|
||||
|
||||
## Problem Frame
|
||||
|
||||
OpenAI `update_plan` lists are scoped per agent session as
|
||||
`openai_plan:<session_id>`. A stage may contain both the root agent session and
|
||||
child/subagent sessions. `StageProjection` currently has only one
|
||||
`todos: Option<TodoListProjection>`, so the reducer must choose which list is
|
||||
the stage-level list. The stage sidebar is a stage-agent summary, so it should
|
||||
show the root stage session's list rather than whichever session most recently
|
||||
created todos.
|
||||
|
||||
## Requirements Trace
|
||||
|
||||
- R1. Root OpenAI plan TODOs must remain the projected `stage.todos` list even
|
||||
when child OpenAI sessions emit TODO events on the same stage.
|
||||
- R2. Later root OpenAI `todo.updated` and `todo.deleted` events must continue
|
||||
to apply after child OpenAI TODO events are observed.
|
||||
- R3. Child OpenAI TODO lists must not create, replace, or mutate
|
||||
`StageProjection.todos`.
|
||||
- R4. Anthropic task projection must remain unchanged because Anthropic task
|
||||
lists are intentionally scoped to the root session and shared across
|
||||
subagents.
|
||||
- R5. Do not change public API shapes, generated API types, or frontend
|
||||
rendering code for this fix.
|
||||
|
||||
## Scope Boundaries
|
||||
|
||||
- Do not add a multi-list TODO projection in this change.
|
||||
- Do not expose child/subagent TODO lists in the sidebar in this change.
|
||||
- Do not change `TodoListProjection`, `StageProjection`, or OpenAPI schemas.
|
||||
- Do not change event serialization, event names, or the agent `update_plan`
|
||||
tool behavior.
|
||||
|
||||
## Context & Research
|
||||
|
||||
### Relevant Code and Patterns
|
||||
|
||||
- `lib/crates/fabro-agent/src/todo_tools.rs` scopes OpenAI plans by
|
||||
`session_id`, producing `openai_plan:<session_id>`.
|
||||
- `lib/crates/fabro-types/src/run_event/mod.rs` already carries
|
||||
`session_id` and `parent_session_id` on event envelopes.
|
||||
- `lib/crates/fabro-store/src/run_state.rs` owns the persisted-event reducer
|
||||
that updates `StageProjection.todos` from `todo.created`, `todo.updated`,
|
||||
and `todo.deleted`.
|
||||
- The existing `todo_reducer` test module in `run_state.rs` is the right place
|
||||
for focused regression coverage.
|
||||
- `apps/fabro-web/app/routes/run-stages.tsx` and
|
||||
`apps/fabro-web/app/components/stage-insights-sidebar.tsx` already render
|
||||
`stage.todos`; no UI change is needed if the projection is corrected.
|
||||
|
||||
### Observed Failing Case
|
||||
|
||||
For run `01KSBT48J14ZMK9HQN48SVMG3T`, stage `simplify_gpt@1` had:
|
||||
|
||||
- Root list `openai_plan:2f4458b9-1128-4a96-8dfa-bff4b73b9c33`: five root
|
||||
todos, all completed by later `todo.updated` events.
|
||||
- Child list `openai_plan:a09b9432-823d-4068-91fa-5c6185578e8e`: three child
|
||||
todos, created with `parent_session_id` set and never updated.
|
||||
|
||||
The child `todo.created` events replaced `stage.todos`, so the sidebar showed
|
||||
the child list as `0/3` even after the root list completed.
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
- Use `parent_session_id` as the root-vs-child signal for OpenAI plan
|
||||
projection. A root stage session has `parent_session_id == None`; child
|
||||
sessions have `parent_session_id != None`.
|
||||
- Ignore child OpenAI plan events for `StageProjection.todos`. This preserves
|
||||
the current single-list schema while making the selected list match the
|
||||
stage sidebar's meaning.
|
||||
- Keep Anthropic task projection unchanged. Anthropic tasks use
|
||||
`anthropic_tasks:<root_session_id>`, so child-session envelopes should still
|
||||
be allowed to update the shared root task list.
|
||||
- Treat legacy OpenAI events without `parent_session_id` as root-compatible for
|
||||
backwards compatibility.
|
||||
|
||||
## Implementation Units
|
||||
|
||||
- [ ] **Unit 1: Add reducer policy for projectable stage TODO events**
|
||||
|
||||
**Goal:** Make the reducer distinguish root OpenAI plan events from child
|
||||
OpenAI plan events before mutating `stage.todos`.
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-store/src/run_state.rs`
|
||||
|
||||
**Approach:**
|
||||
- Add a small helper near the TODO reducer functions, for example
|
||||
`should_project_stage_todo_event(stored: &RunEvent, list_kind:
|
||||
TodoListKind) -> bool`.
|
||||
- Return `false` only when `list_kind == TodoListKind::OpenAiPlan` and
|
||||
`stored.parent_session_id.is_some()`.
|
||||
- Return `true` for root OpenAI events and all Anthropic task events.
|
||||
- Call this helper in the `EventBody::TodoCreated`,
|
||||
`EventBody::TodoUpdated`, and `EventBody::TodoDeleted` match arms before
|
||||
resolving or mutating the stage projection.
|
||||
- Leave `apply_todo_created`, `apply_todo_updated`, and
|
||||
`apply_todo_deleted` focused on list mutation once the caller has decided
|
||||
the event is projectable.
|
||||
|
||||
**Test scenarios:**
|
||||
- Root OpenAI events with no `parent_session_id` still create and update
|
||||
`stage.todos`.
|
||||
- Child OpenAI events with `parent_session_id` do not create `stage.todos`
|
||||
when no root list exists.
|
||||
- Child OpenAI events do not replace an existing root OpenAI list.
|
||||
- Root OpenAI updates still apply after ignored child OpenAI events.
|
||||
|
||||
- [ ] **Unit 2: Add focused reducer regression tests**
|
||||
|
||||
**Goal:** Lock the intended root-list behavior so future TODO projection work
|
||||
does not regress the sidebar.
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-store/src/run_state.rs`
|
||||
|
||||
**Approach:**
|
||||
- Extend the existing `todo_reducer` module rather than creating a new test
|
||||
file.
|
||||
- Add a test helper or local event setup that sets
|
||||
`event.event.parent_session_id = Some(parent_session_id.to_string())` for
|
||||
child-session events.
|
||||
- Add one regression test that reproduces the failing sequence:
|
||||
root OpenAI creates list, child OpenAI creates a different list on the same
|
||||
stage, root OpenAI completes its items. Assert the final projection is the
|
||||
root list and all root statuses are completed.
|
||||
- Add one test proving child OpenAI events alone do not create a stage TODO
|
||||
projection.
|
||||
- Add one test proving Anthropic child-session task events still project.
|
||||
|
||||
**Verification:**
|
||||
- `cargo nextest run -p fabro-store todo_reducer`
|
||||
- `cargo +nightly-2026-04-14 fmt --check --all`
|
||||
|
||||
## System-Wide Impact
|
||||
|
||||
- **API compatibility:** No response schema changes. Existing consumers of
|
||||
`StageProjection.todos` continue to receive a single list.
|
||||
- **UI behavior:** The sidebar should show the root agent's TODO progress for
|
||||
the selected stage. Child OpenAI session plans remain available only in the
|
||||
raw event stream for now.
|
||||
- **Historical runs:** Replaying existing event logs should produce corrected
|
||||
projections because the decision uses envelope fields already persisted on
|
||||
child events.
|
||||
- **Future extensibility:** If child/subagent TODO display is needed later,
|
||||
add a multi-list projection separately rather than overloading
|
||||
`stage.todos`.
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Some legacy child OpenAI events lack `parent_session_id` and still project as root | Accept this for backwards compatibility; only events with explicit child-session evidence are filtered. |
|
||||
| Anthropic child task updates could be accidentally filtered | Gate only `TodoListKind::OpenAiPlan`; add a regression test for `TodoListKind::AnthropicTasks`. |
|
||||
| Root list replacement semantics become ambiguous if a root stage emits multiple OpenAI list IDs | Preserve current root replacement behavior; the fix only prevents child lists from replacing root lists. |
|
||||
|
||||
## Assumptions
|
||||
|
||||
- `parent_session_id == None` is the canonical signal for the root stage agent
|
||||
session in stored event envelopes.
|
||||
- Child OpenAI TODO lists are not part of the current stage sidebar contract.
|
||||
- The correct near-term fix is projection selection, not a frontend workaround
|
||||
or a schema expansion.
|
||||
|
||||
|
||||
## 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**: failed
|
||||
- Script: `cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1`
|
||||
- Output:
|
||||
```
|
||||
error[E0063]: missing field `expected_secret_name` in initializer of `fabro_model::Provider`
|
||||
--> lib/crates/fabro-api/tests/provider_round_trip.rs:14:20
|
||||
|
|
||||
14 | let provider = Provider {
|
||||
| ^^^^^^^^ missing `expected_secret_name`
|
||||
|
||||
error[E0063]: missing field `expected_secret_name` in initializer of `fabro_model::Provider`
|
||||
--> lib/crates/fabro-api/tests/provider_round_trip.rs:51:20
|
||||
|
|
||||
51 | let provider = Provider {
|
||||
| ^^^^^^^^ missing `expected_secret_name`
|
||||
|
||||
For more information about this error, try `rustc --explain E0063`.
|
||||
error: could not compile `fabro-api` (test "provider_round_trip") due to 2 previous errors
|
||||
```
|
||||
|
||||
## Context
|
||||
- failure_class: deterministic
|
||||
- failure_signature: preflight_lint|deterministic|script failed with exit code: <n> ## output error[e0063]: missing field `expected_secret_name` in initializer of `fabro_model::provider` --> lib/crates/fabro-api/tests/provider_round_trip.rs:<n>:<n> | <n> | let provider = provider { | ^^^^^
|
||||
|
||||
|
||||
The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.
|
||||
5
stages/005-fix_lints@1/provider_used.json
Normal file
5
stages/005-fix_lints@1/provider_used.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"mode": "agent",
|
||||
"provider": "anthropic",
|
||||
"model": "claude-opus-4-7"
|
||||
}
|
||||
8
stages/005-fix_lints@1/response.md
Normal file
8
stages/005-fix_lints@1/response.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
Clippy passes cleanly. The fix:
|
||||
|
||||
Added the missing `expected_secret_name` field to the two `Provider` struct initializers in `lib/crates/fabro-api/tests/provider_round_trip.rs`:
|
||||
|
||||
- In `provider_json_matches_openapi_shape`: set to `Some("ANTHROPIC_API_KEY".to_string())` to match Anthropic's vault credential, plus a matching `assert_eq!` on the serialized JSON.
|
||||
- In `provider_omits_optional_fields_when_absent`: set to `None` with an additional assertion that the field is omitted from the serialized JSON (it's marked `#[serde(skip_serializing_if = "Option::is_none")]`).
|
||||
|
||||
`cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings` now succeeds with exit code 0.
|
||||
Loading…
Add table
Reference in a new issue