Deterministic runner logical identities
Available in standalone v0.40.0. The
runner contract admits one captured inline text unit.
After exact admission, its CLI/API receipt includes identity, using algorithm
version sha256-waza-c14n-v1. Validation derives identities without creating
an engine. Native execution and archives remain unavailable.
Execution now returns a terminal envelope.
Read and save an identity
Section titled “Read and save an identity”waza runner validate < examples/runner/unit.json > validation.jsonwaza runner run < examples/runner/unit.json > attempt.jsonBoth use the same original-input producer. A receipt’s identity contains seven
required strings: version, canonical_digest, run, shard, task, trial
and attempt. The digest is 64 lowercase hex characters; each logical ID has
its namespace prefix and a full 64-character hash. Offline validation keeps
waza.runner-unit / 1.0; execution uses waza.runner-terminal / 1.0.
Identity versioning is independent of request, negotiation, local result and
terminal-envelope versions.
Logical identity means declared input, not observed execution, provenance, exactly-once delivery, duplicate suppression, trusted source files or isolation. Repeated dispatch intentionally executes again with the same IDs. A validation receipt has zero engine calls even when all identity fields are present. Invalid/unadmitted/unsupported input has no identity. Runtime admission/factory failure, cancellation, timeout or execution failure after derivation retains it; inspect status, diagnostics and observed calls separately.
Identity hierarchy and stability
Section titled “Identity hierarchy and stability”flowchart LR E["Canonical eval + run key + timeout + request schema"] --> R["run"] R --> S["shard + declared shard key"] S --> T["task + task key + canonical task"] T --> U["trial + one-based ordinal"] U --> A["attempt + one-based ordinal"]Changing the attempt changes only its attempt ID and the complete canonical digest. Changing the trial also changes attempt, not its task/shard/run. Task-source changes preserve run/shard; shard changes preserve run. Eval-source, run-key or request-timeout changes rename the entire hierarchy. A run ID alone is not a manifest of every task in a scheduler’s fleet. No timestamp, PID, hostname, current directory, runtime path, environment, model output, billing or session ID contributes.
Version offers, required/optional capability arrays and allow_paid_execution
are excluded negotiation/permission metadata. They must still pass admission;
their exclusion neither enables native execution nor waives exact-unit and
exactly one required execution profile. The authored executor/model are already
part of the canonical eval input, so adding inline.copilot-text does not
change the algorithm or historical mock identities.
Source and numeric grammar
Section titled “Source and numeric grammar”Captured eval/task input is one UTF-8 YAML mapping, within the runner’s 4 MiB
request limit and a 64-level canonical nesting bound. Identity reads original
authored YAML nodes, never defaulted model JSON. The pinned yaml.v3 resolver
defines tags; changing its effective behavior requires conformance review.
The 4 MiB wire limit applies to the original JSON request bytes. Validation and
execution derive identities from those bytes, so valid unescaped <, > and
& do not become oversized through internal HTML escaping. Sending an escaped
representation that itself exceeds 4 MiB is still rejected.
| Value | Canonical rule |
|---|---|
| Mapping | Unique string keys sorted by unsigned UTF-8 bytes; all declared key/value pairs retained |
| Sequence | Preserve order and length |
| String | Exact decoded Unicode; no NFC/NFD, trimming, case folding, path rewriting or environment expansion |
| Integer | YAML !!int normalized with arbitrary-precision arithmetic; radices/signs/underscores and octal spelling may be equivalent |
| Float | YAML !!float, finite exact decimal coefficient/exponent; never round through binary64; preserve negative float zero |
| Boolean/null | Preserve scalar type; null, absent, false, zero, empty string/map/array remain distinct |
| Presentation | Comments, key order, equivalent quoting, flow/block layout and physical LF/CRLF normalize |
| Escaped text | Explicit "\r\n" differs from "\n"; physical YAML literal-block line breaks normalize to LF |
12.50e-1 and 1.25 encode the same exact decimal 125e-2.
Float 0.0 encodes 0e0, float -0.0 encodes -0e0; neither is integer zero.
Arbitrarily large exponents stay symbolic rather than expanding into zeros.
Use explicit numeric tags for canonical-only values outside the resolver’s native
range. Reject aliases, anchors, merge keys, duplicate/non-string keys, extra
documents, invalid Unicode, timestamps, custom tags and non-finite floats.
Canonical support is wider than execution admission. The current execution profile rejects authored floats, unknown fields, nonempty metadata/context and external paths. Canonical-only vectors illustrate encoding, not executable work. Do not drop declarations or widen admission to make a source execute. Omitted versus explicit defaults can hash differently even when execution behavior matches.
Canonical encoding and namespaces
Section titled “Canonical encoding and namespaces”Structural tokens are ASCII. Lengths count UTF-8 bytes, not UTF-16 units; counts/lengths are unpadded decimal, with no extra separators or terminators.
| Node | Encoding |
|---|---|
| String | s<byte-length>:<decoded text> |
| Integer / float | i<byte-length>:<decimal> / f<byte-length>:<coefficient>e<exponent> |
| False / true / null | b0 / b1 / n |
| Sequence | a<count>:<children> |
| Mapping | m<count>:<string key><value>... in UTF-8 key order |
For example, {v: null} is m1:s1:vn; {v: 0} is
m1:s1:vi1:0; {v: "0"} is m1:s1:vs1:0.
Length framing prevents concatenation ambiguity, including embedded NUL.
Hash each namespace with SHA-256 over UTF-8 version, NUL, namespace, NUL, then a canonical sequence of string fields. Parent hashes are unprefixed lowercase digests; ordinals/timeouts are unpadded decimal strings.
| Namespace | Ordered string fields |
|---|---|
run |
request schema version, run key, request timeout milliseconds, canonical eval bytes |
shard |
run digest, declared shard key |
task |
shard digest, selected task key, canonical task bytes |
trial |
task digest, trial ordinal |
attempt |
trial digest, attempt ordinal |
input |
run, shard, task, trial and attempt digests |
The last hash is canonical_digest. No truncation or salting occurs.
Hash-collision possibility is not eliminated; these are domain-separated
identities with conformance coverage, not mathematical collision-proof evidence.
Frozen conformance vectors
Section titled “Frozen conformance vectors”Download the 29 canonical vectors and 11 admitted producer vectors. The former includes canonical-only numeric/presence controls; its source-profile labels are not proof of admission. The latter uses the real executable example and equivalent, material-change, tuple and permission/offer controls. Both have independent Node references using manually typed canonical trees, not Go output or a second YAML parser.
The real example’s expected complete identity is:
- version
sha256-waza-c14n-v1- canonical_digest
46c427b5aa8bfbb61ab297b8e7b780c31ea7ca831ddaa633099498ed749e8dc5- run
run-a4fe0d7265abd37befd01c024046df1174de8b649ad8bc53a8cbc317e7fcca9f- shard
shard-780d0dd8d03603a682b6228ec2916020839fbb21324dee1ce83bc8f81e81ab81- task
task-fe113f725e5c244fb694124dcff3f073cc78f789891d82e79789a58497699657- trial
trial-ccb6f415db4210a7e071f65c1bcf3dec32860c4fc53e23492070da597a7e8b51- attempt
attempt-03e5dd35b35d71a351cbbd8661d188279b4c35f3fe73d8789abf02f3e0186c70
Verify from a checkout:
node internal/runneridentity/testdata/reference.mjsnode internal/runnerunit/testdata/identity-reference.mjsNO_COLOR=1 go test ./internal/runneridentity ./internal/runnerunit -count=1The source oracle SHA-256 is
3acf83109f3939bed296b005d9802e5bd67c8c6730c6894a3bd411e408c738ae;
the producer oracle is
fea761f0270c22dbf3a79760b8877bfac32ab8e14022f7d058401e149e9e88b0.
Checksums normalize physical vector-file CRLF to LF, not request string content.
Go subprocess tests derive vectors under different directories/timezones.
The same Go tests are selected by native CI; local macOS checks or
cross-compilation are not native Windows/Linux execution evidence.
Never regenerate vectors merely to make changed code pass.
Lab and validation export
Section titled “Lab and validation export”Open waza serve --lab, then Runner contract and Validate selection.
The live API supplies logical lineage, attempt identity, canonical digest and
derivation version under Input-derived logical identity. The panel never
calls /run. Export validation JSON downloads that exact validated report,
not a fabricated observation or historical execution archive. Editing input,
rejection or failed reload clears old success. The synthetic demo invents no IDs.
🔍 Click to enlarge
Release and migration notes
Section titled “Release and migration notes”The v0.40.0 identity layer adds metadata without changing existing local eval/task
schemas, commands or historical result IDs. Receipts lacking identity stay
readable and show unavailable; no implicit backfill or default version applies.
Compare both algorithm version and ID. The unshipped draft
sha256-json-v1 is not this algorithm and produces different IDs.
Recalculate only when complete original declared sources are available and
migration is explicitly requested; retain old IDs as legacy rather than claiming
verified reconstruction.
Any change to canonical fields, type/tag/Unicode semantics, framing, namespaces, parent inclusion or hash algorithm needs a new identity version and new vectors. Offline validation receipts are not execution terminal envelopes. Terminal schema, emission and exit classification are a separate v0.40.0 contract; persistence remains out of scope. See the developer integration contract and runner migration guide. See release scope and availability for the standalone binary, independent extension and digest-bound container boundaries.