feat(server): expose hook_index in run responses; split HookOutput type - #1099
Conversation
|
@shunichironomura |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1099 +/- ##
==========================================
- Coverage 49.60% 49.59% -0.02%
==========================================
Files 38 38
Lines 3822 3825 +3
==========================================
+ Hits 1896 1897 +1
- Misses 1926 1928 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
#1097 (comment) |
|
@shunsuke-shimomura I don't care much about the naming that is not end-user-facing. That being said, my preference (which LLM happens to agree with) is:
|
@shunsuke-shimomura #1097 is a relatively large internal change while this PR is a small correctness fix. So if this PR can be ready soon, it can be merged first. |
Adds `hook_index: int | None = None` to two Python types so the client tracks the same information the Rust server now emits: - `ParameterMatch.hook_index` — pins a query to a specific 0-based position in the phase's array (mirrors the Rust ParameterMatch field from PR ut-issl#973). `to_dict()` includes it only when set, and uses `is not None` so `hook_index=0` is preserved on the wire. - `HookOutput.hook_index` — extracted from the `__meta.hook_index` field of each hook entry in the search response (mirrors the Rust HookMetaQueried field from PR ut-issl#1099). Absent from the response -> `None`, so older servers still parse cleanly. Docstring updated to document the widened validation on the server side ("at least one of file / hook_index / parameter") and to spell out the disambiguation use case for `hook_index`. Tests: 4 new `to_dict` cases (alone, composed, `hook_index=0` edge, default omission) and 1 new `_parse_hooks` case for the missing-`hook_index` fallback. Existing mocked `test_search_runs` now sends `hook_index` in `__meta` and asserts round-trip parsing.
199c3ef to
829e532
Compare
Splits the previous `HookOutput` (used for both upload and response) into two intent-named types: - `HookOutputUploaded` (Deserialize only) — what the CLI sends in `POST /api/v1/upload`. No `hook_index`, because the server authoritatively assigns positions from each phase's array via `.enumerate()`. - `HookOutputQueried` (Serialize only) — what the server returns from run-detail and search responses. Adds `__meta.hook_index: i32` so clients can distinguish the Nth invocation of the same hook_id and reconstruct the original capsula.toml order. Same split for `HookMeta` -> `HookMetaUploaded` / `HookMetaQueried`. Response-side changes: - All three hook-fetching SELECTs pull `hook_index` and `ORDER BY phase, hook_index`, giving a stable, capsula.toml-aligned response order. - `SearchRunResult.pre_run_hooks` / `.post_run_hooks` now hold `HookOutputQueried`; template struct `RunDetailTemplate` follows. New integration test `response_exposes_hook_index_and_preserves_array_order`: - Uploads a 3-hook `pre_run` array with two `capture-command` instances plus one `capture-json`, positions 0/1/2. - Verifies `__meta.hook_index` values are exactly `[0, 1, 2]` in order, and content-level ordering matches the sent array. `.sqlx` offline cache regenerated to match the new SELECT columns. Follow-up (separate PR): mirror `hook_index` in the Python client's `HookOutput` model.
829e532 to
0f9ab15
Compare
Adds `hook_index: int | None = None` to two Python types so the client tracks the same information the Rust server now emits: - `ParameterMatch.hook_index` — pins a query to a specific 0-based position in the phase's array (mirrors the Rust ParameterMatch field from PR ut-issl#973). `to_dict()` includes it only when set, and uses `is not None` so `hook_index=0` is preserved on the wire. - `HookOutput.hook_index` — extracted from the `__meta.hook_index` field of each hook entry in the search response (mirrors the Rust HookMetaQueried field from PR ut-issl#1099). Absent from the response -> `None`, so older servers still parse cleanly. Docstring updated to document the widened validation on the server side ("at least one of file / hook_index / parameter") and to spell out the disambiguation use case for `hook_index`. Tests: 4 new `to_dict` cases (alone, composed, `hook_index=0` edge, default omission) and 1 new `_parse_hooks` case for the missing-`hook_index` fallback. Existing mocked `test_search_runs` now sends `hook_index` in `__meta` and asserts round-trip parsing.
|
change |
shunichironomura
left a comment
There was a problem hiding this comment.
I renamed some struct based on my previous feedback. Note that we should change their occurrences in other locations as well, otherwise it won't compile. Apart from that it looks good to me.
…Upload/HookMetaResponse
Adds `hook_index: int | None = None` to two Python types so the client tracks the same information the Rust server now emits: - `ParameterMatch.hook_index` — pins a query to a specific 0-based position in the phase's array (mirrors the Rust ParameterMatch field from PR ut-issl#973). `to_dict()` includes it only when set, and uses `is not None` so `hook_index=0` is preserved on the wire. - `HookOutput.hook_index` — extracted from the `__meta.hook_index` field of each hook entry in the search response (mirrors the Rust HookMetaQueried field from PR ut-issl#1099). Absent from the response -> `None`, so older servers still parse cleanly. Docstring updated to document the widened validation on the server side ("at least one of file / hook_index / parameter") and to spell out the disambiguation use case for `hook_index`. Tests: 4 new `to_dict` cases (alone, composed, `hook_index=0` edge, default omission) and 1 new `_parse_hooks` case for the missing-`hook_index` fallback. Existing mocked `test_search_runs` now sends `hook_index` in `__meta` and asserts round-trip parsing.
Adds `hook_index: int | None = None` to two Python types so the client tracks the same information the Rust server now emits: - `ParameterMatch.hook_index` — pins a query to a specific 0-based position in the phase's array (mirrors the Rust ParameterMatch field from PR ut-issl#973). `to_dict()` includes it only when set, and uses `is not None` so `hook_index=0` is preserved on the wire. - `HookOutput.hook_index` — extracted from the `__meta.hook_index` field of each hook entry in the search response (mirrors the Rust HookMetaQueried field from PR ut-issl#1099). Absent from the response -> `None`, so older servers still parse cleanly. Docstring updated to document the widened validation on the server side ("at least one of file / hook_index / parameter") and to spell out the disambiguation use case for `hook_index`. Tests: 4 new `to_dict` cases (alone, composed, `hook_index=0` edge, default omission) and 1 new `_parse_hooks` case for the missing-`hook_index` fallback. Existing mocked `test_search_runs` now sends `hook_index` in `__meta` and asserts round-trip parsing.
Adds `hook_index: int | None = None` to two Python types so the client tracks the same information the Rust server now emits: - `ParameterMatch.hook_index` — pins a query to a specific 0-based position in the phase's array (mirrors the Rust ParameterMatch field from PR ut-issl#973). `to_dict()` includes it only when set, and uses `is not None` so `hook_index=0` is preserved on the wire. - `HookOutput.hook_index` — extracted from the `__meta.hook_index` field of each hook entry in the search response (mirrors the Rust HookMetaQueried field from PR ut-issl#1099). Absent from the response -> `None`, so older servers still parse cleanly. Docstring updated to document the widened validation on the server side ("at least one of file / hook_index / parameter") and to spell out the disambiguation use case for `hook_index`. Tests: 4 new `to_dict` cases (alone, composed, `hook_index=0` edge, default omission) and 1 new `_parse_hooks` case for the missing-`hook_index` fallback. Existing mocked `test_search_runs` now sends `hook_index` in `__meta` and asserts round-trip parsing.
Adds `hook_index: int | None = None` to two Python types so the client tracks the same information the Rust server now emits: - `ParameterMatch.hook_index` — pins a query to a specific 0-based position in the phase's array (mirrors the Rust ParameterMatch field from PR ut-issl#973). `to_dict()` includes it only when set, and uses `is not None` so `hook_index=0` is preserved on the wire. - `HookOutput.hook_index` — extracted from the `__meta.hook_index` field of each hook entry in the search response (mirrors the Rust HookMetaQueried field from PR ut-issl#1099). Absent from the response -> `None`, so older servers still parse cleanly. Docstring updated to document the widened validation on the server side ("at least one of file / hook_index / parameter") and to spell out the disambiguation use case for `hook_index`. Tests: 4 new `to_dict` cases (alone, composed, `hook_index=0` edge, default omission) and 1 new `_parse_hooks` case for the missing-`hook_index` fallback. Existing mocked `test_search_runs` now sends `hook_index` in `__meta` and asserts round-trip parsing.
Motivation
After #1036 landed the
hook_indexcolumn onrun_outputs, the DB knows which slot incapsula.toml'spre_run/post_runarray each hook came from — but the server didn't expose that to clients, and hooks came back in DB-default order. That means:capture-commandvs 2ndcapture-command" apart in the response.Approach
Split the previous dual-purpose
HookOutputinto two intent-named types:hook_index?HookOutputUploaded/HookMetaUploadedPOST /api/v1/upload(from CLI)Deserialize.enumerate()HookOutputQueried/HookMetaQueriedSerializeThe upload type dropping
hook_indexis a small but nice invariant: the client cannot forge or omit an authoritative field, and the server side no longer needs a#[serde(default)]compat shim.All three hook-fetching SELECTs now pull
hook_indexand useORDER BY phase, hook_index, so the response list mirrorscapsula.toml's array order.Tests
New integration test
response_exposes_hook_index_and_preserves_array_order:pre_runarray:[capture-command, capture-json, capture-command].GET /api/v1/runs/{id}.hooks[i].__meta.hook_index == [0, 1, 2]and that content-level ordering (stdout / content payload) matches the sent array.Existing regression tests (
test_hook_outputs_storage,multiple_hooks_with_distinct_configs_coexist,multiple_hooks_with_identical_configs_coexist) continue to pass.Follow-ups
hook_index: intin itsHookMeta. Will land as a separate PR.hook_index: Option<i32>toParameterMatchso "the Nth capture" can be pinned in queries. Separate PR on top of this one.Verification
cargo test -p capsula-server --test api_tests response_exposes_hook_index_and_preserves_array_order— new test passescargo test -p capsula-server --test api_tests multiple_hooks— 2/2 existing regressions passcargo test -p capsula-server --test api_tests test_hook_outputs_storage— existing hook storage test passescargo clippy -p capsula-server --all-targets -- -D warningscargo fmt --check -p capsula-server.sqlx/offline cache regenerated and committed