Repository navigation
Feat/80 toml and generator - #83
Conversation
…code The graph-lineage exposure (modules/observability 04 to 10) and the catalogue-interface binding were written for the flat template context that preceded the build context, and no phase rendered them. Decided: retire rather than port, so the move to Python dialects covers only live code. The Alation example, which exercised the catalogue interface, goes with them. Their last versions are at 5fe4803. A build that asks for either still reports it as a gap. The Teradata README loses its catalogue-interface section and the facet description, its declared gap says the code was retired, the package test no longer exempts it, and the release index drops the example and the pattern. The Teradata conformance report is regenerated from a live run later in this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A binding's declarations - quoting, emitted roles, settings, phases,
checks, gaps, types and expression functions - were blocks of our own
notation inside PLATFORM_PROFILE.md, parsed by a custom reader. They are
now implementation/{platform}/binding.toml, read with tomllib. The
authoring standard's §5.1 field tables remain the schema: a key is the
field name in snake_case, and anything in order (phases, checks, gaps)
is an array. Settings are { values, default } and named in snake_case,
so templates read settings['container_space'] and the like.
A platform is now a directory with a binding.toml, for discovery in
verify_skill, conformance and the tests. PLATFORM_PROFILE.md keeps its
physical-design guidance and points to the declarations.
Every rendered SQL file of both reference specifications under all four
example profiles is byte-identical; context and manifest differ only in
the two Teradata setting names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An organisation profile was a markdown file per platform, its rules in our own block notation. It is now two TOML files: - design.toml: the organisation, its classification and retention. It names no platform, so the design role may read it. - implementation.toml: tables every target shares at the top level, and one [targets.<name>] per deployment target, naming its platform. A target may extend another, replacing the tables it states. The Organisation Profile Standard's field tables remain the schema: a field's key is its name in snake_case, the organisation's own names are written as they are, and the loader turns the tables into the profile the resolver already checked and executed, so no rule changed. The template, config/*.example.toml, is the worked example's organisation with duckdb, postgres and teradata targets; the contrasting organisation is examples/it-service-desk-data-product/contrasting/. An organisation keeps its own copies in its own repository. render.py, build_context.py and org_profile.py take the implementation file and --target (and --design where the design file is not its sibling); the context records both files and the target. Every rendered SQL file of both reference specifications under all four example targets is byte-identical, and contexts and manifests are equal as data apart from the recorded inputs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run at 527984c with all three engines live (DuckDB 2.0.0, PostgreSQL 16.11 locally, Teradata 20.00 on the test system): 49, 49 and 60 passed, none failed or skipped. The reports name each build by its target and configuration file, point to binding.toml for declared gaps, and record Teradata's retired graph lineage and catalogue interface as a gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every phase, deploy script and check of DuckDB, PostgreSQL and Teradata now comes from implementation/portable/dialect.py and each platform's dialect.py, with output byte-identical to the Jinja templates for every example target. Not yet done: caller-rendered key allocation and maintenance, removing the templates and Jinja2, the package test that forbids .py in a binding, docs and file-count gates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each platform now generates its phases, deploy script, checks and pipeline
statements from implementation/{platform}/dialect.py, a subclass of the
portable dialect, so a binding is five files: README, PLATFORM_PROFILE,
CONFORMANCE, binding.toml and dialect.py. The 94 templates and the Jinja2
dependency are gone; tooling is standard-library Python only.
- binding.toml no longer declares phases, checks or a deploy template; the
dialect's PHASES and CHECKS do, and the build context drops them.
- The package tests check dialect hygiene: one dialect per binding, a
binding budget of six files, an AST purity check (imports, I/O, product
literals) and a trace that every method is reached.
- TLM-04 reads the SQL in a dialect's string literals.
- verify_skill gates the package at 180 files (warning at 160) under the
200-file upload limit, and design and binding documents at 600 lines.
- The authoring standard, roles, design documents and binding READMEs
describe dialect methods instead of template files.
Generated SQL is byte-identical to the templates' for every example build.
All 125 tests pass on DuckDB 2.0.0, PostgreSQL 16.11 and Teradata
20.00.24, and the conformance reports are regenerated from that run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roles/build.md sends every build to PLATFORM_PROFILE.md §7, SQL Idioms and Driver Constraints, before it writes SQL. On DuckDB §7 was the access boundary and the idioms were §10, so a DuckDB build read the wrong section, and the section check passed because a §7 existed. - DuckDB's SQL idioms move to §7; access, extensions and capabilities follow. - The authoring standard requires §7 to be the SQL idioms on every binding, and test_binding_package enforces it against the route. - The profiles' declarations trailer and the build role's checks table no longer say binding.toml declares phases and checks. Conformance reports regenerated from a live run: 126 tests pass on DuckDB, PostgreSQL and Teradata, none skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI failed four package tests: the validation suite imports every dialect first, Python writes a __pycache__ beside each dialect.py, and the conformance run then counted those caches as extra binding files (one failure per platform and one for implementation/portable). Locally PYTHONDONTWRITEBYTECODE was set, which hid it. - The dialect loader sets sys.dont_write_bytecode while it imports, so generating SQL writes nothing inside implementation/. - The binding file test ignores __pycache__, which is gitignored and never ships, so a stale cache on a developer's machine cannot fail it. Reproduced with bytecode writing on, then replayed CI's two steps: the validation suite passes, and conformance --check --trust-committed teradata passes with the committed reports unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earthshiner
left a comment
There was a problem hiding this comment.
@NathanG-TD, this is a good piece of work.
Taking a platform from 35–45 files to 5 fixes the skill-upload limit, and the dialect approach is easier to reason about than the Jinja templates were. I like the guardrails: the file budget, the purity test and the “every method must be reached” check should stop the file count creeping back.
I read the tooling (render.py, the dialect loader, binding.py, org_profile.py, build_context.py, build_release.py, verify_skill.py), the new tests and the workflow. I did not go through portable/dialect.py or the three platform dialects line by line, so I’m relying on your byte-identical result for the generated SQL. I also didn’t run the suite. CI is green.
One thing I’d fix before merging: the Teradata evidence
CI can’t reach a Teradata system. The Teradata engine tests skip and the job trusts the live run recorded in the committed CONFORMANCE.md. That was reasonable while the SQL came from templates that rarely changed. This PR rewrites all the Teradata generation, though, and nothing ties that report to the current dialect.py. The test only checks the report’s header and status.
The byte-identical comparison is also a one-off. The old templates are deleted, so I couldn’t find anything that would catch a later edit changing the rendered Teradata SQL while CI stays green.
Could we add a golden-file test for the rendered output, or store a hash of the generated SQL in CONFORMANCE.md and compare it in CI? Either would show the live run still matches the code.
Smaller points, none blocking
reached() in test_binding_package.py uses sys.settrace. If anyone runs the suite under coverage or a debugger, the tracer gets replaced and the test could pass when it shouldn’t. The finally: sys.settrace(None) would also switch off a tracer that was already running. An AST or call-graph approach would be sturdier, but I’m fine with this if you note the limitation.
The purity test is a bit weaker than the standard’s wording. It blocks bare calls like open and relies on the import allowlist for the rest. It wouldn’t catch getattr(builtins, ...). It’s a good guardrail, but “makes no file, network, clock or environment call” reads like a guarantee. I’d soften the wording or tighten the check.
Configuration.design_source() finds the design file by replacing “implementation” with “design” in the filename. If the file is called anything else, the design path ends up equal to the implementation path. The user then gets confusing “does not belong here” errors. A clear message, or requiring --design in that case, would be kinder.
In org_profile._block, a TOML list of tables is turned into Python {'a': 1} text with no error. Nested mappings are already rejected, so I’d reject this case too.
Phase filenames now come from position (f"{i:02d}-{phase}.sql"). Inserting a phase renames every later file. Fine if intended, but worth a line in the authoring standard.
dialect_class and dialect_for ignore a repo argument. build_release.py passes repo= around, but dialects always load from the repo this checkout lives in. That matches the old load_binding behaviour, so only a problem if you ever build a release from another checkout.
portable/dialect.py is 1,303 lines. The 600-line budget only covers Markdown, but this is the file people and agents will read most. Splitting it by module could help later.
Please squash on merge, as you said, to fold in the WIP commit.
Overall I’m happy with the direction. If the Teradata evidence gets tied to the code, I’m comfortable approving.
- conformance.py records a SHA-256 digest of every file the reference builds render in each CONFORMANCE.md. --trust-committed stands a committed live run in for skipped engine tests only when its digest matches the current output, so a change to generated SQL needs a new live run before CI passes. - The purity test also rejects getattr/setattr/delattr/vars and the interpreter's own names; the authoring standard calls it a guardrail, not a sandbox. - design_source() no longer falls back to the implementation file when its name lacks "implementation"; the build asks for --design instead. - A TOML list holding tables or lists is rejected, like a nested mapping. - reached() restores the tracer that was running instead of clearing it. - build_bindings refuses a repo other than the checkout the dialects load from. - The authoring standard notes that inserting a phase renames later phase files. - DuckDB's report is regenerated from a live run with its digest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each report now carries the SHA-256 digest of the rendered output. Engines: PostgreSQL 16.9 (psycopg 3.3.8), Teradata 20.00.24.60 (teradatasql 20.0.0.75). Every binding ran 0 failed, 0 skipped. The DuckDB report was already current. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The build context recorded the specification's absolute path, so the rendered context.json, and with it the conformance digest, depended on where the checkout lives. CI's digests could never match a report generated elsewhere. The path is now labelled like the other inputs (repository-relative), and all three reports are regenerated from live runs: DuckDB v2.0.0, PostgreSQL 16.9, Teradata 20.00.24.60. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @earthshiner, all addressed in 177fd5a and aa1437f. Teradata evidence tied to the code Each CONFORMANCE.md now records a SHA-256 digest of every file the reference builds render. Smaller points reached(): restores the tracer that was running instead of clearing it. The comment notes that it displaces coverage or debugger tracers while it runs. |
Title: Python code generation and TOML configuration (#80)
Summary
Every platform binding now generates its SQL from a Python dialect instead of Jinja templates, and an organisation configures the standard in two TOML files instead of one markdown profile per platform. The point is issue #80: the repository is the skill, a skill upload stops at 200 files, and each platform used to cost 35 to 45 files. A platform now costs five, so ten platforms fit with room to spare. Rendered SQL is unchanged.
Net change in shipped files
What changed
Breaking changes for users of the tooling
New CI checks (so the file count can't creep back)
Evidence
Documentation
Notes for reviewers
🤖 Generated with Claude Code