Skip to content

feat(text): author policy programs through a typed expression DSL - #64

Open
thejustinwalsh wants to merge 8 commits into
docs/core-api-surfacefrom
refactor/policy-dsl
Open

feat(text): author policy programs through a typed expression DSL#64
thejustinwalsh wants to merge 8 commits into
docs/core-api-surfacefrom
refactor/policy-dsl

Conversation

@thejustinwalsh

@thejustinwalsh thejustinwalsh commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Policy authoring DSL (D-250)

Policy programs are written against named values instead of register numbers. policyProgram() exposes the semantic handles — inlineOrigin, blockOrigin, fontSize, and color with .red/.green/.blue/.alpha channels (the engine has no background; "foreground" contrasted with nothing) — plus binding fields declared by name (bearingX, uvOriginX, page, …), and addF32/subtractF32/multiplyF32/u32ToF32 with typed constants build an expression graph. compile() lowers it to the same forward-only PolicyOperation records, allocating registers automatically — use-before-write and register exhaustion are compile errors with names in the message — and deduplicating reused values.

The hand-numbered bitmap fragment:

binary('multiplyF32', 15, 7, 2);
binary('addF32', 16, 0, 15);

becomes:

addF32(inlineOrigin, multiplyF32(bearingX, fontSize))

Wire contract unchanged: value types stay a wire-level property of each operation and buffer schema; the DSL's branded PolicyF32Value/PolicyU32Value exist only at authoring time in TypeScript. The four Three programs port to the DSL with per-technique named buffer ids (BITMAP_UV_ORIGIN, SLUG_BAND_TRANSFORM, …).

Proof: a decoded-bytes equivalence test parses old and new policy bytes and asserts identical input tables, buffer schemas, capability sets, program metadata, and per-lane store dataflow against fixtures captured from the hand-numbered programs (register numbering is program-private; the interpreter requires only forward-only writes). The byte goldens re-pin once over that proof and freeze the DSL output from here. 180 package tests and the integration suite are green.

Deferred: renaming the ABI JSON keys (foregroundRedcolorRed, indices unchanged) in the Rust ABI emitter — the DSL already presents color at the authoring surface.

Stack created with GitHub Stacks CLIGive Feedback 💬

Adversarial review closure

Two passes against Codex Sol High's findings, both on this branch:

  1. da67013 — session provenance on DSL values, deep-frozen schemas, schemaPolicyBuffers/schemaFieldTable/schemaDrawBuffers derivations replacing every literal id range, positional field table, and width list; schema-declared glyphOrigin opt-in replacing the Bitmap-layout assumption; three new structural gate patterns. All byte goldens stayed pinned.
  2. efe2f44 — re-review follow-ups: provenance stamped at node construction (O(1) per store, no exponential DAG walk, cross-session mixing fails at the combinator); schema definition validates first and returns an owned deeply-frozen copy (rejection never touches caller data, hostile accessors cannot change validated widths); size-budget notes record measured deltas; package reference documents /core and /tsl with /three/* as compatibility aliases.

Re-review disposition: session provenance and glyph-origin closed; schema authority and freezing closed at this stack's scope. The remaining structural finding — TSL shader lane meaning and the external example still restate schema knowledge — is migration layers 3–4 of docs/planning/raster-technique-contract.md, scheduled with the technique-contract audit.

@thejustinwalsh thejustinwalsh changed the title refactor/policy dsl feat(text): author policy programs through a typed expression DSL Aug 11, 2026
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
Core JS (gzip) 18.38 KB (0%)
Shaper Wasm (gzip) 418.77 KB (0%)
Three.js adapter JS (gzip) 60.08 KB (+4.28% 🔺)
Inter font · Bitmap (gzip) 545.27 KB (0%)
Inter font · MTSDF (gzip) 6.48 MB (0%)
Inter font · Slug (gzip) 604.06 KB (0%)
Font Awesome icons · Bitmap (gzip) 439.53 KB (0%)
Font Awesome icons · MTSDF (gzip) 6.89 MB (0%)
Font Awesome icons · Slug (gzip) 642.64 KB (0%)
Font validator JS (gzip) 134.24 KB (0%)
Runtime bake host JS (gzip) 5.38 KB (0%)
Runtime bake Worker JS (gzip) 142.45 KB (0%)
Font baker JS (gzip) 2.41 KB (0%)
Font baker Wasm (gzip) 379.63 KB (0%)
Bitmap baker JS (gzip) 4.66 KB (0%)
Bitmap baker Wasm (gzip) 228.31 KB (0%)
MTSDF baker JS (gzip) 5.39 KB (0%)
MTSDF baker Wasm (gzip) 209.22 KB (0%)
Slug baker JS (gzip) 4.01 KB (0%)
Slug baker Wasm (gzip) 181.24 KB (0%)

@thejustinwalsh
thejustinwalsh marked this pull request as ready for review August 12, 2026 00:30
The upcoming policy DSL must be a pure authoring-layer change; these
sha256 digests over all four transform/allocation variants make byte
drift in the compiled wire records a loud failure.
Policy programs are written against named values instead of register
numbers (D-250): policyProgram() exposes semantic handles — including
color, which replaces the foreground fiction at the authoring layer;
the engine has no background — and declared binding fields by name,
while addF32/subtractF32/multiplyF32/u32ToF32 and typed constants build
an expression graph that compile() lowers to the same forward-only
PolicyOperation records, allocating registers automatically with
use-before-write and exhaustion as errors and deduplicating reused
values. The four Three programs port to the DSL with per-technique
named buffer ids.

The wire format, validator, and interpreter are untouched, and value
types remain a wire-level property of each operation and buffer schema;
the DSL brands exist only at authoring time. A decoded-bytes
equivalence test proves the port preserves the input tables, buffer
schemas, program metadata, and per-lane store dataflow against the
hand-numbered fixtures; the byte goldens re-pin once over that proof.
The scalar tail costs ~50 ns per record; overlap would save ~100 ns per
tailed span. Below the D-245 admission bar at current draw-span
distributions; recorded so the next profile that blames the packing
pass starts from numbers.
The DSL authoring layer rides the Three bundle and the core subpath;
raw grew by comments and names while minified, gzip, and Brotli stayed
inside their ceilings. Size evidence regenerates with the budgets.
Each raster technique declares its physical shape once, colocated and
exported: defineTechniqueSchema in core; bitmapSchema, msdfSchema, and
slugSchema beside their techniques; decorationSchema and the Three
policy's system buffers in the Three policy. Programs build with
techniqueProgram(schema) and store through schema buffer handles, the
plan executor looks buffers up by declared id — including two literal
lookups the new repository gate caught in the decoration material path —
and the gate forbids literal buffer identity anywhere outside the
declaration sites from now on.

The compiled policy bytes are byte-identical across the change: the
goldens did not move, proving the authority layer is pure naming. The
full contract plan (D-251) records the remaining layers: shader
interfaces derived from schemas, the data-origin axis with the reserved
pretext fallback technique, the external example rebuilt on the
construct, and tsdown bundling with per-subpath size re-basing.
Policy-DSL values carry session provenance: storing a value loaded from
another program's input table throws instead of silently reading a
shifted input. Technique schemas deep-freeze at definition, making the
documented immutability true. The remaining restatement sites derive
from schemas: schemaPolicyBuffers replaces hand-rolled width lists in
the Three programs, schemaFieldTable orders binding readers by declared
names so a missing or misspelled reader is a compile error, the
executor resolves draw buffers by schema name instead of literal id
ranges, and the plan-program registry references the transform system
buffer instead of restating its id. Glyph-origin augmentation is now
schema-declared opt-in metadata rather than assuming Bitmap's layout
for every technique. The structural gate also rejects literal-width
buffer builders, literal id ranges, and restated system ids.

Every policy and binding byte golden stayed pinned: the derivations
reproduce the hand-rolled bytes exactly. Core and Three raw budgets
re-priced (+1.8 KB / +1.9 KB, comment- and name-dominated); compressed
ceilings unchanged.
Session provenance is stamped at node construction and combined in O(1):
shared expression DAGs no longer trigger an exponential graph walk in
store(), and mixing two authoring sessions fails at the combinator.
Schema definition validates the caller's input first and returns an
owned, deeply frozen copy — rejection leaves caller data untouched,
hostile accessors cannot change a validated width afterwards, and only
declared fields are carried. Size-budget notes record the measured
deltas instead of claiming comment-dominated growth, and the package
reference documents /core and /tsl with /three/{bitmap,msdf,slug} as
compatibility aliases.
# Conflicts:
#	docs/log.md
#	docs/packages/benchmarks.md
#	docs/packages/glyph.md
#	docs/planning/benchmark-plan.md
#	docs/planning/decision-register.md
#	docs/planning/font-baker-implementation.md
#	docs/planning/tooling-fixtures.md
#	packages/glyph/src/core/policy-program.ts
#	packages/glyph/src/core/technique-schema.ts
#	packages/glyph/tests/integration/render-policy-equivalence.test.mjs
#	packages/glyph/tests/integration/render-policy-golden.test.mjs
#	packages/glyph/tests/package/policy-program-provenance.test.mjs
#	packages/glyph/tests/package/schema-authority.test.mjs
#	packages/glyph/tests/package/technique-schema.test.mjs
#	packages/glyph/tests/types/policy-program-dsl.test.ts
#	packages/glyph/tests/types/technique-schema.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant