tjs-lang 0.8.7 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CLAUDE.md +19 -5
  2. package/dist/experiments/ambient/probe.d.ts +52 -0
  3. package/dist/index.js +110 -162
  4. package/dist/index.js.map +4 -4
  5. package/dist/scripts/build-editors.d.ts +19 -0
  6. package/dist/src/css/dimensions.d.ts +23 -0
  7. package/dist/src/css/index.d.ts +85 -0
  8. package/dist/src/css/predicates.d.ts +38 -0
  9. package/dist/src/css/shorthands.d.ts +31 -0
  10. package/dist/src/css/style.d.ts +48 -0
  11. package/dist/src/lang/emitters/js-wasm.d.ts +2 -0
  12. package/dist/src/lang/emitters/js.d.ts +9 -1
  13. package/dist/src/lang/index.d.ts +2 -2
  14. package/dist/src/lang/parser-transforms.d.ts +9 -5
  15. package/dist/src/lang/parser-types.d.ts +7 -0
  16. package/dist/src/lang/parser.d.ts +2 -0
  17. package/dist/src/lang/predicate.d.ts +60 -0
  18. package/dist/src/lang/transpiler.d.ts +3 -2
  19. package/dist/src/lang/types.d.ts +16 -0
  20. package/dist/src/schema/index.d.ts +12 -0
  21. package/dist/tjs-browser-from-ts.js +20 -20
  22. package/dist/tjs-browser-from-ts.js.map +3 -3
  23. package/dist/tjs-browser.js +108 -94
  24. package/dist/tjs-browser.js.map +4 -4
  25. package/dist/tjs-css.js +193 -0
  26. package/dist/tjs-css.js.map +7 -0
  27. package/dist/tjs-eval.js +43 -42
  28. package/dist/tjs-eval.js.map +4 -4
  29. package/dist/tjs-from-ts.js +13 -13
  30. package/dist/tjs-from-ts.js.map +2 -2
  31. package/dist/tjs-lang.js +106 -92
  32. package/dist/tjs-lang.js.map +4 -4
  33. package/dist/tjs-runtime.js +10 -0
  34. package/dist/tjs-runtime.js.map +7 -0
  35. package/dist/tjs-schema.js +6 -0
  36. package/dist/tjs-schema.js.map +7 -0
  37. package/dist/tjs-vm.js +55 -54
  38. package/dist/tjs-vm.js.map +4 -4
  39. package/docs/ambient-contracts.md +261 -0
  40. package/docs/type-system-north-star.md +100 -0
  41. package/editors/ace/ajs-mode.js +214 -233
  42. package/editors/codemirror/ajs-language.js +1570 -233
  43. package/editors/editors-build.test.ts +29 -0
  44. package/editors/monaco/ajs-monarch.js +239 -195
  45. package/llms.txt +4 -0
  46. package/package.json +35 -4
  47. package/src/css/css.test.ts +122 -0
  48. package/src/css/dimensions.test.ts +112 -0
  49. package/src/css/dimensions.ts +146 -0
  50. package/src/css/index.ts +243 -0
  51. package/src/css/perf.bench.test.ts +109 -0
  52. package/src/css/predicates.ts +232 -0
  53. package/src/css/property-aware.test.ts +84 -0
  54. package/src/css/shorthands.test.ts +90 -0
  55. package/src/css/shorthands.ts +113 -0
  56. package/src/css/style.test.ts +125 -0
  57. package/src/css/style.ts +134 -0
  58. package/src/index-tsfree.test.ts +58 -0
  59. package/src/lang/emit-verified-predicate.test.ts +95 -0
  60. package/src/lang/emitters/dts.test.ts +31 -0
  61. package/src/lang/emitters/dts.ts +23 -4
  62. package/src/lang/emitters/js-wasm.ts +8 -4
  63. package/src/lang/emitters/js.ts +58 -1
  64. package/src/lang/from-ts.test.ts +1 -1
  65. package/src/lang/generic-verified-predicate.test.ts +39 -0
  66. package/src/lang/index.ts +14 -5
  67. package/src/lang/parser-transforms.ts +113 -15
  68. package/src/lang/parser-types.ts +7 -0
  69. package/src/lang/parser.ts +18 -3
  70. package/src/lang/predicate-evaluator.test.ts +49 -0
  71. package/src/lang/predicate-report.test.ts +78 -0
  72. package/src/lang/predicate.ts +268 -0
  73. package/src/lang/redos-lint.test.ts +81 -0
  74. package/src/lang/transpiler.ts +13 -0
  75. package/src/lang/type-verified-predicate.test.ts +83 -0
  76. package/src/lang/types.ts +17 -0
  77. package/src/lang/typescript-syntax.test.ts +2 -1
  78. package/src/lang/wasm-fallback-warning.test.ts +50 -0
  79. package/src/lang/wasm-intdiv-lint.test.ts +45 -0
  80. package/src/lang/wasm-ready.test.ts +94 -0
  81. package/src/lang/wasm-simd-ops.test.ts +84 -0
  82. package/src/lang/wasm.ts +61 -2
  83. package/src/schema/index.ts +62 -0
  84. package/src/schema/schema.test.ts +66 -0
  85. package/src/use-cases/bootstrap.test.ts +14 -4
@@ -0,0 +1,261 @@
1
+ # Ambient Contracts — probe reality, derive verified predicate contracts
2
+
3
+ > **Status: design note / idea (2026-07-03).** Not built. Captures the direction
4
+ > and a concrete first spike (CSS + DOM). Related: the predicate engine
5
+ > (`src/lang/predicate.ts`), the `tjs-lang/css` library (`src/css/`), the
6
+ > autocomplete introspection bridge (`demo/src/introspection-bridge.ts`,
7
+ > `editors/introspect-value.ts`), and the VM capability model (`src/vm/`).
8
+
9
+ ## The itch
10
+
11
+ Static types are pessimistic about ambient runtime environments (the DOM, Node
12
+ globals, `window`, host objects) in ways that create ceremony without safety.
13
+ The canonical example:
14
+
15
+ ```ts
16
+ element.addEventListener('input', (e) => {
17
+ const value = e.target.value // ❌ TS: Property 'value' does not exist on 'EventTarget'
18
+ })
19
+ ```
20
+
21
+ `e.target` is typed `EventTarget | null`, and `EventTarget` has no `.value`, so
22
+ TypeScript forces `(e.target as HTMLInputElement).value` or a `instanceof` dance.
23
+ But the honest situation is: **at runtime the value is either there or it
24
+ isn't, and if it isn't you find out immediately.** The cast doesn't make it
25
+ safer — it just silences a compiler that can't see the real object. A runtime
26
+ predicate is the truthful tool here:
27
+
28
+ ```tjs
29
+ // pure, total, verifiable — reads props, no cast, no lie
30
+ function hasValueTarget(e) {
31
+ return e != null && e.target != null && typeof e.target.value === 'string'
32
+ }
33
+ // usage: hasValueTarget(e) ? e.target.value : undefined — no ceremony
34
+ ```
35
+
36
+ That predicate is exactly what the verifier accepts (member access is pure;
37
+ `typeof` → `TypeOf`), compiles to native, and — crucially — is **serializable**.
38
+ So the thought: instead of hand-writing these, could tooling **probe a real
39
+ ambient environment and derive the predicate contracts automatically?**
40
+
41
+ ## The payoff: author against an environment you're not running in
42
+
43
+ The headline use (user, 2026-07-03): **type-checking and autocompletion against a
44
+ non-ambient runtime — editing in a Node-based toolchain while _targeting_ the
45
+ browser** (or the reverse, or Electron, or a specific Bun). Normally you have no
46
+ target environment to introspect, so you fall back to a hand-maintained
47
+ `.d.ts` (`lib.dom`) that drifts from what the engine actually does.
48
+
49
+ Because a probed contract is a **serializable predicate**, it decouples _capture_
50
+ from _consumption_ in time and space: probe the real target once (locally or in
51
+ CI), bottle the contract, and a Node-side editor/typechecker consumes it with **no
52
+ target present**. It beats a `.d.ts` on three axes:
53
+
54
+ - **Grounded in reality, not a drifting spec** — it's what the target _actually_
55
+ exposes (we watched happy-dom diverge 678 vs 1 own-keys from real Chrome; a
56
+ captured contract is the real number).
57
+ - **Target-specific and versioned** — capture from the _actual_ target (this
58
+ browser, this Electron, this Bun), selectable/pinned, not a lowest-common-
59
+ denominator lib.
60
+ - **The same artifact validates at runtime** — a `.d.ts` evaporates; a predicate
61
+ contract can stay as a boundary guard, and its enumerable leaves feed
62
+ `suggest()` completions with real values.
63
+
64
+ Why it's plausible _here_ and not a research project: the contract format already
65
+ exists (`$predicate` / `tjs-lang/schema`), the autocomplete provider already has
66
+ an introspection hook (`getMembers` — point it at a captured contract instead of a
67
+ live object), and this is exactly the metadata the pinned "argument-type-driven
68
+ completion" item wants (e.g. `createElement`'s tag→type return shapes), sourced
69
+ from reality instead of hand-authored. See [[introspection-autocomplete]].
70
+
71
+ ## Contract vs. shim — the distinction that decides feasibility
72
+
73
+ "A predicate that stands in for the DOM" can mean two very different things:
74
+
75
+ 1. **A contract / validator** — "does this value behave enough like an
76
+ `HTMLElement` / an input event / a `CSSStyleDeclaration`?" Predicates are
77
+ _pure validators_, so this is squarely in scope: shape, property presence,
78
+ method arity, pure invariants.
79
+ 2. **A behavioral substitute** — something that, when code calls
80
+ `document.createElement('div')`, actually _does_ it. Predicates **cannot** be
81
+ this (they're pure, stateless, effect-free). That's a shim — happy-dom/jsdom
82
+ territory.
83
+
84
+ The valuable, on-thesis move is **#1 as the bridge to #2**: probe the real
85
+ environment, emit a serializable predicate _contract_, and use it to **certify a
86
+ stand-in** in whatever setting. The predicate isn't the DOM — it's the portable,
87
+ verifiable spec a substitute must satisfy, auto-derived from reality and from the
88
+ surface a given program actually touches.
89
+
90
+ ## Why it fits this project
91
+
92
+ - **The introspection probe already exists.** The autocomplete work runs code in
93
+ a real iframe and reads live scope (`introspect-value.ts`,
94
+ `introspection-bridge.ts`); Claude-in-Chrome can drive a _real_ browser. That's
95
+ the "kick the tires" mechanism.
96
+ - **Predicates are the serializable contract form** — verified, ReDoS-safe,
97
+ compile-to-native, travel as data (the `$predicate` keyword already embeds them
98
+ in JSON Schema, now evaluated by `tosijs-schema` via `tjs-lang/schema`).
99
+ - **The VM already models this as capabilities.** It injects `fetch`/`store`/
100
+ `llm`; "DOM-as-capability, predicate-validated at the boundary" is the same
101
+ pattern. A headless setting swaps the stub; the predicate catches divergence.
102
+ - **"Types are examples" already** — deriving a contract from _observed_ real
103
+ values is the same philosophy as `inferTypeFromValue`, sourced from a live probe
104
+ instead of a literal.
105
+
106
+ ## The honest constraints
107
+
108
+ - **Most of the DOM's contract is _behavioral_** (layout, events, mutation),
109
+ which a pure predicate can't capture — only shape + pure invariants. So:
110
+ - **Scope to the used surface.** Trace which ambient APIs the code actually
111
+ touches (in the real iframe) and derive a contract for _that slice_, not all
112
+ of `lib.dom.d.ts`.
113
+ - **Behavioral conformance is a separate, impure harness** — run the same probe
114
+ battery against the real env and a candidate stub, diff the outputs. The
115
+ predicate is the pure shape-gate; the harness is the behavior-gate.
116
+ - **Snapshots drift.** Browsers evolve; a captured contract is a dated snapshot.
117
+ Fine — it's re-derivable, and versioning it is a feature (conformance over
118
+ time).
119
+ - **Non-determinism / side effects** must stay outside predicates (the `effects`
120
+ tag already enforces this): a predicate validates the _data_ crossing the
121
+ boundary, never performs the effect.
122
+
123
+ ## Shape of the tooling (sketch)
124
+
125
+ 1. **Probe** — in a real environment (browser via Claude-in-Chrome, or the
126
+ introspection iframe), walk an ambient object / a program's used surface and
127
+ record: property names + `typeof`, method names + `.length` (arity), and a few
128
+ sampled input→output pairs for pure-ish accessors.
129
+ 2. **Derive** — turn the probe record into a predicate cluster (shape checks +
130
+ `typeof` guards + membership sets), run it through `verifyPredicate` (so the
131
+ emitted contract is itself certified safe), and `suggest()`-mine it for the
132
+ enumerable leaves.
133
+ 3. **Emit** — a `$predicate` schema (naive validators see structure, aware ones
134
+ run the contract) + optionally a `.d.ts`-ish view for editors.
135
+ 4. **Conform** — a harness that validates a stand-in (happy-dom, a hand stub, a
136
+ VM capability) against the contract, failing loud where it diverges. This is
137
+ what would have unblocked the Phase 5 real-`tosijs`-theme measurement (blocked
138
+ because `theme.ts` needs `HTMLElement` at import).
139
+
140
+ ## First spike — CSS + DOM (the convergence point)
141
+
142
+ `element.style` (`CSSStyleDeclaration`) is the ideal first target: it's an
143
+ ambient, stateful host object, and we _already have a full CSS value grammar as
144
+ predicates_ (`tjs-lang/css`). So the loop is concrete and small:
145
+
146
+ - **`event.target` demo** — show the `hasValueTarget` predicate compiles, is
147
+ verified pure, is total, and validates a real input event while rejecting a
148
+ click on a `<div>`. The predicate TypeScript won't let you write cleanly is a
149
+ perfectly ordinary verified predicate. (This is the "asinine" antidote, and
150
+ needs no browser — synthetic event-shaped objects suffice for the unit; a real
151
+ browser confirms on live events.)
152
+ - **`CSSStyleDeclaration` probe** — introspect real `element.style` in a browser,
153
+ derive a shape contract, and diff a happy-dom `style` against it: where does the
154
+ stub lie? Leaf _values_ ride the existing CSS predicates (`isColorValue`,
155
+ `isDimension`, …), so this exercises probe → contract → conformance end-to-end
156
+ while reusing what phases 1–5 built.
157
+
158
+ ## Ask the environment directly — `CSS.supports` (tosijs-ui port finding)
159
+
160
+ The tosijs 2.0 port arrived at the same idea from the other side (see
161
+ `../tosijs/TJS-PORT-DX.md`): the strongest form of "probe reality" for CSS is to
162
+ **ask the browser's own validator** — `CSS.supports(prop, value)` is exact, always
163
+ current with the engine's real support (vendor prefixes, new features, the full
164
+ `calc()`/`var()` grammar), and zero-maintenance. It closes the exact gap the
165
+ hand-authored grammar has: `isCssProperty('align-kontent')` returns `true`
166
+ (identifier-shaped), but `CSS.supports('align-kontent', …)` in a real browser
167
+ returns `false`. Property names for autocomplete come from the same place (a
168
+ throwaway element's `.style` keys / `CSSStyleDeclaration.prototype`).
169
+
170
+ This sharpens the contract-vs-fallback split by **when** validation runs:
171
+
172
+ - **Runtime, in a browser** → `CSS.supports` is ground truth (exact, free). A
173
+ `CSS.supports`-based check is predicate-_safe to run_ but references the ambient
174
+ `CSS` object, so it can't be statically _verified_ (it's effectful/ambient).
175
+ - **Transpile-time / SSR / Node** → no `CSS`, so the hand-authored grammar
176
+ predicates (`isColor`/`isLength`/`isStyleValueFor`) are the portable fallback.
177
+
178
+ So the ideal CSS-value check prefers `CSS.supports` when `typeof CSS !== 'undefined'`
179
+ and falls back to the grammar predicate otherwise — which is exactly the
180
+ probe-reality / portable-fallback pairing this whole note is about, one leaf down.
181
+ **Caveat (verified, and it's the same "the stand-in lies" finding): happy-dom
182
+ stubs `CSS.supports` to return `true` for everything** (even `width: banana`) — so
183
+ tests can't rely on it; ground truth needs a real browser or a real CSS engine.
184
+
185
+ ## Findings from the first spike (2026-07-03)
186
+
187
+ Built in `experiments/ambient/` (`probe.ts` + two demo tests, 7 green):
188
+
189
+ - **The `event.target` predicate is ordinary.** `hasValueTarget(e)` (reads
190
+ `e.target.value`, the thing TS rejects) verifies pure/safe, compiles native, and
191
+ is total — no throw on a missing target. The cast TS demands buys nothing a
192
+ predicate doesn't. (`event-target.demo.test.ts`.)
193
+ - **Host objects hide their surface on the prototype.** Probed a real
194
+ `CSSStyleDeclaration` via happy-dom: `Object.keys(style)` is nearly empty, yet
195
+ `typeof style.color === 'string'` and `typeof style.setProperty === 'function'`.
196
+ So a naive own-enumerable probe derives an empty contract — **the probe must
197
+ walk the prototype chain** (`getOwnPropertyNames` per proto) or check a scoped
198
+ key list. `probeShape` now does both.
199
+ - **Scope to the used surface — the full proto chain is huge.** An `HTMLElement`'s
200
+ chain is hundreds of props; a whole-shape contract would be enormous and
201
+ over-specified. A `keys: [...]` scope (the surface a program actually touches,
202
+ traced from the real env) is the right input. Demonstrated: a 4-key contract for
203
+ `style` verifies safe, accepts a real happy-dom `style` and a conforming hand
204
+ stub, rejects a plain object missing the methods.
205
+ - **Reading accessors during a probe can have side effects** (`offsetWidth` forces
206
+ layout). A scoped key list bounds this; a stricter probe would read property
207
+ _descriptors_ rather than the values. Getters that throw are skipped.
208
+ - **CSS convergence holds.** Leaf values off the real object ride the existing
209
+ `tjs-lang/css` predicates (`isColorValue(style.color)`), so shape-gate (contract)
210
+ + value-grammar (css) compose — the recursive style structure from CSS phase 4
211
+ is the same shape a DOM-derived contract would target.
212
+
213
+ Net: the probe→derive→verify→conform loop works end-to-end on a real host object;
214
+ the open work is sourcing the used-surface scope (trace) and the behavioral half.
215
+
216
+ ### Real-browser findings (via haltija `/eval`, live Chrome — 2026-07-03)
217
+
218
+ Confirmed against a real browser (not happy-dom), which sharpened three points:
219
+
220
+ - **Builtins don't honor `new`.** `new HTMLDivElement()` → `TypeError: Failed to
221
+ construct 'HTMLDivElement': Illegal constructor`. You **don't need `new`** — the
222
+ shape is on the constructor's `.prototype` chain
223
+ (`HTMLDivElement → HTMLElement → Element → Node → EventTarget`), readable
224
+ statically, no instance, no side effects. Cleaner than instantiating.
225
+ - **The manager vends the type.** `document.createElement('div').constructor ===
226
+ HTMLDivElement` (and `'a' → HTMLAnchorElement`, `'input' → HTMLInputElement`).
227
+ So a factory's output gives you _both_ a live example (real default values — the
228
+ "types are examples" leaf) _and_ the type identity (`.constructor` → name +
229
+ `.constructor.prototype` → the surface). Two complementary probe sources that
230
+ cross-check.
231
+ - **Structural contracts are realm-portable; `instanceof` is not — proved.** An
232
+ `<iframe>`'s input: `instanceof HTMLInputElement` (top realm) = **false**, but
233
+ the structural predicate (`typeof x.value === 'string' && x.tagName === 'INPUT'`)
234
+ = **true**. This is the whole argument in one line: nominal DOM types are
235
+ realm-bound (the stand-in problem _is_ a realm problem), a derived structural
236
+ predicate crosses realms. (happy-dom couldn't show this — it shares one
237
+ `HTMLInputElement` across `Window`s; the real browser does.)
238
+ - **Bonus — the stand-in already diverges from reality.** Real Chrome's `style`
239
+ has **678 own keys** (CSS properties as own props); happy-dom's had ~1 (on the
240
+ proto). Same object, wildly different probe result — exactly the conformance gap
241
+ the harness exists to catch, and proof that you must probe _reality_, not a
242
+ stand-in, to derive the contract.
243
+
244
+ ### Generalizes beyond the DOM — Node/Bun internals
245
+
246
+ The probe is environment-agnostic: `process`, `Buffer`, `node:fs`, `Bun.*`,
247
+ `globalThis` are ambient objects with the same shape. Derive a used-surface
248
+ contract from the _real_ runtime and certify a stand-in — e.g. "does my `process`
249
+ usage survive in the browser polyfill?" or "does Bun's `process` satisfy the
250
+ contract my code derived from Node's?". It's the realm/stand-in problem one level
251
+ up: cross-_runtime_ portability instead of cross-_frame_. Same tool.
252
+
253
+ ## Open questions
254
+
255
+ - Where's the line between "derive automatically" and "author, then verify
256
+ against reality"? The probe likely _proposes_ a contract a human trims.
257
+ - Method behavior: can we derive useful pure invariants for methods
258
+ (idempotence, `getComputedStyle(x)` shape) or only signatures?
259
+ - Editor story: a derived `$predicate` contract as the source of both validation
260
+ _and_ autocomplete (the introspection-autocomplete north star) for host objects
261
+ that ship no good `.d.ts`.
@@ -0,0 +1,100 @@
1
+ # North star: JSON-Schema + predicates as the single source of truth for types
2
+
3
+ > **Status: strategic direction (2026-07-06, user-set). Possibly post-1.0. Use as
4
+ > a decision lens NOW.** Related: `$predicate` keyword
5
+ > (`src/lang/predicate-schema.ts`), `createPredicateEvaluator`, the `tjs-lang/css`
6
+ > library, `docs/ambient-contracts.md`, PRINCIPLES.md.
7
+
8
+ ## The principle
9
+
10
+ **A TJS type is, canonically, a JSON-Schema node — optionally carrying a
11
+ `$predicate` (a verified-pure predicate cluster) for the computational part that
12
+ plain JSON-Schema can't express.** Everything else — examples-as-types (`x: 0`),
13
+ TS-derived types, `Type`/`Generic` declarations, `TypeDescriptor` — is **surface
14
+ syntax or an internal projection** of that one canonical form, not a competing
15
+ source of truth.
16
+
17
+ - **Structure** (shape, required keys, enums, ranges, nesting) → JSON-Schema
18
+ keywords, which any validator understands.
19
+ - **Computation** (open value grammars, cross-field invariants, recursive
20
+ grammars — the things TS/JSON-Schema cave to `string`/`any` on) → `$predicate`,
21
+ which a predicate-aware validator runs.
22
+
23
+ Together they express the full range of what TJS types mean, in a **standard,
24
+ serializable, inspectable, cross-language** form.
25
+
26
+ ## The decision lens
27
+
28
+ For any architecture or implementation choice, ask:
29
+
30
+ > **Does this move types toward — or away from — being fully expressible as
31
+ > JSON-Schema + `$predicate`?**
32
+
33
+ Concretely, *toward* looks like:
34
+
35
+ - New type capabilities are added by (a) a JSON-Schema keyword, or (b) a
36
+ predicate — never a bespoke `TypeDescriptor` field that can't round-trip to
37
+ JSON-Schema + `$predicate`.
38
+ - `TypeDescriptor` is treated as a **lossless projection** of the canonical form
39
+ (a convenient in-memory shape), not a superset. If something can live in
40
+ `TypeDescriptor` but not in JSON-Schema + `$predicate`, that's a smell.
41
+ - `.d.ts` emission, runtime validation, inference, and autocomplete all **derive
42
+ from** the canonical form rather than from a parallel representation.
43
+ - The predicate subset stays **small and portable** (see below) so `$predicate`
44
+ can run anywhere — expanding it is a cost, not a free win.
45
+
46
+ *Away* looks like: a TJS-only internal type IR that accretes features JSON-Schema
47
+ + `$predicate` can't carry; validation logic that only the JS runtime can do;
48
+ predicate features that need a full JS engine.
49
+
50
+ ## Why this is the right endgame
51
+
52
+ - **Types as data.** JSON-Schema + `$predicate` is just JSON — it travels, it's
53
+ inspectable, it's diffable, it survives a network hop or a file. Types stop
54
+ being a compile-time-only artifact.
55
+ - **Cross-language by construction.** Any language with a JSON-Schema validator
56
+ gets TJS *structure* for free; add a small predicate VM (below) and it gets the
57
+ *computational* half too. TJS types become a contract multiple runtimes share.
58
+ - **Standard, not bespoke.** We ride the JSON-Schema ecosystem (tooling, docs,
59
+ editors) instead of reinventing it, and add exactly the one thing it lacks.
60
+ - **It's already real.** `tjs-lang/css` is a working JSON-Schema + `$predicate`
61
+ artifact; `tosijs-schema` (via `createPredicateEvaluator`) already runs it;
62
+ `cssStyleSchema()` produces exactly this shape. The endgame exists in miniature.
63
+
64
+ ## The priority that unlocks it: a small, portable predicate VM
65
+
66
+ For "types across language boundaries" to be real, `$predicate` must **run
67
+ anywhere**, not just in JS. The priority is a **reference implementation of a tiny,
68
+ portable VM that safely evaluates the predicate subset** — ideally smaller than a
69
+ JS runtime, implementable in a few hundred lines in any language.
70
+
71
+ It's tractable precisely because the predicate subset is deliberately minimal
72
+ (the verifier enforces it): pure, synchronous, **no loops** (recursion + array
73
+ methods only), fuel-bounded, no IO, a whitelist of pure operations (member access,
74
+ comparisons, `&&`/`||`/ternary, `typeof`, a fixed set of string/array/Math/regex
75
+ methods). That's a small tree-walking interpreter — no closures-over-mutable-state,
76
+ no async, no allocation surprises, no host access.
77
+
78
+ **Key architectural implication — carry the AST, not (only) the source.** Today
79
+ `$predicate` is predicate *source* (JS/AJS text). A portable VM in Rust/Go/Python
80
+ shouldn't have to embed a JS parser. So the canonical portable form of a predicate
81
+ should be its **serialized AST** (JSON) — which is the original AJS thesis ("code
82
+ travels as data") applied here. Source stays the authoring form; the serialized
83
+ AST is the wire/exec form the small VM walks. A `$predicate` could carry either,
84
+ with the AST as the portable default.
85
+
86
+ ## Open questions
87
+
88
+ - **`TypeDescriptor`'s fate:** keep it as a cached projection of JSON-Schema +
89
+ `$predicate`, or eventually retire it? At minimum, guard that it can't express
90
+ what the canonical form can't.
91
+ - **The predicate ISA:** pin down the exact minimal node set + operation whitelist
92
+ the reference VM must support (a spec), and a **conformance suite** every port
93
+ runs — so "TJS types" means the same thing in every language.
94
+ - **AST format:** settle the serialized predicate AST schema (it should itself be
95
+ describable — turtles: the AST format as a JSON-Schema).
96
+ - **Fuel across languages:** the fuel model must be spec'd (per-node cost) so a
97
+ runaway predicate is bounded identically everywhere.
98
+ - **Structure/predicate split:** conventions for what belongs in JSON-Schema
99
+ keywords vs `$predicate` (prefer standard keywords; reach for `$predicate` only
100
+ for the genuinely computational).