edict-program-writer — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited edict-program-writer (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Edict programs are JSON objects — not text files. You produce an AST directly as structured JSON. There is no syntax to learn, only a schema to conform to.
edict_schema to get the JSON Schema (or read schema/edict-schema.json)edict_check (or edict_compile) — if errors come back, fix and resubmitedict_run to execute the compiled WASMEvery program is a module:
{
"kind": "module",
"id": "mod-myprogram-001",
"name": "myprogram",
"imports": [],
"definitions": [ ... ]
}The optional schemaVersion field tracks which schema version the AST targets. You don't need to include it — programs without schemaVersion are treated as v1.0 and auto-migrated to the current version by the compiler. If you include it, set it to the current version (check via edict_version).
A module contains definitions: functions (fn), records (record), enums (enum), type aliases (type), and constants (const).
Every module needs a main function that returns Int (exit code).
Every AST node needs a unique id string. Convention: {kind}-{descriptive-name}-{counter}
| Kind | Prefix | Example |
|---|---|---|
| Module | mod- | mod-hello-001 |
| Function | fn- | fn-main-001 |
| Parameter | param- | param-n-001 |
| Literal | lit- | lit-zero-001 |
| Call | call- | call-print-001 |
| Identifier | ident- | ident-x-001 |
| Binary op | binop- | binop-add-001 |
| If | if- | if-check-001 |
| Let | let- | let-result-001 |
| Match | match- | match-shape-001 |
| Record def | rec- | rec-point-001 |
| Enum def | enum- | enum-shape-001 |
IDs must be globally unique within a module. Duplicate IDs cause a duplicate_id error.
| Type | JSON representation |
|---|---|
| Basic | { "kind": "basic", "name": "Int" } — also Float, String, Bool |
| Array | { "kind": "array", "element": <TypeExpr> } |
| Option | { "kind": "option", "inner": <TypeExpr> } |
| Result | { "kind": "result", "ok": <TypeExpr>, "err": <TypeExpr> } |
| Named | { "kind": "named", "name": "Point" } — references a record/enum |
| Tuple | { "kind": "tuple", "elements": [<TypeExpr>, ...] } |
| Function | { "kind": "fn_type", "params": [...], "effects": [...], "returnType": <TypeExpr> } |
| Unit | { "kind": "unit_type", "base": "Float", "unit": "usd" } |
| Refined | { "kind": "refined", "id": "...", "base": <TypeExpr>, "variable": "x", "predicate": <Expr> } |
Functions declare effects: "pure", "reads", "writes", "io", "fails".
pure function cannot call an io or fails functionio, you must declare ioprint need "io" effectFunctions can have pre and post conditions — verified at compile time by Z3:
{
"kind": "pre",
"id": "pre-001",
"condition": {
"kind": "binop", "id": "cond-001", "op": ">",
"left": { "kind": "ident", "id": "id-n-001", "name": "n" },
"right": { "kind": "literal", "id": "lit-0-001", "value": 0 }
}
}{
"kind": "module", "id": "mod-hello-001", "name": "hello", "imports": [],
"definitions": [{
"kind": "fn", "id": "fn-main-001", "name": "main",
"params": [], "effects": ["io"],
"returnType": { "kind": "basic", "name": "Int" },
"contracts": [],
"body": [
{ "kind": "call", "id": "call-print-001",
"fn": { "kind": "ident", "id": "ident-print-001", "name": "print" },
"args": [{ "kind": "literal", "id": "lit-msg-001", "value": "Hello, World!" }] },
{ "kind": "literal", "id": "lit-ret-001", "value": 0 }
]
}]
}{ "kind": "let", "id": "let-x-001", "name": "x",
"type": { "kind": "basic", "name": "Int" },
"value": { "kind": "literal", "id": "lit-42-001", "value": 42 } }{ "kind": "if", "id": "if-001",
"condition": { "kind": "binop", "id": "cmp-001", "op": ">",
"left": { "kind": "ident", "id": "id-x-001", "name": "x" },
"right": { "kind": "literal", "id": "lit-0-001", "value": 0 } },
"then": [{ "kind": "literal", "id": "lit-pos-001", "value": 1 }],
"else": [{ "kind": "literal", "id": "lit-neg-001", "value": 0 }] }{ "kind": "record", "id": "rec-point-001", "name": "Point",
"fields": [
{ "kind": "field", "id": "field-x-001", "name": "x", "type": { "kind": "basic", "name": "Float" } },
{ "kind": "field", "id": "field-y-001", "name": "y", "type": { "kind": "basic", "name": "Float" } }
] }{ "kind": "record_expr", "id": "rexpr-001", "name": "Point",
"fields": [
{ "name": "x", "value": { "kind": "literal", "id": "lit-x-001", "value": 1.0 } },
{ "name": "y", "value": { "kind": "literal", "id": "lit-y-001", "value": 2.0 } }
] }Every error is a JSON object with an error discriminator and nodeId pointing to the broken node.
| Error | What it means | How to fix |
|---|---|---|
duplicate_id | Two nodes share an ID | Make IDs unique |
unknown_node_kind | Invalid kind value | Check validKinds in the error |
missing_field | Required field absent | Add the field listed in the error |
undefined_reference | Name not declared | Check candidates for similar names |
type_mismatch | Wrong type | Compare expected vs actual in the error |
effect_violation | Missing effect declaration | Add the effect from calleeEffects to your function |
contract_failure | Pre/postcondition violated | Read counterexample for concrete failing inputs |
verification_timeout | Z3 timed out | Simplify the predicate |
Available built-in functions (no need to import):
print(String) -> Int — effect: iointToString(Int) -> String — effect: purefloatToString(Float) -> String — effect: purestring_length(String) -> Int — effect: purestring_concat(String, String) -> String — effect: purestring_replace(String, String, String) -> String — effect: purearray_length(Array<T>) -> Int — effect: purearray_push(Array<T>, T) -> Array<T> — effect: purearray_get(Array<T>, Int) -> T — effect: purePrograms compile to WebAssembly and run in a sandboxed VM with no ambient authority. This is by design — agent-generated code needs stronger isolation than human-reviewed code.
effects: ["io"], you're telling the host "this program needs IO access." The host can refuse to run programs that request unwanted effectsEdictHostAdapter — the host controls what's availabletimeoutMs, maxMemoryMb, sandboxDir) prevent runaway execution and constrain file accessThis means your effect annotations aren't just for passing the compiler — they're the capability contract between your program and the host.
For the complete AST schema, call the edict_schema MCP tool or read schema/edict-schema.json. For example programs covering all features, see the examples/ directory (28 programs).
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.