| id | O.1 |
|---|---|
| title | JSON Escape Mode Contract and Safe Renderer Compatibility |
| phase | O |
| status | complete |
| branch | sprint/o-1-json-mode-contract |
| worktree | ../sc-compose-worktrees/sprint/o-1-json-mode-contract |
| target | integrate/phase-o |
| merge | PR |
Define and implement the two JSON interpolation modes without weakening the
FIX-272 injection fix. Existing unannotated JSON templates use secure auto
escaping and receive migration guidance when they contain the old quoted-
placeholder shape; an explicit legacy mode remains available as a safe
compatibility path. Newly generated or migrated templates use auto explicitly.
No implementation sprint dependency. Start from the phase parent selected by
team-lead on integrate/phase-o after plan approval. O.1 blocks O.2 and O.3
because both consume its effective-mode and diagnostic contracts. O.1 may run
in parallel with unrelated work outside the JSON renderer, but no other Phase O
sprint may implement a second mode resolver or escape algorithm.
crates/sc-composer/src/renderer.rscrates/sc-composer/src/frontmatter/model.rscrates/sc-composer/src/frontmatter/parser.rscrates/sc-composer/src/diagnostics/schema.rscrates/sc-compose/src/commands/template_init.rscrates/sc-compose/src/cli/schema.rscrates/sc-compose/src/render_request/request.rscrates/sc-composer/src/renderer.rstestscrates/sc-compose/tests/cli/templates.rsdocs/requirements.mddocs/migration/json-escape-mode.md(new migration guidance)docs/adrs/0019-json-render-contract-and-fail-closed-output-validation.md(acceptance gate)
- Add the typed
JsonEscapeModedefined by the phase plan's authoritative checked-render contract, withLegacyandAutovalues. - Resolve mode using CLI override, root frontmatter, then the 1.4.1 default
auto. - Restrict mode semantics to effective JSON templates using the existing suffix convention; diagnose format/mode mismatches instead of silently applying JSON rules to another format.
- Implement legacy string-content escaping. It must escape JSON string contents but must not add surrounding quotes already present in the source.
- Keep auto mode's complete-value minijinja JSON escaping and injection protection unchanged.
- Make
template-initgenerate JSON templates withjson_escape_mode: autoand bare placeholders. - Add stable diagnostic codes for compatibility/deprecation and invalid mode
usage; register them in the canonical schema.
validateandvalidate --lintemit exactly:Template uses legacy JSON escape mode. Migrate to bare placeholders (auto mode) to avoid double-quoting issues. See docs/migration/json-escape-mode.md - Document the source contract and migration examples in requirements/help.
legacy: "{{ value }}" -> JSON-escaped string contents, no added quotes
auto: {{ value }} -> complete JSON value, renderer owns string quotes
Legacy is not raw interpolation. A hostile value such as
x", "injected": true must remain one JSON string in both modes.
- auto string round-trip with quote, slash, newline, control character, and Unicode;
- auto injection cannot create a second object key;
- auto object, array, boolean, number, and null values retain type;
- legacy manually quoted string round-trip;
- legacy hostile string cannot inject syntax;
- legacy non-string in a quoted-string position has a stable diagnostic;
- mode precedence: CLI > frontmatter > 1.4.1 default
auto; - warning is emitted once per legacy template;
- invalid mode and non-JSON mode use are diagnosed;
- template-init emits auto mode and bare JSON placeholders;
- template-init followed by render is a semantic JSON round-trip;
- existing HTML/XML/CDATA/Turtle renderer tests remain unchanged.
The canonical Rust signature is:
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum JsonEscapeMode {
Legacy,
Auto,
}The lowercase serde representation is intentional: it is the stable
json_escape_mode: legacy|auto frontmatter and JSON wire contract.
- typed mode and resolver;
- safe legacy and auto behavior;
- stable diagnostic schema entries;
- template-init alignment;
- requirements/help/migration documentation;
- unit and CLI regression tests.
- Existing six-template source shape can be rendered safely in legacy mode.
- Unannotated JSON templates resolve to
auto, while legacy requires an explicit frontmatter or CLI selection. - Auto mode remains safe for the FIX-272 injection value.
- No raw/unescaped legacy path exists.
- New template-init JSON output selects auto mode and renders valid JSON.
- CLI/frontmatter/default precedence is tested.
-
validateandvalidate --lintemit the exact migration-directed deprecation warning and linkdocs/migration/json-escape-mode.md. - No non-JSON renderer behavior changes.
- All required tests and workspace quality checks pass.
- ADR-0019 is accepted before implementation handoff; this sprint does not dispatch source work while the Phase O design-acceptance gate is open.
Run the applicable sc-lint targets against the final O.1 commit. Fix minor
findings in this sprint. For remaining findings, create a fix/ worktree from
this sprint's final commit, grouping by independent rule class (for example,
one renderer crate constant-string group, not one worktree per finding; keep
length refactors separate). Send team-lead the worktree, parent commit, finding
class, evidence, tests, and fix commit. Team-lead creates the PR and routes it
to quality-mgr. O.1 is complete only after required fix PRs are QA-approved,
merged, and O.1 is revalidated on the merged parent.
cargo test --workspace
cargo fmt --all --check
cargo clippy --all-targets --all-features -- -D warnings
git diff --check