Core Checklist¶
An executor implementation is considered Lace Core Compatible when it satisfies every item in this checklist. Partial compatibility must be documented -- an executor may declare which sections it supports and which it does not.
This checklist covers the core language only. Extension system compatibility is defined separately in the Extension Checklist.
Spec version: 0.9.0
1. Parsing¶
- [ ] Parses
.lacesource text against the formal grammar in section 2.1 - [ ] Applies lexical rules from section 2.2:
$var,$$vartokenisation, string escape sequences, comment stripping - [ ] Rejects source text that does not conform to the grammar with a structured parse error including line and column
- [ ] Accepts trailing commas in all list and object positions where the grammar permits them
- [ ] Parses all five HTTP methods:
get,post,put,patch,delete - [ ] Parses call config sub-objects:
redirects,security,timeout - [ ] Parses unknown fields in
redirects,security,timeout, and call root as extension fields -- does not reject them at parse time - [ ] Parses all chain methods in any valid subset and order
- [ ] Parses all three scope forms: shorthand, value+op, full form with
options {} - [ ] Parses
options {}content as free-form key-value pairs -- does not reject unknown keys at parse time - [ ] Parses all three body match forms in
.expect()/.check():schema($var), string literal, variable ref - [ ] Parses
.assert()with bothexpectandcheckclauses, shorthand and full condition forms - [ ] Parses
prevdot-access and array-index expressions - [ ] Parses
thisdot-access expressions - [ ] Produces an internal AST from valid source -- the AST is an implementation detail and is never stored or transmitted
2. Validation¶
All validation rules from lace-spec.md section 12 must be enforced. The following are the behavioural requirements:
- [ ] Reports all validation errors before execution -- does not stop at the first error
- [ ] Distinguishes errors (block execution) from warnings (allow execution with notice)
- [ ] Requires at least one HTTP call per script
- [ ] Requires at least one chain method per call
- [ ] Enforces chain method order:
.expect->.check->.assert->.store->.wait - [ ] Rejects duplicate chain methods on the same call
- [ ] Rejects
.expect()and.check()with zero scopes - [ ] Rejects
this.*references outside a chain method body (cross-call references are not constructible -- each call's chain is its own parser-level scope) - [ ] Rejects function calls in expressions that are not
json,form, orschema - [ ] Validates all
$varreferences against the provided variable registry -- rejects unknown references - [ ] Rejects
$$varassigned more than once across the script - [ ] Rejects
schema($var)where the variable is absent from the registry - [ ] Validates expression syntax in all
.assert()conditions - [ ] Rejects
redirects.maxvalues exceeding the context system maximum - [ ] Rejects
timeout.msvalues exceeding the context system maximum - [ ] Rejects
timeout.retrieswithouttimeout.action: "retry" - [ ] Rejects
clearCookieswhencookieJaris not aselective_clearvariant - [ ] Rejects
cookieJar: "named:"with an empty name - [ ] Rejects
opvalues not in:lt,lte,eq,neq,gte,gt - [ ] Rejects malformed
bodySizesize strings - [ ] Emits a warning for unknown fields when the registering extension is not active
- [ ] Emits a warning for
prev.*references when--prev-resultsis not provided - [ ] Emits a warning when the script contains more than 10 calls
- [ ] Accepts
options {}with unknown field names regardless of extension state -- does not error on unknown option keys
3. Variable Handling¶
- [ ] Accepts a flat key-value map as the variable injection input (
--varsor execution context) - [ ]
$varnameresolves to its value from the injected map - [ ]
$varabsent from the injected map resolves tonull - [ ]
$$varnot yet assigned resolves tonull - [ ]
$$varassigned via.store()is available to all subsequent chain methods and calls in the same run - [ ] Enforces write-once: a second
.store()assignment to the same$$key is rejected at validation time, not at runtime - [ ]
$varin.store()goes toactions.variablesas a write-back;$prefix is stripped from the key in the result; the variable's in-memory value does not change during the current run - [ ] All
$$varfinal values are present inrunVarsin the result, each key appearing exactly once - [ ]
runVarscontains only keys that were actually assigned -- unassigned$$varreferences do not appear
4. Null Semantics¶
- [ ] Missing
$varfrom injected map ->null - [ ] Unassigned
$$var->null - [ ]
prevwhen no previous results provided ->null - [ ] Any field access on
null->null(no exception thrown) - [ ]
nullin string interpolation -> the literal string"null"with a warning recorded in the call'swarningsarray - [ ]
null eq null->true - [ ]
null eq non_null_value->false - [ ]
null neq non_null_value->true - [ ]
nullas operand oflt,gt,lte,gte-> outcome"indeterminate"(no exception, no hard fail) - [ ]
nullas operand of+,-,*,/->nullresult (no exception) - [ ]
schema($var)where$varresolves tonull-> hard fail - [ ]
nullstored via.store()-> valid; appears inrunVarsoractions.variablesas JSONnull
5. HTTP Execution¶
- [ ] Executes calls sequentially in script order
- [ ] Makes real HTTP requests for each call (not simulated)
- [ ] Applies all resolved request config: headers, body, cookies, cookieJar, redirects, security, timeout
- [ ] Performs variable interpolation in
url,headersvalues, andbodystring values before sending - [ ] Records
startedAtandendedAttimestamps per call in UTC ISO 8601 - [ ] Captures all timing fields:
responseTimeMs,dnsMs,connectMs,tlsMs,ttfbMs,transferMs,sizeBytes - [ ] Sets
tlsMsto0for non-HTTPS calls - [ ] Parses response body as JSON when
Content-Typeisapplication/json; raw string otherwise - [ ] Lower-cases all response header keys
- [ ] Enforces
redirects.max-- hard fails when exceeded regardless of other config - [ ] When
security.rejectInvalidCerts: true: hard fails on any TLS error - [ ] When
security.rejectInvalidCerts: false: records TLS error as a warning, continues execution - [ ] Enforces
timeout.msper call - [ ] On timeout with
timeout.action: "fail": hard fails - [ ] On timeout with
timeout.action: "warn": records soft fail, continues - [ ] On timeout with
timeout.action: "retry": retries up totimeout.retriestimes; hard fails after all attempts exhausted - [ ]
timeout.retries: Nmeans N+1 total attempts (1 initial + N retries)
6. Cookie Jar¶
- [ ]
"inherit": continues previous call's jar; first call starts with empty jar - [ ]
"fresh": starts this call with an empty jar, discarding all previous cookies - [ ]
"selective_clear": clears only the keys inclearCookiesfrom the default jar before this call - [ ]
"named:{name}": uses an isolated jar identified byname; creates it if it does not exist - [ ]
"{name}:selective_clear": clears keys inclearCookiesfrom the named jar{name} - [ ] Named jars persist across calls within a run but not between runs
- [ ] Explicit
cookiesobject values are merged with the active jar at send time - [ ]
Set-Cookieresponse headers update the active jar after each call
7. Chain Method Execution¶
.expect()
- [ ] Evaluates all scopes before triggering failure cascade -- does not stop at first failing scope
- [ ] Records every failing scope in
assertions[] - [ ] After all scopes evaluated: if any failed, triggers hard fail cascade
- [ ] Applies default operator per scope name (section 4.4) when
opis omitted - [ ] Passes
options {}object through toassertions[].optionsopaquely -- does not interpret it - [ ]
statuswith array value passes when actual status matches any element - [ ]
tlsscope skipped (not evaluated) whenthis.tls eq 0
.check()
- [ ] Evaluates all scopes before recording failures -- does not stop at first failing scope
- [ ] Records every failing scope in
assertions[] - [ ] After all scopes evaluated: execution continues regardless of outcome
- [ ] Same operator and
optionspass-through rules as.expect()
.store()
- [ ] Skipped entirely when any preceding chain method (
.expect()or.assert({ expect: [...] })) produced a hard fail on the same call - [ ]
$$keywrites to run-scope, available to subsequent calls - [ ]
$keyand plain keys go toactions.variablesin the result;$prefix is stripped from the key - [ ]
$keywrite-back does not change the variable's in-memory value during the current run - [ ] Stored values may be any JSON-serialisable shape (scalar, object, or array)
- [ ]
nullis a valid stored value
.assert()
- [ ] Evaluates all
expectconditions before triggering hard fail cascade - [ ] Evaluates all
checkconditions regardless of outcome - [ ] Records each condition outcome (
"passed","failed","indeterminate") inassertions[] - [ ] Records
actualLhsandactualRhsfor each condition - [ ] Records
expressionstring for each condition - [ ] Passes
options {}per condition through toassertions[].optionsopaquely - [ ] Null operand in ordered comparison or arithmetic ->
"indeterminate"outcome, no error, execution continues - [ ] Hard fail cascade triggers after all
expectconditions evaluated, if any failed
.wait()
- [ ] Pauses execution for the specified number of milliseconds after all other chain methods complete
- [ ] Is always the last chain method executed on a call
8. Body Matching¶
- [ ]
body: schema($var)-- parses the variable's value as a JSON Schema document and validates the response body against it - [ ]
body: schema($var)where$varisnull-- hard fail - [ ]
body: schema($var)where response body is not valid JSON -- hard fail - [ ]
body: "literal"-- compares raw response body string to the literal, case-sensitive exact match - [ ]
body: $var-- compares raw response body string to the runtime value of the variable
9. prev Access¶
- [ ]
prevresolves to the provided previous result object when--prev-resultsis supplied - [ ]
prevresolves tonullwhen no previous results are provided - [ ] All
prev.*field access follows null propagation rules -- no exception thrown on any access path - [ ]
prev.calls[n]accesses the nth call record from the previous result - [ ]
prev.runVars.keyaccesses the named run-scope variable from the previous result - [ ]
prev.outcomeaccesses the overall outcome of the previous result
10. Failure Cascade¶
- [ ] Hard fail on a call skips all remaining chain methods on that call (
.store()included) - [ ] Hard fail on a call marks all subsequent calls as
"skipped"in the result - [ ] Soft failures do not stop execution -- the next chain method runs
- [ ] Notification events (if extension active) from soft failures in earlier calls are still included in the result even after a later hard fail
- [ ]
"skipped"calls havenullforrequest,response, and an emptyassertionsarray
11. Result Structure¶
- [ ] Result contains:
outcome,startedAt,endedAt,elapsedMs,runVars,calls,actions - [ ]
outcomeis"success"when no hard fails occurred;"failure"on hard fail;"timeout"when the cause was a timeout - [ ]
startedAtis the timestamp before the first call begins;endedAtis after all chain methods complete or after cascade stops - [ ]
runVarsis a flat object containing all assigned$$varkeys and their final values - [ ]
callscontains one record per call in script order, including skipped calls - [ ] Each call record contains:
index,outcome,startedAt,endedAt,request,response,assertions,config,warnings,error - [ ]
requestcontains:url(resolved),method,headers(resolved),bodyPath - [ ]
responseisnullfor skipped, timeout (no response received), or connection failure - [ ]
responsecontains:status,statusText,headers,bodyPath, and all timing fields - [ ]
assertionscontains one entry per evaluated scope and condition, in evaluation order - [ ] Each assertion entry contains:
method,outcome, and type-appropriate fields (scope/op/actual/expectedfor scope assertions;kind/index/expression/actualLhs/actualRhsfor assert conditions) - [ ]
assertions[].optionscontains the rawoptions {}object from source --nullif nooptions {}was present - [ ]
configcontains the resolved call config (after defaults applied), including any extension-registered fields passed through - [ ]
warningsis an array of strings -- empty array when no warnings, nevernull - [ ]
erroris a string describing non-assertion failure (connection error, TLS, redirect limit, body too large) ornull - [ ]
actionsis always present, even if empty - [ ]
actions.variablesis present and contains all write-back.store()key-value pairs ($nameand plain keys) when any write-back targets exist;$prefix stripped from$namekeys; absent or empty object otherwise - [ ] All result values are JSON-serialisable
12. Body Storage¶
- [ ] Writes request body to a file on the shared filesystem volume before sending the request
- [ ] Writes response body to a file on the shared filesystem volume after receiving the response
- [ ] File paths follow the convention:
{run_base_dir}/call_{index}_{request|response}.{ext} - [ ] Result JSON contains absolute file paths in
request.bodyPathandresponse.bodyPath - [ ] No body bytes appear in the result JSON itself
- [ ]
bodyPathisnullwhen a body was not captured - [ ] When
bodyPathisnull,bodyNotCapturedReasonis present on the containing object with value"bodyTooLarge","notRequested", or"timeout" - [ ] Run base directory is taken from execution context (configured via
result.bodies.dir)
13. Configuration¶
- [ ] Reads
lace.configTOML from script directory or working directory at startup - [ ] All config values have defaults -- executor runs without a config file
- [ ] Config resolution order: CLI flags -> script-dir config -> working-dir config -> defaults
- [ ]
--config pathoverrides config file location - [ ]
--vars vars.jsonloads variable injection map from JSON file - [ ]
--var KEY=VALUEinjects a single variable; multiple flags merge; overrides--vars - [ ]
--prev-results pathloads previous result JSON - [ ]
--save-to pathoverridesresult.pathfor this run - [ ]
env:VARNAMEconfig values resolved from environment at startup; error if variable unset - [ ]
env:VARNAME:defaultresolved from environment; usesdefaultif variable unset - [ ]
LACE_ENVenvironment variable or--env flagselects the active[lace.config.{env}]section - [ ] Saves result JSON to
result.pathafter execution (directory: timestamped filename; file path: overwrites) - [ ]
result.path = falsedisables result saving
14. Extension Interface (Core Side)¶
These are the core executor's obligations toward the extension system. Full extension system compatibility is in the Extension Checklist.
- [ ] Loads
.laceextfiles listed inexecutor.extensionsat startup; fails with clear error if a file is not found - [ ] Passes
options {}objects through toassertions[].optionsin the result without modification - [ ] Passes extension-registered call config fields through to
calls[n].configin the result without modification - [ ] Fires all twelve hook points at the correct moments:
on before script,on script,on before call,on call,on before expect,on expect,on before check,on check,on before assert,on assert,on before store,on store - [ ] Provides the correct context object at each hook point as defined in
lace-extensions.mdsection 8 - [ ] Enforces extension variable namespace: rejects
emit result.runVarskeys not prefixed with the extension name - [ ] Emits unknown-field warnings (not errors) for extension fields in source when the extension is not active