Skip to content

Add portable output.emit text views - #81

Merged
rickardvh merged 8 commits into
masterfrom
codex/config-output-override
Jul 13, 2026
Merged

rickardvh merged 8 commits into
masterfrom
codex/config-output-override

Conversation

@rickardvh

@rickardvh rickardvh commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • add generic output.emit text_views rendering for generated runtimes
  • tighten the portable contract to scalar-only matching/placeholders, scalar-list join, JSON-domain truthiness, and explicit JSON blocks for arrays/objects
  • align Python and TypeScript JSON block Unicode/key-order behavior and document the text_views language
  • add generated Python/TypeScript execution proof for matched/default views, {}, nested object rows, empty/non-empty conditions, len, join, empty, JSON blocks, root lookup, and malformed/invalid specs

Proof

  • uv run pytest tests/test_primitive_executor.py::test_output_emit_supports_declared_text_views tests/test_public_api.py::test_generated_output_emit_text_views_execute_in_python_and_typescript -q
  • uv run ruff check src/command_generation/primitive_executor.py src/command_generation/targets/typescript.py src/command_generation/primitive_registry.py tests/test_public_api.py tests/test_primitive_executor.py
  • uv run pytest tests/test_public_api.py tests/test_primitive_executor.py -q

Copy link
Copy Markdown
Owner Author

Review finding: this needs changes before merge.

The architectural direction is right: a bounded, host-neutral output.emit view specification is preferable to an AW-specific config_output override. CG should own rendering mechanics, while the host owns labels, ordering, matching policy, and payload semantics.

There are two blocking Python/TypeScript parity defects:

  1. The TypeScript placeholder regex requires at least one character inside {...}, so the valid scalar iteration template - {} is not interpolated. Python renders the current item; TypeScript emits the literal - {}. Please make {} resolve to the current item in TypeScript and prove both scalar and object iteration.

  2. Conditional truthiness differs across targets. Python treats empty lists and objects as false; JavaScript Boolean([]) and Boolean({}) are true. A view such as {"when": "warnings", ...} therefore omits an empty warnings section in Python but emits it in TypeScript. Define JSON-domain truthiness explicitly and implement identical semantics in both runtimes, including the empty filter.

The proof is currently insufficient for a new portable rendering contract. The substantive test executes only the Python primitive, while the generated-runtime test only asserts rendered source strings. Add a package-owned synthetic conformance fixture that executes the same declared views through generated Python and TypeScript callables. It should cover matched/default views, {}, nested object rows, empty/non-empty conditions, len, join, empty, JSON blocks, root lookup, and malformed specs.

The PR metadata also needs correction. The implementation no longer adds a config_output schema override; it adds generic output.emit text_views behavior and reuses the existing local fallback. Update the title/summary accordingly. Since this adds an additive portable primitive/generated-runtime capability to 1.2.1, semver:minor is the appropriate release classification rather than semver:patch; add the relevant generated-runtime/conformance compatibility label as well.

@rickardvh rickardvh changed the title Add generated config output override Add portable output.emit text views Jul 13, 2026
@rickardvh rickardvh added semver:minor Release as a minor semver bump compatibility and removed semver:patch Release as a patch semver bump labels Jul 13, 2026

Copy link
Copy Markdown
Owner Author

Re-review: the original blockers are resolved, but two portable-contract issues remain before merge.

  1. match is still not cross-target for non-scalar JSON values. Python uses structural equality, while TypeScript uses !==, so a view matching an array or object can match in Python and fail in TypeScript. Either restrict match values to JSON scalars and reject arrays/objects identically, or add structural JSON equality in TypeScript. Scalar-only matching is the cleaner bounded contract.

  2. Placeholder and join formatting still differs across Python and TypeScript for valid JSON values. Python uses str(...); TypeScript uses String(...). This diverges for objects, boolean arrays, and some numeric values. Embedded JSON blocks also differ for Unicode escaping. Please define one portable formatting contract. Recommended shape:

    • direct placeholders accept JSON scalars only;
    • join accepts lists of JSON scalars and uses the same scalar formatter;
    • arrays/objects require the explicit json line form;
    • JSON blocks use matching Unicode and key-order semantics in both runtimes.

Add focused generated Python/TypeScript conformance cases for the chosen restrictions and negative paths. Also document the text_views language—matching, truthiness, placeholders, filters, iteration, JSON blocks, and failure behavior—and update the output.emit primitive description accordingly.

The previous {} interpolation and empty collection truthiness defects are fixed, the generated cross-target execution proof is now substantive, PR metadata is corrected, and CI is green.

Copy link
Copy Markdown
Owner Author

Re-review: the previous structured-value and formatting issues are substantially addressed, but four portability/contract issues remain before merge.

  1. Scalar match equality still differs for booleans and numbers. Python ordinary equality treats True == 1 and False == 0; TypeScript strict equality does not. Please implement explicit JSON scalar equality that distinguishes booleans from numbers, with cross-target cases for true versus 1 and false versus 0.

  2. Number formatting is still target-dependent. Python uses integer conversion or json.dumps; TypeScript uses String(value). Values such as 1e-7, 1e-6, and 1e21 can render differently. Either add a canonical cross-target number formatter or narrow portable placeholders/join to finite safe integers and document that restriction.

  3. JSON block key-order parity is not guaranteed. Python preserves insertion order, while JavaScript moves integer-index-like keys ahead of other keys. The documented promise of preserved object key order therefore does not hold for objects such as {"2":"b","1":"a"}. Canonicalize recursive key ordering in both targets, or narrow/document the supported key domain. Number rendering inside JSON blocks must follow the same decision as above.

  4. The documented line grammar says each object line uses exactly one form, but the runtimes do not validate that. A line containing both template and literal silently uses implementation precedence, and invalid nested lines can remain hidden behind a false when. Add structural validation before rendering: exactly one line discriminator, required fields for each form, and mutually exclusive for_each.lines/for_each.template.

The architecture, scalar-only structured-value restrictions, Unicode handling, documentation, negative-path proof, labels, and CI are otherwise in good shape.

Copy link
Copy Markdown
Owner Author

Latest review: two narrow blocking issues remain.

  1. Line-form value types are still not validated. The structural validator enforces the discriminator and allowed keys, but it does not require literal, template, json, when, for_each.path, or for_each.template to be strings. This can still produce Python/TypeScript divergence—for example {"literal": {"a": 1}} renders {'a': 1} in Python and [object Object] in TypeScript. Please require string values for all path/template/literal fields during pre-validation, reject unsupported view-level fields as well, and add generated cross-target negative cases.

  2. Canonical JSON key ordering still diverges for integer-index-like keys. Python inserts recursively sorted string keys and json.dumps preserves that order. TypeScript inserts sorted keys into an object, but JSON.stringify re-enumerates integer-index-like keys numerically. The existing "1"/"2" test does not expose this. Add an exact regression case such as {"10": "a", "2": "b"} and either use a canonical JSON string serializer or deliberately reproduce one target-independent ordering rule in both runtimes.

Everything else from the previous reviews is now addressed: boolean/number scalar equality is explicit, portable numbers are finite safe integers only, malformed hidden lines fail before selection, structured placeholders/joins fail closed, JSON Unicode and number validation are covered, documentation is present, and CI is green.

Copy link
Copy Markdown
Owner Author

Latest review: one final portability issue remains before merge.

default view selection still accepts arbitrary JSON values and evaluates them with host-language truthiness. Python uses bool(...), while TypeScript uses JavaScript truthiness, so values such as [] and {} select differently across targets. For example, {"default": [], "lines": ["Fallback"]} is not a fallback in Python but is a fallback in TypeScript.

Please require default to be a boolean in both validators, update the documentation from “truthy value” to boolean, and add a generated Python/TypeScript negative case for a non-boolean default value.

Everything else from the previous reviews is now addressed: line-field types and view-level keys are validated, booleans and numbers compare distinctly, numbers are restricted to finite safe integers, malformed nested lines fail before selection, canonical JSON serialization preserves lexicographic key order including "10"/"2", documentation is complete, and CI is green. After constraining default, this is ready to merge.

@rickardvh
rickardvh merged commit 3f9a89e into master Jul 13, 2026
2 checks passed
@rickardvh
rickardvh deleted the codex/config-output-override branch July 13, 2026 14:55

Copy link
Copy Markdown
Owner Author

Post-merge review: the final default validation fix is correct, the previous portability findings are resolved, and both CI workflows are green. One cross-target template-substitution defect remains and should be fixed before the 1.3.0 release or downstream migration.

Python renders templates by collecting tokens and repeatedly calling str.replace on the already-modified output. TypeScript performs a single regex-replacement pass over the original template. Placeholder-looking text introduced by an earlier replacement is therefore expanded only in Python.

Reproducer:

{
  "result": {"first": "{second}", "second": "resolved"},
  "template": "{first} {second}"
}

Python produces resolved resolved; TypeScript produces {second} resolved.

Please define substitution as a single pass and change the Python renderer to replace matches through a callback over the original template rather than repeatedly mutating it. Add an executed generated Python/TypeScript regression case where one scalar value contains another valid placeholder expression. Apart from this issue, the merged contract and conformance coverage are in good shape.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compatibility semver:minor Release as a minor semver bump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant