Tasks: the CLI config is editable from the Control Plane UI
Phase 3 of 3. A DAG, not a list: tasks with the same dependencies can run in any order. Each
_Test:_names a row oftesting-plan.md.
graph LR
T1["1 · packaged schema"] --> T2["2 · configschema"]
T2 --> T4["4 · core.config"]
T3["3 · yamlpatch"] --> T4
T4 --> T5["5 · routes + holder"]
T5 --> T6["6 · OpenAPI contract"]
T5 --> T7["7 · client + types"]
T7 --> T8["8 · configModel"]
T8 --> T9["9 · ConfigEditor + Settings"]
T7 --> T10["10 · demo transport"]
T5 --> T11["11 · python tests"]
T9 --> T12["12 · ui tests"]
T9 --> T13["13 · docs"]
T11 --> T14["14 · verification"]
T12 --> T14
T13 --> T14Tasks
[x] 1. Ship the schemas as package data. Copy
.the-loop/cli-config.schema.jsonand.the-loop/collaborators.schema.jsontocli/the_loop/schemas/, with a module docstring inconfigschema.pystating why the copy exists and that.the-loop/is the authored home. Add the byte-parity test. Requirements: NFR4 · Test: T7[x] 2.
the_loop/configschema.py.load_schema()(package data,$refresolved, cached),validate(data, schema) -> list[str]over the ten constraining keywords, andSUPPORTEDfor the keyword guard. Requirements: R3.1, NFR1, NFR4 · Test: T2[x] 3.
the_loop/yamlpatch.py.deep_merge,changed_paths, andapply(text, patch, merged)— mark-based splicing with style preservation, insertion of missing keys and parents, creation of an absent file, and the re-parse verification that makes an unprovable splice raise. Requirements: R2.1–R2.4, R2.8 · Test: T1[x] 4.
the_loop/core/config.py.get_config,get_schema,update_config: read strictly, merge, validate (schema →assert_current→cors_config), splice, atomic replace, computechangedandrestartRequired, emitconfig.updated. Requirements: R1.1–R1.5, R2.5, R2.7, R3.1–R3.5, R4.4, R4.5 · Test: T3[x] 5. Routes and the in-process holder.
GET /api/v1/config,GET /api/v1/config/schema,POST /api/v1/config;_ConfigHolderrefreshed once per request by the existing audit middleware;create_app(..., config_path=…);serve.pypasses the resolved path; a note inapi/mcp.pyrecording that config is deliberately not an MCP tool. Requirements: R1.4, R4.1–R4.3, security design · Test: T4, T5[x] 6. Author the contract. Add the three operations to
docs/api-specs/openapi/the-loop.v1.yaml. Requirements: NFR3 · Test: T6[x] 7. UI transport.
ConfigDocument,JsonSchema,ConfigSaveResultinapi/types.ts;config(),configSchema(),saveConfig()onTheLoopApiandHttpApi. Requirements: R5.6 · Test: T8[x] 8.
ui/src/api/configModel.ts.fieldsOf(schema → sections → groups → typed leaves),getIn/setIn,diff(draft → sparse patch), and the leaf-kind decision including the structured fallback. Requirements: R5.1–R5.4, R5.6 · Test: T8[x] 9.
ui/src/components/ConfigEditor.tsx+ Settings. Render sections with the schema's prose, typed controls, defaults as placeholders, structured fields for unsupported subtrees, save/refuse/restart-required reporting, and the styles. Requirements: R5.1–R5.6 · Test: T9, T11[x] 10. Demo transport. The three calls answered from an in-memory config plus the real schema, so the hosted page demonstrates the editor. Requirements: R5.7 · Test: T9
[x] 11. Python tests.
test_yamlpatch.py,test_configschema.py,test_core_config.py,test_api_config_integration.py,test_config_schema_parity.py— Gherkin docstrings on the integration tests, each naming its requirement. Requirements: all · Test: T1–T5, T7, T10, T14[x] 12. UI tests.
configModel.test.tsand a render test for the editor. Requirements: R5.1–R5.7 · Test: T8, T9, T11[x] 13. Documentation.
docs/capabilities/control-plane.md(the routes and their authority),docs/config/cli/index.md(the config is editable from the UI),docs/cli/commands/service.mdif it enumerates routes,ui/README.md,CHANGELOG.md, the decision record, and the execution log's## Documentationsection. Requirements: skill §capability docs, §user-facing docs · Test: T16 review[x] 14. Verification. Execute
testing-plan.md, record results and evidence underevidence/, tick each activity only once it has actually run. Requirements: all · Test: the plan itself