Skip to content

Commit cedb11f

Browse files
authored
Replace product scaffold with Zig runtime
Merged with green CI and Codex/subagent review as requested. GitHub required-review UI was temporarily relaxed because review is handled agentically in-session.
1 parent 0a82150 commit cedb11f

85 files changed

Lines changed: 5028 additions & 154 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# M1 Docs Block Slice
2+
3+
## Scope
4+
5+
This slice extends the native Zig parser and partial manifest writer with a dbt Core-compatible docs block subset:
6+
7+
- discover `.md` files under configured model paths
8+
- parse `{% docs name %}` / `{% enddocs %}` blocks
9+
- emit `docs` manifest entries keyed as `doc.<package>.<name>`
10+
- resolve whole-description literal `{{ doc("name") }}` and `{{ doc('name') }}`
11+
- attach resolved doc block IDs to model and column `doc_blocks`
12+
- fail loudly for malformed docs blocks, duplicate docs names, missing docs, and dynamic `doc` calls
13+
14+
The product runtime remains Zig. Python changes are limited to tests.
15+
16+
## Compatibility Evidence
17+
18+
The fixture was checked against dbt Core 1.10 with the DuckDB adapter. The written Core manifest showed:
19+
20+
- docs entries include `unique_id`, `resource_type`, `package_name`, `name`, `path`, `original_file_path`, and `block_contents`
21+
- docs `path` is relative to the model path, while `original_file_path` includes the model path
22+
- docs block contents are trimmed before manifest emission
23+
- literal `doc` descriptions are rendered into model and column descriptions
24+
- model and column `doc_blocks` list referenced doc unique IDs
25+
- docs entries do not appear as lineage keys in `child_map`
26+
27+
Fusion preview was also checked as a secondary signal, but Core remains the contract for this M1 slice.
28+
29+
## Validation
30+
31+
Required before merging this slice:
32+
33+
- `zig fmt --check src/project.zig src/root.zig build.zig`
34+
- `zig build`
35+
- `zig build test`
36+
- `pytest -q`
37+
- `git diff --check`
38+
- public-safety and runtime-boundary scans
39+
- public Jaffle Shop DuckDB parse smoke, if the fixture checkout is available
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# M1 Generic Test Node Slice
2+
3+
## Scope
4+
5+
This slice emits dbt-shaped manifest nodes for the simple generic tests already parsed from YAML:
6+
7+
- model-level `unique`
8+
- column-level `unique`
9+
- column-level `not_null`
10+
- `tests:` and `data_tests:` aliases
11+
- test entries in `manifest.nodes`
12+
- parent/child map edges from tests to their attached model
13+
- `ls --resource-type test`
14+
15+
Argument-rich tests such as `accepted_values` and `relationships` remain deferred until the YAML argument parser can preserve nested `arguments:` and relation references.
16+
17+
## Compatibility Evidence
18+
19+
dbt Core 1.10 with the DuckDB adapter was run against the model-properties fixture. The process wrote `manifest.json` before a local event-reporting dependency crash. The observed generic test IDs were:
20+
21+
- `test.model_properties.not_null_customers_customer_id.5c9bf9911d`
22+
- `test.model_properties.unique_customers_.ccc5343706`
23+
- `test.model_properties.unique_customers_customer_id.c5af1ff4b1`
24+
25+
The suffix is the last 10 characters of the md5 hash that dbt computes from the synthesized full test name plus hashable test metadata. The product implementation reproduces that in Zig for this supported subset.
26+
27+
## Validation
28+
29+
Required before merging this slice:
30+
31+
- `zig fmt --check src/project.zig src/root.zig build.zig`
32+
- `zig build`
33+
- `zig build test`
34+
- `pytest -q`
35+
- `zig build -Doptimize=ReleaseSafe`
36+
- `python scripts/check_public_safety.py`
37+
- `python scripts/check_runtime_boundary.py`
38+
- `git diff --check`
39+
- public Jaffle Shop DuckDB parse smoke, if the fixture checkout is available
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# M1 Model Properties Slice
2+
3+
## Scope
4+
5+
This slice extends the native Zig M1 parser to read SQL-backed model properties from YAML files under configured model paths. It remains a deliberately small artifact-first subset, not a general YAML or Jinja implementation.
6+
7+
Implemented surface:
8+
9+
- Scalar model descriptions and dbt-shaped `patch_path`.
10+
- Simple column names and scalar descriptions.
11+
- Simple model and column test names parsed internally for later dbt-shaped test-node work.
12+
- Block-form `config.materialized`.
13+
- Block-form `config.tags` plus top-level inline `tags`.
14+
- `config.enabled: false` for SQL models.
15+
- Disabled SQL model representation under manifest `disabled`.
16+
- Loud failure when an active model `ref`s a disabled model.
17+
- dbt-compatible warnings, not fatal errors, for YAML model properties that do not match a discovered SQL model.
18+
19+
## Compatibility Notes
20+
21+
Active nodes exclude disabled models from `nodes`, `parent_map`, `child_map`, and `dxt ls`. Disabled SQL models are retained in `disabled` as a list under their model unique id, matching the dbt artifact shape more closely than silently dropping them.
22+
23+
The YAML parser is intentionally subset-based. Unsupported richer property forms should fail or remain outside the advertised supported surface until a real YAML layer is introduced.
24+
25+
Parsed generic tests are intentionally not written into model or column objects because dbt manifests represent them as separate `test.*` nodes. Emitting those nodes is a follow-up M1 compatibility slice.
26+
27+
## Validation
28+
29+
Focused fixtures cover model properties, columns, combined source/model YAML, disabled model representation, disabled ref diagnostics, and unmatched model-property warnings.
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# M1 Parser Risk Review
2+
3+
## Scope
4+
5+
This note reviews the first M1 parser slice only: a native Zig `dxt parse`
6+
implementation that loads a small dbt-style project, discovers Tier 0 resources,
7+
builds a partial graph, and writes an intentionally partial but dbt-shaped
8+
`manifest.json`.
9+
10+
The slice should not claim compile compatibility, macro execution, adapter
11+
behavior, selector parity, or full artifact-schema coverage. Its value is a
12+
reviewable native parser path plus enough manifest structure for dbt-oracle
13+
comparison to start.
14+
15+
## Dangerous Overclaims
16+
17+
The highest risk is marketing or test language that says `dxt parse` is
18+
"dbt-compatible" before the parser can handle normal dbt project behavior. For
19+
M1, compatibility should be described as narrow and fixture-backed:
20+
21+
- Safe claim: parses the supported Tier 0 fixture subset and emits a
22+
schema-shaped partial manifest.
23+
- Unsafe claim: parses dbt projects generally.
24+
- Safe claim: extracts simple literal `ref`, `source`, `config`, and `doc`
25+
calls from SQL/Jinja spans.
26+
- Unsafe claim: supports Jinja, macros, dispatch, or dbt parse-time semantics.
27+
- Safe claim: supports a documented subset of YAML used by the M1 fixtures.
28+
- Unsafe claim: supports YAML generally, including anchors, merges, custom tags,
29+
complex scalars, or every property file shape.
30+
- Safe claim: `ls` or selectors use the M1 graph for basic names, tags, paths,
31+
resource types, `+`, and `--exclude` only if tests prove those cases.
32+
- Unsafe claim: selector parity with dbt Core.
33+
- Safe claim: Jaffle Shop DuckDB is an exploratory parse target once Tier 0
34+
passes.
35+
- Unsafe claim: Jaffle Shop support until dbt-oracle counts, IDs, dependency
36+
maps, and diagnostics are compared and pinned.
37+
38+
Unsupported features should fail loudly with structured diagnostics. Silent
39+
skips are more dangerous than incomplete support because they produce artifacts
40+
that look authoritative while losing graph semantics.
41+
42+
## Partial Manifest Strategy
43+
44+
The M1 manifest should be partial by design but useful enough for downstream
45+
comparison. The practical rule is: emit dbt field names only when `dxt` can
46+
populate them with the intended meaning, and keep unknown `dxt` metadata out of
47+
the dbt object unless the target schema permits it.
48+
49+
Minimum useful manifest content:
50+
51+
- `metadata` with explicit `dxt` identity and the target dbt artifact schema
52+
family under test.
53+
- `nodes` for supported models, seeds, singular tests, and generated generic
54+
tests when M1 actually creates them.
55+
- `sources` for YAML-declared sources and tables.
56+
- `macros` only for parsed macro definitions, not executed macro behavior.
57+
- `docs` only for parsed docs blocks.
58+
- `exposures`, `metrics`, `groups`, and semantic resources as empty maps unless
59+
the parser has fixture-backed support for them.
60+
- `disabled` entries when disabled resources are discovered, not dropped.
61+
- `parent_map` and `child_map` derived from resolved refs and sources.
62+
- Stable `unique_id` values for all emitted resources.
63+
- Relative `path` and `original_file_path` values, with no host-specific paths.
64+
65+
Fields that are easy to overstate should stay absent, null, or explicitly
66+
unsupported until implemented. Examples include compiled SQL, relation names,
67+
injected CTEs, macro dependency side effects, adapter-specific configs, catalog
68+
metadata, execution status, checksums if not calculated the dbt way, and
69+
unrendered config if the parser does not preserve it correctly.
70+
71+
The manifest writer should be deterministic. Sort object keys and arrays where
72+
ordering is not semantically meaningful, normalize path separators, and avoid
73+
timestamps or invocation IDs in M1 unless tests normalize them. This keeps
74+
parity diffs focused on parser behavior instead of noise.
75+
76+
## Python Runtime Boundary
77+
78+
Python can remain a strong validation tool for M1, but not a product shortcut.
79+
The product path must be:
80+
81+
```text
82+
dxt parse -> Zig CLI -> Zig project loader -> Zig parser -> Zig graph -> Zig artifact writer
83+
```
84+
85+
Acceptable Python:
86+
87+
- invoking dbt Core as an oracle in tests,
88+
- generating synthetic fixtures,
89+
- normalizing and comparing JSON artifacts,
90+
- validating public artifact schemas,
91+
- scanning for local paths and secrets,
92+
- checking that Python product-runtime files were not introduced.
93+
94+
Blocking Python uses:
95+
96+
- product CLI command dispatch,
97+
- YAML parsing used by the `dxt` binary,
98+
- SQL/Jinja dependency extraction used by the `dxt` binary,
99+
- manifest writing used by the `dxt` binary,
100+
- selector evaluation used by the `dxt` binary,
101+
- spawning Python from `dxt parse`.
102+
103+
The runtime-boundary check should grow with M1. It should fail on Python under
104+
product source paths and should also catch product command implementations that
105+
invoke `python`, `python3`, `dbt`, or developer harness scripts as the parser
106+
implementation.
107+
108+
## No-Dependency Zig Scanner
109+
110+
The first scanner should be byte-oriented, allocation-light, and deliberately
111+
smaller than a renderer. Its job is discovery, not full Jinja execution.
112+
113+
Recommended shape:
114+
115+
- Read each file once and scan bytes for Jinja delimiters: `{{`, `{%`, and
116+
`{#`.
117+
- Treat SQL text as opaque except for entering and leaving Jinja spans.
118+
- Inside expression or statement spans, tokenize only the subset needed for M1:
119+
identifiers, dots, string literals, commas, parentheses, equals, brackets,
120+
whitespace, comments, and simple number/bool/null literals if configs need
121+
them.
122+
- Recognize call forms by token sequence, not substring matching. This avoids
123+
false positives in comments, strings, similarly named functions, or words
124+
such as `reference`.
125+
- Support single-quoted and double-quoted strings with escapes before claiming
126+
literal `ref` or `source` extraction.
127+
- Skip Jinja comments entirely.
128+
- Record file offset, line, and column while scanning so diagnostics do not need
129+
a second full pass.
130+
- Use arenas for per-file tokens and discard them after producing compact
131+
dependency/config records.
132+
- Intern repeated package, resource, path, and config key strings in the graph
133+
allocator.
134+
- Avoid building a generic AST for every file in M1; keep the output as typed
135+
discovery records.
136+
- Bound recursion and token sizes. A malformed Jinja span should return a
137+
location-aware unsupported-syntax error, not allocate until OOM.
138+
- Make scanner behavior deterministic under parallel file walking by sorting
139+
discovered paths before parsing or sorting records before graph construction.
140+
141+
Performance should be measured with simple budgets even in M1. A useful first
142+
gate is "parse each fixture file once, no quadratic rescans, no per-token heap
143+
allocation, no dependency on regex engines or external parser packages." Later
144+
benchmarks can compare against dbt Core, but M1 should at least include a
145+
repeat-parse stress test to catch leaks and accidental global state.
146+
147+
## PR-Blocking Tests
148+
149+
The first M1 parser PR should not merge unless these gates pass:
150+
151+
- `zig build`
152+
- `zig build test`
153+
- black-box `dxt parse` tests against the built native binary
154+
- `pytest -q` for developer harness and safety tests
155+
- public-safety scan for local paths, secrets, logs, caches, and generated noise
156+
- runtime-boundary scan proving the product parser/artifact path is not Python
157+
- diff review confirming no generated artifacts, caches, or fixture outputs are
158+
committed accidentally
159+
160+
Parser behavior tests that should block the PR:
161+
162+
- Missing `dbt_project.yml` fails with a stable diagnostic and nonzero exit.
163+
- A minimal one-model project emits `target/manifest.json`.
164+
- Two models with a literal `ref` produce correct `unique_id`, `depends_on`,
165+
`parent_map`, and `child_map` entries.
166+
- A YAML source plus `source(...)` call produces a source entry and dependency.
167+
- YAML model properties populate descriptions, columns, tags, meta, and basic
168+
tests only for the supported subset.
169+
- A disabled model is represented as disabled and is not silently promoted to an
170+
active node.
171+
- Unsupported YAML or Jinja returns a structured unsupported-feature diagnostic.
172+
- Paths in artifacts are relative and portable.
173+
- Manifest output is deterministic across two consecutive parses.
174+
- `--project-dir` and `--target-path` are honored without leaking host paths.
175+
- Unknown or malformed parser flags still fail through the native CLI.
176+
177+
Compatibility tests that should block the PR:
178+
179+
- A dbt Core oracle run over Tier 0 fixtures is captured in developer-side tests.
180+
- Resource counts by type match the oracle for the supported fixture subset.
181+
- Stable IDs and dependency maps match the oracle where M1 claims support.
182+
- The emitted manifest validates against the pinned schema slice or documented
183+
partial schema harness used for M1.
184+
- Any expected mismatch is listed in a test fixture allowlist with a reason, not
185+
hidden in broad JSON normalization.
186+
187+
Scanner-specific tests that should block the PR:
188+
189+
- `ref` and `source` are not extracted from SQL comments, string literals, Jinja
190+
comments, or similarly named identifiers.
191+
- Single and double quoted literal calls work.
192+
- Package-qualified `ref("pkg", "model")` is either supported and tested or
193+
rejected with a clear diagnostic.
194+
- Malformed Jinja delimiters and unterminated strings fail predictably.
195+
- Large files with many non-Jinja bytes scan in linear time.
196+
- Repeated parse runs do not leak under Zig test allocator coverage.
197+
198+
The PR is ready for review only when its description states exactly which dbt
199+
surfaces are supported, which are intentionally unsupported, which oracle
200+
version/schema slice was used, and the exact validation commands that passed.

0 commit comments

Comments
 (0)