# Fix-Locus #164 โ€” JSON Envelope Contract Migration **Status:** ๐Ÿ“‹ Proposed (2026-04-23, cycle #77). Escalated from pinpoint #164 after gaebal-gajae review recognized product-wide scope. **Class:** Contract migration (not a patch). Affects EVERY `--output-format json` command. **Bundle:** Typed-error family โ€” joins #102 + #121 + #127 + #129 + #130 + #245 + **#164**. Contract-level implementation of ยง4.44 typed-error envelope. --- ## 1. Scope โ€” What This Migration Affects **Every JSON-emitting verb.** Audit across the 14 documented verbs: | Verb | Current top-level keys | Schema-conformant? | |---|---|---| | `doctor` | checks, has_failures, **kind**, message, report, summary | โŒ No (kind=verb-id, flat) | | `status` | config_load_error, **kind**, model, ..., workspace | โŒ No | | `version` | git_sha, **kind**, message, target, version | โŒ No | | `sandbox` | active, ..., **kind**, ...supported | โŒ No | | `help` | **kind**, message | โŒ No (minimal) | | `agents` | action, agents, count, **kind**, summary, working_directory | โŒ No | | `mcp` | action, config_load_error, ..., **kind**, servers | โŒ No | | `skills` | action, **kind**, skills, summary | โŒ No | | `system-prompt` | **kind**, message, sections | โŒ No | | `dump-manifests` | error, hint, **kind**, type | โŒ No (emits error envelope for success) | | `bootstrap-plan` | **kind**, phases | โŒ No | | `acp` | aliases, ..., **kind**, ...tracking | โŒ No | | `export` | file, **kind**, markdown, messages, session_id | โŒ No | | `state` | error, hint, **kind**, type | โŒ No (emits error envelope for success) | **All 14 verbs diverge from SCHEMAS.md.** The gap is 100%, not a partial drift. --- ## 2. The Two Envelope Shapes ### 2a. Current Binary Shape (Flat Top-Level) ```json // Success example (claw doctor --output-format json) { "kind": "doctor", // verb identity "checks": [...], "summary": {...}, "has_failures": false, "report": "...", "message": "..." } // Error example (claw doctor foo --output-format json) { "error": "unrecognized argument...", // string, not object "hint": "Run `claw --help` for usage.", "kind": "cli_parse", // error classification (overloaded) "type": "error" // not in schema } ``` **Properties:** - Flat top-level - `kind` field is **overloaded** (verb-id in success, error-class in error) - No common wrapper metadata (timestamp, exit_code, schema_version) - `error` is a string, not a structured object ### 2b. Documented Schema Shape (Nested, Wrapped) ```json // Success example (per SCHEMAS.md) { "timestamp": "2026-04-22T10:10:00Z", "command": "doctor", "exit_code": 0, "output_format": "json", "schema_version": "1.0", "data": { "checks": [...], "summary": {...}, "has_failures": false } } // Error example (per SCHEMAS.md) { "timestamp": "2026-04-22T10:10:00Z", "command": "doctor", "exit_code": 1, "output_format": "json", "schema_version": "1.0", "error": { "kind": "parse", // enum, nested "operation": "parse_args", "target": "subcommand `doctor`", "retryable": false, "message": "unrecognized argument...", "hint": "Run `claw --help` for usage." } } ``` **Properties:** - Common metadata wrapper (timestamp, command, exit_code, output_format, schema_version) - `data` (payload) vs. `error` (failure) as **sibling fields**, never coexisting - `kind` in error is the enum from ยง4.44 (filesystem/auth/session/parse/runtime/mcp/delivery/usage/policy/unknown) - `error` is a structured object with operation/target/retryable --- ## 3. Migration Strategy โ€” Phased Rollout **Principle:** Don't break downstream consumers mid-migration. Support both shapes during overlap, then deprecate. ### Phase 1 โ€” Dual-Envelope Mode (Opt-In) **Deliverables:** - New flag: `--envelope-version=2.0` (or `--schema-version=2.0`) - When flag set: emit new (schema-conformant) envelope - When flag absent: emit current (flat) envelope - SCHEMAS.md: add "Legacy (v1.0)" section documenting current flat shape alongside v2.0 **Implementation:** - Single `envelope_version` parameter in `CliOutputFormat` enum - Every verb's JSON writer checks version, branches accordingly - Shared wrapper helper: `wrap_v2(payload, command, exit_code)` **Consumer impact:** Opt-in. Existing consumers unchanged. New consumers can opt in. **Timeline estimate:** ~2 days for 14 verbs + shared wrapper + tests. ### Phase 2 โ€” Default Version Bump **Deliverables:** - Default changes from v1.0 โ†’ v2.0 - New flag: `--legacy-envelope` to opt back into flat shape - Migration guide added to SCHEMAS.md and CHANGELOG - Release notes: "Breaking change in envelope, pre-migration opt-in available via --legacy-envelope" **Consumer impact:** Existing consumers must add `--legacy-envelope` OR update to v2.0 schema. Grace period = "until Phase 3." **Timeline estimate:** Immediately after Phase 1 ships. ### Phase 3 โ€” Flat-Shape Deprecation **Deliverables:** - `--legacy-envelope` flag prints deprecation warning to stderr - SCHEMAS.md "Legacy v1.0" section marked DEPRECATED - v3.0 release (future): remove flag entirely, binary only emits v2.0 **Consumer impact:** Full migration required by v3.0. **Timeline estimate:** Phase 3 after ~6 months of Phase 2 usage. --- ## 4. Implementation Details ### 4a. Shared Wrapper Helper ```rust // rust/crates/rusty-claude-cli/src/json_envelope.rs (new file) pub fn wrap_v2_success(command: &str, data: T) -> Value { serde_json::json!({ "timestamp": chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), "command": command, "exit_code": 0, "output_format": "json", "schema_version": "2.0", "data": data, }) } pub fn wrap_v2_error(command: &str, error: StructuredError) -> Value { serde_json::json!({ "timestamp": chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), "command": command, "exit_code": 1, "output_format": "json", "schema_version": "2.0", "error": { "kind": error.kind, "operation": error.operation, "target": error.target, "retryable": error.retryable, "message": error.message, "hint": error.hint, }, }) } pub struct StructuredError { pub kind: &'static str, // enum from ยง4.44 pub operation: String, pub target: String, pub retryable: bool, pub message: String, pub hint: Option, } ``` ### 4b. Per-Verb Migration Pattern ```rust // Before (current flat shape): match output_format { CliOutputFormat::Json => { serde_json::to_string_pretty(&DoctorOutput { kind: "doctor", checks, summary, has_failures, message, report, }) } CliOutputFormat::Text => render_text(&data), } // After (v2.0 with v1.0 fallback): match (output_format, envelope_version) { (CliOutputFormat::Json, 2) => { json_envelope::wrap_v2_success("doctor", DoctorData { checks, summary, has_failures }) } (CliOutputFormat::Json, 1) => { // Legacy flat shape (with deprecation warning at Phase 3) serde_json::to_value(&LegacyDoctorOutput { kind: "doctor", ...}) } (CliOutputFormat::Text, _) => render_text(&data), } ``` ### 4c. Error Classification Migration Current error `kind` values (found in binary): - `cli_parse`, `no_managed_sessions`, `unknown`, `missing_credentials`, `session_not_found` Target v2.0 enum (per ยง4.44): - `filesystem`, `auth`, `session`, `parse`, `runtime`, `mcp`, `delivery`, `usage`, `policy`, `unknown` **Migration table:** | Current kind | v2.0 error.kind | |---|---| | `cli_parse` | `parse` | | `no_managed_sessions` | `session` (with operation: "list_sessions") | | `missing_credentials` | `auth` | | `session_not_found` | `session` (with operation: "resolve_session") | | `unknown` | `unknown` | --- ## 5. Acceptance Criteria 1. **Schema parity:** Every `--output-format json` command emits v2.0 envelope shape exactly per SCHEMAS.md 2. **Success/error symmetry:** Success envelopes have `data` field; error envelopes have `error` object; never both 3. **kind semantic unification:** `data.kind` = verb identity (when present); `error.kind` = enum from ยง4.44. No overloading. 4. **Common metadata:** `timestamp`, `command`, `exit_code`, `output_format`, `schema_version` present in ALL envelopes 5. **Dual-mode support:** `--envelope-version=1|2` flag allows opt-in/opt-out during migration 6. **Tests:** Per-verb golden test fixtures for both v1.0 and v2.0 envelopes 7. **Documentation:** SCHEMAS.md documents both versions with deprecation timeline --- ## 6. Risks ### 6a. Breaking Change Risk Phase 2 (default version bump) WILL break consumers that depend on flat-shape envelope. Mitigations: - Dual-mode flag allows opt-in testing before default change - Long grace period (Phase 3 deprecation ~6 months post-Phase 2) - Clear migration guide + example consumer code ### 6b. Implementation Risk 14 verbs to migrate. Each verb has its own success shape (`checks`, `agents`, `phases`, etc.). Payload structure stays the same; only the wrapper changes. Mechanical but high-volume. **Estimated diff size:** ~200 lines per verb ร— 14 verbs = ~2,800 lines (mostly boilerplate). **Mitigation:** Start with doctor, status, version as pilot. If pattern works, batch remaining 11. ### 6c. Error Classification Remapping Risk Changing `kind: "cli_parse"` to `error.kind: "parse"` is a breaking change even within the error envelope. Consumers doing `response["kind"] == "cli_parse"` will break. **Mitigation:** Document explicitly in migration guide. Provide sed script if needed. --- ## 7. Deliverables Summary | Item | Phase | Effort | |---|---|---| | `json_envelope.rs` shared helper | Phase 1 | 1 day | | 14 verb migrations (pilot 3 + batch 11) | Phase 1 | 2 days | | `--envelope-version` flag | Phase 1 | 0.5 day | | Dual-mode tests (golden fixtures) | Phase 1 | 1 day | | SCHEMAS.md updates (v1.0 + v2.0) | Phase 1 | 0.5 day | | Default version bump | Phase 2 | 0.5 day | | Deprecation warnings | Phase 3 | 0.5 day | | Migration guide doc | Phase 1 | 0.5 day | **Total estimate:** ~6 developer-days for Phase 1 (the core work). Phases 2/3 are cheap follow-ups. --- ## 8. Rollout Timeline (Proposed) - **Week 1:** Phase 1 โ€” dual-mode support + pilot migration (3 verbs) - **Week 2:** Phase 1 completion โ€” remaining 11 verbs + full test coverage - **Week 3:** Stabilization period, gather consumer feedback - **Month 2:** Phase 2 โ€” default version bump - **Month 8:** Phase 3 โ€” deprecation warnings - **v3.0 release:** Remove `--legacy-envelope` flag, v1.0 shape no longer supported --- ## 9. Related - **ROADMAP #164:** The originating pinpoint (this document is its fix-locus) - **ROADMAP ยง4.44:** Typed-error contract (defines the error.kind enum this migration uses) - **SCHEMAS.md:** The envelope schema this migration makes reality - **Typed-error family:** #102, #121, #127, #129, #130, #245, **#164** --- **Cycle #77 locus doc. Ready for author review + pilot implementation decision.**