Skip to content
Phero Lang
Menu

Platform / how it works

From one file to generated code.

One example file, quoted verbatim, followed through every stage the toolchain has. Nothing below is illustrative: the shell lines are executed by CI against the released binary, and the artifact fields are read from the artifact this repository commits.

SOURCE → CHECK → CORE IR → BACKENDS → OUTPUT

01 / The source

A domain, declared once

A .mox file declares a package and what lives in it. Every other surface reads this file rather than restating it.

This is examples/support/support.mox, lines 3–32, unchanged:

examples/support/support.mox · lines 3–32
enum TicketState {
    Open as "open" = 0
    InProgress as "in_progress" = 1
    Resolved as "resolved" = 2
}

class User {
    id readonly String userId
    String name
    boolean active
    refers Ticket[] assignedTickets opposite assignee
}

class Ticket {
    id readonly String ticketNo
    String title
    boolean internal
    int refundCents
    TicketState state
    refers User[0..1] assignee opposite assignedTickets
    derived boolean needsRefund {
        expr { refundCents > 0 }
    }
    op boolean raiseRefund(int cents) {
        expr { refundCents + cents <= 5000 }
    }
    op boolean approveRefund(int cents) {
        expr { refundCents + cents <= 100000 }
    }
}

Four lines carry the design decisions:

  • refers User[0..1] assignee opposite assignedTickets declares both ends of one relation. The compiler checks that the two ends agree, so a relation cannot be half described.
  • derived boolean needsRefund is computed, not stored: it has a body and no stored value, and the generators know the difference.
  • op boolean raiseRefund(int cents) is an operation whose body is written in the shared expression language and type-checked against int arithmetic and the class's own features.
  • TicketState is a closed enum. A state machine declared in a design over this class has to use its literals — which is the next surface's problem, not this file's.

02 / Check

The compiler refuses to be surprised

Parsing, resolution, validation and type-checking all happen before a byte of artifact is written. A file that fails any of them produces no model at all.

bashexecuted by CI
pherolang check examples/support/support.mox

It prints OK examples/support/support.mox and nothing else. An error blocks the model outright: an unresolved type, a duplicated declaration, an opposite that does not pair, or a guard that does not type as boolean all end the run with no artifact written.

Each diagnostic carries a stable code allocated in one central registry, so a code in an issue stays the same code in the editor:

bashexecuted by CI
pherolang explain E0104

prints Unknown action reference — the meaning of the code, not a mangled message number. The full registry is generated from the compiler's own table.

03 / Compile

The artifact is the product

Lowering happens once. What leaves the compiler is a versioned JSON document that describes the model completely enough to be read without the model.

bashexecuted by CI
pherolang ir examples/support/support.mox -o support.phero.json

The first command writes the artifact and prints nothing. The second reads it back with no compiler in the loop — no model opened, no import resolved, no grammar involved:

bashexecuted by CI
pherolang artifact check support.phero.json

It prints OK support.phero.json.

support.phero.json · head
{
  "formatVersion": 2,
  "pheroVersion": "0.3.0",
  "packages": [ … ]
}

Two fields, two jobs. formatVersion is the compatibility gate: a reader accepts that number or refuses the file, and it is the only thing a reader may gate on.pheroVersion is provenance — which build ofpherolang wrote these bytes — and nothing more.

The package body follows (nz.example.support here). Evolution is additive: a new optional field can appear without changing the bytes of a model that does not use it, because the goldens are compared byte for byte. From 1.0 that becomes a promise — no golden changes without a formatVersion bump — and it is what lets a reader pinned to formatVersion 2 keep reading later artifacts while the number stays where it is.

Every artifact this site talks about is committed undersite/data/artifacts/, one per surface, and regenerated by CI from the released binary. Versioning and release policy.

04 / Backends

Pure functions from artifact to output

Each backend is handed a compiled value. None of them sees a syntax tree, and none of them can: the compiler does not hand one out.

The in-tree backends: the crate, the artifact it reads, and the output it emits.
BackendReadsEmits
phero-backend-cedarCore IR + ActorModelCedar policies and an entity schema
phero-backend-cmmnCmmnModel + domaina cmmn-rs CaseDefinition
phero-backend-dmnDmnModelthe model the dsntk engine evaluates
phero-backend-jsonschemaCore IRJSON Schema — wire and api profiles
phero-backend-rustCore IRarena-based Rust model code

05 / Output

Two targets, one shape

The same source, generated twice. Neither run reads the other's output, and neither one can invent a field the model does not have.

bashexecuted by CI
pherolang gen rust examples/support/support.mox -o support-gen

It prints generated Rust model code into support-gen: arena-based Rust model code, generated from the compiled model rather than from the syntax tree. An expr operation body is parsed, typed and lowered at generation time; a rust body is emitted verbatim, by definition trusted code.

bashexecuted by CI
pherolang gen json-schema examples/support/support.mox --profile wire -o support-schema

It prints generated JSON Schema into support-schema. The wire profile is the JSON contract the canonical instance documents are validated against; the api profile is a flattened DTO projection for OpenAPI-style use, over the same model.

Cedar policy is the third target from this same domain, and it needs one more file: an.actor surface that imports the domain and declares who may do what under which condition. Policy is a separate artifact and a separate dimension — it never appears in the domain model or in its schemas.

The two artifacts are then checked against each other — which is the point of writing policy in the same language as the model:the authorisation surface has the shape of that file, andthe platform overview explains the cross-surface pass.

Continue

Run it yourself

The install page has the archive for your platform; the walkthrough runs these same commands end to end from a clean checkout, with the exact output of each one.