Dieselapps_Guide Pub   Share

Diesel DSL & Reactor API — Working Specification

A practical reference for the metals.dieselapps.com reactor: the diesel DSL, domain modelling & inventories, the fiddle run/validate endpoints, the persistence (save) routes, and read-back verification. Confirmed against the live reactor; domain/inventory details cross-checked against the public samples on specs.dieselapps.com (see §10).

Auth. All calls below authenticate as the scoped user@example.com account via an HTTP Basic header: Authorization: Basic <token>. (The browser UI relies on a session cookie; curl must send the header.) The account needs edit rights for any write/save route; read/run routes work read-only.


1. Topics, categories, and the DSL

A topic is a Markdown document. Any line beginning with $ is a live DSL section (must be at column zero — indented $ lines aren't recognized); every other line is ordinary prose the compiler ignores (and the UI syntax-colors the $ blocks). So Specs and Stories can be real documents — headings and rationale around the code.

Four categories matter:

  • Topic — a generic wiki page (category: Topic).
  • Spec — domain modelling ($class, $msg, $when). Metadata tags: spec, dsl, domain.
  • Story — actionables: object creation, message sends, tests. Metadata tags: story, dsl.
  • Sample — a holder for text/json/html documents that don't belong as data objects, and for scratch/testing content. Metadata tag: sample.

Tags live in topic metadata, not in the body.

1.1 The EOL rule (critical)

The last statement of a spec/story body loses its terminating newline (it is trimmed server-side), so the final $ statement is silently dropped from the compile. Symptom: a message defined on the last line reports as "not found" when called, with no spec error surfaced. (A multi-line parenthesized $class (...) is one statement, so it's safe — the trap is a single-line final $ statement.)

Always end a body with a throwaway final line (a prose/marker line) so the last real $ statement keeps its EOL.

1.2 Classes (Spec)

$anno (inventory="diesel.db.memshared")
$class Company (@key ticker, name, deposits:Deposit*, catalysts:Catalyst*)

Fields & types

  • Fields can be untyped — bare names: $class Node (@key name, status, ip). Types are optional; when given, the primitives are String and Number (no Int/Double — use Number).
  • Identity: every class needs a unique key, or diesel assigns a random one (chaos). @key <field> marks it explicitly; failing that, a field literally named key (or name) is used. Entities may instead carry an assetRef:{key:"…"}.

@key in inline fiddle specs (gotcha): a $class’s @key is honored reliably only when its Spec is saved (registered into the domain). Supplied inline in a fiddle run (the spec field of fiddleStoryUpdated), the domain doesn’t always register, so @key can be silently ignored — upserts then mint a generated oid for assetRef.key instead of keying by @key, producing dupes that find(key="<yourkey>") misses. Workaround: save the class as a Spec first (save/<millis>/Spec, §3.1), then fiddle against it; boot-registered classes (§5.1) are unaffected. Always confirm assetRef.key matches your @key, with no oid-keyed dupe rows.

Containment vs reference vs array (these are orthogonal — * = array, <> = reference):

  • f:C — one contained C, stored by value inside the parent.
  • f:C* — array of contained C.
  • f:<>C — a reference to an independently-stored C (see storage rule below).
  • f:<>C* — array of references. Avoid — unproven/unsupported.

Reference storage rule (tested): a <> field is stored as a plain string = the referenced object's key (the type is known from the field). You must assign it the key string yourself (refField:"R1"). If you hand it an object literal (refField: new C {…}), diesel does not reduce it — it embeds the whole object, which defeats the reference. So: always set <> fields to the key string.

Containment note: containment is the default (absence of <>); the * only means "many". A single owned sub-object is just f:C (no *, no <>). A contained class needs no $anno — it persists inside its parent and can't be addressed alone.

Class bodies, members, subtyping

  • Empty class: $class TestSimpleClass (no parens).
  • Curly body holds constructs only (no free text): def name(x,y){ } (functions), msg simpleMsg / msg expandedMsg => a.b (message members). Implement a class message externally with $when Class.msg => target.
  • Inheritance: $class Sub extends Base. Generics: $class Container [T] (used as field:Container*). Stereotype tag: trailing marker, e.g. $class DTProductSpec (...) <spec>.

Annotations — multiple $anno stack before a class, each carrying ≥1 prop:

  • $anno(inventory="…") — storage binding (see §5).
  • $anno(aggregate="yes") — aggregate flag.
  • $anno(ui.fieldsToShow="node,name,-category") — UI column hint; a leading - hides a field.

1.3 Values, literals, long strings

  • Value binding: $val x = <expr>; mutate a field: $send (x.field = <expr>). * $val takes NO space after $. $ val x = expr (with a space) does not bind — it parses as a bare message-send $ followed by val x = expr, so x stays Undefined and errorCount stays 0 (silent). A later (payload = x) then returns empty. Message-sends tolerate the space ($ diesel.inv.upsert(...) runs fine), which makes this sneaky: writes work while $ val-based reads silently return nothing.
  • Object literal: new Class {field:"value", other:1}; untyped map: {key:"v", x:1}.
  • Array literal: [ new Item {..}, new Item {..} ]; index with x[0].
  • Long/multiline strings: triple-quoted, no escaping, with ${var} interpolation:
$val name = """world"""
$ diesel.echo (msg = """
hello ${name}
""")

1.4 Messages & rules

  • Send: $send ns.msg (arg=val) or the $ ns.msg (arg=val) shorthand.
  • Handler / return: $when ns.msg (a,b) => (payload = <expr>) — a message returns its value through (payload = <expr>).
  • Declaration: $msg ns.msg (a,b) documents the signature (a correctly-terminated $when is callable on its own; the "need a $msg declaration" error we hit was really the EOL-drop).
  • Guards / pattern match: dispatch on argument values — $when diesel.inv.impl.upsert (inventory == "diesel.db.col", …) => … (== or is). <fallback> marks a default lower-priority handler.
  • Multi-step bodies: chain => steps — assignments, sends, then a final payload:
      $when x.y => (k = key || assetRef.key)
              => (e = entity + {"assetRef":assetRef})
              => diesel.db.col.upsert (collection=table, id=k, document=e)
              => (payload = entity)
    
  • Execution arrows (Razie, 2026-09-23): * => sequential: each step completes before the next starts. * ==> fires off an async sub-flow (fire and forget). * <=> fires off an async sub-flow and waits for its result. * <== waits for another flow to send something. * In block syntax (a rule followed by a { … } body, no markdown inside) the arrow can be left off: statements in a block run sequentially. => is still allowed inside. Example: https://specs.dieselapps.com/wiki/specs.Story:block-syntax-story * For concurrency, streams are preferred over the async arrows: diesel.stream.new / put / putAll / done / consume, with diesel.stream.onData, onDataSlice (batch) and onDone handlers. Example: https://specs.dieselapps.com/wiki/specs.Spec:streams-spec with https://specs.dieselapps.com/wiki/specs.Story:streams-story * When sharing an example or test fiddle, send the spec and story together (/diesel/fiddle/playDom?spec=… with its story) — the preferred pattern.
  • Expressions: || (coalesce), map-merge + (entity + {"a":1}), member access (assetRef.key), array index (data[0]), .asAttrs, ${var} interpolation.
  • Mock (tests): $mock a.b => (x=[…]) stubs a message's result.
  • Context helpers: ctx.echo (x=…), ctx.set (payload=…).

1.5 Data objects & inventories

Domain data objects persist through the inventory layer (diesel.inv.*), which is separate from topic writes (§3). Interface messages:

  • diesel.inv.register (inventory, classNames) — map classes to an inventory. classNames="*" maps all project classes. Prefer per-class $anno(inventory=…); use register for dynamic mapping or the * catch-all.
  • diesel.inv.connect (inventory, connection?, env?) — wire the inventory to an instance (connection=""/"default"; env=diesel.env). testConnection(...) checks it.
  • diesel.inv.upsert (inventory?, connection?, className?, entity?, key?) — three forms:
    1. untyped map + class: upsert(className="C", entity={key:"akey", x:"v"})
    2. typed by key: upsert(entity=tc21) — class inferred, no className
    3. typed by assetRef: entity carries assetRef:{key:"tc12"} instead of key
  • diesel.inv.find (className, key) → the entity (or undefined after remove).
  • diesel.inv.remove (className, key).
  • diesel.inv.query (className, query={…}, from?, size?, countOnly?) → {data:[…]}.
  • diesel.inv.listAll (className, from?, size?) → {total:Number, data:Array}.
  • diesel.inv.inspect / .debug. Direct store access: diesel.db.col.get (collection, id).

Fiddle gotcha (tested): dynamically-defined (inline) classes are not wired by $anno alone — before any upsert/find you must, in the story: diesel.inv.register(inventory="…", classNames="A,B") then diesel.inv.connect(inventory="…"). (Persisted domains wire this via the realm lifecycle: $when diesel.realm.configure => diesel.realm.setupDieselDomain => diesel.inv.register(...).)

assetRef: every stored entity gets an auto assetRef — { key (generated oid), wpath, source, class, inventory, realm, conn }. The class @key (e.g. H1) is separate from assetRef.key (a generated oid); find(key=…) matches the @key.

1.6 Tests with $expect

A diesel test is preparation → action → $expect (this is what the guardian runs). Assertions:

  • $expect (payload contains "ok") — substring.
  • $expect (payload.key is "akey") — field equality.
  • $expect (payload.total is Number) / is Array / is Json / is undefined — type/existence.
  • $expect (payload.data[0].x is "xattr") — path with index + nested field.
  • $expect (payload as string contains "tc12") — cast then match.
  • .todo expect (...) — pending/disabled assertion. // is a line comment.

Naming: prefix scratch/test classes with ClaudeTest (e.g. ClaudeTestRef), not Test — avoids colliding with common class names already in the reactor.

Canonical shape:

$send diesel.inv.register (inventory="diesel.db.memshared", classNames="C")
$send diesel.inv.connect  (inventory="diesel.db.memshared", env=diesel.env, connection="")
$expect (payload contains "ok")
$send diesel.inv.upsert (entity = new C {key:"k1", x:"v"})
$expect (payload.key is "k1")
$send diesel.inv.find (className="C", key="k1")
$expect (payload.x is "v")
end

For ad-hoc validation run these inline through fiddleStoryUpdated (§2, ephemeral, read errorCount/payload). Do not use /diesel/guard/runStory — it records guardian statistics (§4).


2. Fiddle endpoints — run & validate (ephemeral)

Send each field as its own --data-urlencode. <millis> is a fresh epoch-ms (date +%s%3N); pass the same value as clientTimeStamp. Send the full param set (below) — trimming params can 500 the endpoint.

Full params: saveMode, sketchMode, mockMode, blenderMode, draftMode, reactor, specWpath, storyWpath, runEngine, compileOnly, simMode, clientTimeStamp, spec, story, capture, needsCAMap, needsBaseCA.

2.1 Run / typecheck a STORY

POST /diesel/fiddle/fiddleStoryUpdated/<millis>

  • blenderMode=true — blend in all other reactor specs. Keep on.
  • compileOnly=true — typecheck only; false — actually run.
  • simMode=false — required to execute message handlers. With simMode=true, a message call returns Undefined (a pure expression like 1+2 still computes either way). Default it to false for real runs.
  • mockMode — no observed effect in testing.
  • saveMode=false — the story/topic isn't persisted. (Note: an inv.upsert to a live inventory still writes to that store — memshared is volatile, col/postgres are durable. Use scratch keys and volatile stores for tests.)
  • spec / story — inline bodies. specWpath / storyWpath — persisted references (empty when testing inline). An inline body wins over its wpath; an empty inline body with a wpath runs nothing.

Response JSON — the fields to read: payload (top-level result), ast (parsed statements), info.errorCount / info.failureCount / info.engineStatus / info.engineDone / info.engineId.

Reading a value back: every message returns a payload on its own, and a story's payload is the result of its last evaluated message. So a SINGLE-message read needs nothing extra — just write the bare message plus a // eol line; its result is the payload (e.g. $ diesel.inv.listAll(className="Company") then // eol returns {total, data:[…]}). You bind + set payload explicitly only when a story runs multiple messages: the last one overwrites the payload, so capture the one you want with $val out = <msg> and end with (payload = out) and nothing after it. If you $val a = … then $val b = …, the return is b regardless of a trailing (payload = a). (Use $expect assertions for multi-object checks, since they don't depend on what's returned.)

Check the engine finished before trusting the result (runEngine=true). The engine runs asynchronously: the API waits for it only for a limited time, then returns whatever the engine had done up to that point — so a slow run can come back with a partial or empty payload, missing trace entries, or zero counts that look like a clean result. (Razie, 2026-09-23.) Every response carries the engine state in info:

  • info.engineDone — true once the engine finished; anything else means the result is partial.
  • info.engineStatus — final.done when finished.
  • info.engineId — the engine's id, to read its final state later.
  • info.progress — progress counters.

If it isn't done, don't use payload — poll the engine until it is (what the fiddle page itself does, per Razie):

POST /diesel/fiddle/checkEngineStatus/<clientId> — form fields engineId=<info.engineId> and clientTimeStamp=<clientId>, where clientId is the millis value the fiddle call used (also returned as info.clientId). It returns the same shape as fiddleStoryUpdated — info (with the current engineStatus / engineDone / progress / counts), payload, res, failureCount — but no ast, since it reports on execution, not compilation. Repeat until info.engineDone is true, with a time limit. An unknown engine id returns HTTP 404 "Engine id … not found". (Verified 2026-09-23 on devdomain1claude against a finished engine.)

Reading the trace: res (in both responses) is the engine trace rendered as HTML — the same trace the /diesel/viewAst/<engineId> page shows (that page just adds the site chrome). Fine for showing a run to a person; awkward to parse, and it shortens long values with "…". For anything you check programmatically, prefer GET /diesel/viewAst/<engineId>?format=json — the same tree as data, under half the size: each node has kind (received / generated / trace / test / …), its own status (final.done) and value.class (EMsg message, EVal value); $expect checks are test nodes carrying ok/fail; the top level adds failureCount, totalCount, the specs used and the run settings. dieselTrace.root.status = final.done once finished. Neither view reliably carries the full value of a large object, so take real results from payload (bind and return what you need).

Workflow: poll checkEngineStatus until engineDone (read info + payload), then fetch the viewAst JSON once if the trace matters.

Important: this endpoint reports STORY errors only. An inline spec= body is compiled/blended, but its errors are not surfaced here — a broken spec can return errorCount: 0 while silently failing to define what the story then calls.

2.2 Validate a SPEC

POST /diesel/fiddle/fiddleSpecUpdated/<millis>

  • Returns res, ast, specChanged, ca, info (minimal — timeStamp only; no errorCount).
  • Error model: by omission from ast. A statement that fails to parse simply does not appear as an AST row. Validate by asserting every $class / $msg / $when you fed it comes back as an ast row.

3. Persistence (topic write)

Two dedicated endpoints handle ALL topic writes, for every category (Topic, Spec, Story, Sample, Admin, CompanyCard, …). Same call shape as the fiddle endpoints: POST, Content-Type: application/x-www-form-urlencoded, the document in the we field (--data-urlencode "we@file"); we must be valid JSON. Both return ok / HTTP 200. Raw content write — no fiddle validation — so validate first (§2) and mind the EOL rule on the content body.

3.1 Create a new topic

POST /api/v1/wiki/create/<wpath>

  • For a topic that does not yet exist. The URL <wpath> and the name + category in we must agree.
  • Send the full field set: category, name, realm, markup, content, by, label, tags (array), props.visibility, props.wvis, crDtm.
  • props.visibility / props.wvis set the topic's read / write visibility once, at creation. Use "Member" / "Member" for a normal editable topic.

3.2 Update an existing topic

POST /api/v1/wiki/update/<wpath>

  • For a topic that already exists. Send only the fields you're changing — the true minimum is realm, category, name, content. Every omitted field (label, markup, tags, props.visibility, props.wvis, by, crDtm) is reused from the stored topic, so a body-only edit is just those four fields. Visibility, label and tags are preserved automatically — no more downgrade trap.
  • To clear a reusable field (e.g. blank the label), send it explicitly empty — omission means "keep", not "clear".

Field shapes (both endpoints)

  • category — Topic | Spec | Story | Sample | Admin | CompanyCard | … (any category — verified for Topic and Spec).
  • markup — md | text | json | xml | js | scala | html (diesel-in-md uses md; CSS / stylesheet topics use text — css is rejected).
  • by — MUST be { "$oid": "<id>" } (a bare string oid → HTTP 500). Sir.Claude is 6aae558f9755330a2ae1c455.
  • crDtm — MUST be { "$date": <epoch-millis> } (an ISO string → HTTP 500); may be omitted.
  • tags — a JSON array, e.g. ["console","ui"] (a bare string is wrong).
  • Idempotent: an identical write is a no-op → HTTP 404 OOPS … Error: no change (harmless). /content reads can briefly cache after a write, so a read-back may lag a beat.

On update, write-visibility, label and tags reuse from the stored topic whenever omitted — there is no "send the full we every time or the topic silently becomes un-editable" trap. Writing a Spec through these triggers realm.configure (same underlying write layer), so it remains the way to re-run inventory bootstrap after editing EnvironmentSettings.

Minimal we for a create:

on
{
  "category": "Topic",
  "name": "My_Topic",
  "label": "My Topic",
  "markup": "md",
  "content": "hello",
  "by": { "$oid": "6aae558f9755330a2ae1c455" },
  "tags": [],
  "realm": "metals",
  "props": { "visibility": "Member", "wvis": "Member" },
  "crDtm": { "$date": 1790000000000 }
}

Minimal we for an update (body-only edit — everything else reused):

on
{ "category": "Topic", "name": "My_Topic", "realm": "metals", "content": "hello again" }

3.3 Data objects (not topics)

Data objects (Company / Analysis / …) are not topics — write them through the inventory layer (diesel.inv.upsert, §1.5) or the react route POST /diesel/react/diesel.inv.upsert, never the topic-write API.


4. Read-back & verification (read-only)

  • GET /api/v1/wiki/content/<wpath> — raw topic body (text/plain), no execution, nothing recorded. The "did my content land?" check.
  • GET /api/v1/wiki/json/<wpath> — full topic envelope as JSON (application/json): category, name, label, markup, content, by, tags, realm, ver, props{visibility,wvis}, crDtm, updDtm, _id. The only read that shows metadata — the "did it land with the right label / visibility / tags / version?" check.
  • GET /api/v1/wiki/tag/<t1>/<t2> — JSON {total, data:[{wpath}]} of topics carrying all the given tags (AND). Use claude / test to find scratch topics.
  • GET /diesel/dom/cat/<Category> / GET /diesel/dom/list/<Class> — HTML category / entity listings.

404-vs-401 quirk: on the /content and /json reads a nonexistent wpath returns 401 (the login-wall HTML), not 404 — "not found" and "not authorized" collapse. A 401 on a read may simply mean the topic doesn't exist; don't chase a credential ghost.

Do NOT verify by running /diesel/guard/runStory/<wpath> — that runs the story as a real, tracked guardian test and pollutes stats. Use an ephemeral inline fiddle run (§2) or the /content read-back.

4.1 Delete a topic

POST /api/v1/wiki/delete/<wpath> → DELETED forever ok - no way back! Deleted N topics, HTTP 200. Irreversible — no undo. Confirm by a follow-up /content read returning 401/404.


5. Inventories available here

This reactor's connect trace lists these inventory impls:

  • diesel.db.col — durable collection/document store (collection + id + document).
  • diesel.db.postgres — durable relational store.
  • diesel.db.memshared — volatile in-memory shared store (clears on restart).
  • diesel.monkeys — (test/demo).
  • diesel.inv.default — fallback (copy of diesel.db.col).

There is no diesel.db.inmem impl here (despite the public samples using it). The ledger classes now live on diesel.db.col (durable); memshared remains connected as a volatile scratch store.

Clearing / direct store ops. memshared supports a full wipe: $ diesel.db.memshared.clear. col has no safe .clear — it's the real durable store, so remove per-entity instead (diesel.inv.remove(className, key)), or address the collection directly: diesel.db.<inv>.get|remove|upsert|query (collection=<Class>, id=<key>, document=…).

5.1 Auto-registering inventories (EnvironmentSettings)

Spec:EnvironmentSettings is a special spec loaded on server start OR whenever the spec changes. It's the right place to pre-register and connect the inventories the domain needs, so stories and loaders don't each repeat the register/connect dance.

Two EnvironmentSettings exist:

  • specs.Spec:EnvironmentSettings — the shared base, inherited by every reactor. Do not add rules here (its own banner warns: rules leak into every derived reactor — crons would fire everywhere).
  • metals.Spec:EnvironmentSettings — our own override. Implementing our own is exactly the sanctioned case, so project rules go here. (It shadows the base for metals, so carry over anything the base provided — at minimum the diesel.before → setEnv rule, which we preserved; setEnv is idempotent anyway.)

The pattern is a diesel.realm.configure lifecycle rule that registers then connects each inventory. On first registration you don't pass classNames — register the inventory, connect it, and the per-class $anno(inventory=…) does the class→inventory mapping. (An inventory can be re-registered later to add classes if the anno ever doesn't take.) Our live metals.Spec:EnvironmentSettings:

Set environment constants, and register + connect the metals domain inventories.

$when <trace> diesel.before
=> diesel.setEnv(env=diesel.env, user=diesel.username)

$when diesel.realm.configure
=> diesel.inv.register (inventory="diesel.db.memshared")
=> diesel.inv.connect  (inventory="diesel.db.memshared", env=diesel.env, connection="")
=> diesel.inv.register (inventory="diesel.db.col")
=> diesel.inv.connect  (inventory="diesel.db.col", env=diesel.env, connection="")

end

Verified: after writing this spec (the change fires realm.configure), a bare diesel.inv.upsert/find/listAll works with no register/connect in the story — the lifecycle rule + $anno wire it reactor-wide. Both memshared (volatile) and col (durable) are connected; the ledger has been repointed to col and populated end-to-end (nested deposits/catalysts round-trip; entities key by @key).

Repointing an inventory is just: change the class $anno(inventory="…") and re-save the spec. The save fires realm.configure, which re-registers the class to the new inventory — no story or code changes. (Confirmed swapping the ledger memshared → col: the identical populate story ran unchanged, source on the stored entities flipping to diesel.db.col:default.)

Register the inventory before writing. An entity upserted before its inventory is registered falls through diesel.inv.default, which mints a generated oid for assetRef.key instead of using the class @key. It then lands in the collection under that oid (not the intended key), renders wrong in the UI, and won't dedupe on the real key. With the EnvironmentSettings registration in place, writes key correctly by @key. (To clean up such orphans, remove them by their oid: diesel.db.col.remove(collection="Company", id="<oid>").)

Writing a Spec via the create / update API (§3) triggers realm.configure — a convenient way to re-run bootstrap after editing EnvironmentSettings.


6. wpath format

Use the colon form everywhere — read and write: metals.<Category>:<Name> (e.g. metals.Spec:test-spec, metals.Topic:My_Topic). (the write endpoints tolerate a dot form in the URL too, but the /api/v1/wiki/content read-back only resolves the colon form, so colon is uniform — prefer it.)


7. End-to-end workflow

| Artifact | Validate | Persist | Confirm | |---|---|---|---| | Spec | fiddleSpecUpdated — all declarations in ast | /api/v1/wiki/create (new) · /update (existing) | /api/v1/wiki/content (+ /json) | | Story | fiddleStoryUpdated — simMode=false, errorCount 0 | /api/v1/wiki/create · /update | /api/v1/wiki/content | | Topic | (JSON validity) | /api/v1/wiki/create · /update | /api/v1/wiki/content · /json | | Data object | run a $expect story (§1.6) inline | diesel.inv.upsert (register+connect first) | diesel.inv.find / /diesel/dom/list/<Class> |

Because diesel.inv.upsert is a message, confirm a data write by reading it back, not by errorCount alone.


8. Reference: the metals-ledger domain model

Company is the master node keyed by Canadian-primary ticker; Deposit/Catalyst nest by value; Analysis is its own class keyed by ticker (latest overwrites). Inventory is the volatile placeholder until repointed (§5).

$anno (inventory="diesel.db.memshared")
$class Company (@key ticker, name, exchange, sector, commodity, stage, jurisdiction, tags, source, thesis, link, deposits:Deposit*, catalysts:Catalyst*)

$class Deposit (name, commodity, lat:Number, lon:Number, lassonde)

$class Catalyst (kind, note, dueDate, magnitude)

$anno (inventory="diesel.db.memshared")
$class Analysis (@key ticker, framework, composite:Number, verdict, ninepSummary, verdictSummary, runDate)

Deposit/Catalyst carry no $anno — contained by value in Company. Optional refinement now that references are understood: instead of the manual ticker-join, a Company could carry analysisRef:<>Analysis (storing the ticker key string) — but the current separate-class-join is fine and keeps Analysis independently queryable.


9. Curl skeletons

Run a story (real execution — full param set):

bash
TS=$(date +%s%3N)
curl -s "https://metals.dieselapps.com/diesel/fiddle/fiddleStoryUpdated/${TS}?" \
  -H 'Content-Type: application/x-www-form-urlencoded' -H "Authorization: Basic ${DIESEL_AUTH}" \
  --data-urlencode 'saveMode=false' --data-urlencode 'sketchMode=false' \
  --data-urlencode 'mockMode=false' --data-urlencode 'blenderMode=true' \
  --data-urlencode 'draftMode=false' --data-urlencode 'reactor=metals' \
  --data-urlencode 'specWpath=' --data-urlencode 'storyWpath=' \
  --data-urlencode 'runEngine=true' --data-urlencode 'compileOnly=false' \
  --data-urlencode 'simMode=false' --data-urlencode "clientTimeStamp=${TS}" \
  --data-urlencode 'spec=' --data-urlencode 'story=$val x = 1+2
end' \
  --data-urlencode 'capture=' --data-urlencode 'needsCAMap=false' --data-urlencode 'needsBaseCA=false'

Create a topic:

bash
curl -s -H "Authorization: Basic ${DIESEL_AUTH}" \
  --data-urlencode 'we@my_topic.json' \
  "https://metals.dieselapps.com/api/v1/wiki/create/metals.Topic:My_Topic"

Update a topic (body-only — label / visibility / tags reused):

bash
curl -s -H "Authorization: Basic ${DIESEL_AUTH}" \
  --data-urlencode 'we={"category":"Topic","name":"My_Topic","realm":"metals","content":"new body"}' \
  "https://metals.dieselapps.com/api/v1/wiki/update/metals.Topic:My_Topic"

Read it back — body, then full envelope:

bash
curl -s -H "Authorization: Basic ${DIESEL_AUTH}" \
  "https://metals.dieselapps.com/api/v1/wiki/content/metals.Topic:My_Topic"
curl -s -H "Authorization: Basic ${DIESEL_AUTH}" \
  "https://metals.dieselapps.com/api/v1/wiki/json/metals.Topic:My_Topic"

Delete a topic (irreversible):

bash
curl -s -X POST -H "Authorization: Basic ${DIESEL_AUTH}" \
  "https://metals.dieselapps.com/api/v1/wiki/delete/metals.Topic:My_Topic"

10. Sample specs (reference)

Public examples on specs.dieselapps.com (fetch raw via /api/v1/wiki/content/<wpath>):

  • Spec:diesel-domain-spec — system domain classes; stacked $anno, ui.fieldsToShow, diesel.inv.register in a realm lifecycle handler.
  • Spec:domain-spec — class bodies (def/msg), extends, generics [T], references <> vs containment, an e-commerce sample.
  • Spec:diesel-inv-spec — the full diesel.inv.* interface + per-inventory impls.
  • Story:diesel-inv-story — canonical CRUD test with $expect, across col / inmem / memshared.

Expression / execution idioms (market-dashboard build, 2026-09-22)

  • $msg is optional; if present it MUST match the $when signature. Custom $when rules fire in fiddleStoryUpdated only with runEngine=true, and via GET /diesel/react/<message> (session-authed, incl. same-origin from an apphtml page).
  • => steps are strictly sequential (Razie, 2026-09-23): each step waits for the previous step's payload and variables, otherwise flows would be a holy mess. What runs asynchronously is the flow itself, relative to its caller (see §2.1 on the async engine). An earlier note here claimed bare => subRule sends race and need diesel.engine.sync for ordering — that was wrong.
  • sizeOf(x) = collection length. [0] / [n-1] indexing works; .size / .last / .reverse / .init / .length do NOT.
  • js:{ <javascript> } embeds JS; the last expression is the return (ISO timestamps, date math).
  • x is Number is the type filter. In diesel number != string evaluates FALSE, so != "" / != "." silently drop NUMERIC rows — use filter (x => x is Number).
  • ctx.csvToJson(separator=",", hasHeaders=true) → array of row objects (numeric cells stay Numbers, blanks become ""; dynamic key row[seriesVar] works).
  • snakk.json / snakk.text do HTTP (payload holds the result); snakk.parse.json parses a JSON string sitting in payload into an object. Nested access payload.a.b[0].c. Pass a browser User-Agent header.
  • CRITICAL: diesel.inv.find and db.col.get return nested object fields (e.g. metrics) as a SERIALIZED JSON STRING — snakk.parse.json before dotting in or +-merging. diesel.inv.listAll (react route) returns them already parsed.
  • + merges two real objects (adds/overwrites keys): mObj + { k : v }.
  • A same-named $when rule in the saved blend SHADOWS an inline spec's rule of that name — test scratch rules under a unique name.
  • Reactor outbound reachability: Stooq BLOCKED (JS anti-bot, UA does not help); FRED fredgraph.csv works; Yahoo v8/finance/chart works; Yahoo v7/finance/quote 401 (needs a crumb).

Testing stories and checking runs

  • Conformance tests: claude-expr-test (expressions, 85 expects) and claude-rules-test with claude-rules-spec (rules and domain). Known quirks are kept in each story's Issues section at the bottom (markdown after the last statement).
  • Start every test story with a ## section heading - $expect needs a target; without one every expect throws "must follow a $send".
  • The parser is positive: a $ line it doesn't recognize just stays markdown. Verify a run with three signals, not failureCount alone: (1) unrecognized $ lines in the rendered wiki field of the fiddle response (the UI lints these on screen); (2) fail-error entries in the engine trace (res), counted in info.errorCount; (3) failed expects (failureCount). The AST types also show how a line was read (e.g. :Boolean present or not).
  • Runner: claude-test-runner runs a story ephemerally through fiddleStoryUpdated and prints all three signals.
  • Parser notes: grammar findings from the source, gotchas and the replacement plan are in Replacing_the_parser.


Was this useful?  

By: Sir.Claude | 2026-09-20 .. 2026-09-23 | Tags: dsl , guide


Viewed 5 times ( | History | Print ) this page.

You need to log in to post a comment!

© Copyright DieselApps, 2012-2026, all rights reserved.