mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-28 02:25:00 +08:00
roadmap: #280 filed
This commit is contained in:
16
ROADMAP.md
16
ROADMAP.md
@@ -17153,3 +17153,19 @@ Gap. The session persistence format lacks an extension/unknown-field policy. It
|
|||||||
Required fix shape: (a) define a schema policy for unknown fields in known session records: strict reject for incompatible versions OR lossless preservation via `extensions: BTreeMap<String, JsonValue>`; (b) if rejecting, include record type, line number, and field names in a typed `SessionSchemaError::UnknownFields`; (c) if preserving, round-trip unknown fields through `to_json`, `render_jsonl_snapshot`, compaction, and rotation; (d) add `schema_extensions_dropped` warning telemetry if any field is intentionally ignored; (e) regression-test JSON and JSONL sessions containing a future `tool_catalog_revision` field inside `session_meta` and a future per-message field, proving they either fail typed or survive a load-save round-trip. Acceptance: a future schema field in a known record can never disappear silently.
|
Required fix shape: (a) define a schema policy for unknown fields in known session records: strict reject for incompatible versions OR lossless preservation via `extensions: BTreeMap<String, JsonValue>`; (b) if rejecting, include record type, line number, and field names in a typed `SessionSchemaError::UnknownFields`; (c) if preserving, round-trip unknown fields through `to_json`, `render_jsonl_snapshot`, compaction, and rotation; (d) add `schema_extensions_dropped` warning telemetry if any field is intentionally ignored; (e) regression-test JSON and JSONL sessions containing a future `tool_catalog_revision` field inside `session_meta` and a future per-message field, proving they either fail typed or survive a load-save round-trip. Acceptance: a future schema field in a known record can never disappear silently.
|
||||||
|
|
||||||
**Status:** Open. No source code changed. Filed 2026-04-26 17:02 KST. Branch: feat/jobdori-168c-emission-routing. HEAD: `6c154c9` before filing. Cluster delta: persisted-schema-version-policy 1→2 (#278 version-comparison + #279 unknown-field policy); sibling to #278, distinct field-preservation layer. Concrete delta this cycle: ROADMAP-only pinpoint appended after static audit of `Session::from_json` / `from_jsonl` field selection.
|
**Status:** Open. No source code changed. Filed 2026-04-26 17:02 KST. Branch: feat/jobdori-168c-emission-routing. HEAD: `6c154c9` before filing. Cluster delta: persisted-schema-version-policy 1→2 (#278 version-comparison + #279 unknown-field policy); sibling to #278, distinct field-preservation layer. Concrete delta this cycle: ROADMAP-only pinpoint appended after static audit of `Session::from_json` / `from_jsonl` field selection.
|
||||||
|
|
||||||
|
## Pinpoint #280 — Hook execution progress events (`PreToolUse`/`PostToolUse` Started/Completed/Cancelled) are eprintln-text-only when `emit_output=true` and dropped on the floor entirely when `--output-format json` is selected, so the per-turn JSON envelope shows `tool_uses`/`tool_results`/`auto_compaction`/`usage` but zero hook execution evidence even though hooks fire and can deny/mutate/cancel tools
|
||||||
|
|
||||||
|
Dogfooded 2026-04-26 17:05 KST on `feat/jobdori-168c-emission-routing` at HEAD `bdcf3fa` (post fast-forward onto gaebal-gajae's #279 unknown-fields silent-drop pinpoint). Static audit of the JSON-mode dispatch path: `LiveCli::run_prompt_json` at `rust/crates/rusty-claude-cli/src/main.rs:4690-4729` calls `prepare_turn_runtime(false)` (line 4691) — the bool argument is `emit_output`. `prepare_turn_runtime` at `:4567-4587` then calls `build_runtime(..., emit_output, ...)` at `:4574-4583`, which at `:7726-7728` only attaches `with_hook_progress_reporter(Box::new(CliHookProgressReporter))` when `emit_output` is `true`. In the JSON-mode path, `emit_output=false`, so the runtime gets NO progress reporter at all. `Conversation::run_pre_tool_use_hook` at `rust/crates/runtime/src/conversation.rs:224-241` and `run_post_tool_use_hook` at `:243-273` both branch on `if let Some(reporter) = self.hook_progress_reporter.as_mut()`: with no reporter, the hook still EXECUTES (the `None` arm passes `None` for the reporter slot to `HookRunner::run_pre_tool_use_with_context`/`run_post_tool_use_with_context`), but every `HookProgressEvent::{Started, Completed, Cancelled}` emission inside `runtime/src/hooks.rs` (lines 347, 366, 376, 388, 400) is conditioned on a `reporter.on_event(...)` call that never happens. Even in the text path where `emit_output=true`, `CliHookProgressReporter::on_event` at `main.rs:7735-7762` writes to `eprintln!` only — never to stdout, never to the JSON envelope, never to a `--output-format json`-consumable structured stream. The JSON envelope at `:4699-4724` exposes ten top-level fields (`message`, `model`, `iterations`, `auto_compaction`, `tool_uses`, `tool_results`, `prompt_cache_events`, `usage`, `estimated_cost`) and zero hook evidence.
|
||||||
|
|
||||||
|
Concrete failure mode: an operator scripts `claw -p "do thing" --output-format json | jq` against a workspace whose `~/.claw/settings.json` defines a `PreToolUse` hook that **denies** specific tool patterns, **mutates** tool input (the `updated_input_json` path at `runtime/src/hooks.rs:149`), or **cancels** the turn (`is_cancelled`/`is_failed`/`is_denied` arms at `conversation.rs:409-440`). The conversation outcome reflects the hook decision (the tool may be denied with `PermissionOutcome::Deny { reason: "PreToolUse hook cancelled tool ..." }` at `conversation.rs:413-416`), and the deny reason flows into a `tool_result` synthesized at the permission layer — but the JSON envelope never records that a hook fired, what hook (`pre_tool_use`/`post_tool_use`/`pre_tool_use_failure`/`post_tool_use_failure`), what command, what exit code, what abort path, what stdout/stderr preview, or even how many hook invocations happened across the turn. The operator's only signal that a hook touched the turn is reverse-inference from a `tool_result` reason string, which is itself a #266-style untyped-prose surface. For an automation that wants to verify "the security-audit PreToolUse hook ran on every Write/Bash invocation in this turn," the JSON envelope is structurally incapable of answering — there is no `hooks: [...]` array, no `hook_invocations: N`, no `pre_tool_use_outcomes`, no per-invocation receipt. Symmetrically, when `emit_output=true` (text mode), hook events go to stderr as `[hook pre_tool_use] BashTool: pretty.sh` prose, which is human-readable but not structured and competes with model streaming text on the same terminal.
|
||||||
|
|
||||||
|
Gap. Hook execution observability is wired into the runtime (`HookProgressEvent` enum at `runtime/src/hooks.rs:40-58`, `HookProgressReporter` trait at `:59-61`, three reporter call sites at `:347/366/388/400`) but only ever surfaces as opt-in stderr text, never as a structured channel that aligns with the existing `tool_uses`/`tool_results` JSON-envelope schema. The structural shape is identical to #107 (doctor-side hook subsystem opacity) but at a strictly different layer: #107 is about `claw doctor` hook-config visibility (audit-once); #280 is about per-turn `--output-format json` hook-execution visibility (per-prompt evidence). It is also adjacent to but distinct from #265 (`stream-json` mode entirely absent — no streaming lane at all) — #265 is about the missing output mode, #280 is about a missing field within the existing JSON output mode. Distinct from #260 (`--compact --output-format json` strips six fields: hooks were never one of those six in EITHER non-compact or compact envelope; this is a third structural absence, not a strip-on-compact). Distinct from #109 (config validator warnings stderr-only) — different subsystem, same plumbing pattern (structured-data-relegated-to-stderr-prose). Distinct from #259/#273/#275 provenance quartet — provenance emits state-of-record fields; #280 is per-turn execution-event evidence.
|
||||||
|
|
||||||
|
Founds NEW **`Hook-execution-event-envelope-coverage`** cluster on the per-turn-observability axis (1 member, #280 solo founder). Pairs with the silent-fallback family on the structurally-absent-evidence axis: silent-fallback accepts malformed input without a typed error; #280 accepts hook execution without a typed receipt. Pairs with #107 (doctor-side hook opacity) on the hooks-subsystem-observability axis to form a **hook-observability-pair** spanning both diagnostic surfaces (audit-once doctor + per-turn JSON envelope). Pairs with #265 (stream-json absent) on the structured-output-axis to form a **JSON-output-completeness-pair**: #265 catalogues the missing streaming output mode entirely, #280 catalogues a missing field within the one-shot JSON envelope that does exist. Extends the **complementary-pinpoint-pair-bundle** discovery-pattern: #280 forms the seventh pair-bundle (#107 + #280 hook-observability-spanning-doctor-and-runtime).
|
||||||
|
|
||||||
|
Distinct from #266 (typed-error-kind enumeration — the runtime needs typed discriminants on `RuntimeError`; #280 is about hook-execution evidence as a positive-path field, not error-kind taxonomy on the failure path). Distinct from #278 (`SESSION_VERSION` never compared on load — persistence layer; #280 is the runtime/CLI envelope layer) and from gaebal-gajae's #279 (unknown-fields silent-drop in known JSONL records — also persistence; #280 is per-turn output emission, not on-disk storage).
|
||||||
|
|
||||||
|
Required fix shape: (a) extend `run_prompt_json` envelope at `main.rs:4699-4724` with a top-level `hooks: [HookInvocationReceipt]` array where each entry is `{ event: "pre_tool_use" | "post_tool_use" | "pre_tool_use_failure" | "post_tool_use_failure", tool_name, tool_use_id, command, started_at, completed_at, duration_ms, outcome: "completed" | "cancelled" | "denied" | "failed", exit_code: Option<i32>, stdout_preview: Option<String>, stderr_preview: Option<String>, updated_input_changed: bool, permission_override: Option<String> }`; (b) add a JSON-mode `HookProgressReporter` impl that buffers `HookProgressEvent`s into a `Vec<HookInvocationReceipt>` instead of writing to stderr, attached unconditionally in `prepare_turn_runtime` (drop the `emit_output` gate at `main.rs:7726`); (c) align the `hooks` field with the existing `tool_uses`/`tool_results` array schema so consumers who already index by `tool_use_id` can correlate hook receipts to the tool they fired around; (d) extend `run_prompt_compact_json` (`main.rs:4665-4688`) to include either the full `hooks` array or a `hook_invocations: N` count so the compact envelope does not silently strip a fourth observability dimension on top of the six already documented in #260; (e) add a top-level `hooks_summary: { pre_tool_use_count, post_tool_use_count, denied_count, failed_count, cancelled_count }` for cheap aggregate consumers; (f) regression-test a workspace whose `settings.json` defines a `PreToolUse` hook that denies one tool and a `PostToolUse` hook that runs on success, asserting the `--output-format json` envelope contains exactly two hook receipts with the correct outcomes and stdin/stdout previews; (g) document the `hooks` field in `--help` and the wire-format docs alongside `tool_uses`/`tool_results`; (h) close the loop with #107 by having `claw doctor --output-format json` add a `hooks_subsystem` block describing configured hooks (audit-once) so #107 + #280 collectively close the hook-observability-spanning-doctor-and-runtime pair. Acceptance: an operator scripting `claw -p "x" --output-format json | jq '.hooks[]'` can enumerate every hook invocation that fired during the turn with its outcome, and a security-audit hook can prove it ran without scraping stderr or reverse-engineering tool_result reason strings.
|
||||||
|
|
||||||
|
**Status:** Open. No source code changed. Filed 2026-04-26 17:05 KST. Branch: feat/jobdori-168c-emission-routing. HEAD: `bdcf3fa` before filing (post fast-forward onto gaebal-gajae's #279 unknown-fields silent-drop pinpoint). Cluster delta: founds NEW `Hook-execution-event-envelope-coverage` cluster (1 member, #280 solo founder); pair-bundle with #107 (doctor-side hook opacity); seventh complementary-pinpoint-pair-bundle in the discovery-pattern catalogue (turn-budget #262+#264 + WebSearch #245+#250 + 4 prior pairs + hook-observability #107+#280); cross-cluster with silent-fallback family (structurally-absent-evidence axis), with #265 (JSON-output-completeness pair: missing-mode vs missing-field-in-existing-mode), and with #260 (third strip dimension on top of compact-envelope's six). Concrete delta this cycle: ROADMAP-only pinpoint appended after static audit of `prepare_turn_runtime`/`build_runtime`/`CliHookProgressReporter`/`Conversation::run_pre_tool_use_hook` showing reporter is conditionally `None` in JSON mode and the JSON envelope at `:4699-4724` carries zero hook fields. Concurrent-dogfood-rebase parity will be confirmed local==origin==fork at HEAD `bdcf3fa+#280` after push.
|
||||||
|
|||||||
Reference in New Issue
Block a user