# ENC/PEL1-RESULT/1 — Canonical Encoding for PEL/1 Execution Results Status: Approved Owner: Niklas Rydberg Version: 0.3.0 SoT: Yes Last Updated: 2025-11-30 Linked Phase Pack: PH06 Tags: [binary-minimalism, deterministic, execution] **Document ID:** `ENC/PEL1-RESULT/1` **Profile ID:** `PEL_ENC_EXECUTION_RESULT_V1 = 0x0103` **Layer:** Scheme Encoding Profile (PEL/1 Surface Result) **Depends on (normative):** * `ASL/1-CORE v0.4.x` — value model (`Artifact`, `TypeTag`, `Reference`, `OctetString`) * `ENC/ASL1-CORE v1.0.x` — canonical encodings for `Artifact` and `Reference` * `PEL/1-CORE v0.3.x` — `ExecutionResultValue`, `ExecutionStatus`, `ExecutionErrorSummary`, `DiagnosticEntry` * `PEL/1-SURF v0.2.x` — surface execution result model (`SurfaceExecutionResult` shape) **Integrates with (informative):** * `HASH/ASL1 v0.2.x` — ASL1 hash family for result Artifact identity * `SUBSTRATE/STACK-OVERVIEW v0.4.x` — layering discipline * `ENC/PEL-PROGRAM-DAG/1 v0.2.x` — list / Utf8 / params encoding conventions * `ENC/PEL-TRACE-DAG/1 v0.1.x` — `DiagnosticEntry` encoding pattern * TypeTag registry (for `TYPE_TAG_PEL1_RESULT_1` assignment) > The Profile ID `PEL_ENC_EXECUTION_RESULT_V1` is a configuration label. > It is **not** embedded into payloads. Encoders and decoders select this encoding profile by context (scheme descriptor, engine/store configuration), not per value. © 2025 Niklas Rydberg. --- ## License Except where otherwise noted, this document (text and diagrams) is licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0). The identifier registries and mapping tables (e.g. TypeTag IDs, HashId assignments, EdgeTypeId tables) are additionally made available under CC0 1.0 Universal (CC0) to enable unrestricted reuse in implementations and derivative specifications. Code examples in this document are provided under the Apache License 2.0 unless explicitly stated otherwise. Test vectors, where present, are dedicated to the public domain under CC0 1.0. --- ## 0. Overview `ENC/PEL1-RESULT/1` defines the **canonical binary encoding** of the surface-level execution result structure defined by `PEL/1-SURF` for PEL/1 executions. Concretely, this profile specifies: * the binary layout of the PEL/1 surface result value (`SurfaceExecutionResult`), including: * `scheme_ref`, `program_ref`, * `input_refs`, `output_refs`, * `params_ref`, `store_failure`, `trace_ref`, * an **inline** encoding of `ExecutionResultValue` from `PEL/1-CORE`; * how that value is embedded into ASL/1 `Artifact.bytes` as `ResultBytes`; * the dedicated `TypeTag` used for result Artifacts under this profile. Result Artifacts are ordinary ASL/1 Artifacts with: ```text Artifact.type_tag = TYPE_TAG_PEL1_RESULT_1 Artifact.bytes = ResultBytes ``` Identity of a result Artifact is then derived from canonical `ArtifactBytes` (`ENC/ASL1-CORE v1`) and a chosen `HashId` (typically `HASH-ASL1-256`), but hash algorithm choice is **not** part of this encoding spec. This profile is: * **injective** — distinct logical surface result values → distinct `ResultBytes`; * **stable and deterministic** — same logical value → same bytes across implementations and time; * **streaming-friendly** — encoders/decoders can operate in a single forward pass. It does **not** redefine PEL/1 execution semantics or store behavior; it only fixes the layout of the surface result value described by `PEL/1-SURF`. > **Encoding vs PEL versioning** > `pel1_version` fields in this profile encode the PEL/1-CORE major version (`1`), > **not** the encoding profile version. Encoding version is selected by context > via `PEL_ENC_EXECUTION_RESULT_V1` and `TYPE_TAG_PEL1_RESULT_1`, not by any > on-wire version field. --- ## 1. Scope & Layering ### 1.1 Purpose This specification defines: * The **binary layout** of: * `ResultBytes` — top-level surface result encoding (`SurfaceExecutionResult`); * `ExecutionResultValueBytes` — inline encoding of `ExecutionResultValue` (`PEL/1-CORE`); * `DiagnosticEntryBytes` — scheme diagnostics, reusing the pattern from `ENC/PEL-TRACE-DAG/1`; * `StoreFailureBytes` — store-resolution diagnostics, aligned with `PEL/1-SURF`; * an internal wrapper for embedded `Reference` values (`EncodedRef`). * The **field ordering**, integer widths, list framing, and presence flags. * The **binding** between `ResultBytes` and a dedicated result `TypeTag`. It does **not** define: * PEL/1-CORE execution semantics (`Exec_s`) or scheme semantics; * PEL/1-SURF surface semantics (how stores are wired, which Artifacts *must* be persisted); * hash algorithms or Reference derivation (from `ASL/1-CORE` + `HASH/ASL1`); * trace graph semantics or edges (`TGK/1-CORE` and profiles). ### 1.2 Layering constraints In line with `SUBSTRATE/STACK-OVERVIEW`: * `ENC/PEL1-RESULT/1` is a **scheme-specific encoding profile** for a PEL/1 surface result structure. * It MUST NOT redefine: * `Artifact`, `TypeTag`, `Reference`, `HashId` (`ASL/1-CORE`); * `ExecutionResultValue`, `ExecutionStatus`, `ExecutionErrorSummary`, `DiagnosticEntry` (`PEL/1-CORE`); * the logical surface result structure (from `PEL/1-SURF`). * It is **storage-neutral** and **policy-neutral**: * it does not talk about particular stores, transports, or policies; * it does not depend on TGK, CIL, FER, FCT, OI, or any domain profiles. This profile defines exactly one canonical encoding for PEL/1 surface result values of the shape described in §3.1. --- ## 2. Conventions RFC 2119 terms (**MUST**, **SHOULD**, **MAY**, etc.) are normative. ### 2.1 Integer encodings All multi-byte integers in this profile are encoded as **big-endian** (network byte order), as in `ENC/ASL1-CORE` and other PEL encodings: * `u8` — 1 byte * `u16` — 2 bytes * `u32` — 4 bytes * `u64` — 8 bytes Only fixed-width integers are used in this specification. ### 2.2 Lists A list of values of some type `T` is encoded as: ```text List = count (u32) element_0 element_1 ... element_{count-1} ``` * `count` is the number of elements (MAY be zero). * Elements are encoded in order using the canonical encoding for `T`. This **u32 length + elements** pattern matches `ENC/PEL-PROGRAM-DAG/1` and `ENC/PEL-TRACE-DAG/1`. ### 2.3 Utf8String If this profile needs UTF-8 strings, it uses the same `Utf8String` encoding as other PEL encodings: ```text Utf8String = length (u32) bytes[0..length-1] ``` * `length` is the number of bytes. * `bytes` MUST be well-formed UTF-8. * There is no terminator or padding. The core surface result structure currently does not include free-form text fields; diagnostics use an opaque blob type instead (§2.4). ### 2.4 Blob32 (Octet blob with 32-bit length) For diagnostic messages and other opaque payloads, we use: ```text Blob32 = length (u32) bytes[0..length-1] ``` * `bytes` is an arbitrary `OctetString`; interpretation is profile- or scheme-specific. * `length` MAY be zero. This is the same `Blob32` convention used by `ENC/PEL-TRACE-DAG/1`. It is a profile-local convenience; it does **not** override the general `OctetString` encoding from `ENC/ASL1-CORE` (which uses `u64` lengths at the ASL core level). ### 2.5 Embedded Reference (`EncodedRef`) Within this encoding, `Reference` values are embedded using a length-prefixed wrapper over canonical `ReferenceBytes` from `ENC/ASL1-CORE v1`: ```text EncodedRef = ref_len (u32) ref_bytes (byte[0..ref_len-1]) // canonical ReferenceBytes ``` Where: * `ref_bytes` MUST be the canonical `ReferenceBytes` encoding for a `Reference`: ```text ReferenceBytes :: hash_id (u16) digest (byte[...]) // remaining bytes in the frame ``` * `ref_len` MUST be the exact length (in bytes) of `ref_bytes`, and MUST be ≥ 2. Decoders MUST: * Read `ref_len (u32)`, then `ref_bytes[0..ref_len-1]`. * Decode `ref_bytes` as `ReferenceBytes` per `ENC/ASL1-CORE v1`. * Reject encodings where: * `ref_len < 2`, or * `ref_bytes` is not a valid `ReferenceBytes` sequence. This pattern is identical to the `EncodedRef` used by `ENC/PEL-TRACE-DAG/1`. #### 2.5.1 Optional `EncodedRef` Optional `Reference` fields are encoded as: ```text OptionalEncodedRef = has_ref (u8) [ EncodedRef ] // only if has_ref = 0x01 ``` * `has_ref = 0x00` → no value present; no `EncodedRef` follows. * `has_ref = 0x01` → exactly one `EncodedRef` follows. * Any other `has_ref` value MUST be treated as an encoding error. ### 2.6 Embedded `StoreFailure` (`EncodedStoreFailure`) `StoreFailure` (see §3.3) is embedded using: ```text StoreFailureBytes :: phase (u8) // StoreFailurePhase error_code (u8) // StoreErrorCode failing_ref (EncodedRef) ``` Optional `StoreFailure` is encoded as: ```text OptionalStoreFailureBytes :: has_store_failure (u8) [ store_failure (StoreFailureBytes) ] // if has_store_failure = 0x01 ``` * `has_store_failure = 0x00` → no `store_failure` present. * `has_store_failure = 0x01` → exactly one `StoreFailureBytes` follows. * Any other value MUST be treated as an encoding error. Decoders MUST: * Validate `phase` and `error_code` are within the sets defined in `PEL/1-SURF` (or treat out-of-range values as encoding errors or semantic errors per policy). * Decode `failing_ref` via `EncodedRef`. --- ## 3. Logical Surface Result Model (Reference) This section restates, in condensed form, the logical PEL/1 surface result structure from `PEL/1-SURF`. The normative source for semantics is `PEL/1-SURF`; this profile only encodes the agreed structure. ### 3.1 SurfaceExecutionResult structure `PEL/1-SURF` defines the logical payload of the surface ExecutionResult artifact as: ```text SurfaceExecutionResult { pel1_version : uint16 core_result : ExecutionResultValue scheme_ref : SchemeRef // echo program_ref : ProgramRef input_refs : InputRefList output_refs : OutputRefList params_ref : optional ParamsRef store_failure: optional StoreFailure trace_ref : optional TraceRef } ``` Where `ExecutionResultValue` is from `PEL/1-CORE` (§3.2), and: ```text SchemeRef = Reference ProgramRef = Reference InputRef = Reference OutputRef = Reference ParamsRef = Reference TraceRef = Reference InputRefList = list OutputRefList = list ``` Semantics (summarised from `PEL/1-SURF`): * `pel1_version` reaffirms the PEL major version (`1` for `PEL/1-CORE`). * `core_result` is the PEL/1-CORE execution result for this run. * `scheme_ref`, `program_ref`, `input_refs`, `params_ref` reflect the artifacts used to *start* the execution. * `output_refs` reflect the artifacts produced and successfully persisted by the surface for this run. * `store_failure`, when present, describes a **store-resolution error** that prevented `Exec_s` from being called, or prevented inputs/params from being resolved, in which case `core_result.status` is `INVALID_PROGRAM` or `INVALID_INPUTS` for store-level reasons. * `trace_ref`, when present, points at a trace Artifact whose logical value is defined by some trace profile (e.g. `PEL/TRACE-DAG/1`). Store failures, transport errors, or environment failures that prevent construction of this artifact are **not** part of `SurfaceExecutionResult`; those are handled at the surface/API layer, not via this encoding. ### 3.2 ExecutionResultValue (from PEL/1-CORE) From `PEL/1-CORE`: ```text ExecutionStatus = uint8 ExecutionErrorKind = uint8 ExecutionErrorSummary { kind : ExecutionErrorKind status_code : uint32 } DiagnosticEntry { code : uint32 message : OctetString // scheme-defined; SHOULD be UTF-8 text } ExecutionResultValue { pel1_version : uint16 status : ExecutionStatus scheme_ref : SchemeRef // Reference summary : ExecutionErrorSummary diagnostics : list } ``` Invariants (from `PEL/1-CORE`): * `pel1_version` MUST be 1. * `status` and `summary` MUST satisfy: * `status = OK` → `summary.kind = NONE`, `summary.status_code = 0`. * `status = SCHEME_UNSUPPORTED` → `summary.kind = SCHEME`. * `status = INVALID_PROGRAM` → `summary.kind = PROGRAM`. * `status = INVALID_INPUTS` → `summary.kind = INPUTS`. * `status = RUNTIME_FAILED` → `summary.kind = RUNTIME` and `summary.status_code != 0`. * `scheme_ref` MUST match the scheme under which execution was interpreted. * `diagnostics` MUST be deterministic for a given `(scheme_ref, program, inputs, params)`. This profile defines a canonical inline encoding for `ExecutionResultValue` and `DiagnosticEntry` inside `ResultBytes`. ### 3.3 StoreFailure (from PEL/1-SURF) `PEL/1-SURF` refines store-resolution errors via: ```text StoreErrorCode = uint8 StoreErrorCode { NOT_FOUND = 1 // Store returned ERR_NOT_FOUND INTEGRITY = 2 // Store returned ERR_INTEGRITY UNSUPPORTED = 3 // Store returned ERR_UNSUPPORTED } StoreFailurePhase = uint8 StoreFailurePhase { PROGRAM = 1 INPUT = 2 } StoreFailure { phase : StoreFailurePhase error_code : StoreErrorCode failing_ref : Reference } ``` Semantics: * `phase = PROGRAM` indicates the failure happened while resolving `program_ref`. * `phase = INPUT` indicates it happened while resolving an input or `params_ref`. * `failing_ref` MUST be the exact `Reference` passed to `get` that produced the error. * `error_code` MUST match the store’s reported error. This encoding profile provides a canonical encoding for `StoreFailure` via `StoreFailureBytes` and `OptionalStoreFailureBytes` (§2.6, §4.3). --- ## 4. Encoding This section defines: * `DiagnosticEntryBytes` — inline diagnostic entry encoding; * `ExecutionResultValueBytes` — inline `ExecutionResultValue` encoding; * `StoreFailureBytes` / `OptionalStoreFailureBytes` — store-resolution diagnostics; * `ResultBytes` — top-level PEL/1 surface result encoding. Field ordering, integer widths, and presence flags are fixed and MUST NOT vary. ### 4.1 DiagnosticEntry encoding Logical: ```text DiagnosticEntry { code : uint32 message : OctetString } ``` Canonical encoding (matching `ENC/PEL-TRACE-DAG/1`): ```text DiagnosticEntryBytes :: code (u32) message (Blob32) ``` Where `Blob32` is defined in §2.4. * `code (u32)` encodes the diagnostic or error code. * `message` is an opaque byte blob. Schemes MAY agree to use UTF-8 text here, but this encoding does not enforce it. Decoders MUST: * Read `code (u32)`. * Read `message` as `Blob32`. * Treat truncated blobs as encoding errors. ### 4.2 ExecutionResultValue encoding Logical: ```text ExecutionResultValue { pel1_version : uint16 status : ExecutionStatus scheme_ref : SchemeRef summary : ExecutionErrorSummary diagnostics : list } ``` Canonical encoding: ```text ExecutionResultValueBytes :: pel1_version (u16) status (u8) // ExecutionStatus scheme_ref (EncodedRef) summary_kind (u8) // ExecutionErrorKind summary_status_code (u32) // ExecutionErrorSummary.status_code diag_count (u32) diagnostics (DiagnosticEntryBytes[0..diag_count-1]) ``` Field semantics: 1. `pel1_version (u16)` * MUST be `1` for `ExecutionResultValue` values defined by `PEL/1-CORE v0.3.x`. * Decoders: * MUST accept `pel1_version = 1`. * MUST treat other values as encoding errors for this profile revision. 2. `status (u8)` * Encodes `ExecutionStatus` as defined in `PEL/1-CORE`. * Decoders SHOULD treat out-of-range `status` values as encoding errors (rather than attempting to map them to an “unknown” variant). 3. `scheme_ref (EncodedRef)` * Encodes the `SchemeRef` (`Reference`) under which the scheme was interpreted. * MUST match the `scheme_ref` at the surface level (§4.4.2, §6.2). 4. `summary_kind (u8)` and `summary_status_code (u32)` * Encode `ExecutionErrorSummary.kind` and `ExecutionErrorSummary.status_code` respectively. * Encoders MUST set these to satisfy the invariants from `PEL/1-CORE §2.4` for the chosen `status`. * Decoders MAY validate consistency; inconsistency is a semantic error, not strictly an encoding error, but implementations MAY choose to reject such values. 5. `diag_count (u32)` and `diagnostics (DiagnosticEntryBytes[..])` * Encodes the list of diagnostics as a `List`. * `diag_count` is the number of entries; MAY be zero. * Decoders MUST reject truncated sequences where fewer than `diag_count` entries are present. This layout is also suitable for use by other profiles that choose to store `ExecutionResultValue` as a dedicated Artifact; see §5 (informative note). ### 4.3 StoreFailure encoding Logical: ```text StoreFailure { phase : StoreFailurePhase error_code : StoreErrorCode failing_ref : Reference } ``` Canonical encoding: ```text StoreFailureBytes :: phase (u8) // StoreFailurePhase error_code (u8) // StoreErrorCode failing_ref (EncodedRef) ``` Optional presence: ```text OptionalStoreFailureBytes :: has_store_failure (u8) [ store_failure (StoreFailureBytes) ] // if has_store_failure = 0x01 ``` Field semantics: 1. `phase (u8)` * Encodes `StoreFailurePhase`: * `1` → PROGRAM * `2` → INPUT * Decoders SHOULD treat other values as encoding errors or semantic errors per implementation policy. 2. `error_code (u8)` * Encodes `StoreErrorCode`: * `1` → NOT_FOUND * `2` → INTEGRITY * `3` → UNSUPPORTED * Decoders SHOULD treat other values as encoding errors or semantic errors per implementation policy. 3. `failing_ref (EncodedRef)` * Encodes the `Reference` that failed to resolve. 4. `has_store_failure (u8)` * `0x00` → no `store_failure` present. * `0x01` → exactly one `StoreFailureBytes` follows. * Any other value MUST be treated as an encoding error. Decoders MUST reject truncated values (e.g., missing `failing_ref` bytes). ### 4.4 Surface Result encoding (`ResultBytes`) Logical (from §3.1): ```text SurfaceExecutionResult { pel1_version : uint16 core_result : ExecutionResultValue scheme_ref : SchemeRef program_ref : ProgramRef input_refs : InputRefList output_refs : OutputRefList params_ref : optional ParamsRef store_failure: optional StoreFailure trace_ref : optional TraceRef } ``` Canonical encoding: ```text ResultBytes :: pel1_version (u16) scheme_ref (EncodedRef) program_ref (EncodedRef) input_ref_count (u32) input_refs (EncodedRef[0..input_ref_count-1]) output_ref_count (u32) output_refs (EncodedRef[0..output_ref_count-1]) has_params_ref (u8) [ params_ref (EncodedRef) ] // if has_params_ref == 0x01 has_store_failure (u8) [ store_failure (StoreFailureBytes) ] // if has_store_failure == 0x01 has_trace_ref (u8) [ trace_ref (EncodedRef) ] // if has_trace_ref == 0x01 core_result (ExecutionResultValueBytes) ``` Field semantics: 1. `pel1_version (u16)` * MUST be `1` for surface results representing `PEL/1-CORE v0.3.x` executions. * Decoders MUST treat any other value as an encoding error for this profile revision. 2. `scheme_ref (EncodedRef)` * Encodes the scheme descriptor `SchemeRef` (`Reference`) for the run. * MUST match `ExecutionResultValue.scheme_ref` encoded inside `core_result.scheme_ref`; see §6.2. 3. `program_ref (EncodedRef)` * Encodes the program Artifact reference used for this run. 4. `input_ref_count (u32)` and `input_refs (EncodedRef[..])` * Encodes the ordered list of input references provided to the surface. * `input_ref_count` MAY be zero. * Encoders MUST preserve the logical order of `inputs`. * Decoders MUST reject truncated sequences with fewer than `input_ref_count` entries. 5. `output_ref_count (u32)` and `output_refs (EncodedRef[..])` * Encodes the ordered list of output references produced and successfully persisted by the surface. * `output_ref_count` MAY be zero even when `core_result.status = OK` for schemes that legitimately produce no outputs. * Encoders MUST preserve the logical output order defined by the scheme/surface. * Decoders MUST reject truncated sequences. 6. `has_params_ref (u8)` and `params_ref (EncodedRef)` * Encodes an optional parameters Artifact reference. * `has_params_ref = 0x00` → no `params_ref` present. * `has_params_ref = 0x01` → exactly one `EncodedRef` follows and encodes `params_ref`. * Any other `has_params_ref` value MUST be treated as an encoding error. * Surfaces that do not support parameter Artifacts MUST always emit `has_params_ref = 0x00`. 7. `has_store_failure (u8)` and `store_failure (StoreFailureBytes)` * Encodes an optional `StoreFailure` as defined in §3.3 and §4.3. * `has_store_failure = 0x00` → no `store_failure` present. * `has_store_failure = 0x01` → exactly one `StoreFailureBytes` follows. * Any other `has_store_failure` value MUST be treated as an encoding error. * When `store_failure` is present: * `core_result.status` MUST be `INVALID_PROGRAM` or `INVALID_INPUTS` according to the phase (PROGRAM or INPUT) as defined in `PEL/1-SURF`. * `core_result.summary.kind` MUST be `PROGRAM` or `INPUTS` accordingly. 8. `has_trace_ref (u8)` and `trace_ref (EncodedRef)` * Encodes an optional reference to a trace Artifact (e.g. a `PEL/TRACE-DAG/1` value encoded under `ENC/PEL-TRACE-DAG/1`). * `has_trace_ref` uses the same conventions as `has_params_ref`. * Surfaces that never produce traces MUST always emit `has_trace_ref = 0x00`. 9. `core_result (ExecutionResultValueBytes)` * Encodes the inline `ExecutionResultValue` for this run as defined in §4.2. * The `pel1_version`, `status`, `scheme_ref`, `summary`, and `diagnostics` fields MUST respect `PEL/1-CORE` invariants. * `core_result.scheme_ref` MUST equal the surface-level `scheme_ref`. --- ## 5. Result Artifact Binding ### 5.1 TypeTag Result Artifacts for this profile MUST be ASL/1 Artifacts with: ```text Artifact { bytes = ResultBytes type_tag = TYPE_TAG_PEL1_RESULT_1 } ``` Where: * `TYPE_TAG_PEL1_RESULT_1` is a `TypeTag` with a concrete `tag_id` assigned in the global TypeTag registry for PEL/1 surface results. This encoding profile: * refers to `TYPE_TAG_PEL1_RESULT_1` symbolically; and * does not assign a numeric `tag_id` (that belongs in the registry). ### 5.2 Identity via ASL/1-CORE With `ENC/ASL1-CORE v1` and the "ASL1" hash family (`HASH/ASL1`): 1. Canonical `ArtifactBytes` for a result Artifact: ```text ArtifactBytes = encode_artifact_core_v1( Artifact{ bytes = ResultBytes, type_tag = TYPE_TAG_PEL1_RESULT_1 } ) ``` 2. Canonical `Reference` for any chosen `HashId = HID`: ```text digest = H(ArtifactBytes) // H from HASH/ASL1 for HID reference = Reference { hash_id = HID, digest = digest } ``` All conformant implementations using the same `(EncodingProfileId, HashId)` pair MUST agree on: * `ResultBytes` for a given logical surface result value, * the resulting `ArtifactBytes` for the result Artifact, * the resulting `Reference`. > **Informative note (ExecutionResultValue as Artifact)** > Other profiles MAY choose to store `ExecutionResultValue` as a dedicated > Artifact using `ExecutionResultValueBytes` as `Artifact.bytes` and a dedicated > TypeTag (e.g. `TYPE_TAG_PEL_EXECUTION_RESULT_VALUE_1`). Such a binding is > intentionally left out of this version; if introduced, it SHOULD reuse > `ExecutionResultValueBytes` exactly to preserve injectivity across inline and > stored forms. --- ## 6. Canonicality & Determinism ### 6.1 Injectivity The mapping: ```text SurfaceExecutionResult -> ResultBytes ``` defined by this profile MUST be **injective**: * If two logical `SurfaceExecutionResult` values differ (per `PEL/1-SURF` and `PEL/1-CORE` semantics), then: ```text ResultBytes(V1) != ResultBytes(V2) ``` Injectivity is ensured by: * fixed field ordering; * explicit presence flags for all optional references and store failures; * deterministic list ordering (`input_refs`, `output_refs`, `diagnostics`); * inclusion of all logically relevant fields. Similarly, the mapping: ```text ExecutionResultValue -> ExecutionResultValueBytes ``` MUST be injective. ### 6.2 Stability & consistency For any fixed logical value and encoding profile version: * All conformant encoders MUST produce the same `ExecutionResultValueBytes` and `ResultBytes`. * Encodings MUST be stable across implementations, platforms, and time. Encoders MUST NOT: * reorder list elements (`input_refs`, `output_refs`, `diagnostics`); * omit or reorder fields; * vary integer widths or encodings; * introduce alternative layouts for version negotiation. In addition, encoders MUST enforce: * `pel1_version` fields for both the surface result and the inline `ExecutionResultValue` are `1`. * `scheme_ref` in the surface header and `scheme_ref` inside `ExecutionResultValue` are identical. * When `store_failure` is present, `core_result.status` and `core_result.summary.kind` reflect `INVALID_PROGRAM`/`PROGRAM` or `INVALID_INPUTS`/`INPUTS` as mandated by `PEL/1-SURF`. ### 6.3 Extension and evolution (informative) `ENC/PEL1-RESULT/1` is intended to evolve **additively**: * New optional fields SHOULD be introduced via new encoding profiles or clearly versioned layouts, rather than overloading this layout in incompatible ways. * Any future revision that extends `ResultBytes` or `ExecutionResultValueBytes` MUST preserve all existing fields, their order, and their semantics for `pel1_version = 1`. * Changes that would alter the meaning of existing bytes (for the same logical value) MUST be expressed as a new profile or a new result TypeTag, not as a silent change to this profile. --- ## 7. Error Handling (Encoding Layer) Decoders for this profile MUST treat as **encoding errors**: 1. **Truncated values** * Insufficient bytes to read any declared integer, length prefix, `EncodedRef`, `DiagnosticEntryBytes`, `StoreFailureBytes`, or `ExecutionResultValueBytes`. 2. **Invalid `pel1_version`** * `pel1_version != 1` in either: * the surface-level header (§4.4), or * `ExecutionResultValueBytes` (§4.2). 3. **Invalid presence flags** * `has_params_ref`, `has_store_failure`, or `has_trace_ref` not in `{0x00, 0x01}`. 4. **Invalid `EncodedRef`** * `ref_len < 2`, or * `ref_bytes` cannot be decoded as canonical `ReferenceBytes` under `ENC/ASL1-CORE v1`. 5. **Invalid list counts** * Fewer entries than indicated by `input_ref_count`, `output_ref_count`, or `diag_count`. 6. **Invalid `Utf8String` (if used)** * Any `Utf8String` field present in a future extension that is not valid UTF-8. Decoders SHOULD also treat as encoding errors: * `status` values outside the range defined by `PEL/1-CORE` for `ExecutionStatus`; * `summary_kind` values outside the range defined for `ExecutionErrorKind`; * `phase` or `error_code` values outside the ranges defined for `StoreFailurePhase` and `StoreErrorCode`, if the implementation chooses to enforce these at the encoding layer. Decoders MAY treat as either encoding errors or semantic errors (per implementation policy): * mismatches between `status` and `summary` invariants (e.g. `status = OK` but `summary.kind != NONE` or `summary.status_code != 0`); * inconsistencies between `store_failure` presence and `core_result.status` / `core_result.summary.kind`. Mapping from encoding errors to external error codes (e.g. `ERR_PEL_RESULT_ENC_INVALID`) is implementation-specific. These are strictly encoding-layer issues; store failures, scheme errors, and runtime failures are represented inside `ExecutionResultValue` and `StoreFailure` and handled by PEL/1-CORE and PEL/1-SURF. --- ## 8. Streaming & Implementation Notes Encoders and decoders MUST support **single-pass** operation: * **Encoding**: * Encoders MUST be able to generate `ResultBytes` in a single forward pass over the logical `SurfaceExecutionResult`, assuming the structure is known in memory (or the list counts are precomputed). * They MAY need to compute `input_ref_count`, `output_ref_count`, and `diag_count` before emitting the corresponding lists. * **Decoding**: * Decoders MUST be able to decode `ResultBytes` in a single forward pass, with no backtracking. * All length prefixes appear before their content; no speculative reads are required. For large results: * Implementations MAY stream lists (e.g. `input_refs`, `output_refs`, `diagnostics`) to consumers as they decode them. * Implementations MUST ensure that any observable behavior (including error reporting and any reconstructed value) is independent of I/O chunking or buffer boundaries. Implementations may also: * compute hashes incrementally over `ArtifactBytes` (for result Artifacts), * pipeline decoding of `ResultBytes` directly into downstream consumers (e.g. inspection, provenance edge generators) without materializing the entire object in memory, as long as the logical value is representable. --- ## 9. Conformance An implementation is **ENC/PEL1-RESULT/1–conformant** if it: 1. **Implements the layouts exactly** * Encodes and decodes `ExecutionResultValueBytes`, `StoreFailureBytes`, and `ResultBytes` exactly as defined in §4.2, §4.3, and §4.4. * Uses the correct field ordering, integer widths, list framing, and presence flags. 2. **Uses canonical sub-encodings** * Embeds `Reference` values using `EncodedRef` over canonical `ReferenceBytes` (`ENC/ASL1-CORE v1`). * Encodes `DiagnosticEntry` using `DiagnosticEntryBytes` with `Blob32` as in §4.1. * Encodes `StoreFailure` using `StoreFailureBytes` as in §4.3. 3. **Preserves injectivity and stability** * Ensures distinct logical `SurfaceExecutionResult` values produce distinct `ResultBytes`. * Ensures distinct logical `ExecutionResultValue` values produce distinct `ExecutionResultValueBytes`. * Ensures the same logical values always encode to the same bytes (no configuration or environment affecting layout). 4. **Binds to result Artifacts correctly** * When forming surface result Artifacts, sets: ```text Artifact.bytes = ResultBytes Artifact.type_tag = TYPE_TAG_PEL1_RESULT_1 ``` * Uses `ENC/ASL1-CORE v1` and `HASH/ASL1` for identity derivation. 5. **Respects layering** * Does not depend on TGK, CIL, FER, FCT, or OI to encode or decode result values. * Does not reinterpret or override `ExecutionResultValue` or `StoreFailure` semantics beyond what is specified in `PEL/1-CORE` and `PEL/1-SURF`. Everything else — storage configuration, trace persistence, provenance edges, receipts, facts, overlays — is outside the scope of this encoding profile, provided it does not contradict the rules above. --- ## 10. Informative Example > This example illustrates field layout only. > Values and hex are illustrative, not normative test vectors. Consider a simple run with: * `pel1_version = 1` * `scheme_ref = S` * `program_ref = P` * Inputs: `input_refs = [I0, I1]` * Outputs: `output_refs = [O0]` * `params_ref = absent` * `store_failure = absent` * `trace_ref = T` * Inline `core_result : ExecutionResultValue`: ```text pel1_version = 1 status = OK scheme_ref = S summary.kind = NONE summary.status_code = 0 diagnostics = [] // empty ``` Then `ResultBytes` are: ```text pel1_version = 0001 ; u16 scheme_ref = EncodedRef(S) program_ref = EncodedRef(P) input_ref_count = 00000002 ; 2 input_refs = EncodedRef(I0) EncodedRef(I1) output_ref_count = 00000001 ; 1 output_refs = EncodedRef(O0) has_params_ref = 00 ; none has_store_failure = 00 ; none has_trace_ref = 01 trace_ref = EncodedRef(T) ; Inline core_result (ExecutionResultValueBytes) pel1_version = 0001 status = 00 ; OK (ExecutionStatus) scheme_ref = EncodedRef(S) summary_kind = 00 ; NONE (ExecutionErrorKind) summary_status_code = 00000000 ; 0 diag_count = 00000000 ; no diagnostics ; (no DiagnosticEntryBytes follow) ``` Where each `EncodedRef(X)` is: ```text ref_len(X) (u32) || ReferenceBytes(X) ``` with `ReferenceBytes(X)` = `hash_id (u16)` + `digest` bytes, per `ENC/ASL1-CORE v1`. All conformant encoders MUST produce the same `ResultBytes` for this logical surface result value; all conformant decoders MUST reconstruct the same logical value from those bytes. --- ## Document History * **0.3.0 (2025-11-17):** Promoted the ExecutionResult encoding profile to Approved status and pinned PH06 vectors/tests to this canonical layout. * **0.2.3 (2025-11-17):** Aligned logical shape and encoding with `PEL/1-SURF v0.2.1` (added `StoreFailure` encoding, removed `exec_result_ref`, renamed inline `result_value` to `core_result`). * **0.2.2 (2025-11-16):** Tightened consistency rules between surface header and inline `ExecutionResultValue`, clarified PEL vs encoding versioning, and strengthened decoding recommendations for out-of-range status values. * **0.2.1 (2025-11-16):** Aligned `ExecutionResultValueBytes` field order with `PEL/1-CORE`, clarified flag semantics for unsupported optional fields, and added an evolution note for future extensions. * **0.2.0 (2025-11-16):** Integrated earlier CODEx draft ideas; defined explicit `ExecutionResultValueBytes`, aligned diagnostics encoding with `ENC/PEL-TRACE-DAG/1`, and fixed full `ResultBytes` layout with `EncodedRef`/`u32` list framing. * **0.1.0 (2025-11-16):** Initial skeleton for `ENC/PEL1-RESULT/1` (high-level scope, dependencies, and rough layout outline).