Phase 3d: add actionable configuration parse errors#641
Conversation
582b133 to
7c288be
Compare
7c288be to
fdf1f96
Compare
There was a problem hiding this comment.
Pull request overview
Adds a path-aware JSON deserialization layer to produce more actionable configuration parse errors (policy path + source location), while preserving the state-aware stdout envelope contract by routing parse/dispatch diagnostics only to auxiliary sinks.
Changes:
- Introduces
config_deserializeto distinguish JSON syntax errors vs typed policy-shape errors, including full JSON paths, source locations, control-character escaping, and secret-value redaction. - Refactors request loading/discrimination to borrow
RawValuefor state-aware discrimination (avoids building a full untyped JSON AST) and preserves byte/line locations by masking theexperimentalsubtree. - Adjusts logger + state-aware driver behavior to keep stdout reserved for envelopes/script output and avoid duplicating state-aware errors into primary stderr/buffer output.
Show a summary per file
| File | Description |
|---|---|
| src/core/wxc/src/main.rs | Logs state-aware dispatch errors only to auxiliary diagnostic sinks before emitting error envelopes. |
| src/core/wxc_common/src/wire.rs | Improves root-shape error messaging (expecting) and documents optionality expectations for experimental. |
| src/core/wxc_common/src/state_aware_request.rs | Uses path-aware deserialize for per-backend phase configs and prefixes errors with experimental.<backend>.<phase>. |
| src/core/wxc_common/src/state_aware_dispatch.rs | Extends tests to assert envelope-ready error paths for typed state-aware backend configs. |
| src/core/wxc_common/src/logger.rs | Adds log_diagnostic_line to write only to auxiliary sinks (file/diagnostic pipe), and refactors log_line accordingly. |
| src/core/wxc_common/src/lib.rs | Wires in the new internal config_deserialize module. |
| src/core/wxc_common/src/config_parser.rs | Refactors request parsing/logging, adds RawValue discriminator + experimental masking for location preservation. |
| src/core/wxc_common/src/config_deserialize.rs | New: central path-aware deserialize + formatting/redaction/escaping for diagnostics. |
| src/core/wxc_common/Cargo.toml | Enables serde_json raw_value support and adds serde_path_to_error. |
| src/Cargo.toml | Adds workspace dependency on serde_path_to_error. |
| src/Cargo.lock | Locks serde_path_to_error. |
| docs/versioning.md | Documents the new “actionable parse errors” behavior and guarantees. |
| docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md | Documents auxiliary-only routing for state-aware parse-phase failures. |
| .github/copilot-instructions.md | Updates documented config flow to include config_deserialize. |
Review details
- Files reviewed: 13/14 changed files
- Comments generated: 2
- Review effort level: Low
aec0002 to
cf263ca
Compare
deb054c to
14bd6cb
Compare
This PR adds actionable configuration parse errors: the parser reports malformed JSON separately from typed policy-shape errors and includes the full policy path plus whole-file source location in every diagnostic, without leaking secrets or letting untrusted text corrupt diagnostics or the state-aware stdout response envelope. Details * New config_deserialize module: a path-aware deserialize layer that distinguishes JSON syntax errors from typed policy errors, attaching the full JSON path and whole-file line/column, escaping control and invisible formatting characters (incl. U+2028/U+2029 and bidi overrides), and redacting secret-bearing values. * State-aware per-backend per-phase configs deserialize positionally from the retained request text, so typed errors carry whole-file coordinates matching base-config errors; navigation uses owned-key maps so escaped sibling keys never silently drop the location, with graceful fallback to the value-based path. * Request loading borrows RawValue to discriminate one-shot vs state-aware without building a full untyped AST, masking the experimental subtree to preserve base-config source locations. * Diagnostic routing keeps stdout reserved for the state-aware envelope and script output; parse and dispatch errors go to auxiliary sinks (log file / diagnostic pipe) only and are emitted exactly once. Filesystem-path and schema-version diagnostics escape untrusted text via the shared escape_diagnostic_text helper. * Exec dispatch drops the parsed request (and its retained source text) before the blocking child run and stdio relay. * Docs updated: versioning.md, the state-aware API doc, and the copilot-instructions config-flow description. Tests * Added parser coverage: syntax-vs-typed classification, whole-file line/column (computed, not hardcoded), escaped-sibling-key location preservation, exact column, CRLF line stability, positional secret redaction, source-present locator fallback, malformed-telemetry single auxiliary emission with an empty primary buffer, and a pinned serde_json positioned-error display-suffix contract. * cargo fmt --all -- --check: passed. * cargo clippy -p wxc_common -p wxc --all-targets -- -D warnings: clean. * cargo test -p wxc_common -p wxc: 514 + 26 passed. * node scripts/versioning/check-schema-codegen.js, check-sdk-types-codegen.js, check-schema-versions.js: passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Generated-with: gpt-5.6-sol Generated-with: claude-opus-4.8 Copilot-Session: dd87e65c-7e38-4d0e-8238-147c2153a0e8
14bd6cb to
e30ac71
Compare
| self | ||
| } | ||
|
|
||
| #[cfg(test)] |
There was a problem hiding this comment.
question (non-blocking): Any reason to have this function as a test?
| // Do not log here: state-aware parse errors are routed centrally | ||
| // and exactly once by the outer `load_mxc_request*` wrapper via | ||
| // `log_error(..., ErrorOutput::DiagnosticOnly)`. Logging here as | ||
| // well produced a duplicate auxiliary diagnostic (finding F2). | ||
| // Returning the error keeps stdout clean (envelope-owned) and | ||
| // yields a single auxiliary-sink line. |
There was a problem hiding this comment.
note: this comment still refers to other iterations I believe (F2).
| // token supplied where the object is expected) is pathed at `user`, not at | ||
| // `wamToken`, so mark the whole subtree sensitive. Over-redaction here fails | ||
| // safe — it only replaces an error value with a location, never leaks one. | ||
| "user", |
There was a problem hiding this comment.
note: This here would make us redact anything that starts with user right?
|
|
||
| #[test] | ||
| fn state_aware_malformed_telemetry_logs_once_and_keeps_primary_clean() { | ||
| // F2 regression: the malformed-telemetry error must reach the auxiliary |
There was a problem hiding this comment.
note: Note about comment about a previous iteration/PR maybe
| /// line breaks that some terminals/log viewers honor, and escaping zero-width / | ||
| /// joiner / interlinear characters prevents concealing or forging log and | ||
| /// error-envelope content rendered in a terminal or editor. | ||
| fn is_diagnostic_format_character(character: char) -> bool { |
There was a problem hiding this comment.
thought: I do wonder if there is a library we can use for this sort of thing. I asked this one to copilot and interestingly it gave me this crate https://crates.io/crates/unicode-general-category where we check by category. What are your thoughts on this? We could test this with your tests below.
use unicode_general_category::{get_general_category, GeneralCategory as Gc};
fn is_diagnostic_format_character(character: char) -> bool {
matches!(
get_general_category(character),
Gc::Format | Gc::LineSeparator | Gc::ParagraphSeparator
)
}
This PR adds actionable configuration parse errors: the parser reports
malformed JSON separately from typed policy-shape errors and includes the full
policy path plus whole-file source location in every diagnostic, without leaking
secrets or letting untrusted text corrupt diagnostics or the state-aware stdout
response envelope.
Details
config_deserializemodule: a path-aware deserialize layer thatdistinguishes JSON syntax errors from typed policy errors, attaching the full
JSON path and whole-file line/column, escaping control and invisible
formatting characters (incl. U+2028/U+2029 and bidi overrides), and redacting
secret-bearing values.
retained request text, so typed errors carry whole-file coordinates matching
base-config errors; navigation uses owned-key maps so escaped sibling keys
never silently drop the location, with graceful fallback to the value-based
path.
RawValueto discriminate one-shot vs state-awarewithout building a full untyped AST, masking the
experimentalsubtree topreserve base-config source locations.
script output; parse and dispatch errors go to auxiliary sinks (log file /
diagnostic pipe) only and are emitted exactly once. Filesystem-path and
schema-version diagnostics escape untrusted text via the shared
escape_diagnostic_texthelper.the blocking child run and stdio relay.
docs/versioning.md, the state-aware API doc, and thecopilot-instructions config-flow description.
Tests
(computed, not hardcoded), escaped-sibling-key location preservation, exact
column, CRLF line stability, positional secret redaction, source-present
locator fallback, malformed-telemetry single auxiliary emission with an empty
primary buffer, and a pinned
serde_jsonpositioned-error display-suffixcontract.
cargo fmt --all -- --check: passed.cargo clippy -p wxc_common -p wxc --all-targets -- -D warnings: clean.cargo test -p wxc_common -p wxc: 514 + 26 passed.node scripts/versioning/check-schema-codegen.js,check-sdk-types-codegen.js,check-schema-versions.js: passed.