Thesis
A web application’s identity is the set of routes, pages, data loaders, UI islands, and effects that determine how requests are answered. Frameworks encode that identity in many dialects. CWL is one consolidated inscription — the DNA of the web — so humans can audit it and machines can emit from the same model.
CWL maps 1:1 to WebIR (@chrysalis/webir 2.0.2).
There is no second private IR for one migration sprint. Convert peels and emits through that pair;
Secure may bridge surface to traffic DNA. Neither owns the grammar.
If meaning cannot be recovered honestly, emit hole reason;.
Guessing is a forged gene. Silent stubs are cancer.
Packages (pillar tree)
| Package | Version | Role |
|---|---|---|
@chrysalis/cwl | 1.0.26 | Language pin + CLI. Exports parser, print, diagnose, lsp-map, dna-seed |
@chrysalis/webir | 2.0.2 | Semantic IR: Module, Effect, dialects web-request / effect / data |
@chrysalis/runtime-cwl | 2.0.2 | In-process HTTP simulate — createCwlRuntime, compileCwlRoutes, startCwlServer |
runtime-cwl-browser | 2.0.2 | RFC-0019 island contract markers (data-cwl-island) — no hydration claim |
runtime-cwl-worker | 2.0.2 | Worker wrap of runtime fetch |
emit-runtime-cwl | 2.0.2 | WebIR → deployable Node project (routes.cwl, webir.json, Dockerfile) |
DNA seed is not a separate npm package — it is @chrysalis/cwl/dna-seed
(source staged from scripts/hub-ingest/cwl-dna-seed.mjs).
Public npm is not the default path; GitHub Packages / file: pins.
Convert junctions packages/cwl and packages/webir into this tree.
Pipeline
.author.cwl
→ parseCwlModule / printCwlModule / diagnoseCwlSource
→ (optional) cwl-ingest → WebIR Module
→ runtime-cwl simulate | emit-runtime-cwl | thin emit reverse
→ dna-seed → draft app-dna-v1 (+ bridge envelope)
→ Secure Helix: strip → promote/sign → compare ⊆ DNA → enforce
Canonical sources live under scripts/hub-ingest/ and sync into
packages/cwl/lib/ via sync:cwl-package-lib.
Editor grammar (TextMate only): editors/vscode/syntaxes/cwl.tmLanguage.json
— gate test:cwl-grammar → CWL_GRAMMAR_OK.
There is no PEG/Ohm grammar; the parser is hand-written JavaScript.
Parser and AST
Primary files: cwl-parser.mjs, cwl-print.mjs,
cwl-diagnose.mjs (schemaVersion 6),
cwl-fmt.mjs, cwl-ingest.mjs, cwl-control-lower.mjs.
parseCwlModule returns approximately:
{
moduleName, moduleLine, file,
routes[], moduleUses[], moduleAuthUses[],
imports[], importLines[], components[]
}
Each route carries method, path, pathParams, name,
surfaceKind (api|page), effects,
handler param arrays (path/query/body/multipart), responseStatus,
streamKind, loadBody, earlyGuards, foreachBindings,
attachmentHoles, and body.
Body kinds: object | literal | html | ui | hole.
RFC-0021 projectable conditions lower to WebIR; opaque g_* residuals are
skipped on ingest (no invented verify) and diagnosed as opaque-residual (info).
if_guard ::= "if" cond_expr "{" (status | return)* "}"
cond_expr ::= or_expr | opaque_ident
or_expr ::= and_expr ("||" and_expr)*
and_expr ::= unary_expr ("&&" unary_expr)*
unary_expr ::= "!" IDENT | cmp_expr
cmp_expr ::= IDENT ("==" | "!=") literal
opaque_ident ::= IDENT (* g_* residuals *)
foreach_bind ::= "foreach" IDENT "as" [IDENT "=>"] IDENT "{" (return)* "}"
Named surfaces
| Surface | Syntax | RFCs | Status |
|---|---|---|---|
| API | @route + handler | 0001–0008 | Shipped · golds 01–08 |
| Pages | @page + return html | 0010, 0011, 0014 | Shipped |
| Data | load { … } | 0013 | Deepening · gold 27 runtime-ok |
| UI | return ui, @component, islands | 0017–0019, 0024, 0028 | Language shipped; hydration non-goal |
| Effects | effects:, use auth|json|urlencoded | 0007, 0001, 0020 | Presets + middleware golds 22/30 |
| Control | if / foreach | 0021 | Accepted · golds 19, 23 |
| Modules | module, import | 0009 | Shipped · gold 12 |
| DNA bridge | surface ↔ app-dna-v1 | 0022, 0023 | Contract · golds 24, 34 |
| Transport | multipart · SSE | 0026, 0027 | Accepted · golds 31, 32 |
Not CWL surfaces: Chimera (migration shell), emit backends (targets), databases/queues/SDKs, or Helix traffic DNA itself.
Minimal syntax
module api;
use json;
use urlencoded;
@route GET "/items/:id"
handler item_show {
effects: none;
param id;
return { ok: true, id: id };
}
@page GET "/"
page home {
effects: none;
return html "<h1>Welcome</h1>";
}
@route POST "/legacy"
handler legacy_post {
effects: io, db;
hole legacy:invoice_create "delegate to PHP stack";
}
Language golds 01–35
Under fixtures/language-gold/. Gate: npm run test:language.
| Id | Proves |
|---|---|
| 01–08 | Literals, path/query/header/cookie/body, status, auth effects, content-type |
| 09–16 | Pages, load, holes, multi-file, middleware, headers, HTML interp, layout |
| 17–19 | UI v0/v1, early-exit / foreach |
| 20–23 | Probes, form-action hole, effects middleware, nested control |
| 24 / 34 | DNA bridge + deploy profiles; surfaces deepen (SSE/multipart/HEAD) |
| 25–28 | Island kinds, nested literals, load redirect/error/cookie, Set-Cookie header |
| 29–33 | Transport holes, executable effects, multipart, SSE, named island contracts |
| 35 | Urlencoded form POST + body (tip 1.0.26 · runtime-ok) |
RFC index (accepted)
Each RFC cites cross-language evidence and must lower to WebIR without a second IR.
Canonical list: docs/language/CWL-RFC.md.
| RFC | Title |
|---|---|
| 0001 | Module use json / use urlencoded |
| 0002 | Path parameters (:id templates) |
| 0003 | Query parameters |
| 0004 | Headers and cookies |
| 0005 | JSON request body fields |
| 0006 | Response status |
| 0007 | Auth presets and effects |
| 0008 | Response content-type |
| 0009 | Multi-file modules (import) |
| 0010 | Full-stack page surface |
| 0011 | Layout imports + page params |
| 0012 | Full-stack component holes (SvelteKit) |
| 0013 | Page load / SSR data (v2: redirect/error/cookie) |
| 0014 | HTML interpolation in @page |
| 0015 | Production readiness probes |
| 0016 | Form action probe + hole catalog |
| 0017–0019 | Native UI v0/v1, components, client islands |
| 0020 | Effects middleware chains |
| 0021 | Early-exit cond expressions + foreach |
| 0022 | CWL ↔ app-dna-v1 bridge (contract only) |
| 0023 | Deploy / DNA profiles (cwl-deploy-profile-v1) |
| 0024 | Island kinds (Wasm / vendor / opaque) |
| 0025 | Nested structured object/array literals |
| 0026 | Multipart field/file part bindings |
| 0027 | SSE single-shot stream sse; |
| 0028 | Named client islands + form event contracts |
DNA seed (RFC-0022 / 0023)
APIs: cwlSurfaceToDraftDna, seedDraftDnaFromCwlPath,
dnaBridgeContractEqual, pathTemplateShapeEqual,
responseKeyFingerprint, loadDeployProfile.
Gate: test:cwl-dna-bridge → CWL_DNA_BRIDGE_OK.
Identity key: `${host} ${METHOD} ${path_template}`. Per-route seed fields:
| Field | Rule |
|---|---|
host | Profile / opts else "default"; unknown key in hosts{} throws |
method | Uppercased |
path_template | Authored path (named params kept) |
content_class | html if html/ui/page; json if object; other if SSE/opaque |
status_classes | If status set → [floor(N/100)*100] else [] |
response_key_fingerprint | JSON only — sorted key paths depth ≤ 2; else null |
request_key_fingerprint | Sorted body + multipart field/file names |
query_key_fingerprint | Sorted query binding names |
Bridge envelope only (stripped before certified DNA): cwl_surface,
cwl_effects, optional cwl_stream / multipart annotations.
CWL holes are not copied into DNA holes[].
Path shape: :id ≡ :userId via pathTemplateShapeEqual.
Deploy profile schema: cwl-deploy-profile-v1 (external artifact).
Diagnose codes
| Code | Sev | Meaning |
|---|---|---|
parse | error | Parser threw |
module-name | warn | Missing module |
duplicate-route | warn | Same method+path twice |
uncatalogued-hole | warn | Hole not in catalog |
catalogued-hole | info | Honest catalogued hole |
surface-mismatch | warn | @page vs @route body kind mismatch |
param-unused | warn | Declared param unused in HTML |
opaque-residual | info | Authored g_* skipped on ingest |
no-routes | info | Empty module |
ok = no errors; warns do not fail check.
Catalog includes origin peels (hub-svelte:*, hub-next:*, …) and
kept tip hole unsupported:websocket.
Tip 1.0.26
Genome deepen (2026-08-21): ingest preserves authored HTML shell beside
load { redirect|error } (gold 27); gold 35-form-urlencoded
for use urlencoded; + body bindings.
Prior: emit reverse 1.0.25 · DNA bridge 1.0.24 · UI islands 1.0.23.
WebSocket duplex remains unsupported:websocket.
CLI and gates
cwl parse | print | fmt | diagnose | check | emit-check | dna-seed
| Command | Token |
|---|---|
test:language | Full language chain |
test:cwl-dna-bridge | CWL_DNA_BRIDGE_OK |
test:cwl-hole-catalog | CWL_HOLE_CATALOG_OK |
test:cwl-grammar | CWL_GRAMMAR_OK |
smoke:ut-spine | UT_SPINE_OK |
smoke:ut-evidence | UT_EVIDENCE_OK |
npm run build:webir
CWL_REQUIRE_WEBIR=1 npm run test:language
npm run smoke:cwl-ingest-matrix
npm run smoke:cwl-runtime-matrix
npm run smoke:cwl-emit
UT ↔ Helix spine (CWL owns the gate)
scripts/smoke-ut-spine.mjs (G10125). Convert does not own this spine.
Invariant: CWL owns surface contract; Helix disposes DNA; Convert does not own this spine.
- Assert gold
24-dna-bridge/routes.cwl - Validate optional
deploy-profile.json - Always run
gate-cwl-dna-bridge.mjs - If sibling Secure present: seed → strip → promote+sign → compare ⊆ DNA → score allow/deny
- Write
reports/ut-spine/ut-spine.json; printUT_SPINE_OK
CWL gold (24-dna-bridge)
→ RFC-0022 contract gate
→ Helix seed → strip → promote(+HMAC)
→ compare: CWL surface ⊆ certified DNA
→ enforce allow/deny
More: Traffic decides · Helix · WebIR.
Non-goals
- Origin PL dialects as CWL grammar (Convert peels only).
- Nest / LiveView / Flutter / middleware-onion façades.
- Inventing mail/SQL/rate/EventSource/upload/WS runtimes.
- UI island hydration / client JS execution in
runtime-cwl. - Firewall policy (Secure); customer migrations (Convert).
- WebSocket duplex as a finished gene (kept hole).
Edit language only in chrysalis-cwl; Convert syncs junctions.
Next: WebIR ·
Convert ·
Helix ·
System overview