diff --git a/CMakeLists.txt b/CMakeLists.txt index 42541c6..c63cc3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,7 @@ set(AMDUAT_ASL_SRCS src/near_core/asl/collection_view.c src/near_core/asl/none.c src/near_core/asl/artifact_io.c + src/near_core/asl/identity.c src/near_core/asl/io.c src/near_core/asl/index_accel.c src/near_core/asl/index_bloom.c @@ -78,6 +79,8 @@ set(AMDUAT_ASL_SRCS src/near_core/asl/store.c src/near_core/asl/ref_text.c src/near_core/asl/registry.c + src/near_core/bytestore.c + src/near_core/fps.c ) set(AMDUAT_ASL_LOG_STORE_SRCS @@ -106,6 +109,8 @@ set(AMDUAT_ENC_SRCS src/near_core/enc/asl_log.c src/near_core/enc/asl_core_index.c src/near_core/enc/asl_tgk_exec_plan.c + src/near_core/enc/pcb1.c + src/near_core/enc/fcs1.c src/near_core/enc/pel_program_dag.c src/near_core/enc/pel_program_dag_desc.c src/near_core/enc/pel_trace_dag.c @@ -441,6 +446,26 @@ target_link_libraries(amduat_test_asl_tgk_exec_plan ) add_test(NAME asl_tgk_exec_plan COMMAND amduat_test_asl_tgk_exec_plan) +add_executable(amduat_test_fps tests/enc/test_fps.c) +target_include_directories(amduat_test_fps + PRIVATE ${AMDUAT_INTERNAL_DIR} + PRIVATE ${AMDUAT_INCLUDE_DIR} +) +target_link_libraries(amduat_test_fps + PRIVATE amduat_enc amduat_hash_asl1 amduat_asl amduat_util +) +add_test(NAME fps COMMAND amduat_test_fps) + +add_executable(amduat_test_fcs_pcb1 tests/enc/test_fcs_pcb1.c) +target_include_directories(amduat_test_fcs_pcb1 + PRIVATE ${AMDUAT_INTERNAL_DIR} + PRIVATE ${AMDUAT_INCLUDE_DIR} +) +target_link_libraries(amduat_test_fcs_pcb1 + PRIVATE amduat_enc amduat_hash_asl1 amduat_asl amduat_util +) +add_test(NAME fcs_pcb1 COMMAND amduat_test_fcs_pcb1) + add_executable(amduat_test_tgk1_edge tests/enc/test_tgk1_edge.c) target_include_directories(amduat_test_tgk1_edge PRIVATE ${AMDUAT_INTERNAL_DIR} @@ -471,6 +496,17 @@ target_link_libraries(amduat_test_tgk_store_mem ) add_test(NAME tgk_store_mem COMMAND amduat_test_tgk_store_mem) +add_executable(amduat_test_tgk_error_taxonomy + tests/tgk/test_tgk_error_taxonomy.c) +target_include_directories(amduat_test_tgk_error_taxonomy + PRIVATE ${AMDUAT_INTERNAL_DIR} + PRIVATE ${AMDUAT_INCLUDE_DIR} +) +target_link_libraries(amduat_test_tgk_error_taxonomy + PRIVATE amduat_tgk +) +add_test(NAME tgk_error_taxonomy COMMAND amduat_test_tgk_error_taxonomy) + add_executable(amduat_test_tgk_store_fs tests/tgk/test_tgk_store_fs.c) target_include_directories(amduat_test_tgk_store_fs PRIVATE ${AMDUAT_INTERNAL_DIR} @@ -598,6 +634,28 @@ target_link_libraries(amduat_test_asl_index_accel ) add_test(NAME asl_index_accel COMMAND amduat_test_asl_index_accel) +add_executable(amduat_test_asl_log_encoding + tests/asl/test_asl_log_encoding.c) +target_include_directories(amduat_test_asl_log_encoding + PRIVATE ${AMDUAT_INTERNAL_DIR} + PRIVATE ${AMDUAT_INCLUDE_DIR} +) +target_link_libraries(amduat_test_asl_log_encoding + PRIVATE amduat_asl +) +add_test(NAME asl_log_encoding COMMAND amduat_test_asl_log_encoding) + +add_executable(amduat_test_asl_error_taxonomy + tests/asl/test_asl_error_taxonomy.c) +target_include_directories(amduat_test_asl_error_taxonomy + PRIVATE ${AMDUAT_INTERNAL_DIR} + PRIVATE ${AMDUAT_INCLUDE_DIR} +) +target_link_libraries(amduat_test_asl_error_taxonomy + PRIVATE amduat_asl +) +add_test(NAME asl_error_taxonomy COMMAND amduat_test_asl_error_taxonomy) + add_executable(amduat_test_asl_derivation_index_fs tests/asl/test_asl_derivation_index_fs.c) target_include_directories(amduat_test_asl_derivation_index_fs diff --git a/docs/SUBSTRATE_CONTRACT_V0_1.md b/docs/SUBSTRATE_CONTRACT_V0_1.md index e3cc7dd..86747c8 100644 --- a/docs/SUBSTRATE_CONTRACT_V0_1.md +++ b/docs/SUBSTRATE_CONTRACT_V0_1.md @@ -17,11 +17,14 @@ it is introduced in this change and implemented in code (referenced below). 2. **Reference** = `(hash_id, digest)`; equality is hash-id + digest equality. Sources: `tier1/asl-1-core.md`, `include/amduat/asl/core.h`. -3. **Reference derivation** is based on canonical encoding + hash family; the API - exposes this via `amduat_asl_ref_derive`. - Sources: `tier1/asl-1-core.md`, `include/amduat/asl/ref_derive.h`. +3. **Reference derivation** uses canonical encoding plus the ASL1 domain + separator (`"AMDUAT:ASL1:ART\0"`) and hash family; the API exposes this via + `amduat_asl_ref_derive`. + Sources: `tier1/asl-1-core.md`, `include/amduat/asl/ref_derive.h`, + `include/amduat/asl/identity.h`. -4. **Hash family** in use for the stack is HASH/ASL1 (e.g., SHA-256). +4. **Hash family** in use for the stack is HASH/ASL1 (e.g., SHA-256), with + registry/version metadata exposed in the core API. Sources: `tier1/hash-asl1.md`, `include/amduat/hash/asl1.h`. --- diff --git a/docs/adr-031-asl-tgk-index-separation.md b/docs/adr-031-asl-tgk-index-separation.md new file mode 100644 index 0000000..2544059 --- /dev/null +++ b/docs/adr-031-asl-tgk-index-separation.md @@ -0,0 +1,50 @@ +# ADR-031 — ASL + TGK Index Separation + +Status: Accepted +Date: 2026-02-21 +Decision Owner: Architecture + +## Context + +ASL index/log provide artifact visibility, replay, and tombstone semantics. +TGK store provides graph query semantics over TGK edge artifacts. We must decide +whether to **merge** ASL and TGK indexes into one unified index structure, or +keep them separated with explicit cross-layer invariants. + +## Decision + +We **reject** a merged ASL+TGK index. + +ASL index/log and TGK index remain **separate layers** with strict invariants +that keep them consistent. + +## Rationale + +* ASL index is about **artifact visibility and replay**; TGK index is about + **graph query acceleration**. Their update streams and invariants differ. +* Keeping them separate preserves clean layering and deterministic replay. +* TGK indexes can be rebuilt or swapped without changing ASL semantics. +* A merged index risks coupling graph query policies with storage visibility, + complicating federation and policy enforcement. + +## Invariants (Normative) + +1. **Visibility alignment:** TGK indexes MUST only index artifacts visible under + ASL log replay at the same `(snapshot_id, log_position)`. +2. **Tombstone enforcement:** TGK indexes MUST respect ASL tombstones and + tombstone lifts at their `logseq`. +3. **No new identity:** TGK nodes/edges MUST use ASL `Reference` identities; + TGK MUST NOT introduce a distinct object identity scheme. +4. **Deterministic derivation:** TGK index updates MUST be deterministically + derived from ASL-admitted records (segments/log). No side-channel writes. +5. **Replay equivalence:** Replaying ASL log + sealed segments MUST yield the + same TGK index state (up to encoding) as an online build. +6. **Snapshot consistency:** TGK queries at a snapshot MUST be consistent with + ASL `IndexState` for that snapshot. + +## Consequences + +* ASL and TGK index encodings remain separate specs. +* Conformance tests for ASL and TGK can evolve independently, with explicit + cross-layer checks for the invariants above. +* TGK index rebuilds remain possible without touching ASL log/segment formats. diff --git a/include/amduat/asl/identity.h b/include/amduat/asl/identity.h new file mode 100644 index 0000000..976dee3 --- /dev/null +++ b/include/amduat/asl/identity.h @@ -0,0 +1,28 @@ +#ifndef AMDUAT_ASL_IDENTITY_H +#define AMDUAT_ASL_IDENTITY_H + +#include "amduat/asl/core.h" +#include "amduat/hash/asl1.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const char *hash_registry; + uint16_t hash_registry_version; + amduat_octets_t domain_separator; +} amduat_asl_identity_rules_t; + +/* Domain separator for ASL/1 artifact identity hashing. */ +amduat_octets_t amduat_asl_identity_domain_separator(void); + +const amduat_asl_identity_rules_t *amduat_asl_identity_rules(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_ASL_IDENTITY_H */ diff --git a/include/amduat/bytestore.h b/include/amduat/bytestore.h new file mode 100644 index 0000000..7afac89 --- /dev/null +++ b/include/amduat/bytestore.h @@ -0,0 +1,137 @@ +#ifndef AMDUAT_BYTESTORE_H +#define AMDUAT_BYTESTORE_H + +#include "amduat/asl/core.h" +#include "amduat/hash/asl1.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + AMDUAT_BYTESTORE_OK = 0, + AMDUAT_BYTESTORE_ERR_POLICY_SIZE = 1, + AMDUAT_BYTESTORE_ERR_IDENTITY_MISMATCH = 2, + AMDUAT_BYTESTORE_ERR_STREAM_ORDER = 3, + AMDUAT_BYTESTORE_ERR_STREAM_TRUNCATED = 4, + AMDUAT_BYTESTORE_ERR_AREA_VIOLATION = 5, + AMDUAT_BYTESTORE_ERR_CRASH_SIMULATION = 6, + AMDUAT_BYTESTORE_ERR_STORE_MISSING = 7, + AMDUAT_BYTESTORE_ERR_UNSUPPORTED = 8, + AMDUAT_BYTESTORE_ERR_IO = 9, + AMDUAT_BYTESTORE_ERR_INVALID = 10 +} amduat_bytestore_error_t; + +typedef struct { + amduat_hash_id_t hash_id; + size_t max_object_size; + const char *public_root; + const char *secure_root; +} amduat_bytestore_config_t; + +typedef struct { + bool present; + size_t size; +} amduat_bytestore_stat_t; + +typedef struct { + /* Return 1 for a chunk, 0 for end-of-stream, -1 on error. */ + int (*next)(void *ctx, amduat_octets_t *out_chunk); + void *ctx; +} amduat_bytestore_stream_t; + +typedef struct { + amduat_bytestore_error_t (*put)(void *ctx, + amduat_octets_t payload, + amduat_reference_t *out_cid); + amduat_bytestore_error_t (*put_stream)(void *ctx, + amduat_bytestore_stream_t stream, + amduat_reference_t *out_cid); + amduat_bytestore_error_t (*import_cor)(void *ctx, + amduat_octets_t envelope, + amduat_reference_t *out_cid); + amduat_bytestore_error_t (*export_cor)(void *ctx, + amduat_reference_t cid, + amduat_octets_t *out_envelope); + amduat_bytestore_error_t (*get)(void *ctx, + amduat_reference_t cid, + amduat_octets_t *out_bytes); + amduat_bytestore_error_t (*stat)(void *ctx, + amduat_reference_t cid, + amduat_bytestore_stat_t *out_stat); + amduat_bytestore_error_t (*assert_area_isolation)(void *ctx, + const char *public_root, + const char *secure_root); + amduat_bytestore_error_t (*validate_config)(void *ctx, + amduat_bytestore_config_t config); +} amduat_bytestore_ops_t; + +static inline void amduat_bytestore_ops_init(amduat_bytestore_ops_t *ops) { + if (ops == NULL) { + return; + } + ops->put = NULL; + ops->put_stream = NULL; + ops->import_cor = NULL; + ops->export_cor = NULL; + ops->get = NULL; + ops->stat = NULL; + ops->assert_area_isolation = NULL; + ops->validate_config = NULL; +} + +typedef struct { + amduat_bytestore_config_t config; + amduat_bytestore_ops_t ops; + void *ctx; +} amduat_bytestore_t; + +void amduat_bytestore_init(amduat_bytestore_t *store, + amduat_bytestore_config_t config, + amduat_bytestore_ops_t ops, + void *ctx); + +amduat_bytestore_error_t amduat_bytestore_put(amduat_bytestore_t *store, + amduat_octets_t payload, + amduat_reference_t *out_cid); +amduat_bytestore_error_t amduat_bytestore_put_stream( + amduat_bytestore_t *store, + amduat_bytestore_stream_t stream, + amduat_reference_t *out_cid); +amduat_bytestore_error_t amduat_bytestore_import_cor( + amduat_bytestore_t *store, + amduat_octets_t envelope, + amduat_reference_t *out_cid); +amduat_bytestore_error_t amduat_bytestore_export_cor( + amduat_bytestore_t *store, + amduat_reference_t cid, + amduat_octets_t *out_envelope); +amduat_bytestore_error_t amduat_bytestore_get(amduat_bytestore_t *store, + amduat_reference_t cid, + amduat_octets_t *out_bytes); +amduat_bytestore_error_t amduat_bytestore_stat( + amduat_bytestore_t *store, + amduat_reference_t cid, + amduat_bytestore_stat_t *out_stat); +amduat_bytestore_error_t amduat_bytestore_assert_area_isolation( + amduat_bytestore_t *store, + const char *public_root, + const char *secure_root); + +bool amduat_bytestore_cid_for_payload(amduat_hash_id_t hash_id, + amduat_octets_t payload, + amduat_reference_t *out_cid); + +bool amduat_bytestore_cid_for_stream(amduat_hash_id_t hash_id, + amduat_bytestore_stream_t stream, + amduat_reference_t *out_cid); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_BYTESTORE_H */ diff --git a/include/amduat/enc/fcs1.h b/include/amduat/enc/fcs1.h new file mode 100644 index 0000000..f35dc36 --- /dev/null +++ b/include/amduat/enc/fcs1.h @@ -0,0 +1,52 @@ +#ifndef AMDUAT_ENC_FCS1_H +#define AMDUAT_ENC_FCS1_H + +#include "amduat/asl/core.h" +#include "amduat/enc/pcb1.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + AMDUAT_FCS1_OK = 0, + AMDUAT_FCS1_ERR_HEADER_INVALID = 1, + AMDUAT_FCS1_ERR_TAG_ORDER = 2, + AMDUAT_FCS1_ERR_UNKNOWN_TAG = 3, + AMDUAT_FCS1_ERR_PARAMETER_FORMAT = 4, + AMDUAT_FCS1_ERR_PCB_ARITY_MISMATCH = 5, + AMDUAT_FCS1_ERR_CYCLE_DETECTED = 6, + AMDUAT_FCS1_ERR_VARINT_NON_MINIMAL = 7, + AMDUAT_FCS1_ERR_TRAILING_BYTES = 8, + AMDUAT_FCS1_ERR_INVALID = 9 +} amduat_fcs1_error_t; + +typedef struct { + amduat_reference_t function_ptr; + amduat_reference_t parameter_block; + uint32_t arity; +} amduat_fcs1_descriptor_t; + +typedef bool (*amduat_fcs1_resolve_fn)(void *ctx, + amduat_reference_t ref, + amduat_octets_t *out_bytes); + +amduat_fcs1_error_t amduat_fcs1_decode_v1(amduat_octets_t bytes, + amduat_fcs1_descriptor_t *out_desc); + +amduat_fcs1_error_t amduat_fcs1_validate_v1( + amduat_octets_t bytes, + amduat_fcs1_resolve_fn resolver, + void *resolver_ctx); + +void amduat_fcs1_descriptor_free(amduat_fcs1_descriptor_t *desc); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_ENC_FCS1_H */ diff --git a/include/amduat/enc/pcb1.h b/include/amduat/enc/pcb1.h new file mode 100644 index 0000000..8567341 --- /dev/null +++ b/include/amduat/enc/pcb1.h @@ -0,0 +1,38 @@ +#ifndef AMDUAT_ENC_PCB1_H +#define AMDUAT_ENC_PCB1_H + +#include "amduat/asl/core.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + AMDUAT_PCB1_OK = 0, + AMDUAT_PCB1_ERR_HEADER_INVALID = 1, + AMDUAT_PCB1_ERR_TAG_ORDER = 2, + AMDUAT_PCB1_ERR_UNKNOWN_TAG = 3, + AMDUAT_PCB1_ERR_MANIFEST_ORDER = 4, + AMDUAT_PCB1_ERR_DIGEST_MISMATCH = 5, + AMDUAT_PCB1_ERR_VARINT_NON_MINIMAL = 6, + AMDUAT_PCB1_ERR_TRAILING_BYTES = 7, + AMDUAT_PCB1_ERR_INVALID = 8 +} amduat_pcb1_error_t; + +typedef struct { + uint32_t slot_count; +} amduat_pcb1_manifest_t; + +amduat_pcb1_error_t amduat_pcb1_decode_v1(amduat_octets_t bytes, + amduat_pcb1_manifest_t *out_manifest, + amduat_octets_t *out_slot_data); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_ENC_PCB1_H */ diff --git a/include/amduat/fps.h b/include/amduat/fps.h new file mode 100644 index 0000000..516e93e --- /dev/null +++ b/include/amduat/fps.h @@ -0,0 +1,61 @@ +#ifndef AMDUAT_FPS_H +#define AMDUAT_FPS_H + +#include "amduat/asl/core.h" +#include "amduat/hash/asl1.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + AMDUAT_FPS_OK = 0, + AMDUAT_FPS_ERR_IO_FAILURE = 1, + AMDUAT_FPS_ERR_NORMALIZATION = 2, + AMDUAT_FPS_ERR_CID_NOT_FOUND = 3, + AMDUAT_FPS_ERR_SLICE_RANGE = 4, + AMDUAT_FPS_ERR_EMPTY_INPUTS = 5, + AMDUAT_FPS_ERR_REV_ALIGNMENT = 6, + AMDUAT_FPS_ERR_INVALID_LEVEL = 7, + AMDUAT_FPS_ERR_SPLICE_RANGE = 8, + AMDUAT_FPS_ERR_INVALID = 9 +} amduat_fps_error_t; + +typedef enum { + AMDUAT_FPS_REVERSE_BITS = 0, + AMDUAT_FPS_REVERSE_BYTES = 1, + AMDUAT_FPS_REVERSE_WORDS = 2, + AMDUAT_FPS_REVERSE_LONGS = 3 +} amduat_fps_reverse_level_t; + +amduat_fps_error_t amduat_fps_slice(amduat_octets_t input, + size_t offset, + size_t length, + amduat_octets_t *out); + +amduat_fps_error_t amduat_fps_concat(const amduat_octets_t *inputs, + size_t input_count, + amduat_octets_t *out); + +amduat_fps_error_t amduat_fps_reverse(amduat_octets_t input, + amduat_fps_reverse_level_t level, + amduat_octets_t *out); + +amduat_fps_error_t amduat_fps_splice(amduat_octets_t a, + size_t offset, + amduat_octets_t b, + amduat_octets_t *out); + +bool amduat_fps_cid_for_payload(amduat_hash_id_t hash_id, + amduat_octets_t payload, + amduat_reference_t *out_cid); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_FPS_H */ diff --git a/include/amduat/hash/asl1.h b/include/amduat/hash/asl1.h index 7af61e4..70958d6 100644 --- a/include/amduat/hash/asl1.h +++ b/include/amduat/hash/asl1.h @@ -34,6 +34,15 @@ typedef struct { } amduat_hash_asl1_stream_t; enum { AMDUAT_HASH_ASL1_ID_SHA256 = 0x0001 }; +enum { + AMDUAT_HASH_ASL1_VERSION_MAJOR = 0, + AMDUAT_HASH_ASL1_VERSION_MINOR = 2, + AMDUAT_HASH_ASL1_VERSION_PATCH = 5, + AMDUAT_HASH_ASL1_VERSION = 0x0205 +}; + +#define AMDUAT_HASH_ASL1_FAMILY "ASL1" +#define AMDUAT_HASH_ASL1_REGISTRY "HASH/ASL1" amduat_octets_t amduat_hash_asl1_key(amduat_hash_id_t hash_id, uint8_t out[2]); @@ -49,6 +58,11 @@ bool amduat_hash_asl1_digest(amduat_hash_id_t hash_id, amduat_octets_t input, uint8_t *out, size_t out_len); +bool amduat_hash_asl1_digest_domain(amduat_hash_id_t hash_id, + amduat_octets_t domain_separator, + amduat_octets_t input, + uint8_t *out, + size_t out_len); bool amduat_hash_asl1_stream_init(amduat_hash_id_t hash_id, amduat_hash_asl1_stream_t *out); diff --git a/registry/README.md b/registry/README.md index d405c18..9d6619c 100644 --- a/registry/README.md +++ b/registry/README.md @@ -24,6 +24,9 @@ are data nodes that can be hashed and referenced. ## JSONL manifests +Common conventions for all registry manifests are captured in +`registry/manifest.schema.md`. + Each registry has a `registry/.jsonl` manifest. Each line is one entry. The manifest is the source of truth for codegen and documentation tables. diff --git a/registry/enc-asl1-core.jsonl b/registry/enc-asl1-core.jsonl index b09c2fd..24a9a6f 100644 --- a/registry/enc-asl1-core.jsonl +++ b/registry/enc-asl1-core.jsonl @@ -1 +1 @@ -{"registry":"ENC/ASL1-CORE","profile_id":"0x0001","handle":"amduat.enc.asl1.core.profile.0001@1","name":"ASL_ENC_CORE_V1","version":"0x0105","status":"mandatory","spec_ref":"ENC/ASL1-CORE v1.0.5","notes":"Canonical encoding profile for ASL/1-CORE artifacts and references.","descriptor_sha256":"ed4ce5f50d4cb62d5e420da377ffcc0a767939f795b2c9ea00e57d79e12d93c2"} +{"registry":"ENC/ASL1-CORE","profile_id":"0x0001","handle":"amduat.enc.asl1.core.profile.0001@1","name":"ASL_ENC_CORE_V1","version":"0x0106","status":"mandatory","spec_ref":"ENC/ASL1-CORE v1.0.6","notes":"Canonical encoding profile for ASL/1-CORE artifacts and references.","descriptor_sha256":"2d86c1367149e37d076a63fc91673dbc56fe08df83c94a11e323a0185948a87b"} diff --git a/registry/manifest.schema.md b/registry/manifest.schema.md new file mode 100644 index 0000000..fc24e7f --- /dev/null +++ b/registry/manifest.schema.md @@ -0,0 +1,43 @@ +# Registry Manifest Schema (core) + +This file defines the common conventions for registry JSONL manifests in +`vendor/amduat/registry/`. Each registry then supplies a registry-specific +schema that fixes the key field(s) and the descriptor field ordering used for +`descriptor_sha256`. + +## JSONL rules + +- Each line is one JSON object (JSONL). +- Fields must match the registry-specific schema. +- Required fields must be present even if the value is `null`. + +## Handle naming + +Handles are stable identifiers in the `amduat` namespace: + +``` +amduat....@ +``` + +`id` is the canonical key for the registry (usually lowercase, zero-padded hex). +`version` increments only if the meaning of the key changes. + +## Canonical descriptor digest + +Each entry includes `descriptor_sha256`, the SHA-256 digest (lowercase hex) of +its canonical descriptor JSON object. The canonical descriptor object: + +- Includes the descriptor fields defined by the registry-specific schema. +- Uses the exact field ordering specified by the registry-specific schema. +- Excludes the `descriptor_sha256` field itself. +- Uses standard JSON encoding with no extra whitespace. +- Is hashed over its UTF-8 byte sequence. + +Registry-specific schema files define the precise field ordering and any +additional constraints on descriptor fields. + +## Canonical key encoding + +Each registry defines the byte encoding of its key. The encoding is used when +constructing ASL registry artifacts. See the registry-specific schema for the +exact key encoding (e.g., big-endian `u16` for `HASH/ASL1`). diff --git a/src/near_core/asl/identity.c b/src/near_core/asl/identity.c new file mode 100644 index 0000000..c98b17f --- /dev/null +++ b/src/near_core/asl/identity.c @@ -0,0 +1,18 @@ +#include "amduat/asl/identity.h" + +static const uint8_t k_asl_identity_domain_sep[] = { + 'A', 'M', 'D', 'U', 'A', 'T', ':', 'A', + 'S', 'L', '1', ':', 'A', 'R', 'T', 0x00}; + +amduat_octets_t amduat_asl_identity_domain_separator(void) { + return amduat_octets(k_asl_identity_domain_sep, + sizeof(k_asl_identity_domain_sep)); +} + +const amduat_asl_identity_rules_t *amduat_asl_identity_rules(void) { + static const amduat_asl_identity_rules_t k_rules = { + AMDUAT_HASH_ASL1_REGISTRY, + AMDUAT_HASH_ASL1_VERSION, + {k_asl_identity_domain_sep, sizeof(k_asl_identity_domain_sep)}}; + return &k_rules; +} diff --git a/src/near_core/asl/ref_derive.c b/src/near_core/asl/ref_derive.c index 9e37cac..4179151 100644 --- a/src/near_core/asl/ref_derive.c +++ b/src/near_core/asl/ref_derive.c @@ -1,5 +1,6 @@ #include "amduat/asl/ref_derive.h" +#include "amduat/asl/identity.h" #include "amduat/enc/asl1_core.h" #include "amduat/enc/asl1_core_codec.h" #include "amduat/hash/asl1.h" @@ -53,10 +54,11 @@ bool amduat_asl_ref_derive(amduat_artifact_t artifact, return false; } - if (!amduat_hash_asl1_digest(hash_id, - artifact_bytes, - digest, - hash_desc->digest_len)) { + if (!amduat_hash_asl1_digest_domain(hash_id, + amduat_asl_identity_domain_separator(), + artifact_bytes, + digest, + hash_desc->digest_len)) { amduat_octets_free(&artifact_bytes); free(digest); return false; diff --git a/src/near_core/bytestore.c b/src/near_core/bytestore.c new file mode 100644 index 0000000..a650ff0 --- /dev/null +++ b/src/near_core/bytestore.c @@ -0,0 +1,235 @@ +#include "amduat/bytestore.h" + +#include +#include + +static const char k_amduat_bytestore_domain_sep[] = "CAS:OBJ\0"; + +static bool amduat_bytestore_trim_trailing_slash(const char *input, + size_t *out_len) { + size_t len; + + if (input == NULL || out_len == NULL) { + return false; + } + len = strlen(input); + while (len > 1u && input[len - 1u] == '/') { + len--; + } + *out_len = len; + return true; +} + +static bool amduat_bytestore_path_is_prefix(const char *parent, + size_t parent_len, + const char *child, + size_t child_len) { + if (parent_len == 0u || child_len == 0u) { + return false; + } + if (child_len < parent_len) { + return false; + } + if (memcmp(parent, child, parent_len) != 0) { + return false; + } + if (child_len == parent_len) { + return true; + } + return child[parent_len] == '/'; +} + +void amduat_bytestore_init(amduat_bytestore_t *store, + amduat_bytestore_config_t config, + amduat_bytestore_ops_t ops, + void *ctx) { + if (store == NULL) { + return; + } + store->config = config; + store->ops = ops; + store->ctx = ctx; +} + +amduat_bytestore_error_t amduat_bytestore_put(amduat_bytestore_t *store, + amduat_octets_t payload, + amduat_reference_t *out_cid) { + if (store == NULL || store->ops.put == NULL) { + return AMDUAT_BYTESTORE_ERR_UNSUPPORTED; + } + return store->ops.put(store->ctx, payload, out_cid); +} + +amduat_bytestore_error_t amduat_bytestore_put_stream( + amduat_bytestore_t *store, + amduat_bytestore_stream_t stream, + amduat_reference_t *out_cid) { + if (store == NULL || store->ops.put_stream == NULL) { + return AMDUAT_BYTESTORE_ERR_UNSUPPORTED; + } + return store->ops.put_stream(store->ctx, stream, out_cid); +} + +amduat_bytestore_error_t amduat_bytestore_import_cor( + amduat_bytestore_t *store, + amduat_octets_t envelope, + amduat_reference_t *out_cid) { + if (store == NULL || store->ops.import_cor == NULL) { + return AMDUAT_BYTESTORE_ERR_UNSUPPORTED; + } + return store->ops.import_cor(store->ctx, envelope, out_cid); +} + +amduat_bytestore_error_t amduat_bytestore_export_cor( + amduat_bytestore_t *store, + amduat_reference_t cid, + amduat_octets_t *out_envelope) { + if (store == NULL || store->ops.export_cor == NULL) { + return AMDUAT_BYTESTORE_ERR_UNSUPPORTED; + } + return store->ops.export_cor(store->ctx, cid, out_envelope); +} + +amduat_bytestore_error_t amduat_bytestore_get(amduat_bytestore_t *store, + amduat_reference_t cid, + amduat_octets_t *out_bytes) { + if (store == NULL || store->ops.get == NULL) { + return AMDUAT_BYTESTORE_ERR_UNSUPPORTED; + } + return store->ops.get(store->ctx, cid, out_bytes); +} + +amduat_bytestore_error_t amduat_bytestore_stat( + amduat_bytestore_t *store, + amduat_reference_t cid, + amduat_bytestore_stat_t *out_stat) { + if (store == NULL || store->ops.stat == NULL) { + return AMDUAT_BYTESTORE_ERR_UNSUPPORTED; + } + return store->ops.stat(store->ctx, cid, out_stat); +} + +amduat_bytestore_error_t amduat_bytestore_assert_area_isolation( + amduat_bytestore_t *store, + const char *public_root, + const char *secure_root) { + size_t public_len; + size_t secure_len; + + if (store != NULL && store->ops.assert_area_isolation != NULL) { + return store->ops.assert_area_isolation(store->ctx, public_root, + secure_root); + } + if (!amduat_bytestore_trim_trailing_slash(public_root, &public_len) || + !amduat_bytestore_trim_trailing_slash(secure_root, &secure_len)) { + return AMDUAT_BYTESTORE_ERR_INVALID; + } + if (public_len == 0u || secure_len == 0u) { + return AMDUAT_BYTESTORE_ERR_INVALID; + } + if (public_len == secure_len && + memcmp(public_root, secure_root, public_len) == 0) { + return AMDUAT_BYTESTORE_ERR_AREA_VIOLATION; + } + if (amduat_bytestore_path_is_prefix(public_root, public_len, secure_root, + secure_len)) { + return AMDUAT_BYTESTORE_ERR_AREA_VIOLATION; + } + if (amduat_bytestore_path_is_prefix(secure_root, secure_len, public_root, + public_len)) { + return AMDUAT_BYTESTORE_ERR_AREA_VIOLATION; + } + return AMDUAT_BYTESTORE_OK; +} + +bool amduat_bytestore_cid_for_payload(amduat_hash_id_t hash_id, + amduat_octets_t payload, + amduat_reference_t *out_cid) { + const amduat_hash_asl1_desc_t *desc; + uint8_t *digest; + amduat_octets_t domain_sep; + + if (out_cid == NULL) { + return false; + } + *out_cid = amduat_reference(0, amduat_octets(NULL, 0u)); + desc = amduat_hash_asl1_desc_lookup(hash_id); + if (desc == NULL || desc->digest_len == 0u) { + return false; + } + digest = (uint8_t *)malloc(desc->digest_len); + if (digest == NULL) { + return false; + } + domain_sep = amduat_octets(k_amduat_bytestore_domain_sep, + sizeof(k_amduat_bytestore_domain_sep) - 1u); + if (!amduat_hash_asl1_digest_domain(hash_id, domain_sep, payload, digest, + desc->digest_len)) { + free(digest); + return false; + } + *out_cid = amduat_reference(hash_id, amduat_octets(digest, desc->digest_len)); + return true; +} + +bool amduat_bytestore_cid_for_stream(amduat_hash_id_t hash_id, + amduat_bytestore_stream_t stream, + amduat_reference_t *out_cid) { + amduat_hash_asl1_stream_t hasher; + const amduat_hash_asl1_desc_t *desc; + uint8_t *digest; + amduat_octets_t domain_sep; + amduat_octets_t chunk; + int next_status; + + if (out_cid == NULL || stream.next == NULL) { + return false; + } + *out_cid = amduat_reference(0, amduat_octets(NULL, 0u)); + desc = amduat_hash_asl1_desc_lookup(hash_id); + if (desc == NULL || desc->digest_len == 0u) { + return false; + } + if (!amduat_hash_asl1_stream_init(hash_id, &hasher)) { + return false; + } + domain_sep = amduat_octets(k_amduat_bytestore_domain_sep, + sizeof(k_amduat_bytestore_domain_sep) - 1u); + if (!amduat_hash_asl1_stream_update(&hasher, domain_sep)) { + amduat_hash_asl1_stream_destroy(&hasher); + return false; + } + while (true) { + chunk = amduat_octets(NULL, 0u); + next_status = stream.next(stream.ctx, &chunk); + if (next_status == 0) { + break; + } + if (next_status < 0) { + amduat_hash_asl1_stream_destroy(&hasher); + return false; + } + if (chunk.len != 0u && chunk.data == NULL) { + amduat_hash_asl1_stream_destroy(&hasher); + return false; + } + if (chunk.len != 0u && + !amduat_hash_asl1_stream_update(&hasher, chunk)) { + amduat_hash_asl1_stream_destroy(&hasher); + return false; + } + } + digest = (uint8_t *)malloc(desc->digest_len); + if (digest == NULL) { + amduat_hash_asl1_stream_destroy(&hasher); + return false; + } + if (!amduat_hash_asl1_stream_final(&hasher, digest, desc->digest_len)) { + free(digest); + amduat_hash_asl1_stream_destroy(&hasher); + return false; + } + amduat_hash_asl1_stream_destroy(&hasher); + *out_cid = amduat_reference(hash_id, amduat_octets(digest, desc->digest_len)); + return true; +} diff --git a/src/near_core/enc/asl1_core.c b/src/near_core/enc/asl1_core.c index 1b563c5..266e162 100644 --- a/src/near_core/enc/asl1_core.c +++ b/src/near_core/enc/asl1_core.c @@ -1,7 +1,7 @@ #include "amduat/enc/asl1_core.h" static amduat_enc_asl1_core_profile_desc_t g_enc_asl1_core_profiles[] = { - {AMDUAT_ENC_ASL1_CORE_V1, "ASL_ENC_CORE_V1", 0x0105, NULL}}; + {AMDUAT_ENC_ASL1_CORE_V1, "ASL_ENC_CORE_V1", 0x0106, NULL}}; amduat_octets_t amduat_enc_asl1_core_key( amduat_asl_encoding_profile_id_t profile_id, diff --git a/src/near_core/enc/asl_tgk_exec_plan.c b/src/near_core/enc/asl_tgk_exec_plan.c index 4a99b69..33e2972 100644 --- a/src/near_core/enc/asl_tgk_exec_plan.c +++ b/src/near_core/enc/asl_tgk_exec_plan.c @@ -166,6 +166,9 @@ static bool amduat_asl_tgk_exec_plan_validate_operator( if (((uint32_t)op->flags & ~AMDUAT_ASL_TGK_EXEC_PLAN_OP_FLAG_MASK) != 0u) { return false; } + if (op->snapshot.logseq_min > op->snapshot.logseq_max) { + return false; + } if (op->input_count > AMDUAT_ASL_TGK_EXEC_PLAN_INPUT_CAP) { return false; } diff --git a/src/near_core/enc/fcs1.c b/src/near_core/enc/fcs1.c new file mode 100644 index 0000000..26f0a55 --- /dev/null +++ b/src/near_core/enc/fcs1.c @@ -0,0 +1,294 @@ +#include "amduat/enc/fcs1.h" + +#include "amduat/enc/asl1_core_codec.h" + +#include + +enum { + AMDUAT_FCS1_HEADER_LEN = 7, + AMDUAT_FCS1_TAG_FUNCTION = 0x30, + AMDUAT_FCS1_TAG_PARAMS = 0x31, + AMDUAT_FCS1_TAG_ARITY = 0x32, + AMDUAT_FCS1_TAG_LEGACY_MIN = 0x33, + AMDUAT_FCS1_TAG_LEGACY_MAX = 0x36 +}; + +typedef struct { + const uint8_t *data; + size_t len; + size_t offset; +} amduat_fcs1_cursor_t; + +static bool amduat_fcs1_read_varint(amduat_fcs1_cursor_t *cur, + uint64_t *out, + bool *out_minimal) { + uint64_t value = 0u; + uint32_t shift = 0u; + size_t bytes = 0u; + + if (cur == NULL || out == NULL || out_minimal == NULL) { + return false; + } + while (cur->offset < cur->len) { + uint8_t byte = cur->data[cur->offset++]; + value |= ((uint64_t)(byte & 0x7fu)) << shift; + bytes++; + if ((byte & 0x80u) == 0u) { + break; + } + shift += 7u; + if (shift >= 64u) { + return false; + } + } + if (bytes == 0u || cur->offset > cur->len) { + return false; + } + { + size_t minimal_len = 1u; + uint64_t tmp = value; + while (tmp >= 0x80u) { + tmp >>= 7u; + minimal_len++; + } + *out_minimal = (minimal_len == bytes); + } + *out = value; + return true; +} + +static bool amduat_fcs1_read_bytes(amduat_fcs1_cursor_t *cur, + amduat_octets_t *out, + bool *out_minimal) { + uint64_t len; + bool minimal; + + if (!amduat_fcs1_read_varint(cur, &len, &minimal)) { + return false; + } + if (len > SIZE_MAX || cur->len - cur->offset < (size_t)len) { + return false; + } + *out = amduat_octets(cur->data + cur->offset, (size_t)len); + cur->offset += (size_t)len; + *out_minimal = minimal; + return true; +} + +static bool amduat_fcs1_has_header(amduat_octets_t bytes) { + static const uint8_t magic[4] = {'F', 'C', 'S', '1'}; + + if (bytes.len < AMDUAT_FCS1_HEADER_LEN || bytes.data == NULL) { + return false; + } + if (memcmp(bytes.data, magic, sizeof(magic)) != 0) { + return false; + } + if (bytes.data[4] != 0x01u || bytes.data[5] != 0x00u || + bytes.data[6] != 0x00u) { + return false; + } + return true; +} + +void amduat_fcs1_descriptor_free(amduat_fcs1_descriptor_t *desc) { + if (desc == NULL) { + return; + } + amduat_reference_free(&desc->function_ptr); + amduat_reference_free(&desc->parameter_block); + desc->arity = 0u; +} + +amduat_fcs1_error_t amduat_fcs1_decode_v1(amduat_octets_t bytes, + amduat_fcs1_descriptor_t *out_desc) { + amduat_fcs1_cursor_t cur; + uint8_t last_tag = 0u; + bool seen_function = false; + bool seen_params = false; + bool seen_arity = false; + + if (out_desc == NULL) { + return AMDUAT_FCS1_ERR_INVALID; + } + out_desc->function_ptr = amduat_reference(0, amduat_octets(NULL, 0u)); + out_desc->parameter_block = amduat_reference(0, amduat_octets(NULL, 0u)); + out_desc->arity = 0u; + + if (!amduat_fcs1_has_header(bytes)) { + return AMDUAT_FCS1_ERR_HEADER_INVALID; + } + + cur.data = bytes.data; + cur.len = bytes.len; + cur.offset = AMDUAT_FCS1_HEADER_LEN; + + while (cur.offset < cur.len) { + uint8_t tag = cur.data[cur.offset++]; + bool minimal = true; + + if (tag <= last_tag) { + return AMDUAT_FCS1_ERR_TAG_ORDER; + } + last_tag = tag; + + if (tag == AMDUAT_FCS1_TAG_FUNCTION || + tag == AMDUAT_FCS1_TAG_PARAMS) { + amduat_octets_t cid_bytes; + if (!amduat_fcs1_read_bytes(&cur, &cid_bytes, &minimal)) { + return AMDUAT_FCS1_ERR_INVALID; + } + if (!minimal) { + return AMDUAT_FCS1_ERR_VARINT_NON_MINIMAL; + } + if (tag == AMDUAT_FCS1_TAG_FUNCTION) { + if (seen_function) { + return AMDUAT_FCS1_ERR_TAG_ORDER; + } + if (!amduat_enc_asl1_core_decode_reference_v1(cid_bytes, + &out_desc->function_ptr)) { + return AMDUAT_FCS1_ERR_INVALID; + } + seen_function = true; + } else { + if (seen_params) { + return AMDUAT_FCS1_ERR_TAG_ORDER; + } + if (!amduat_enc_asl1_core_decode_reference_v1(cid_bytes, + &out_desc->parameter_block)) { + return AMDUAT_FCS1_ERR_INVALID; + } + seen_params = true; + } + } else if (tag == AMDUAT_FCS1_TAG_ARITY) { + uint64_t arity_raw = 0u; + if (seen_arity) { + return AMDUAT_FCS1_ERR_TAG_ORDER; + } + if (!amduat_fcs1_read_varint(&cur, &arity_raw, &minimal)) { + return AMDUAT_FCS1_ERR_INVALID; + } + if (!minimal) { + return AMDUAT_FCS1_ERR_VARINT_NON_MINIMAL; + } + if (arity_raw > UINT32_MAX) { + return AMDUAT_FCS1_ERR_INVALID; + } + out_desc->arity = (uint32_t)arity_raw; + seen_arity = true; + } else if (tag >= AMDUAT_FCS1_TAG_LEGACY_MIN && + tag <= AMDUAT_FCS1_TAG_LEGACY_MAX) { + return AMDUAT_FCS1_ERR_UNKNOWN_TAG; + } else { + return AMDUAT_FCS1_ERR_UNKNOWN_TAG; + } + } + + if (!seen_function || !seen_params || !seen_arity) { + return AMDUAT_FCS1_ERR_TAG_ORDER; + } + if (cur.offset != cur.len) { + return AMDUAT_FCS1_ERR_TRAILING_BYTES; + } + return AMDUAT_FCS1_OK; +} + +static amduat_fcs1_error_t amduat_fcs1_validate_cycle( + amduat_reference_t ref, + amduat_fcs1_resolve_fn resolver, + void *resolver_ctx, + amduat_reference_t *stack, + size_t stack_len, + size_t stack_cap) { + amduat_octets_t bytes = amduat_octets(NULL, 0u); + amduat_fcs1_descriptor_t desc; + amduat_fcs1_error_t err; + + if (resolver == NULL) { + return AMDUAT_FCS1_ERR_INVALID; + } + if (!resolver(resolver_ctx, ref, &bytes)) { + return AMDUAT_FCS1_OK; + } + if (!amduat_fcs1_has_header(bytes)) { + amduat_octets_free(&bytes); + return AMDUAT_FCS1_OK; + } + + for (size_t i = 0u; i < stack_len; ++i) { + if (amduat_reference_eq(stack[i], ref)) { + amduat_octets_free(&bytes); + return AMDUAT_FCS1_ERR_CYCLE_DETECTED; + } + } + + memset(&desc, 0, sizeof(desc)); + err = amduat_fcs1_decode_v1(bytes, &desc); + amduat_octets_free(&bytes); + if (err != AMDUAT_FCS1_OK) { + amduat_fcs1_descriptor_free(&desc); + return err; + } + + if (stack_len >= stack_cap) { + amduat_fcs1_descriptor_free(&desc); + return AMDUAT_FCS1_ERR_INVALID; + } + stack[stack_len] = desc.function_ptr; + err = amduat_fcs1_validate_cycle(desc.function_ptr, + resolver, + resolver_ctx, + stack, + stack_len + 1u, + stack_cap); + amduat_fcs1_descriptor_free(&desc); + return err; +} + +amduat_fcs1_error_t amduat_fcs1_validate_v1( + amduat_octets_t bytes, + amduat_fcs1_resolve_fn resolver, + void *resolver_ctx) { + amduat_fcs1_descriptor_t desc; + amduat_octets_t pcb_bytes = amduat_octets(NULL, 0u); + amduat_pcb1_manifest_t manifest; + amduat_octets_t slot_data; + amduat_fcs1_error_t err; + amduat_pcb1_error_t pcb_err; + amduat_reference_t stack[16]; + + memset(&desc, 0, sizeof(desc)); + err = amduat_fcs1_decode_v1(bytes, &desc); + if (err != AMDUAT_FCS1_OK) { + return err; + } + + if (resolver == NULL || + !resolver(resolver_ctx, desc.parameter_block, &pcb_bytes)) { + amduat_fcs1_descriptor_free(&desc); + return AMDUAT_FCS1_ERR_PARAMETER_FORMAT; + } + + pcb_err = amduat_pcb1_decode_v1(pcb_bytes, &manifest, &slot_data); + amduat_octets_free(&pcb_bytes); + if (pcb_err != AMDUAT_PCB1_OK) { + amduat_fcs1_descriptor_free(&desc); + return AMDUAT_FCS1_ERR_PARAMETER_FORMAT; + } + (void)slot_data; + + if (desc.arity != manifest.slot_count) { + amduat_fcs1_descriptor_free(&desc); + return AMDUAT_FCS1_ERR_PCB_ARITY_MISMATCH; + } + + stack[0] = desc.function_ptr; + err = amduat_fcs1_validate_cycle(desc.function_ptr, + resolver, + resolver_ctx, + stack, + 1u, + sizeof(stack) / sizeof(stack[0])); + amduat_fcs1_descriptor_free(&desc); + return err; +} diff --git a/src/near_core/enc/pcb1.c b/src/near_core/enc/pcb1.c new file mode 100644 index 0000000..b2361fa --- /dev/null +++ b/src/near_core/enc/pcb1.c @@ -0,0 +1,256 @@ +#include "amduat/enc/pcb1.h" + +#include + +enum { + AMDUAT_PCB1_HEADER_LEN = 7, + AMDUAT_PCB1_TAG_MANIFEST = 0x50, + AMDUAT_PCB1_TAG_DATA = 0x51, + AMDUAT_PCB1_DIGEST_LEN = 32 +}; + +typedef struct { + const uint8_t *data; + size_t len; + size_t offset; +} amduat_pcb1_cursor_t; + +static bool amduat_pcb1_read_varint(amduat_pcb1_cursor_t *cur, + uint64_t *out, + bool *out_minimal) { + uint64_t value = 0u; + uint32_t shift = 0u; + size_t start; + size_t bytes = 0u; + + if (cur == NULL || out == NULL || out_minimal == NULL) { + return false; + } + start = cur->offset; + while (cur->offset < cur->len) { + uint8_t byte = cur->data[cur->offset++]; + value |= ((uint64_t)(byte & 0x7fu)) << shift; + bytes++; + if ((byte & 0x80u) == 0u) { + break; + } + shift += 7u; + if (shift >= 64u) { + return false; + } + } + if (bytes == 0u || cur->offset > cur->len) { + return false; + } + { + size_t minimal_len = 1u; + uint64_t tmp = value; + while (tmp >= 0x80u) { + tmp >>= 7u; + minimal_len++; + } + *out_minimal = (minimal_len == bytes); + } + *out = value; + return true; +} + +static bool amduat_pcb1_read_bytes(amduat_pcb1_cursor_t *cur, + amduat_octets_t *out, + bool *out_minimal) { + uint64_t len; + bool minimal; + + if (!amduat_pcb1_read_varint(cur, &len, &minimal)) { + return false; + } + if (len > SIZE_MAX || cur->len - cur->offset < (size_t)len) { + return false; + } + *out = amduat_octets(cur->data + cur->offset, (size_t)len); + cur->offset += (size_t)len; + *out_minimal = minimal; + return true; +} + +static amduat_pcb1_error_t amduat_pcb1_parse_manifest( + amduat_octets_t bytes, + amduat_pcb1_manifest_t *out_manifest) { + amduat_pcb1_cursor_t cur; + uint64_t slot_count = 0u; + uint64_t last_index = 0u; + bool minimal; + + if (out_manifest == NULL) { + return AMDUAT_PCB1_ERR_INVALID; + } + out_manifest->slot_count = 0u; + cur.data = bytes.data; + cur.len = bytes.len; + cur.offset = 0u; + + if (!amduat_pcb1_read_varint(&cur, &slot_count, &minimal)) { + return AMDUAT_PCB1_ERR_INVALID; + } + if (!minimal) { + return AMDUAT_PCB1_ERR_VARINT_NON_MINIMAL; + } + if (slot_count > UINT32_MAX) { + return AMDUAT_PCB1_ERR_INVALID; + } + + for (uint64_t i = 0u; i < slot_count; ++i) { + uint64_t index; + uint64_t name_len; + uint64_t type; + uint64_t digest_len; + amduat_octets_t name; + amduat_octets_t digest; + + if (!amduat_pcb1_read_varint(&cur, &index, &minimal)) { + return AMDUAT_PCB1_ERR_INVALID; + } + if (!minimal) { + return AMDUAT_PCB1_ERR_VARINT_NON_MINIMAL; + } + if (i > 0u && index <= last_index) { + return AMDUAT_PCB1_ERR_MANIFEST_ORDER; + } + last_index = index; + + if (!amduat_pcb1_read_varint(&cur, &name_len, &minimal)) { + return AMDUAT_PCB1_ERR_INVALID; + } + if (!minimal) { + return AMDUAT_PCB1_ERR_VARINT_NON_MINIMAL; + } + if (name_len > SIZE_MAX || cur.len - cur.offset < (size_t)name_len) { + return AMDUAT_PCB1_ERR_INVALID; + } + name = amduat_octets(cur.data + cur.offset, (size_t)name_len); + (void)name; + cur.offset += (size_t)name_len; + + if (!amduat_pcb1_read_varint(&cur, &type, &minimal)) { + return AMDUAT_PCB1_ERR_INVALID; + } + if (!minimal) { + return AMDUAT_PCB1_ERR_VARINT_NON_MINIMAL; + } + (void)type; + + if (!amduat_pcb1_read_varint(&cur, &digest_len, &minimal)) { + return AMDUAT_PCB1_ERR_INVALID; + } + if (!minimal) { + return AMDUAT_PCB1_ERR_VARINT_NON_MINIMAL; + } + if (digest_len != AMDUAT_PCB1_DIGEST_LEN) { + return AMDUAT_PCB1_ERR_DIGEST_MISMATCH; + } + if (cur.len - cur.offset < AMDUAT_PCB1_DIGEST_LEN) { + return AMDUAT_PCB1_ERR_INVALID; + } + digest = amduat_octets(cur.data + cur.offset, AMDUAT_PCB1_DIGEST_LEN); + (void)digest; + cur.offset += AMDUAT_PCB1_DIGEST_LEN; + } + + if (cur.offset != cur.len) { + return AMDUAT_PCB1_ERR_TRAILING_BYTES; + } + + out_manifest->slot_count = (uint32_t)slot_count; + return AMDUAT_PCB1_OK; +} + +amduat_pcb1_error_t amduat_pcb1_decode_v1(amduat_octets_t bytes, + amduat_pcb1_manifest_t *out_manifest, + amduat_octets_t *out_slot_data) { + static const uint8_t magic[4] = {'P', 'C', 'B', '1'}; + amduat_pcb1_cursor_t cur; + uint8_t version; + uint8_t flags; + uint8_t rsv; + uint8_t tag; + uint8_t last_tag = 0u; + bool minimal; + bool seen_manifest = false; + bool seen_data = false; + amduat_octets_t manifest_bytes = amduat_octets(NULL, 0u); + amduat_octets_t slot_data = amduat_octets(NULL, 0u); + + if (out_manifest == NULL || out_slot_data == NULL) { + return AMDUAT_PCB1_ERR_INVALID; + } + out_manifest->slot_count = 0u; + *out_slot_data = amduat_octets(NULL, 0u); + + if (bytes.len < AMDUAT_PCB1_HEADER_LEN || bytes.data == NULL) { + return AMDUAT_PCB1_ERR_HEADER_INVALID; + } + if (memcmp(bytes.data, magic, sizeof(magic)) != 0) { + return AMDUAT_PCB1_ERR_HEADER_INVALID; + } + version = bytes.data[4]; + flags = bytes.data[5]; + rsv = bytes.data[6]; + if (version != 0x01u || flags != 0x00u || rsv != 0x00u) { + return AMDUAT_PCB1_ERR_HEADER_INVALID; + } + + cur.data = bytes.data; + cur.len = bytes.len; + cur.offset = AMDUAT_PCB1_HEADER_LEN; + + while (cur.offset < cur.len) { + tag = cur.data[cur.offset++]; + if (tag <= last_tag) { + return AMDUAT_PCB1_ERR_TAG_ORDER; + } + last_tag = tag; + if (tag == AMDUAT_PCB1_TAG_MANIFEST) { + if (seen_manifest) { + return AMDUAT_PCB1_ERR_TAG_ORDER; + } + if (!amduat_pcb1_read_bytes(&cur, &manifest_bytes, &minimal)) { + return AMDUAT_PCB1_ERR_INVALID; + } + if (!minimal) { + return AMDUAT_PCB1_ERR_VARINT_NON_MINIMAL; + } + seen_manifest = true; + } else if (tag == AMDUAT_PCB1_TAG_DATA) { + if (seen_data) { + return AMDUAT_PCB1_ERR_TAG_ORDER; + } + if (!amduat_pcb1_read_bytes(&cur, &slot_data, &minimal)) { + return AMDUAT_PCB1_ERR_INVALID; + } + if (!minimal) { + return AMDUAT_PCB1_ERR_VARINT_NON_MINIMAL; + } + seen_data = true; + } else { + return AMDUAT_PCB1_ERR_UNKNOWN_TAG; + } + } + + if (!seen_manifest || !seen_data) { + return AMDUAT_PCB1_ERR_TAG_ORDER; + } + if (cur.offset != cur.len) { + return AMDUAT_PCB1_ERR_TRAILING_BYTES; + } + + { + amduat_pcb1_error_t err = + amduat_pcb1_parse_manifest(manifest_bytes, out_manifest); + if (err != AMDUAT_PCB1_OK) { + return err; + } + } + + *out_slot_data = slot_data; + return AMDUAT_PCB1_OK; +} diff --git a/src/near_core/fps.c b/src/near_core/fps.c new file mode 100644 index 0000000..cb6b96f --- /dev/null +++ b/src/near_core/fps.c @@ -0,0 +1,202 @@ +#include "amduat/fps.h" + +#include +#include + +static const char k_amduat_fps_domain_sep[] = "CAS:OBJ\0"; + +static uint8_t amduat_fps_reverse_bits8(uint8_t value) { + value = (uint8_t)(((value & 0xf0u) >> 4) | ((value & 0x0fu) << 4)); + value = (uint8_t)(((value & 0xccu) >> 2) | ((value & 0x33u) << 2)); + value = (uint8_t)(((value & 0xaau) >> 1) | ((value & 0x55u) << 1)); + return value; +} + +static bool amduat_fps_alloc_output(size_t len, amduat_octets_t *out) { + uint8_t *buf; + + if (out == NULL) { + return false; + } + *out = amduat_octets(NULL, 0u); + if (len == 0u) { + return true; + } + buf = (uint8_t *)malloc(len); + if (buf == NULL) { + return false; + } + *out = amduat_octets(buf, len); + return true; +} + +amduat_fps_error_t amduat_fps_slice(amduat_octets_t input, + size_t offset, + size_t length, + amduat_octets_t *out) { + if (out == NULL || (input.len != 0u && input.data == NULL)) { + return AMDUAT_FPS_ERR_INVALID; + } + if (offset > input.len || length > input.len - offset) { + return AMDUAT_FPS_ERR_SLICE_RANGE; + } + if (!amduat_fps_alloc_output(length, out)) { + return AMDUAT_FPS_ERR_IO_FAILURE; + } + if (length != 0u) { + memcpy((void *)out->data, input.data + offset, length); + } + return AMDUAT_FPS_OK; +} + +amduat_fps_error_t amduat_fps_concat(const amduat_octets_t *inputs, + size_t input_count, + amduat_octets_t *out) { + size_t total = 0u; + size_t i; + uint8_t *cursor; + + if (out == NULL) { + return AMDUAT_FPS_ERR_INVALID; + } + if (input_count == 0u) { + return AMDUAT_FPS_ERR_EMPTY_INPUTS; + } + if (inputs == NULL) { + return AMDUAT_FPS_ERR_INVALID; + } + for (i = 0u; i < input_count; ++i) { + if (inputs[i].len != 0u && inputs[i].data == NULL) { + return AMDUAT_FPS_ERR_INVALID; + } + if (SIZE_MAX - total < inputs[i].len) { + return AMDUAT_FPS_ERR_IO_FAILURE; + } + total += inputs[i].len; + } + if (!amduat_fps_alloc_output(total, out)) { + return AMDUAT_FPS_ERR_IO_FAILURE; + } + cursor = (uint8_t *)out->data; + for (i = 0u; i < input_count; ++i) { + if (inputs[i].len != 0u) { + memcpy(cursor, inputs[i].data, inputs[i].len); + cursor += inputs[i].len; + } + } + return AMDUAT_FPS_OK; +} + +amduat_fps_error_t amduat_fps_reverse(amduat_octets_t input, + amduat_fps_reverse_level_t level, + amduat_octets_t *out) { + size_t unit_size; + size_t i; + size_t unit_count; + + if (out == NULL || (input.len != 0u && input.data == NULL)) { + return AMDUAT_FPS_ERR_INVALID; + } + + if (level == AMDUAT_FPS_REVERSE_BITS) { + if (!amduat_fps_alloc_output(input.len, out)) { + return AMDUAT_FPS_ERR_IO_FAILURE; + } + for (i = 0u; i < input.len; ++i) { + size_t dst = input.len - 1u - i; + ((uint8_t *)out->data)[dst] = amduat_fps_reverse_bits8(input.data[i]); + } + return AMDUAT_FPS_OK; + } + + if (level == AMDUAT_FPS_REVERSE_BYTES) { + unit_size = 1u; + } else if (level == AMDUAT_FPS_REVERSE_WORDS) { + unit_size = 2u; + } else if (level == AMDUAT_FPS_REVERSE_LONGS) { + unit_size = 4u; + } else { + return AMDUAT_FPS_ERR_INVALID_LEVEL; + } + + if (unit_size != 0u && input.len % unit_size != 0u) { + return AMDUAT_FPS_ERR_REV_ALIGNMENT; + } + if (!amduat_fps_alloc_output(input.len, out)) { + return AMDUAT_FPS_ERR_IO_FAILURE; + } + unit_count = unit_size == 0u ? 0u : input.len / unit_size; + for (i = 0u; i < unit_count; ++i) { + size_t src_offset = i * unit_size; + size_t dst_offset = (unit_count - 1u - i) * unit_size; + memcpy((uint8_t *)out->data + dst_offset, + input.data + src_offset, + unit_size); + } + return AMDUAT_FPS_OK; +} + +amduat_fps_error_t amduat_fps_splice(amduat_octets_t a, + size_t offset, + amduat_octets_t b, + amduat_octets_t *out) { + size_t total; + + if (out == NULL || + (a.len != 0u && a.data == NULL) || + (b.len != 0u && b.data == NULL)) { + return AMDUAT_FPS_ERR_INVALID; + } + if (offset > a.len) { + return AMDUAT_FPS_ERR_SPLICE_RANGE; + } + if (SIZE_MAX - a.len < b.len) { + return AMDUAT_FPS_ERR_IO_FAILURE; + } + total = a.len + b.len; + if (!amduat_fps_alloc_output(total, out)) { + return AMDUAT_FPS_ERR_IO_FAILURE; + } + if (offset != 0u) { + memcpy((uint8_t *)out->data, a.data, offset); + } + if (b.len != 0u) { + memcpy((uint8_t *)out->data + offset, b.data, b.len); + } + if (a.len > offset) { + memcpy((uint8_t *)out->data + offset + b.len, + a.data + offset, + a.len - offset); + } + return AMDUAT_FPS_OK; +} + +bool amduat_fps_cid_for_payload(amduat_hash_id_t hash_id, + amduat_octets_t payload, + amduat_reference_t *out_cid) { + const amduat_hash_asl1_desc_t *desc; + amduat_octets_t domain_sep; + uint8_t *digest; + + if (out_cid == NULL) { + return false; + } + *out_cid = amduat_reference(0, amduat_octets(NULL, 0u)); + desc = amduat_hash_asl1_desc_lookup(hash_id); + if (desc == NULL || desc->digest_len == 0u) { + return false; + } + digest = (uint8_t *)malloc(desc->digest_len); + if (digest == NULL) { + return false; + } + domain_sep = amduat_octets(k_amduat_fps_domain_sep, + sizeof(k_amduat_fps_domain_sep) - 1u); + if (!amduat_hash_asl1_digest_domain(hash_id, domain_sep, payload, digest, + desc->digest_len)) { + free(digest); + return false; + } + *out_cid = amduat_reference(hash_id, amduat_octets(digest, desc->digest_len)); + return true; +} diff --git a/src/near_core/hash/asl1.c b/src/near_core/hash/asl1.c index ee4c314..3d02520 100644 --- a/src/near_core/hash/asl1.c +++ b/src/near_core/hash/asl1.c @@ -125,6 +125,58 @@ bool amduat_hash_asl1_digest(amduat_hash_id_t hash_id, return desc->impl.digest(desc->impl.ctx, input, out, desc->digest_len); } +bool amduat_hash_asl1_digest_domain(amduat_hash_id_t hash_id, + amduat_octets_t domain_separator, + amduat_octets_t input, + uint8_t *out, + size_t out_len) { + amduat_hash_asl1_stream_t stream; + uint8_t *buffer; + size_t total_len; + + if (out == NULL) { + return false; + } + if ((domain_separator.len != 0 && domain_separator.data == NULL) || + (input.len != 0 && input.data == NULL)) { + return false; + } + + if (amduat_hash_asl1_stream_init(hash_id, &stream)) { + bool ok = amduat_hash_asl1_stream_update(&stream, domain_separator) && + amduat_hash_asl1_stream_update(&stream, input) && + amduat_hash_asl1_stream_final(&stream, out, out_len); + amduat_hash_asl1_stream_destroy(&stream); + return ok; + } + + if (domain_separator.len > SIZE_MAX - input.len) { + return false; + } + total_len = domain_separator.len + input.len; + buffer = (uint8_t *)malloc(total_len); + if (buffer == NULL) { + return false; + } + if (domain_separator.len != 0) { + memcpy(buffer, domain_separator.data, domain_separator.len); + } + if (input.len != 0) { + memcpy(buffer + domain_separator.len, input.data, input.len); + } + + if (!amduat_hash_asl1_digest(hash_id, + amduat_octets(buffer, total_len), + out, + out_len)) { + free(buffer); + return false; + } + + free(buffer); + return true; +} + static bool amduat_hash_asl1_sha256_stream_update(void *ctx, amduat_octets_t input) { if (input.len != 0 && input.data == NULL) { diff --git a/src/tools/amduat_conformance_run.c b/src/tools/amduat_conformance_run.c index 997c2ae..d36b304 100644 --- a/src/tools/amduat_conformance_run.c +++ b/src/tools/amduat_conformance_run.c @@ -17,6 +17,8 @@ static void usage(FILE *out) { " --backend Backend to run (repeatable)\n" " --workdir Workdir for backend state\n" " --fixtures Fixtures root (default: tests/fixtures/conformance)\n" + " --json Emit machine-readable JSON summary\n" + " --json-out Write JSON summary to a file\n" " --help Show this help\n"); } @@ -85,6 +87,68 @@ static int print_results(const char *suite, return rc; } +static void json_escape(FILE *out, const char *value) { + const unsigned char *p = (const unsigned char *)value; + fputc('"', out); + while (*p != '\0') { + switch (*p) { + case '\\': + case '"': + fputc('\\', out); + fputc(*p, out); + break; + case '\n': + fputs("\\n", out); + break; + case '\r': + fputs("\\r", out); + break; + case '\t': + fputs("\\t", out); + break; + default: + if (*p < 0x20) { + fprintf(out, "\\u%04x", (unsigned int)*p); + } else { + fputc(*p, out); + } + break; + } + p++; + } + fputc('"', out); +} + +static const char *status_string(amduat_conformance_status_t status) { + switch (status) { + case AMDUAT_CONFORMANCE_STATUS_PASS: + return "PASS"; + case AMDUAT_CONFORMANCE_STATUS_SKIP: + return "SKIPPED"; + case AMDUAT_CONFORMANCE_STATUS_FAIL: + default: + return "FAIL"; + } +} + +static void json_write_results(FILE *out, + const amduat_conformance_backend_result_t *results, + size_t results_len) { + size_t i; + fputc('[', out); + for (i = 0; i < results_len; ++i) { + if (i != 0) { + fputc(',', out); + } + fputs("{\"backend\":", out); + json_escape(out, results[i].backend); + fputs(",\"status\":", out); + json_escape(out, status_string(results[i].status)); + fputc('}', out); + } + fputc(']', out); +} + static const char *fallback_fixtures_root(const char *path) { struct stat st; @@ -106,10 +170,16 @@ int main(int argc, char **argv) { const char *suite = "all"; const char *fixtures = "tests/fixtures/conformance"; const char *workdir = "/tmp/amduat_conformance"; + const char *json_out_path = NULL; char *backend_selection = NULL; bool include_optional = env_truthy(getenv("AMDUAT_CONFORMANCE_INCLUDE_OPTIONAL")); + bool json_out = false; int i; int rc = 0; + amduat_conformance_backend_result_t *asl_results = NULL; + size_t asl_results_len = 0u; + amduat_conformance_backend_result_t *tgk_results = NULL; + size_t tgk_results_len = 0u; for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "--help") == 0) { @@ -149,6 +219,15 @@ int main(int argc, char **argv) { fixtures = argv[++i]; continue; } + if (strcmp(argv[i], "--json") == 0) { + json_out = true; + continue; + } + if (strcmp(argv[i], "--json-out") == 0 && i + 1 < argc) { + json_out = true; + json_out_path = argv[++i]; + continue; + } fprintf(stderr, "unknown argument: %s\n", argv[i]); usage(stderr); @@ -167,33 +246,31 @@ int main(int argc, char **argv) { } if (strcmp(suite, "asl") == 0 || strcmp(suite, "all") == 0) { - amduat_conformance_backend_result_t *results = NULL; - size_t results_len = 0u; int suite_rc = amduat_asl_store_conformance_run( backend_selection, fixtures, workdir, include_optional, true, - &results, &results_len); + &asl_results, &asl_results_len); if (suite_rc != 0) { rc = 1; } - if (print_results("asl", results, results_len) != 0) { - rc = 1; + if (!json_out) { + if (print_results("asl", asl_results, asl_results_len) != 0) { + rc = 1; + } } - free(results); } if (strcmp(suite, "tgk") == 0 || strcmp(suite, "all") == 0) { - amduat_conformance_backend_result_t *results = NULL; - size_t results_len = 0u; int suite_rc = amduat_tgk_store_conformance_run( backend_selection, fixtures, workdir, include_optional, true, - &results, &results_len); + &tgk_results, &tgk_results_len); if (suite_rc != 0) { rc = 1; } - if (print_results("tgk", results, results_len) != 0) { - rc = 1; + if (!json_out) { + if (print_results("tgk", tgk_results, tgk_results_len) != 0) { + rc = 1; + } } - free(results); } if (strcmp(suite, "asl") != 0 && strcmp(suite, "tgk") != 0 && @@ -203,6 +280,40 @@ int main(int argc, char **argv) { rc = 1; } + if (json_out) { + FILE *out = stdout; + if (json_out_path != NULL) { + out = fopen(json_out_path, "w"); + if (out == NULL) { + fprintf(stderr, "failed to open json output: %s\n", json_out_path); + rc = 1; + } + } + if (out != NULL) { + fputs("{\"suite\":", out); + json_escape(out, suite); + fputs(",\"fixtures\":", out); + json_escape(out, fixtures); + fputs(",\"workdir\":", out); + json_escape(out, workdir); + fputs(",\"include_optional\":", out); + fputs(include_optional ? "true" : "false", out); + fputs(",\"results\":{", out); + fputs("\"asl\":", out); + json_write_results(out, asl_results, asl_results_len); + fputs(",\"tgk\":", out); + json_write_results(out, tgk_results, tgk_results_len); + fputs("},\"status\":", out); + json_escape(out, rc == 0 ? "PASS" : "FAIL"); + fputs("}\n", out); + if (out != stdout) { + fclose(out); + } + } + } + + free(asl_results); + free(tgk_results); free(backend_selection); return rc; } diff --git a/tests/asl/test_asl_error_taxonomy.c b/tests/asl/test_asl_error_taxonomy.c new file mode 100644 index 0000000..dc87b10 --- /dev/null +++ b/tests/asl/test_asl_error_taxonomy.c @@ -0,0 +1,139 @@ +#include "amduat/asl/store.h" + +#include +#include + +static amduat_asl_store_error_t stub_put(void *ctx, + amduat_artifact_t artifact, + amduat_reference_t *out_ref) { + amduat_hash_id_t *hash_id = (amduat_hash_id_t *)ctx; + if (out_ref != NULL) { + uint8_t digest = 0xaa; + *out_ref = amduat_reference(*hash_id, + amduat_octets(&digest, sizeof(digest))); + } + (void)artifact; + return AMDUAT_ASL_STORE_OK; +} + +static amduat_asl_store_error_t stub_get_not_found(void *ctx, + amduat_reference_t ref, + amduat_artifact_t *out_artifact) { + (void)ctx; + (void)ref; + (void)out_artifact; + return AMDUAT_ASL_STORE_ERR_NOT_FOUND; +} + +static amduat_asl_store_error_t stub_get_io(void *ctx, + amduat_reference_t ref, + amduat_artifact_t *out_artifact) { + (void)ctx; + (void)ref; + (void)out_artifact; + return AMDUAT_ASL_STORE_ERR_IO; +} + +static int test_not_found_mapping(void) { + amduat_asl_store_ops_t ops; + amduat_asl_store_t store; + amduat_asl_store_config_t cfg = {0}; + amduat_reference_t ref; + amduat_artifact_t artifact; + amduat_asl_store_error_t err; + + amduat_asl_store_ops_init(&ops); + ops.get = stub_get_not_found; + cfg.hash_id = 0x0001u; + amduat_asl_store_init(&store, cfg, ops, NULL); + + ref = amduat_reference(cfg.hash_id, amduat_octets(NULL, 0u)); + memset(&artifact, 0, sizeof(artifact)); + err = amduat_asl_store_get(&store, ref, &artifact); + return err == AMDUAT_ASL_STORE_ERR_NOT_FOUND ? 0 : 1; +} + +static int test_io_mapping(void) { + amduat_asl_store_ops_t ops; + amduat_asl_store_t store; + amduat_asl_store_config_t cfg = {0}; + amduat_reference_t ref; + amduat_artifact_t artifact; + amduat_asl_store_error_t err; + + amduat_asl_store_ops_init(&ops); + ops.get = stub_get_io; + cfg.hash_id = 0x0001u; + amduat_asl_store_init(&store, cfg, ops, NULL); + + ref = amduat_reference(cfg.hash_id, amduat_octets(NULL, 0u)); + memset(&artifact, 0, sizeof(artifact)); + err = amduat_asl_store_get(&store, ref, &artifact); + return err == AMDUAT_ASL_STORE_ERR_IO ? 0 : 1; +} + +static int test_integrity_mapping(void) { + amduat_asl_store_ops_t ops; + amduat_asl_store_t store; + amduat_asl_store_config_t cfg = {0}; + amduat_artifact_t artifact; + amduat_reference_t ref; + amduat_hash_id_t wrong_hash = 0x00ffu; + amduat_asl_store_error_t err; + + amduat_asl_store_ops_init(&ops); + ops.put = stub_put; + cfg.hash_id = 0x0001u; + amduat_asl_store_init(&store, cfg, ops, &wrong_hash); + + memset(&artifact, 0, sizeof(artifact)); + err = amduat_asl_store_put(&store, artifact, &ref); + if (err != AMDUAT_ASL_STORE_ERR_INTEGRITY) { + return 1; + } + amduat_reference_free(&ref); + return 0; +} + +static int test_unsupported_mapping(void) { + amduat_asl_store_ops_t ops; + amduat_asl_store_t store; + amduat_asl_store_config_t cfg = {0}; + amduat_reference_t ref; + amduat_artifact_t artifact; + amduat_asl_store_error_t err; + + amduat_asl_store_ops_init(&ops); + cfg.hash_id = 0x0001u; + amduat_asl_store_init(&store, cfg, ops, NULL); + + ref = amduat_reference(cfg.hash_id, amduat_octets(NULL, 0u)); + memset(&artifact, 0, sizeof(artifact)); + err = amduat_asl_store_get(&store, ref, &artifact); + if (err != AMDUAT_ASL_STORE_ERR_UNSUPPORTED) { + return 1; + } + + err = amduat_asl_store_put(&store, artifact, &ref); + return err == AMDUAT_ASL_STORE_ERR_UNSUPPORTED ? 0 : 1; +} + +int main(void) { + if (test_not_found_mapping() != 0) { + fprintf(stderr, "not_found mapping failed\n"); + return 1; + } + if (test_io_mapping() != 0) { + fprintf(stderr, "io mapping failed\n"); + return 1; + } + if (test_integrity_mapping() != 0) { + fprintf(stderr, "integrity mapping failed\n"); + return 1; + } + if (test_unsupported_mapping() != 0) { + fprintf(stderr, "unsupported mapping failed\n"); + return 1; + } + return 0; +} diff --git a/tests/asl/test_asl_log_encoding.c b/tests/asl/test_asl_log_encoding.c new file mode 100644 index 0000000..731f55a --- /dev/null +++ b/tests/asl/test_asl_log_encoding.c @@ -0,0 +1,86 @@ +#include "amduat/enc/asl_log.h" + +#include +#include +#include +#include + +static void fill_hash(uint8_t *out, uint8_t seed) { + for (size_t i = 0; i < 32u; ++i) { + out[i] = (uint8_t)(seed + i); + } +} + +static bool records_equal(const amduat_asl_log_record_t *lhs, + const amduat_asl_log_record_t *rhs) { + if (lhs->logseq != rhs->logseq || lhs->record_type != rhs->record_type || + lhs->payload.len != rhs->payload.len) { + return false; + } + if (lhs->payload.len != 0 && + memcmp(lhs->payload.data, rhs->payload.data, lhs->payload.len) != 0) { + return false; + } + if (memcmp(lhs->record_hash, rhs->record_hash, sizeof(lhs->record_hash)) != 0) { + return false; + } + return true; +} + +int main(void) { + uint8_t payload_a[4] = {0x01, 0x02, 0x03, 0x04}; + uint8_t payload_b[8] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}; + uint8_t payload_c[2] = {0xaa, 0xbb}; + amduat_asl_log_record_t records[3]; + amduat_asl_log_record_t *decoded = NULL; + size_t decoded_len = 0u; + amduat_octets_t bytes; + + memset(records, 0, sizeof(records)); + + records[0].logseq = 1u; + records[0].record_type = AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL; + records[0].payload = amduat_octets(payload_a, sizeof(payload_a)); + fill_hash(records[0].record_hash, 0x10); + + records[1].logseq = 2u; + records[1].record_type = AMDUAT_ASL_LOG_RECORD_TOMBSTONE; + records[1].payload = amduat_octets(payload_b, sizeof(payload_b)); + fill_hash(records[1].record_hash, 0x20); + + records[2].logseq = 3u; + records[2].record_type = AMDUAT_ASL_LOG_RECORD_SNAPSHOT_ANCHOR; + records[2].payload = amduat_octets(payload_c, sizeof(payload_c)); + fill_hash(records[2].record_hash, 0x30); + + if (!amduat_enc_asl_log_encode_v1(records, 3u, &bytes)) { + fprintf(stderr, "encode failed\n"); + return 1; + } + + if (!amduat_enc_asl_log_decode_v1(bytes, &decoded, &decoded_len)) { + fprintf(stderr, "decode failed\n"); + amduat_octets_free(&bytes); + return 1; + } + + if (decoded_len != 3u) { + fprintf(stderr, "decoded length mismatch\n"); + amduat_octets_free(&bytes); + amduat_enc_asl_log_free(decoded, decoded_len); + return 1; + } + + for (size_t i = 0; i < decoded_len; ++i) { + if (!records_equal(&records[i], &decoded[i])) { + fprintf(stderr, "record mismatch at %zu\n", i); + amduat_octets_free(&bytes); + amduat_enc_asl_log_free(decoded, decoded_len); + return 1; + } + } + + amduat_octets_free(&bytes); + amduat_enc_asl_log_free(decoded, decoded_len); + return 0; +} diff --git a/tests/asl/test_asl_ref_derive.c b/tests/asl/test_asl_ref_derive.c index b43e96e..1b8e8de 100644 --- a/tests/asl/test_asl_ref_derive.c +++ b/tests/asl/test_asl_ref_derive.c @@ -1,3 +1,4 @@ +#include "amduat/asl/identity.h" #include "amduat/asl/ref_derive.h" #include "amduat/enc/asl1_core.h" #include "amduat/enc/asl1_core_codec.h" @@ -39,10 +40,11 @@ static int test_ref_derive_roundtrip(void) { fprintf(stderr, "digest buffer too small\n"); goto cleanup; } - if (!amduat_hash_asl1_digest(ref.hash_id, - artifact_bytes, - digest, - hash_desc->digest_len)) { + if (!amduat_hash_asl1_digest_domain(ref.hash_id, + amduat_asl_identity_domain_separator(), + artifact_bytes, + digest, + hash_desc->digest_len)) { fprintf(stderr, "hash digest failed\n"); goto cleanup; } diff --git a/tests/enc/test_asl_tgk_exec_plan.c b/tests/enc/test_asl_tgk_exec_plan.c index 6798b3f..0bcd242 100644 --- a/tests/enc/test_asl_tgk_exec_plan.c +++ b/tests/enc/test_asl_tgk_exec_plan.c @@ -13,6 +13,77 @@ enum { k_input_offset = 97 }; +static bool read_file(const char *path, uint8_t **out_bytes, size_t *out_len) { + FILE *fp; + long size; + uint8_t *buffer; + size_t read_len; + + if (path == NULL || out_bytes == NULL || out_len == NULL) { + return false; + } + *out_bytes = NULL; + *out_len = 0u; + + fp = fopen(path, "rb"); + if (fp == NULL) { + return false; + } + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + size = ftell(fp); + if (size < 0) { + fclose(fp); + return false; + } + if (fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + return false; + } + + buffer = (uint8_t *)malloc((size_t)size + 1u); + if (buffer == NULL) { + fclose(fp); + return false; + } + read_len = fread(buffer, 1u, (size_t)size, fp); + fclose(fp); + if (read_len != (size_t)size) { + free(buffer); + return false; + } + buffer[size] = '\0'; + + *out_bytes = buffer; + *out_len = (size_t)size; + return true; +} + +static bool build_fixture_path(char *out, size_t out_len) { + const char *needle = "/tests/enc/"; + const char *path = __FILE__; + const char *pos = strstr(path, needle); + size_t prefix_len; + + if (pos == NULL) { + return false; + } + prefix_len = (size_t)(pos - path); + if (prefix_len + strlen("/tests/fixtures/conformance/" + "asl_tgk_exec_plan/artifacts/plan0.bin") + 1u > + out_len) { + return false; + } + + memcpy(out, path, prefix_len); + out[prefix_len] = '\0'; + strcat(out, "/tests/fixtures/conformance/" + "asl_tgk_exec_plan/artifacts/plan0.bin"); + return true; +} + static void store_u32_le(uint8_t *out, uint32_t value) { out[0] = (uint8_t)(value & 0xffu); out[1] = (uint8_t)((value >> 8) & 0xffu); @@ -217,6 +288,68 @@ cleanup: return exit_code; } +static int test_invalid_snapshot_range(void) { + amduat_asl_tgk_exec_operator_def_t operators[1]; + amduat_asl_tgk_exec_plan_t plan; + + memset(operators, 0, sizeof(operators)); + fill_default_params(&operators[0].params); + operators[0].op_id = 1u; + operators[0].op_type = AMDUAT_ASL_TGK_EXEC_OP_SEGMENT_SCAN; + operators[0].snapshot.logseq_min = 10u; + operators[0].snapshot.logseq_max = 1u; + + plan.plan_version = AMDUAT_ASL_TGK_EXEC_PLAN_VERSION; + plan.operator_count = 1u; + plan.operators = operators; + + if (amduat_enc_asl_tgk_exec_plan_encode_v1(&plan, + &(amduat_octets_t){0})) { + fprintf(stderr, "encode unexpectedly succeeded\n"); + return 1; + } + return 0; +} + +static int test_fixture_plan0(void) { + char path[512]; + uint8_t *bytes = NULL; + size_t len = 0u; + amduat_asl_tgk_exec_plan_t plan; + int exit_code = 1; + + if (!build_fixture_path(path, sizeof(path))) { + fprintf(stderr, "fixture path build failed\n"); + return exit_code; + } + if (!read_file(path, &bytes, &len)) { + fprintf(stderr, "fixture read failed: %s\n", path); + return exit_code; + } + + if (!amduat_enc_asl_tgk_exec_plan_decode_v1(amduat_octets(bytes, len), + &plan)) { + fprintf(stderr, "fixture decode failed\n"); + goto cleanup; + } + + if (plan.plan_version != AMDUAT_ASL_TGK_EXEC_PLAN_VERSION || + plan.operator_count != 1u || + plan.operators[0].op_type != AMDUAT_ASL_TGK_EXEC_OP_SEGMENT_SCAN || + plan.operators[0].snapshot.logseq_max != 10u) { + fprintf(stderr, "fixture contents mismatch\n"); + goto cleanup_plan; + } + + exit_code = 0; + +cleanup_plan: + amduat_enc_asl_tgk_exec_plan_free(&plan); +cleanup: + free(bytes); + return exit_code; +} + int main(void) { if (test_round_trip() != 0) { return 1; @@ -227,5 +360,11 @@ int main(void) { if (test_truncated_decode() != 0) { return 1; } - return test_invalid_input_count(); + if (test_invalid_input_count() != 0) { + return 1; + } + if (test_invalid_snapshot_range() != 0) { + return 1; + } + return test_fixture_plan0(); } diff --git a/tests/enc/test_fcs_pcb1.c b/tests/enc/test_fcs_pcb1.c new file mode 100644 index 0000000..f9c41bc --- /dev/null +++ b/tests/enc/test_fcs_pcb1.c @@ -0,0 +1,236 @@ +#include "amduat/enc/fcs1.h" +#include "amduat/enc/asl1_core_codec.h" +#include "amduat/fps.h" + +#include +#include +#include +#include + +static bool encode_varint(uint64_t value, uint8_t **out, size_t *out_len) { + uint8_t buf[10]; + size_t len = 0u; + + do { + uint8_t byte = (uint8_t)(value & 0x7fu); + value >>= 7u; + if (value != 0u) { + byte |= 0x80u; + } + buf[len++] = byte; + } while (value != 0u); + + *out = (uint8_t *)malloc(len); + if (*out == NULL) { + return false; + } + memcpy(*out, buf, len); + *out_len = len; + return true; +} + +static bool append_bytes(uint8_t **buf, size_t *len, size_t *cap, + const void *data, size_t data_len) { + if (*len + data_len > *cap) { + size_t next = *cap == 0u ? 64u : *cap * 2u; + while (next < *len + data_len) { + next *= 2u; + } + uint8_t *next_buf = (uint8_t *)realloc(*buf, next); + if (next_buf == NULL) { + return false; + } + *buf = next_buf; + *cap = next; + } + memcpy(*buf + *len, data, data_len); + *len += data_len; + return true; +} + +static bool append_varint(uint8_t **buf, size_t *len, size_t *cap, + uint64_t value) { + uint8_t *enc = NULL; + size_t enc_len = 0u; + bool ok = encode_varint(value, &enc, &enc_len) && + append_bytes(buf, len, cap, enc, enc_len); + free(enc); + return ok; +} + +static bool build_pcb1_bytes(amduat_octets_t *out_bytes, + amduat_reference_t *out_cid) { + uint8_t *buf = NULL; + size_t len = 0u; + size_t cap = 0u; + uint8_t manifest[1 + 1 + 1 + 1 + 32]; + size_t manifest_len = 0u; + uint8_t slot_data[] = {0x01, 0x02, 0x03}; + amduat_octets_t payload; + amduat_octets_t cid_bytes; + + manifest[manifest_len++] = 0x01; /* slot count */ + manifest[manifest_len++] = 0x01; /* index */ + manifest[manifest_len++] = 0x00; /* name len */ + manifest[manifest_len++] = 0x00; /* type */ + manifest[manifest_len++] = 0x20; /* digest len */ + memset(manifest + manifest_len, 0, 32); + manifest_len += 32; + + if (!append_bytes(&buf, &len, &cap, "PCB1", 4u)) { + return false; + } + { + uint8_t header[3] = {0x01, 0x00, 0x00}; + if (!append_bytes(&buf, &len, &cap, header, sizeof(header))) { + free(buf); + return false; + } + } + + if (!append_bytes(&buf, &len, &cap, (uint8_t[]){0x50}, 1u) || + !append_varint(&buf, &len, &cap, manifest_len) || + !append_bytes(&buf, &len, &cap, manifest, manifest_len)) { + free(buf); + return false; + } + + if (!append_bytes(&buf, &len, &cap, (uint8_t[]){0x51}, 1u) || + !append_varint(&buf, &len, &cap, sizeof(slot_data)) || + !append_bytes(&buf, &len, &cap, slot_data, sizeof(slot_data))) { + free(buf); + return false; + } + + payload = amduat_octets(buf, len); + if (!amduat_fps_cid_for_payload(AMDUAT_HASH_ASL1_ID_SHA256, + payload, + out_cid)) { + free(buf); + return false; + } + if (!amduat_enc_asl1_core_encode_reference_v1(*out_cid, &cid_bytes)) { + amduat_reference_free(out_cid); + free(buf); + return false; + } + amduat_octets_free(&cid_bytes); + + *out_bytes = payload; + return true; +} + +static bool build_fcs1_bytes(amduat_octets_t *out_bytes, + amduat_reference_t function_ref, + amduat_reference_t pcb_ref, + uint32_t arity) { + uint8_t *buf = NULL; + size_t len = 0u; + size_t cap = 0u; + amduat_octets_t func_bytes; + amduat_octets_t pcb_bytes; + + if (!amduat_enc_asl1_core_encode_reference_v1(function_ref, &func_bytes) || + !amduat_enc_asl1_core_encode_reference_v1(pcb_ref, &pcb_bytes)) { + return false; + } + + if (!append_bytes(&buf, &len, &cap, "FCS1", 4u)) { + return false; + } + { + uint8_t header[3] = {0x01, 0x00, 0x00}; + if (!append_bytes(&buf, &len, &cap, header, sizeof(header))) { + free(buf); + return false; + } + } + + if (!append_bytes(&buf, &len, &cap, (uint8_t[]){0x30}, 1u) || + !append_varint(&buf, &len, &cap, func_bytes.len) || + !append_bytes(&buf, &len, &cap, func_bytes.data, func_bytes.len)) { + free(buf); + amduat_octets_free(&func_bytes); + amduat_octets_free(&pcb_bytes); + return false; + } + + if (!append_bytes(&buf, &len, &cap, (uint8_t[]){0x31}, 1u) || + !append_varint(&buf, &len, &cap, pcb_bytes.len) || + !append_bytes(&buf, &len, &cap, pcb_bytes.data, pcb_bytes.len)) { + free(buf); + amduat_octets_free(&func_bytes); + amduat_octets_free(&pcb_bytes); + return false; + } + + if (!append_bytes(&buf, &len, &cap, (uint8_t[]){0x32}, 1u) || + !append_varint(&buf, &len, &cap, arity)) { + free(buf); + amduat_octets_free(&func_bytes); + amduat_octets_free(&pcb_bytes); + return false; + } + + amduat_octets_free(&func_bytes); + amduat_octets_free(&pcb_bytes); + *out_bytes = amduat_octets(buf, len); + return true; +} + +typedef struct { + amduat_reference_t pcb_ref; + amduat_octets_t pcb_bytes; +} resolver_ctx_t; + +static bool resolve_ref(void *ctx, + amduat_reference_t ref, + amduat_octets_t *out_bytes) { + resolver_ctx_t *state = (resolver_ctx_t *)ctx; + + if (state == NULL || out_bytes == NULL) { + return false; + } + if (!amduat_reference_eq(ref, state->pcb_ref)) { + return false; + } + return amduat_octets_clone(state->pcb_bytes, out_bytes); +} + +int main(void) { + amduat_octets_t pcb_bytes; + amduat_reference_t pcb_ref; + amduat_octets_t fcs_bytes; + amduat_reference_t func_ref; + uint8_t func_digest[32]; + resolver_ctx_t ctx; + amduat_fcs1_error_t err; + + if (!build_pcb1_bytes(&pcb_bytes, &pcb_ref)) { + return 1; + } + memset(func_digest, 0x11, sizeof(func_digest)); + func_ref = amduat_reference(AMDUAT_HASH_ASL1_ID_SHA256, + amduat_octets(func_digest, + sizeof(func_digest))); + if (!build_fcs1_bytes(&fcs_bytes, func_ref, pcb_ref, 1u)) { + amduat_reference_free(&pcb_ref); + amduat_octets_free(&pcb_bytes); + return 1; + } + + ctx.pcb_ref = pcb_ref; + ctx.pcb_bytes = pcb_bytes; + err = amduat_fcs1_validate_v1(fcs_bytes, resolve_ref, &ctx); + if (err != AMDUAT_FCS1_OK) { + amduat_reference_free(&pcb_ref); + amduat_octets_free(&pcb_bytes); + amduat_octets_free(&fcs_bytes); + return 1; +} + + amduat_reference_free(&pcb_ref); + amduat_octets_free(&pcb_bytes); + amduat_octets_free(&fcs_bytes); + return 0; +} diff --git a/tests/enc/test_fps.c b/tests/enc/test_fps.c new file mode 100644 index 0000000..895bfc6 --- /dev/null +++ b/tests/enc/test_fps.c @@ -0,0 +1,626 @@ +#include "amduat/fps.h" + +#include +#include +#include +#include + +typedef struct { + const uint8_t *data; + size_t len; + size_t offset; +} cursor_t; + +static bool read_file(const char *path, uint8_t **out_bytes, size_t *out_len) { + FILE *fp; + long size; + uint8_t *buffer; + size_t read_len; + + if (path == NULL || out_bytes == NULL || out_len == NULL) { + return false; + } + *out_bytes = NULL; + *out_len = 0u; + + fp = fopen(path, "rb"); + if (fp == NULL) { + return false; + } + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + size = ftell(fp); + if (size < 0) { + fclose(fp); + return false; + } + if (fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + return false; + } + + buffer = (uint8_t *)malloc((size_t)size + 1u); + if (buffer == NULL) { + fclose(fp); + return false; + } + read_len = fread(buffer, 1u, (size_t)size, fp); + fclose(fp); + if (read_len != (size_t)size) { + free(buffer); + return false; + } + buffer[size] = '\0'; + + *out_bytes = buffer; + *out_len = (size_t)size; + return true; +} + +static bool build_fixture_path(char *out, size_t out_len) { + const char *needle = "/tests/enc/"; + const char *path = __FILE__; + const char *pos = strstr(path, needle); + size_t prefix_len; + + if (pos == NULL) { + return false; + } + prefix_len = (size_t)(pos - path); + if (prefix_len + strlen("/tests/fixtures/conformance/" + "fps_1/manifest.json") + 1u > + out_len) { + return false; + } + + memcpy(out, path, prefix_len); + out[prefix_len] = '\0'; + strcat(out, "/tests/fixtures/conformance/fps_1/manifest.json"); + return true; +} + +static bool cursor_expect(cursor_t *cur, const char *literal) { + size_t len = strlen(literal); + if (cur->len - cur->offset < len) { + return false; + } + if (memcmp(cur->data + cur->offset, literal, len) != 0) { + return false; + } + cur->offset += len; + return true; +} + +static bool cursor_skip_ws(cursor_t *cur) { + while (cur->offset < cur->len) { + char c = (char)cur->data[cur->offset]; + if (c != ' ' && c != '\n' && c != '\r' && c != '\t') { + break; + } + cur->offset++; + } + return true; +} + +static bool cursor_read_string(cursor_t *cur, char **out) { + size_t start; + size_t end; + char *buf; + + if (!cursor_skip_ws(cur) || cur->offset >= cur->len || + cur->data[cur->offset] != '"') { + return false; + } + cur->offset++; + start = cur->offset; + while (cur->offset < cur->len && cur->data[cur->offset] != '"') { + cur->offset++; + } + if (cur->offset >= cur->len) { + return false; + } + end = cur->offset; + cur->offset++; + + buf = (char *)malloc(end - start + 1u); + if (buf == NULL) { + return false; + } + memcpy(buf, cur->data + start, end - start); + buf[end - start] = '\0'; + *out = buf; + return true; +} + +static bool cursor_read_number(cursor_t *cur, size_t *out) { + size_t start; + size_t value = 0u; + bool has_digit = false; + + if (!cursor_skip_ws(cur)) { + return false; + } + start = cur->offset; + while (cur->offset < cur->len) { + char c = (char)cur->data[cur->offset]; + if (c < '0' || c > '9') { + break; + } + has_digit = true; + value = value * 10u + (size_t)(c - '0'); + cur->offset++; + } + if (!has_digit || start == cur->offset) { + return false; + } + *out = value; + return true; +} + +static bool hex_decode(const char *hex, uint8_t **out, size_t *out_len) { + size_t len; + size_t i; + uint8_t *buf; + + if (hex == NULL || out == NULL || out_len == NULL) { + return false; + } + len = strlen(hex); + if (len % 2u != 0u) { + return false; + } + buf = (uint8_t *)malloc(len / 2u); + if (buf == NULL) { + return false; + } + for (i = 0u; i < len; i += 2u) { + unsigned int byte; + if (sscanf(hex + i, "%2x", &byte) != 1) { + free(buf); + return false; + } + buf[i / 2u] = (uint8_t)byte; + } + *out = buf; + *out_len = len / 2u; + return true; +} + +static bool hex_eq(const char *hex, amduat_octets_t bytes) { + uint8_t *decoded = NULL; + size_t decoded_len = 0u; + bool ok = false; + + if (!hex_decode(hex, &decoded, &decoded_len)) { + return false; + } + ok = decoded_len == bytes.len && + (decoded_len == 0u || + memcmp(decoded, bytes.data, decoded_len) == 0); + free(decoded); + return ok; +} + +static bool cid_matches(const char *hex, amduat_octets_t output) { + amduat_reference_t cid; + size_t hex_len = strlen(hex); + char *lower = NULL; + char *scratch = NULL; + bool ok = false; + + if (!amduat_fps_cid_for_payload(AMDUAT_HASH_ASL1_ID_SHA256, output, &cid)) { + return false; + } + lower = (char *)malloc(hex_len + 1u); + if (lower == NULL) { + amduat_reference_free(&cid); + return false; + } + scratch = (char *)malloc(cid.digest.len * 2u + 1u); + if (scratch == NULL) { + free(lower); + amduat_reference_free(&cid); + return false; + } + for (size_t i = 0u; i < hex_len; ++i) { + char c = hex[i]; + if (c >= 'A' && c <= 'F') { + c = (char)(c - 'A' + 'a'); + } + lower[i] = c; + } + lower[hex_len] = '\0'; + for (size_t i = 0u; i < cid.digest.len; ++i) { + sprintf(scratch + i * 2u, "%02x", cid.digest.data[i]); + } + ok = strcmp(lower, scratch) == 0; + free(lower); + free(scratch); + amduat_reference_free(&cid); + return ok; +} + +static bool parse_manifest_cases(cursor_t *cur) { + if (!cursor_skip_ws(cur) || cur->offset >= cur->len || + cur->data[cur->offset] != '[') { + return false; + } + cur->offset++; + while (true) { + char *field = NULL; + char *value = NULL; + char *op = NULL; + char *input = NULL; + char *insert = NULL; + char *output = NULL; + char *cid_hex = NULL; + char *level = NULL; + char *inputs[4] = {0}; + size_t inputs_len = 0u; + size_t offset = 0u; + size_t length = 0u; + bool has_offset = false; + bool has_length = false; + bool has_inputs = false; + bool has_input = false; + bool has_insert = false; + bool has_level = false; + bool has_output = false; + bool has_cid = false; + bool ok = true; + + if (!cursor_skip_ws(cur)) { + return false; + } + if (cur->offset < cur->len && cur->data[cur->offset] == ']') { + cur->offset++; + return true; + } + if (cur->offset >= cur->len || cur->data[cur->offset] != '{') { + return false; + } + cur->offset++; + while (true) { + if (!cursor_read_string(cur, &field)) { + ok = false; + break; + } + if (!cursor_skip_ws(cur) || cur->offset >= cur->len || + cur->data[cur->offset] != ':') { + ok = false; + break; + } + cur->offset++; + if (strcmp(field, "id") == 0 || + strcmp(field, "op") == 0 || strcmp(field, "input") == 0 || + strcmp(field, "insert") == 0 || strcmp(field, "output") == 0 || + strcmp(field, "cid_sha256") == 0 || strcmp(field, "level") == 0) { + if (!cursor_read_string(cur, &value)) { + ok = false; + break; + } + if (strcmp(field, "id") == 0) { + free(value); + value = NULL; + } else if (strcmp(field, "op") == 0) { + op = value; + } else if (strcmp(field, "input") == 0) { + input = value; + has_input = true; + } else if (strcmp(field, "insert") == 0) { + insert = value; + has_insert = true; + } else if (strcmp(field, "output") == 0) { + output = value; + has_output = true; + } else if (strcmp(field, "cid_sha256") == 0) { + cid_hex = value; + has_cid = true; + } else { + level = value; + has_level = true; + } + } else if (strcmp(field, "offset") == 0 || + strcmp(field, "length") == 0) { + size_t num = 0u; + if (!cursor_read_number(cur, &num)) { + ok = false; + break; + } + if (strcmp(field, "offset") == 0) { + offset = num; + has_offset = true; + } else { + length = num; + has_length = true; + } + } else if (strcmp(field, "inputs") == 0) { + if (!cursor_skip_ws(cur) || cur->offset >= cur->len || + cur->data[cur->offset] != '[') { + ok = false; + break; + } + cur->offset++; + while (true) { + if (!cursor_skip_ws(cur)) { + ok = false; + break; + } + if (cur->offset < cur->len && cur->data[cur->offset] == ']') { + cur->offset++; + break; + } + if (inputs_len >= 4u) { + ok = false; + break; + } + if (!cursor_read_string(cur, &inputs[inputs_len++])) { + ok = false; + break; + } + if (!cursor_skip_ws(cur)) { + ok = false; + break; + } + if (cur->offset < cur->len && cur->data[cur->offset] == ',') { + cur->offset++; + continue; + } + } + has_inputs = true; + } else { + ok = false; + break; + } + + free(field); + field = NULL; + if (!cursor_skip_ws(cur)) { + ok = false; + break; + } + if (cur->offset < cur->len && cur->data[cur->offset] == ',') { + cur->offset++; + continue; + } + if (cur->offset < cur->len && cur->data[cur->offset] == '}') { + cur->offset++; + break; + } + } + + if (!ok || op == NULL || !has_output || !has_cid) { + ok = false; + } + + if (ok) { + amduat_octets_t output_bytes = amduat_octets(NULL, 0u); + amduat_octets_t out = amduat_octets(NULL, 0u); + amduat_octets_t input_bytes = amduat_octets(NULL, 0u); + amduat_octets_t insert_bytes = amduat_octets(NULL, 0u); + uint8_t *decoded = NULL; + size_t decoded_len = 0u; + uint8_t *insert_decoded = NULL; + size_t insert_len = 0u; + + if (!hex_decode(output, &decoded, &decoded_len)) { + ok = false; + } else { + output_bytes = amduat_octets(decoded, decoded_len); + } + + if (ok && !cid_matches(cid_hex, output_bytes)) { + ok = false; + } + + if (ok && strcmp(op, "slice") == 0) { + if (!has_input || !has_offset || !has_length) { + ok = false; + } else if (!hex_decode(input, &decoded, &decoded_len)) { + ok = false; + } else { + input_bytes = amduat_octets(decoded, decoded_len); + if (amduat_fps_slice(input_bytes, offset, length, &out) != + AMDUAT_FPS_OK) { + ok = false; + } else if (!hex_eq(output, out)) { + ok = false; + } + } + } else if (ok && strcmp(op, "concat") == 0) { + if (!has_inputs) { + ok = false; + } else { + amduat_octets_t in_octets[4]; + for (size_t i = 0u; i < inputs_len; ++i) { + if (!hex_decode(inputs[i], &decoded, &decoded_len)) { + ok = false; + break; + } + in_octets[i] = amduat_octets(decoded, decoded_len); + } + if (ok) { + if (amduat_fps_concat(in_octets, inputs_len, &out) != + AMDUAT_FPS_OK) { + ok = false; + } else if (!hex_eq(output, out)) { + ok = false; + } + } + for (size_t i = 0u; i < inputs_len; ++i) { + free((void *)in_octets[i].data); + } + } + } else if (ok && strcmp(op, "reverse") == 0) { + amduat_fps_reverse_level_t lvl = AMDUAT_FPS_REVERSE_BYTES; + if (!has_input || !has_level) { + ok = false; + } else if (strcmp(level, "bit") == 0) { + lvl = AMDUAT_FPS_REVERSE_BITS; + } else if (strcmp(level, "byte") == 0) { + lvl = AMDUAT_FPS_REVERSE_BYTES; + } else if (strcmp(level, "word") == 0) { + lvl = AMDUAT_FPS_REVERSE_WORDS; + } else if (strcmp(level, "long") == 0) { + lvl = AMDUAT_FPS_REVERSE_LONGS; + } else { + ok = false; + } + if (ok && !hex_decode(input, &decoded, &decoded_len)) { + ok = false; + } + if (ok) { + input_bytes = amduat_octets(decoded, decoded_len); + if (amduat_fps_reverse(input_bytes, lvl, &out) != AMDUAT_FPS_OK) { + ok = false; + } else if (!hex_eq(output, out)) { + ok = false; + } + } + } else if (ok && strcmp(op, "splice") == 0) { + if (!has_input || !has_insert || !has_offset) { + ok = false; + } else if (!hex_decode(input, &decoded, &decoded_len) || + !hex_decode(insert, &insert_decoded, &insert_len)) { + ok = false; + } else { + input_bytes = amduat_octets(decoded, decoded_len); + insert_bytes = amduat_octets(insert_decoded, insert_len); + if (amduat_fps_splice(input_bytes, offset, insert_bytes, &out) != + AMDUAT_FPS_OK) { + ok = false; + } else if (!hex_eq(output, out)) { + ok = false; + } + } + } else if (ok) { + ok = false; + } + + amduat_octets_free(&out); + free((void *)output_bytes.data); + if (input_bytes.data != NULL) { + free((void *)input_bytes.data); + } + if (insert_bytes.data != NULL) { + free((void *)insert_bytes.data); + } + } + + free(op); + free(input); + free(insert); + free(output); + free(cid_hex); + free(level); + for (size_t i = 0u; i < inputs_len; ++i) { + free(inputs[i]); + } + + if (!ok) { + return false; + } + + if (!cursor_skip_ws(cur)) { + return false; + } + if (cur->offset < cur->len && cur->data[cur->offset] == ',') { + cur->offset++; + continue; + } + if (cur->offset < cur->len && cur->data[cur->offset] == ']') { + cur->offset++; + return true; + } + } +} + +static bool parse_manifest(uint8_t *bytes, size_t len) { + cursor_t cur; + char *field = NULL; + char *schema = NULL; + bool ok = false; + + cur.data = bytes; + cur.len = len; + cur.offset = 0u; + + if (!cursor_skip_ws(&cur) || cur.offset >= cur.len || + cur.data[cur.offset] != '{') { + return false; + } + cur.offset++; + + while (true) { + if (!cursor_read_string(&cur, &field)) { + break; + } + if (!cursor_skip_ws(&cur) || cur.offset >= cur.len || + cur.data[cur.offset] != ':') { + break; + } + cur.offset++; + if (strcmp(field, "schema") == 0) { + if (!cursor_read_string(&cur, &schema)) { + break; + } + } else if (strcmp(field, "cases") == 0) { + if (!parse_manifest_cases(&cur)) { + break; + } + } else { + break; + } + + free(field); + field = NULL; + if (!cursor_skip_ws(&cur)) { + break; + } + if (cur.offset < cur.len && cur.data[cur.offset] == ',') { + cur.offset++; + continue; + } + if (cur.offset < cur.len && cur.data[cur.offset] == '}') { + cur.offset++; + break; + } + } + + if (schema != NULL && + strcmp(schema, "amduat.conformance.fps.v0") == 0) { + ok = true; + } + + free(field); + free(schema); + return ok; +} + +int main(void) { + char path[512]; + uint8_t *bytes = NULL; + size_t len = 0u; + int exit_code = 1; + + if (!build_fixture_path(path, sizeof(path))) { + fprintf(stderr, "fixture path build failed\n"); + return exit_code; + } + if (!read_file(path, &bytes, &len)) { + fprintf(stderr, "fixture read failed: %s\n", path); + return exit_code; + } + if (!parse_manifest(bytes, len)) { + fprintf(stderr, "fixture parse failed\n"); + goto cleanup; + } + + exit_code = 0; + +cleanup: + free(bytes); + return exit_code; +} diff --git a/tests/fixtures/conformance/README.md b/tests/fixtures/conformance/README.md new file mode 100644 index 0000000..23cb9e2 --- /dev/null +++ b/tests/fixtures/conformance/README.md @@ -0,0 +1,22 @@ +# Conformance Fixtures + +This directory holds small, canonical fixture sets and manifests used by the +core conformance harness. + +## ASL/1-STORE fixtures + +- `asl_put_get/manifest.json` — byte fixtures for PUT/GET idempotency. + +## TGK/STORE/1 fixtures + +- `tgk_edges/manifest.json` — canonical edge payload fixtures. + +## PEL trace TGK vectors + +- `../../vectors/pel_trace_tgk_test_vectors_v0.json` +- `../../vectors/pel_trace_tgk_test_vectors_v1.json` + +## Generator + +`generate_manifest.py` can (re)generate the ASL/1-STORE fixture manifest with +canonical ordering and file digests. diff --git a/tests/fixtures/conformance/asl_put_get/manifest.json b/tests/fixtures/conformance/asl_put_get/manifest.json index 6760b6c..c1bb79e 100644 --- a/tests/fixtures/conformance/asl_put_get/manifest.json +++ b/tests/fixtures/conformance/asl_put_get/manifest.json @@ -1,8 +1,23 @@ { "schema": "amduat.conformance.fixtures.v0", "fixtures": [ - {"id": "empty", "artifact": "a0.bin", "notes": "empty payload"}, - {"id": "hello", "artifact": "a1.bin", "notes": "ascii payload"}, - {"id": "binary", "artifact": "a2.bin", "notes": "0..255"} + { + "id": "a0", + "artifact": "artifacts/a0.bin", + "size": "0", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "id": "a1", + "artifact": "artifacts/a1.bin", + "size": "12", + "sha256": "29e86b2096b77c51796e607cbac976bf507c443c2efd14ab343a5c07f9978644" + }, + { + "id": "a2", + "artifact": "artifacts/a2.bin", + "size": "256", + "sha256": "40aff2e9d2d8922e47afd4648e6967497158785fbd1da870e7110266bf944880" + } ] } diff --git a/tests/fixtures/conformance/asl_tgk_exec_plan/artifacts/plan0.bin b/tests/fixtures/conformance/asl_tgk_exec_plan/artifacts/plan0.bin new file mode 100644 index 0000000..5c77959 Binary files /dev/null and b/tests/fixtures/conformance/asl_tgk_exec_plan/artifacts/plan0.bin differ diff --git a/tests/fixtures/conformance/asl_tgk_exec_plan/manifest.json b/tests/fixtures/conformance/asl_tgk_exec_plan/manifest.json new file mode 100644 index 0000000..54a9bca --- /dev/null +++ b/tests/fixtures/conformance/asl_tgk_exec_plan/manifest.json @@ -0,0 +1,12 @@ +{ + "schema": "amduat.conformance.fixtures.v0", + "fixtures": [ + { + "id": "plan0", + "artifact": "artifacts/plan0.bin", + "size": "137", + "sha256": "3633b7a6ca915bb4be8837648890ed91a2a67781b837edd6c00a09a7a2d3cc7b", + "notes": "ASL/TGK exec plan v1 fixture" + } + ] +} diff --git a/tests/fixtures/conformance/fps_1/manifest.json b/tests/fixtures/conformance/fps_1/manifest.json new file mode 100644 index 0000000..c1f0cba --- /dev/null +++ b/tests/fixtures/conformance/fps_1/manifest.json @@ -0,0 +1,65 @@ +{ + "schema": "amduat.conformance.fps.v0", + "cases": [ + { + "id": "concat0", + "op": "concat", + "inputs": [ + "dead", + "beef" + ], + "output": "deadbeef", + "cid_sha256": "42f90312198cb86ae022fd58e8dd825cc0fea31bf17685040f40a6a49bf55330" + }, + { + "id": "reverse_bits0", + "op": "reverse", + "level": "bit", + "input": "01f0", + "output": "0f80", + "cid_sha256": "3a26c2b229a79ed7337cacc1d68e5aa34c3cb12c6027a5bdfb6ae87c9433390e" + }, + { + "id": "reverse_bytes0", + "op": "reverse", + "level": "byte", + "input": "01020304", + "output": "04030201", + "cid_sha256": "56f83c32ac7262d4aff0e766e8f3ddbaa325f928ddfb7764aeb1143ed3417d4e" + }, + { + "id": "reverse_longs0", + "op": "reverse", + "level": "long", + "input": "0102030405060708", + "output": "0506070801020304", + "cid_sha256": "f720714eb46590c97470c8970768c0162322feb7cfd85a3e3f9af85653729eca" + }, + { + "id": "reverse_words0", + "op": "reverse", + "level": "word", + "input": "0102030405060708", + "output": "0708050603040102", + "cid_sha256": "ae08a370a31a4dccd5ce7f86629e0823087f1ac8f29aa52c7fc9a0b515e38389" + }, + { + "id": "slice0", + "op": "slice", + "input": "000102030405", + "offset": 2, + "length": 3, + "output": "020304", + "cid_sha256": "8d81247326859e1abafaf9cbf25cdb8c088c778b3ec109cdf9085bd8f2787fac" + }, + { + "id": "splice0", + "op": "splice", + "input": "010203", + "offset": 1, + "insert": "ff", + "output": "01ff0203", + "cid_sha256": "a82fb8838858d19c3c258205d6de59dcb8d0f97d3898cfbbe848098aaedec6a7" + } + ] +} diff --git a/tests/fixtures/conformance/generate_manifest.py b/tests/fixtures/conformance/generate_manifest.py new file mode 100755 index 0000000..1f629af --- /dev/null +++ b/tests/fixtures/conformance/generate_manifest.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +import argparse +import hashlib +import json +import os +from pathlib import Path + + +def sha256_file(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def build_asl_manifest(root: Path) -> dict: + artifacts_dir = root / "artifacts" + fixtures = [] + for entry in sorted(artifacts_dir.iterdir()): + if not entry.is_file() or entry.suffix != ".bin": + continue + artifact_rel = f"artifacts/{entry.name}" + fixtures.append( + { + "id": entry.stem, + "artifact": artifact_rel, + "size": str(entry.stat().st_size), + "sha256": sha256_file(entry), + } + ) + return {"schema": "amduat.conformance.fixtures.v0", "fixtures": fixtures} + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Generate conformance fixture manifests." + ) + parser.add_argument( + "root", + type=Path, + help="Fixture directory containing artifacts/", + ) + parser.add_argument( + "--out", + type=Path, + default=None, + help="Output manifest path (defaults to /manifest.json)", + ) + args = parser.parse_args() + + manifest = build_asl_manifest(args.root) + out_path = args.out or (args.root / "manifest.json") + out_path.parent.mkdir(parents=True, exist_ok=True) + with out_path.open("w", encoding="utf-8") as f: + json.dump(manifest, f, indent=2, sort_keys=False) + f.write("\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/fixtures/conformance/manifest.schema.md b/tests/fixtures/conformance/manifest.schema.md new file mode 100644 index 0000000..870f3a4 --- /dev/null +++ b/tests/fixtures/conformance/manifest.schema.md @@ -0,0 +1,99 @@ +# Conformance Fixture Manifests + +This directory contains small, canonical fixture sets used by conformance tests. +Each fixture set publishes a JSON manifest with deterministic ordering. + +## Common rules + +- JSON must be UTF-8, no comments. +- Arrays are ordered and MUST be in canonical order as specified by the schema. +- Artifact references use the canonical `ReferenceBytes` hex encoding when + present (lowercase hex, no `0x` prefix). +- `sha256` fields are lowercase hex of the raw bytes. + +## Schema: `amduat.conformance.fixtures.v0` + +Used by simple ASL/1-STORE byte fixtures (e.g. `asl_put_get`). + +``` +{ + "schema": "amduat.conformance.fixtures.v0", + "fixtures": [ + { + "id": "string", + "artifact": "relative/path.bin", + "size": "0", + "sha256": "hex", + "notes": "optional string" + } + ] +} +``` + +Rules: + +- `fixtures` MUST be sorted by ascending `id`. +- `artifact` is a path relative to the manifest file. +- `size` is the byte length of the artifact file encoded as a decimal string. +- `sha256` is the digest of the artifact file contents. + +## Schema: `amduat.conformance.tgk_edges.v0` + +Used by TGK edge fixture sets. + +``` +{ + "schema": "amduat.conformance.tgk_edges.v0", + "edges": [ + { + "id": "string", + "type": 0, + "from": ["refhex"], + "to": ["refhex"], + "payload": "hex", + "notes": "optional string" + } + ] +} +``` + +Rules: + +- `edges` MUST be sorted by ascending `id`. +- `from`/`to` are lists of `ReferenceBytes` hex (lowercase). +- `payload` is hex-encoded edge payload bytes (lowercase). + +## Schema: `amduat.conformance.fps.v0` + +Used by FPS/1 primitive conformance vectors. + +``` +{ + "schema": "amduat.conformance.fps.v0", + "cases": [ + { + "id": "string", + "op": "slice|concat|reverse|splice", + "input": "hex", + "inputs": ["hex"], + "insert": "hex", + "offset": 0, + "length": 0, + "level": "bit|byte|word|long", + "output": "hex", + "cid_sha256": "hex" + } + ] +} +``` + +Rules: + +- `cases` MUST be sorted by ascending `id`. +- `input` is used for unary operations. +- `inputs` is used for `concat`. +- `insert` is used for `splice`. +- `offset` and `length` are required for `slice` and `splice`. +- `level` is required for `reverse`. +- `output` is the expected output payload bytes. +- `cid_sha256` is `SHA-256("CAS:OBJ\\0" || output)` in lowercase hex. diff --git a/tests/tgk/test_tgk_error_taxonomy.c b/tests/tgk/test_tgk_error_taxonomy.c new file mode 100644 index 0000000..dae9a44 --- /dev/null +++ b/tests/tgk/test_tgk_error_taxonomy.c @@ -0,0 +1,103 @@ +#include "amduat/tgk/store.h" + +#include +#include + +static amduat_tgk_graph_error_t stub_resolve_edge(void *ctx, + amduat_reference_t ref, + amduat_tgk_edge_body_t *out_body) { + amduat_tgk_graph_error_t *err = (amduat_tgk_graph_error_t *)ctx; + (void)ref; + (void)out_body; + return *err; +} + +static int test_not_edge_mapping(void) { + amduat_tgk_store_ops_t ops; + amduat_tgk_store_t store; + amduat_tgk_store_config_t cfg; + amduat_tgk_graph_error_t err = GS_ERR_NOT_EDGE; + + memset(&cfg, 0, sizeof(cfg)); + memset(&ops, 0, sizeof(ops)); + ops.resolve_edge = stub_resolve_edge; + amduat_tgk_store_init(&store, cfg, ops, &err); + + if (amduat_tgk_store_resolve_edge(&store, amduat_reference(0, amduat_octets(NULL, 0u)), NULL) != + GS_ERR_NOT_EDGE) { + return 1; + } + return 0; +} + +static int test_artifact_error_mapping(void) { + amduat_tgk_store_ops_t ops; + amduat_tgk_store_t store; + amduat_tgk_store_config_t cfg; + amduat_tgk_graph_error_t err = GS_ERR_ARTIFACT_ERROR; + + memset(&cfg, 0, sizeof(cfg)); + memset(&ops, 0, sizeof(ops)); + ops.resolve_edge = stub_resolve_edge; + amduat_tgk_store_init(&store, cfg, ops, &err); + + if (amduat_tgk_store_resolve_edge(&store, amduat_reference(0, amduat_octets(NULL, 0u)), NULL) != + GS_ERR_ARTIFACT_ERROR) { + return 1; + } + return 0; +} + +static int test_unsupported_mapping(void) { + amduat_tgk_store_ops_t ops; + amduat_tgk_store_t store; + amduat_tgk_store_config_t cfg; + + memset(&cfg, 0, sizeof(cfg)); + memset(&ops, 0, sizeof(ops)); + amduat_tgk_store_init(&store, cfg, ops, NULL); + + if (amduat_tgk_store_resolve_edge(&store, amduat_reference(0, amduat_octets(NULL, 0u)), NULL) != + GS_ERR_UNSUPPORTED) { + return 1; + } + return 0; +} + +static int test_integrity_mapping(void) { + amduat_tgk_store_ops_t ops; + amduat_tgk_store_t store; + amduat_tgk_store_config_t cfg; + amduat_tgk_graph_error_t err = GS_ERR_INTEGRITY; + + memset(&cfg, 0, sizeof(cfg)); + memset(&ops, 0, sizeof(ops)); + ops.resolve_edge = stub_resolve_edge; + amduat_tgk_store_init(&store, cfg, ops, &err); + + if (amduat_tgk_store_resolve_edge(&store, amduat_reference(0, amduat_octets(NULL, 0u)), NULL) != + GS_ERR_INTEGRITY) { + return 1; + } + return 0; +} + +int main(void) { + if (test_not_edge_mapping() != 0) { + fprintf(stderr, "not edge mapping failed\n"); + return 1; + } + if (test_artifact_error_mapping() != 0) { + fprintf(stderr, "artifact error mapping failed\n"); + return 1; + } + if (test_unsupported_mapping() != 0) { + fprintf(stderr, "unsupported mapping failed\n"); + return 1; + } + if (test_integrity_mapping() != 0) { + fprintf(stderr, "integrity mapping failed\n"); + return 1; + } + return 0; +} diff --git a/tier1/asl-1-core.md b/tier1/asl-1-core.md index f0b55d0..f3e4809 100644 --- a/tier1/asl-1-core.md +++ b/tier1/asl-1-core.md @@ -2,9 +2,9 @@ Status: Approved Owner: Niklas Rydberg -Version: 0.4.1 +Version: 0.4.2 SoT: Yes -Last Updated: 2025-11-16 +Last Updated: 2026-02-21 Linked Phase Pack: N/A Tags: [deterministic, binary-minimalism] @@ -20,7 +20,7 @@ Tags: [deterministic, binary-minimalism] **Informative references:** * `ENC/ASL1-CORE v1.x` — canonical encoding profile (`ASL_ENC_CORE_V1`) -* `HASH/ASL1 v0.2.2` — ASL1 hash family and `HashId` assignments +* `HASH/ASL1 v0.2.5` — ASL1 hash family and `HashId` assignments * `ASL/1-STORE v0.4.0` — content-addressable store over ASL/1-CORE * `TGK/1-CORE v0.7.0` — trace graph kernel over `Reference` * `PEL/1` — execution substrate @@ -289,12 +289,16 @@ then the Reference `R` for `A` under `(P, H)` is: ```text ArtifactBytes = encode_P(A) -digest = H(ArtifactBytes) +digest = H("AMDUAT:ASL1:ART\0" || ArtifactBytes) Reference = { hash_id = HID, digest = digest } ``` +The domain separator is the ASCII byte sequence `AMDUAT:ASL1:ART` followed by a +single `0x00` terminator (16 bytes total). The separator MUST be present +verbatim and MUST NOT be modified or omitted. + > **ASL/CORE-REF-DERIVE/1** -> Any component that claims to derive References from Artifacts for a given `(EncodingProfileId, HashId)` **MUST** use this exact procedure. +> Any component that claims to derive References from Artifacts for a given `(EncodingProfileId, HashId)` **MUST** use this exact procedure, including the domain separator. ### 4.3 Deterministic agreement lemma (informative) @@ -306,7 +310,7 @@ For any two conformant implementations that share: then for any Artifact `A`: * both will compute identical `ArtifactBytes`, -* both will compute identical `digest = H(ArtifactBytes)`, +* both will compute identical `digest = H("AMDUAT:ASL1:ART\0" || ArtifactBytes)`, * both will form identical `Reference {hash_id = HID, digest = digest}`. This is the basis for cross-Store and cross-domain determinism in Amduat. @@ -316,6 +320,7 @@ This is the basis for cross-Store and cross-domain determinism in Amduat. While ASL/1-CORE is conceptually family-agnostic, the **Amduat 2.0 substrate** standardizes: * `ASL_ENC_CORE_V1` as the canonical Artifact encoding profile; +* `HASH/ASL1` as the canonical hash namespace for `Reference.hash_id` values; * `HASH-ASL1-256` (`HashId = 0x0001`) as the canonical default hash algorithm for identity-critical surfaces. Other `(EncodingProfileId, HashId)` pairs are allowed but must be explicitly declared by the consuming profile or implementation. @@ -495,4 +500,5 @@ Everything else — API design, transport formats, performance characteristics, ## Document History +* **0.4.2 (2026-02-21):** Added ASL/1 identity domain separation and clarified hash namespace usage. * **0.4.1 (2025-11-16):** Registered as Tier-1 spec and aligned to the Amduat 2.0 substrate baseline. diff --git a/tier1/asl-core-index-1.md b/tier1/asl-core-index-1.md index e8dde9f..e594270 100644 --- a/tier1/asl-core-index-1.md +++ b/tier1/asl-core-index-1.md @@ -1,10 +1,10 @@ # ASL/1-CORE-INDEX — Semantic Index Model -Status: Draft +Status: Approved Owner: Niklas Rydberg -Version: 0.1.0 -SoT: No -Last Updated: 2025-11-16 +Version: 1.0.0 +SoT: Yes +Last Updated: 2026-02-21 Linked Phase Pack: N/A Tags: [deterministic, index, semantics] diff --git a/tier1/asl-indexes-1.md b/tier1/asl-indexes-1.md index fe53f6d..ec6aeb6 100644 --- a/tier1/asl-indexes-1.md +++ b/tier1/asl-indexes-1.md @@ -1,10 +1,10 @@ # ASL/INDEXES/1 -- Index Taxonomy and Relationships -Status: Draft +Status: Approved Owner: Architecture -Version: 0.1.0 -SoT: No -Last Updated: 2025-01-17 +Version: 1.0.0 +SoT: Yes +Last Updated: 2026-02-21 Linked Phase Pack: N/A Tags: [indexes, content, structural, materialization] diff --git a/tier1/asl-log-1.md b/tier1/asl-log-1.md index 333b313..3fff102 100644 --- a/tier1/asl-log-1.md +++ b/tier1/asl-log-1.md @@ -115,6 +115,22 @@ in `ENC-ASL-LOG`. The envelope MUST include: * `payload` (type-specific bytes) * `record_hash` (hash-chained integrity) +### 3.4 Log Segment Identity (Optional) + +Implementations MAY store the log as **multiple segments** (e.g., rotated files). +If segmented storage is used, each segment MUST be assigned a **store-local** +`segment_id` (opaque `uint64_t`) and MUST carry a **segment hash** computed over +the exact bytes of the segment as defined in `ENC/ASL-LOG/1`. + +A log segment identity is the tuple: + +``` +(segment_id, logseq_min, logseq_max, segment_hash) +``` + +This identity is used only for storage metadata and audit; it does not change +the ASL/LOG/1 semantics or record ordering. + --- ## 4. Record Types (Normative) @@ -152,6 +168,8 @@ Semantics: * Segment MUST be immutable. * All referenced blocks MUST already be sealed. * Segment contents are not re-logged. +* `segment_hash` MUST be the SHA-256 hash of the exact segment bytes as encoded + by `ENC/ASL-CORE-INDEX/1`, including the finalized footer. ### 4.2 TOMBSTONE @@ -211,6 +229,9 @@ typedef struct { Semantics: +* `snapshot_id` is an opaque, store-assigned `uint64_t` identifier persisted in + the anchor payload. +* The anchor's `logseq` is the snapshot's log position. * Defines a replay checkpoint. * Enables log truncation below anchor with care. @@ -249,7 +270,8 @@ typedef struct { To reconstruct CURRENT: -1. Load latest snapshot anchor (if any). +1. Load latest snapshot anchor (if any). If absent, treat + `snapshot_id = 0` and `snapshot.logseq = 0`. 2. Initialize visible segments from that snapshot. 3. Replay all log records with `logseq > snapshot.logseq`. 4. Apply records in order: diff --git a/tier1/asl-store-index-1.md b/tier1/asl-store-index-1.md index 473561d..7bd9bec 100644 --- a/tier1/asl-store-index-1.md +++ b/tier1/asl-store-index-1.md @@ -87,6 +87,10 @@ Excludes: * Memory residency or caching * Federation, PEL, or TGK semantics (see `TGK/1` and `TGK/1-CORE`) +**ByteStore boundary:** raw byte persistence, COR/1 import/export, and SA/PA +isolation are defined in `BYTESTORE/1` (DDS §11). ASL/STORE-INDEX/1 assumes a +conformant ByteStore beneath the index layer. + --- ## 3. Core Concepts @@ -144,6 +148,10 @@ as the inclusive `logseq` upper bound defined by `ASL/LOG/1`, not a byte offset into the log file. Snapshot anchors use their record `logseq` as the snapshot's log position. +**Implementation note (identity):** SnapshotID values are store-assigned +`uint64_t` identifiers persisted in `SNAPSHOT_ANCHOR` records. If no snapshot +anchor exists, `SnapshotID` is treated as `0`. + ### 3.5 Artifact Location * **ArtifactExtent**: `(BlockID, offset, length)` identifying a byte slice within a block. diff --git a/tier1/asl-tgk-execution-plan-1.md b/tier1/asl-tgk-execution-plan-1.md index e5b3c36..ba2823c 100644 --- a/tier1/asl-tgk-execution-plan-1.md +++ b/tier1/asl-tgk-execution-plan-1.md @@ -1,10 +1,10 @@ # ASL/TGK-EXEC-PLAN/1 -- Unified Execution Plan Semantics -Status: Draft +Status: Approved Owner: Architecture -Version: 0.1.0 -SoT: No -Last Updated: 2025-01-17 +Version: 1.0.0 +SoT: Yes +Last Updated: 2026-02-21 Linked Phase Pack: N/A Tags: [execution, query, tgk, determinism] @@ -82,6 +82,10 @@ Each operator includes: * `traversal`: optional traversal parameters * `aggregation`: optional aggregation parameters +Snapshot bounds MUST satisfy: + +* `logseq_min <= logseq_max` + --- ## 2.1 Query Abstraction (Informative) diff --git a/tier1/bytestore-1.md b/tier1/bytestore-1.md new file mode 100644 index 0000000..c3c2809 --- /dev/null +++ b/tier1/bytestore-1.md @@ -0,0 +1,83 @@ +# BYTESTORE/1 — ByteStore API and Persistence Boundary + +Status: Draft +Owner: Architecture +Version: 0.1.0 +SoT: No +Last Updated: 2026-02-21 +Tags: [bytestore, persistence, determinism] + +**Document ID:** `BYTESTORE/1` +**Layer:** L0 — Persistence boundary (bytes only) + +**Depends on (normative):** + +* `DDS` §11 — ByteStore API & Persistence Discipline +* `SRS` FR-BS-001…005 — determinism + durability requirements +* `COR/1` — canonical envelope bytes +* `ICD/1` — `max_object_size` policy + +**Informative references:** + +* `ADR-030` — ByteStore Persistence Contract +* `AMDUAT_BYTESTORE` header — `vendor/amduat/include/amduat/bytestore.h` + +--- + +## 1. Purpose + +BYTESTORE/1 defines the **code-level API surface** for ByteStore and maps it to +the behavioural requirements already specified in DDS and SRS. + +It does not define new semantics; DDS §11 remains the source of truth. + +--- + +## 2. API Surface (Normative) + +The following API signatures are required (see `amduat/bytestore.h`): + +* `put(payload) -> cid` +* `put_stream(stream) -> cid` +* `import_cor(envelope) -> cid` +* `export_cor(cid) -> envelope` +* `get(cid) -> bytes` +* `stat(cid) -> {present, size}` +* `assert_area_isolation(public_root, secure_root)` + +The error codes exposed by the header MUST map to ADR-006 lanes as specified in +DDS §11.7. + +--- + +## 3. Deterministic Identity (Normative) + +The CID derivation is fixed: + +``` +cid = algo_id || H("CAS:OBJ\0" || payload) +``` + +Implementations MUST use the exact domain separator and hash rules defined in +DDS §11.2 and SRS FR-BS-001. + +--- + +## 4. Persistence Discipline (Normative) + +All ByteStore writes MUST follow the fsync ladder from DDS §11.4 and enforce: + +* `ICD/1 max_object_size` +* COR/1 import/export byte identity +* SA/PA isolation +* Streaming determinism + +--- + +## 5. Non-Goals + +BYTESTORE/1 does not define: + +* Physical storage layout +* Replication protocols +* Transport or API gateways diff --git a/tier1/dds.md b/tier1/dds.md index 902bd33..62bf1b0 100644 --- a/tier1/dds.md +++ b/tier1/dds.md @@ -453,6 +453,104 @@ Phase 04 establishes deterministic linkage between FLS/1 envelopes and CRS/1 con * CRS payload headers MUST match `CRS1` (concept) or `CRR1` (relation) when linkage metadata declares the type ⇒ `ERR_FLS_PAYLOAD_KIND`. * Payloads MAY be CRS/1 concepts or CRR/1 relations; FLS/1 envelopes never mutate CRS graphs. +##### 7.8.1.1 Session Envelope (Normative) + +Session envelopes bind a linkage session to its channel catalog and termination ledger. + +**Header:** `MAGIC="FLS1"`, `VERSION=0x01`, `FLAGS=0x00`, `RSV=0x00`. + +**TLVs (strict order; no padding):** + +| Tag | Field | Type | Card. | Notes | +| ------ | ---------------------- | ------ | ----- | ----- | +| `0x60` | `source_cid` | CID | 1 | Sender surface identity. | +| `0x61` | `target_cid` | CID | 1 | Recipient surface identity. | +| `0x66` | `session_id` | BYTES | 1 | 16-byte opaque session ID. | +| `0x67` | `channel_catalog_cid` | CID | 1 | CID of `FLSC` channel catalog. | +| `0x68` | `termination_ledger_cid` | CID | 0-1 | CID of `FLSL` termination ledger. | +| `0x64` | `timestamp` | UINT64 | 0-1 | Optional bounded timing evidence. | +| `0x65` | `signature` | BYTES | 0-1 | Ed25519 signature over `"AMDUAT:FLS\0" || canonical bytes`. | + +**Validation:** + +* `session_id` MUST be exactly 16 bytes ⇒ `ERR_FLS_SESSION_ID_LENGTH`. +* `channel_catalog_cid` MUST resolve to an `FLSC` header ⇒ `ERR_FLS_CATALOG_KIND`. +* `termination_ledger_cid` (if present) MUST resolve to an `FLSL` header ⇒ `ERR_FLS_LEDGER_KIND`. + +##### 7.8.1.2 Channel Catalog (Normative) + +Channel catalogs enumerate message channels and their ordering bounds. + +**Header:** `MAGIC="FLSC"`, `VERSION=0x01`, `FLAGS=0x00`, `RSV=0x00`. + +**Payload layout:** + +``` +channel_count : U32 +entries : ChannelEntry[channel_count] + +ChannelEntry = + channel_id : U32 + kind : U16 + flags : U16 + first_seq : U64 + last_seq : U64 +``` + +**Rules:** + +* `entries` MUST be sorted by ascending `channel_id` ⇒ `ERR_FLS_CHANNEL_ORDER`. +* `first_seq <= last_seq` for each entry ⇒ `ERR_FLS_CHANNEL_RANGE`. +* `channel_id` MUST be unique ⇒ `ERR_FLS_CHANNEL_DUPLICATE`. + +##### 7.8.1.3 Message Envelope (Normative) + +Message envelopes bind a payload to a channel and sequence position. + +**Header:** `MAGIC="FLSM"`, `VERSION=0x01`, `FLAGS=0x00`, `RSV=0x00`. + +**TLVs (strict order; no padding):** + +| Tag | Field | Type | Card. | Notes | +| ------ | ------------ | ------ | ----- | ----- | +| `0x66` | `session_id` | BYTES | 1 | 16-byte session ID. | +| `0x69` | `channel_id` | U32 | 1 | Channel identifier. | +| `0x6A` | `seq` | U64 | 1 | Monotonic sequence number within channel. | +| `0x62` | `payload_cid`| CID | 1 | Content payload CID. | +| `0x64` | `timestamp` | UINT64 | 0-1 | Optional bounded timing evidence. | +| `0x65` | `signature` | BYTES | 0-1 | Optional Ed25519 signature. | + +**Rules:** + +* `session_id` MUST be exactly 16 bytes ⇒ `ERR_FLS_SESSION_ID_LENGTH`. +* `seq` MUST be within the bounds declared in the channel catalog ⇒ `ERR_FLS_SEQ_RANGE`. + +##### 7.8.1.4 Termination Ledger (Normative) + +Termination ledgers declare channel completion bounds. + +**Header:** `MAGIC="FLSL"`, `VERSION=0x01`, `FLAGS=0x00`, `RSV=0x00`. + +**Payload layout:** + +``` +channel_count : U32 +entries : TerminationEntry[channel_count] + +TerminationEntry = + channel_id : U32 + final_seq : U64 + reason_code : U16 + rsv : U16 + closed_at : U64 +``` + +**Rules:** + +* `entries` MUST be sorted by ascending `channel_id` ⇒ `ERR_FLS_CHANNEL_ORDER`. +* `final_seq` MUST be ≥ the channel’s `last_seq` from the catalog ⇒ `ERR_FLS_SEQ_RANGE`. +* `closed_at` MUST be non-zero when a termination entry is present. + #### 7.8.2 CRS/1 Concept & Relation TLVs (Normative) > **Scope:** Deterministic CRS/1 byte layout as ratified by ADR-020 v1.1.0. All TLVs diff --git a/tier1/enc-asl-core-index-1.md b/tier1/enc-asl-core-index-1.md index d2aa1c2..84ed2c8 100644 --- a/tier1/enc-asl-core-index-1.md +++ b/tier1/enc-asl-core-index-1.md @@ -1,10 +1,10 @@ # ENC/ASL-CORE-INDEX/1 — Encoding Specification for ASL Core Index -Status: Draft +Status: Approved Owner: Niklas Rydberg -Version: 0.1.0 -SoT: No -Last Updated: 2025-11-16 +Version: 1.0.0 +SoT: Yes +Last Updated: 2026-02-21 Linked Phase Pack: N/A Tags: [encoding, index, deterministic] diff --git a/tier1/enc-asl-log-1.md b/tier1/enc-asl-log-1.md index e8be67f..32bb5d4 100644 --- a/tier1/enc-asl-log-1.md +++ b/tier1/enc-asl-log-1.md @@ -141,6 +141,21 @@ continue replay without failure. * Unknown `record_type` values are the only exception: they MUST be skipped using `payload_len` and MUST NOT break replay determinism. +### 5.1 Log Segment Hashing (Optional) + +If a store persists the log in **segments** (multiple files), the following +rules apply: + +* **Segment hash:** `segment_hash = SHA-256(segment_bytes)` where + `segment_bytes` are the exact on-disk bytes of that segment, starting at the + `LogHeader` and ending at the final `record_hash`. +* **Hash-chain continuity:** the `prev_record_hash` for the first record in a + segment MUST be: + * all-zeroes for the first segment in a domain, or + * the last record hash of the immediately preceding segment. +* **Segment identity:** `(segment_id, logseq_min, logseq_max, segment_hash)` + is the canonical identity tuple for storage metadata and audit. + --- ## 6. Record Type IDs (v1) diff --git a/tier1/enc-asl1-core.md b/tier1/enc-asl1-core.md index 7e4c4d9..f870da3 100644 --- a/tier1/enc-asl1-core.md +++ b/tier1/enc-asl1-core.md @@ -2,9 +2,9 @@ Status: Approved Owner: Niklas Rydberg -Version: 1.0.5 +Version: 1.0.6 SoT: Yes -Last Updated: 2025-11-16 +Last Updated: 2026-02-21 Linked Phase Pack: N/A Tags: [deterministic, binary-minimalism] @@ -16,11 +16,11 @@ Tags: [deterministic, binary-minimalism] **Depends on (normative):** -* **ASL/1-CORE v0.4.1** (value model: `Artifact`, `TypeTag`, `Reference`, `HashId`) +* **ASL/1-CORE v0.4.2** (value model: `Artifact`, `TypeTag`, `Reference`, `HashId`) **Integrates with (cross-profile rules):** -* **HASH/ASL1 v0.2.4** (ASL1 hash family: registry of `HashId → algorithm, digest length`) +* **HASH/ASL1 v0.2.5** (ASL1 hash family: registry of `HashId → algorithm, digest length`) * This profile does **not** depend on HASH/ASL1 to define its layouts. * When both profiles are implemented, additional cross-checks apply (see §4.4, §5). @@ -424,10 +424,14 @@ For encoding profile `ASL_ENC_CORE_V1`, the canonical rule for constructing `Ref ```text ArtifactBytes = encode_artifact_core_v1(A) -digest = H(ArtifactBytes) +digest = H("AMDUAT:ASL1:ART\0" || ArtifactBytes) Reference = { hash_id = HID, digest = digest } ``` +The domain separator is the ASCII byte sequence `AMDUAT:ASL1:ART` followed by a +single `0x00` terminator (16 bytes total). The separator MUST be present +verbatim and MUST NOT be modified or omitted. + where: * `A` is an `Artifact` (ASL/1-CORE), @@ -461,7 +465,7 @@ If two implementations: then for any `Artifact A` they MUST: * produce identical `ArtifactBytes = encode_artifact_core_v1(A)`, -* produce identical `digest = H(ArtifactBytes)`, +* produce identical `digest = H("AMDUAT:ASL1:ART\0" || ArtifactBytes)`, * produce identical `Reference` and `ReferenceBytes = encode_reference_core_v1(Reference)`. This is the determinism foundation used by ASL/1-STORE, PEL/1, FER/1, and FCT/1. @@ -506,10 +510,10 @@ Canonical `ArtifactBytes`: 00 0000000000000002 DEAD ``` -Digest with `HASH-ASL1-256` (SHA-256): +Digest with `HASH-ASL1-256` (SHA-256) and domain separation: ```text -digest = SHA-256(00 0000000000000002 DEAD) +digest = SHA-256("AMDUAT:ASL1:ART\0" || 00 0000000000000002 DEAD) ``` Assuming `HashId = 0001` for `HASH-ASL1-256`, the `ReferenceBytes` are: @@ -555,7 +559,7 @@ Hashing and `ReferenceBytes` proceed as in §6.1. ## 7. Conformance -An implementation conforms to `ENC/ASL1-CORE v1.0.5` if and only if it: +An implementation conforms to `ENC/ASL1-CORE v1.0.6` if and only if it: 1. **Correctly encodes and decodes Artifacts** @@ -573,7 +577,7 @@ An implementation conforms to `ENC/ASL1-CORE v1.0.5` if and only if it: 3. **Implements canonical hashing correctly** - * Uses `ArtifactBytes` from `encode_artifact_core_v1` as the **only** input to ASL1 hash functions when deriving `Reference`s under this profile. + * Uses the ASL/1-CORE domain separator (`"AMDUAT:ASL1:ART\0"`) plus `ArtifactBytes` from `encode_artifact_core_v1` as the **only** input to ASL1 hash functions when deriving `Reference`s under this profile. * Computes `Reference` via the canonical rule in §5.1. * Does not derive `Reference`s from non-canonical or alternative encodings in contexts that claim to use `ASL_ENC_CORE_V1`. @@ -595,4 +599,5 @@ Everything else — transport, storage layout, replication, indexing, overlays, ## Document History +* **1.0.6 (2026-02-21):** Added ASL/1-CORE identity domain separation to the reference derivation rule and updated registry metadata. * **1.0.5 (2025-11-16):** Registered as Tier-1 spec and aligned to the Amduat 2.0 substrate baseline. diff --git a/tier1/fcs-1.md b/tier1/fcs-1.md new file mode 100644 index 0000000..775195b --- /dev/null +++ b/tier1/fcs-1.md @@ -0,0 +1,95 @@ +# FCS/1 — Function Composition Schema (Execution-Only) + +Status: Approved +Owner: Architecture +Version: 1.0.0 +SoT: Yes +Last Updated: 2026-02-21 +Linked Phase Pack: PH07 +Tags: [execution, descriptor, deterministic] + +**Document ID:** `FCS/1` +**Layer:** L2 — Function descriptor schema for deterministic execution + +**Depends on (normative):** + +* `DDS` — canonical FCS/1 v1-min TLV layout and validation rules +* `PCB1` — parameter block encoding rules (DDS §7.3) +* `ASL/1-CORE v0.4.x` — logical value model (`Artifact`, `Reference`) + +--- + +## 0. Purpose & Scope + +`FCS/1` defines the **execution-only** descriptor schema for composite and +custom functions. It explicitly excludes policy, intent, scope, authority, and +other governance metadata (those belong in `FCT/1`). + +`FCS/1` descriptors are immutable and content-addressed; identical descriptors +MUST hash to identical CIDs. + +--- + +## 1. Descriptor Model (logical) + +An `FCS/1` descriptor captures the deterministic execution recipe: + +``` +FCSDescriptor { + function_ptr : CID // FPS/1 primitive or nested FCS/1 descriptor + parameter_block : CID // PCB1 parameter block + arity : uint32 +} +``` + +* `function_ptr` MAY reference another `FCS/1` descriptor (composition), or a + primitive function descriptor (e.g., `FPS/1`). +* `parameter_block` MUST reference a valid PCB1 payload. +* `arity` defines the expected number of input slots (must match PCB1). + +--- + +## 2. Canonical Encoding (v1-min) + +The canonical byte encoding for `FCS/1` descriptors is defined by DDS §7.2: + +* COR/1 framing with strict TLV order. +* Required TLVs only: `function_ptr (0x30)`, `parameter_block (0x31)`, + `arity (0x32)`. + +No other TLVs are permitted in v1-min. + +--- + +## 3. Validation Rules + +Implementations MUST reject descriptors that violate any of the following: + +1. TLVs are not in strict ascending order, or appear more than once. +2. `parameter_block` does not decode as a valid PCB1 envelope. +3. `arity` does not match PCB1 slot manifest length. +4. The descriptor graph (following nested `function_ptr` references) is cyclic. +5. Any legacy or unknown governance TLV appears. + +Error mapping follows DDS (e.g., `ERR_FCS_TAG_ORDER`, `ERR_FCS_PARAMETER_FORMAT`, +`ERR_PCB_ARITY_MISMATCH`, `ERR_FCS_CYCLE_DETECTED`, `ERR_FCS_UNKNOWN_TAG`). + +--- + +## 4. Composition Semantics + +* Composition is **pure**: a descriptor that references another `FCS/1` + descriptor inherits its deterministic execution recipe. +* The composed descriptor graph MUST be acyclic. +* Registries MUST admit only canonical, validated `FCS/1` descriptors. + +--- + +## 5. Conformance + +An implementation is **FCS/1–conformant** if it: + +* Accepts only v1-min canonical encodings. +* Enforces the validation rules in §3. +* Treats descriptors as immutable and content-addressed. +* Rejects any descriptor with policy/intent metadata embedded. diff --git a/tier1/fer-1.md b/tier1/fer-1.md new file mode 100644 index 0000000..3998f62 --- /dev/null +++ b/tier1/fer-1.md @@ -0,0 +1,183 @@ +# FER/1 — Execution Receipts and Evidence (Core Semantics) + +Status: Draft +Owner: Architecture +Version: 0.1.0 +SoT: Yes +Last Updated: 2026-02-21 +Linked Phase Pack: PH07 +Tags: [evidence, receipt, deterministic, execution] + +**Document ID:** `FER/1` +**Layer:** L2 — Evidence receipts over PEL/1 execution + +**Depends on (normative):** + +* `ASL/1-CORE v0.4.x` — value model (`Artifact`, `Reference`) +* `PEL/1-SURF v0.2.x` — surface execution results +* `TGK/1-CORE v0.7.x` — trace graph kernel +* `ENC/FER1-RECEIPT/1` — canonical receipt encoding +* `DDS` (FER/1 layout and validation rules) + +--- + +## 0. Purpose & Scope + +`FER/1` defines **receipt semantics** for deterministic execution evidence. It +standardizes the logical fields of execution receipts and the minimal execution +artifacts that higher layers can rely on, while delegating concrete encodings to +`ENC/FER1-RECEIPT/1` and DDS. + +`FER/1` does **not** define policy, intent, authority, or certification rules +(those belong to `FCT/1` and higher layers). + +--- + +## 1. Core Concepts + +### 1.1 Execution Artifacts (logical schemas) + +`FER/1` uses four logical artifact shapes. Each is an ASL/1 `Artifact` whose +encoding is profile-defined (not specified here). + +**ExecutionIntent** — records the inputs and environment for a run: + +``` +ExecutionIntent { + function_ref : Reference // FCS/1 descriptor or equivalent + program_ref : Reference // program artifact (scheme-specific) + input_manifest_ref : Reference // GS/1 manifest (ordered, deduped) + params_ref : optional Reference + snapshot_id : uint64 // snapshot identity (0 if absent) + execution_config_ref : optional Reference // snapshot-scoped config + environment_ref : Reference // environment capsule (ICD/1 / PH03) +} +``` + +**ExecutionOutput** — binds outputs to a run (used when outputs are multiple or +need explicit naming): + +``` +ExecutionOutput { + output_refs : list // ordered + result_ref : Reference // surface ExecutionResult artifact + trace_ref : optional Reference +} +``` + +**ExecutionReceipt** — evidence of a run (canonical encoding in +`ENC/FER1-RECEIPT/1`): + +``` +ExecutionReceipt { + function_ref : Reference + input_manifest_ref : Reference + environment_ref : Reference + evaluator_id : bytes + output_ref : Reference + executor_refs : list + parity_entries : list + started_at : uint64 + completed_at : uint64 + // optional fields per FER/1 v1.1 (DDS): executor_fingerprint, logs, + // determinism_level, rng_seed, limits, parent, context, witnesses, run_id, + // signature, signature_ext +} +``` + +**ExecutionObservation** — optional, typed evidence produced alongside a run: + +``` +ExecutionObservation { + kind : enum // e.g. stderr, stdout, metrics, trace + payload_ref : Reference // artifact containing the observation payload + payload_sha256 : bytes[32] +} +``` + +### 1.2 Program and input conventions + +* `function_ref` MUST reference the **function descriptor** used for evaluation + (e.g., FCS/1 descriptor). +* `program_ref` MUST reference the program artifact used by that descriptor. +* `input_manifest_ref` MUST reference a **canonical manifest artifact** that + enumerates inputs in deterministic order. +* `params_ref`, when present, MUST be an immutable artifact included in the + input manifest. + +### 1.3 Snapshot binding + +All receipts are **snapshot-bound**: + +* `snapshot_id` in the intent MUST be the snapshot identity used for input + resolution (0 when absent). +* Receipts and outputs are interpreted relative to that snapshot. + +--- + +## 2. Output Reference Fallback + +`ExecutionReceipt.output_ref` is a single reference. The value MUST resolve as: + +1. The sole output artifact, **if the run produces exactly one output**; or +2. An `ExecutionOutput` artifact when there are multiple outputs; or +3. The surface `ExecutionResult` artifact, **only** if outputs are empty and + the result is the only durable artifact. + +This rule keeps receipts stable while supporting multi-output runs and +zero-output executions. + +--- + +## 3. TGK Traceability Edges (FER/1 profile) + +When ingesting receipts, a `TGK/FER/1` profile SHOULD emit traceability edges +using TGK edge artifacts. Edge type assignments are defined by the profile; the +semantics are: + +* `EDGE_EXECUTION`: `program_ref -> output_ref` +* `EDGE_CONSUMED_BY`: `input_ref -> output_ref` +* `EDGE_PRODUCED_BY`: `output_ref -> receipt_ref` +* `EDGE_REALIZES`: `receipt_ref -> function_ref` +* `EDGE_OBSERVED_BY`: `output_ref -> observation_ref` (optional) + +Edges MUST be derivable solely from receipt content and referenced artifacts. + +--- + +## 4. Determinism & Validation (FER/1 v1.1) + +`FER/1` v1.1 receipts are **parity-first** and deterministic. Implementations +MUST enforce the DDS validation rules, including: + +* Strict TLV order and no unknown tags. +* `executor_set` and `parity_vector` ordered byte-lexicographically and aligned. +* `parity_entries[i].output_ref` equals the top-level `output_ref`. +* `started_at <= completed_at` and monotonic time envelope. +* Determinism level defaults to `D1_bit_exact`; any other level requires an + explicit `rng_seed`. +* `run_id`, when present, MUST equal the deterministic hash over + `{function_ref, input_manifest_ref, environment_ref, executor_fingerprint}`. +* Receipts MUST NOT encode policy, intent, or authority metadata. + +The canonical binary layout for receipts is defined in +`ENC/FER1-RECEIPT/1` and DDS. + +--- + +## 5. Interactions + +* `PEL/1-SURF` provides the surface `ExecutionResult` artifact referenced by + `ExecutionOutput.result_ref`. +* `FCT/1` binds policy, authority, and certification to one or more FER/1 + receipts. + +--- + +## 6. Conformance + +An implementation is **FER/1–conformant** if it: + +* Produces receipts that validate under `ENC/FER1-RECEIPT/1` and DDS rules. +* Uses the output reference fallback rules in §2. +* Emits TGK edges (when enabled) that are derivable from receipt content. diff --git a/tier1/fps-1.md b/tier1/fps-1.md new file mode 100644 index 0000000..916c766 --- /dev/null +++ b/tier1/fps-1.md @@ -0,0 +1,137 @@ +# FPS/1 — Functional Primitive Surface + +Status: Approved +Owner: Architecture +Version: 1.0.0 +SoT: Yes +Last Updated: 2026-02-21 +Tags: [primitives, determinism, bytes] + +**Document ID:** `FPS/1` +**Layer:** L1 — Deterministic functional primitives over payload bytes + +**Depends on (normative):** + +* `ASL/1-CORE` — `Artifact`, `Reference`, hash identity +* `HASH/ASL1` — hash family registry +* `ADR-006` — deterministic error semantics +* `ADR-015` — CAS rejection governance (alignment) + +**Informative references:** + +* `SRS` §12 — Functional Primitive Surface summary + +--- + +## 0. Conventions + +The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, and **MAY** are +to be interpreted as in RFC 2119. + +FPS/1 defines **pure, deterministic** operations over canonical byte payloads. +The primitives are functional: identical inputs MUST produce identical outputs. + +--- + +## 1. CID Derivation + +All primitives produce exactly one output payload and one CID. + +The CID for a payload is derived as: + +``` +cid = algo_id || H("CAS:OBJ\0" || payload) +``` + +* `algo_id` is an `ASL/1-CORE` hash identifier. +* For v1, `algo_id` defaults to `HASH/ASL1` SHA-256. +* The domain separator **MUST** be exactly `"CAS:OBJ\0"`. + +--- + +## 2. Primitives (Normative) + +### 2.1 put + +``` +put(payload_bytes) -> CID +``` + +Canonical write; identity only. Storage durability is out of scope here. + +Errors: `ERR_IO_FAILURE`, `ERR_NORMALIZATION` + +### 2.2 get + +``` +get(CID) -> payload_bytes +``` + +Fetch canonical bytes from a conformant store. + +Errors: `ERR_CID_NOT_FOUND` + +### 2.3 slice + +``` +slice(CID, offset, length) -> CID +``` + +Extract a contiguous byte range. + +Errors: `ERR_SLICE_RANGE` + +### 2.4 concatenate + +``` +concatenate([CID1, ..., CIDn]) -> CID +``` + +Join payloads in order. + +Errors: `ERR_EMPTY_INPUTS` + +### 2.5 reverse + +``` +reverse(CID, level) -> CID +``` + +Reverse payload order at a specified granularity: + +* `bit` — reverse all bits in the payload +* `byte` — reverse byte order +* `word` — reverse 2-byte units +* `long` — reverse 4-byte units + +Errors: `ERR_REV_ALIGNMENT`, `ERR_INVALID_LEVEL` + +### 2.6 splice + +``` +splice(CID_a, offset, CID_b) -> CID +``` + +Insert payload `b` into `a` at `offset`. + +Errors: `ERR_SPLICE_RANGE` + +--- + +## 3. Determinism & Closure + +* Identical inputs MUST produce identical outputs. +* Inputs are immutable. +* Outputs MUST be valid inputs to any FPS/1 primitive. + +--- + +## 4. Conformance Vectors + +The canonical vectors live in: + +``` +/amduat/tests/fixtures/conformance/fps_1/ +``` + +Vectors are required for each primitive and include output bytes and CID. diff --git a/tier1/ms-1.md b/tier1/ms-1.md new file mode 100644 index 0000000..eb564c5 --- /dev/null +++ b/tier1/ms-1.md @@ -0,0 +1,203 @@ +# MS/1 — Concept Mapping Surface + +Status: Draft +Owner: Architecture +Version: 0.1.0 +SoT: No +Last Updated: 2026-02-21 +Tags: [mapping, concepts, determinism] + +**Document ID:** `MS/1` +**Layer:** L2 — Deterministic concept-to-data mapping surface + +**Depends on (normative):** + +* `ASL/1-CORE` — `Artifact`, `Reference`, TypeTag identity +* `TGK/1` — concept nodes and predicate edges +* `ADR-003` — canonical encoding discipline (BCF/1) +* `ADR-006` — deterministic error semantics + +**Informative references:** + +* `amduatd-api-endpoint-spec.target.md` §4.12 (MS/1 mapping surface target) + +--- + +## 0. Conventions + +The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, and **MAY** are +to be interpreted as in RFC 2119. + +MS/1 defines the **deterministic mapping** from `(Concept, ContextFrame)` to +outputs. It does not define transport or API endpoints. + +--- + +## 1. Core Concepts + +### 1.1 Concept + +Concepts are graph nodes (TGK) with stable references. + +### 1.2 Context Frame + +A **Context Frame** is a canonical, content-addressed artifact encoding the +bindings needed to execute a concept mapping. + +Logical model: + +``` +ContextFrame = { + bindings: Map + predicates: Map // optional +} +``` + +`Value` is one of: + +* `DataRef` — an `ASL/1-CORE` reference to data bytes +* `Scalar` — a canonical scalar (`bool`, `int`, `float`, `string`, `null`) + +### 1.3 Mapping Result + +`ms_map` returns exactly one of: + +* `produced` — a canonical output artifact +* `gap` — missing required bindings +* `ambiguity` — multiple admissible bindings + +--- + +## 2. Context Frame Canonical Encoding (Normative) + +Context Frames MUST be encoded deterministically as a canonical BCF/1 map: + +``` +{ + "bindings": { : ... }, + "predicates": { : ... } // optional +} +``` + +Rules: + +1. Keys are `ReferenceBytes` hex (lowercase, no `0x`), sorted + byte-lexicographically by raw `ReferenceBytes`. +2. `Value` encoding is canonical BCF/1: + * `DataRef` is encoded as `{"ref": }`. + * `Scalar` is encoded as the canonical scalar value. +3. No duplicate keys. +4. Missing `predicates` is equivalent to an empty map. + +The resulting bytes are content-addressed; the `ContextFrame` CID is the +identity of the canonical bytes. + +Context Frame artifacts MUST use `type_tag=amduat.context_frame`. + +--- + +## 3. Mapping Entrypoint (Normative) + +``` +ms_map(concept_ref, context_frame, attachments?, concept_inputs?) -> Result +``` + +### 3.1 Effective Frame + +The **effective** Context Frame is the canonical merge of: + +1. `context_frame` bindings +2. `attachments` (data refs) +3. `concept_inputs` (concept refs) + +Rules: + +* `attachments` are added as bindings under their declared concept keys. +* `concept_inputs` are added as bindings where the value is a concept ref. +* Conflicts (same key, different value) MUST yield `ambiguity`. + +### 3.2 Required Keys + +A concept MAY declare required bindings via TGK edges: + +* `ms.requires_key` — `concept_ref -> key_concept_ref` +* `ms.requires_predicate` — `concept_ref -> predicate_concept_ref` + +Rules: + +* Missing required `ms.requires_key` bindings MUST yield `gap`. +* Missing required `ms.requires_predicate` bindings MUST yield `gap`. + +### 3.3 Result Determination + +Given a concept and effective frame: + +1. If any required binding is missing ⇒ return `gap`. +2. If any required binding is present with multiple admissible values ⇒ return + `ambiguity`. +3. Otherwise, execute deterministically and return `produced`. + +All outcomes MUST be deterministic for identical inputs. + +--- + +## 4. Gap & Ambiguity Artifacts (Normative) + +Gap and Ambiguity are **first-class artifacts** stored for audit. + +Canonical payloads (BCF/1 map, deterministic ordering): + +### 4.1 Gap + +``` +{ + "concept_ref": , + "context_ref": , + "missing_keys": [...], + "missing_predicates": [...] +} +``` + +### 4.2 Ambiguity + +``` +{ + "concept_ref": , + "context_ref": , + "ambiguous_keys": [ + { "key": , "candidates": [...] } + ] +} +``` + +* Lists are sorted by key, then candidate bytes. +* Missing arrays are encoded as empty arrays. + +Type tags: + +* Gap: `amduat.ms.gap` +* Ambiguity: `amduat.ms.ambiguity` + +--- + +## 5. Deterministic Error Codes (ADR-006) + +Errors MUST be mapped to deterministic codes: + +* `ERR_MS_INVALID_FRAME` — non-canonical or malformed Context Frame. +* `ERR_MS_KEY_MISSING` — required key absent (Gap). +* `ERR_MS_PREDICATE_MISSING` — required predicate absent (Gap). +* `ERR_MS_AMBIGUOUS` — multiple admissible bindings (Ambiguity). +* `ERR_MS_UNSUPPORTED` — unsupported scalar or value kind. + +These errors MUST NOT depend on runtime ordering or external state. + +--- + +## 6. Non-Goals + +MS/1 does not define: + +* Transport or HTTP APIs +* Execution engines or optimizers +* Concept graph schemas beyond required key/predicate edges diff --git a/tier1/pcb1.md b/tier1/pcb1.md new file mode 100644 index 0000000..4cc43b4 --- /dev/null +++ b/tier1/pcb1.md @@ -0,0 +1,82 @@ +# PCB1 — Parameter Block Encoding (FCS/1) + +Status: Approved +Owner: Architecture +Version: 1.0.0 +SoT: Yes +Last Updated: 2026-02-21 +Linked Phase Pack: PH07 +Tags: [execution, parameters, deterministic] + +**Document ID:** `PCB1` +**Layer:** L2 — Parameter block encoding for FCS/1 descriptors + +**Depends on (normative):** + +* `DDS` — PCB1 envelope and TLV layout (DDS §7.3) +* `ASL/1-CORE v0.4.x` — logical value model + +--- + +## 0. Purpose & Scope + +`PCB1` defines a **deterministic, canonical** encoding for parameter blocks used +by `FCS/1` descriptors. It standardizes slot manifests, slot data ordering, and +arity validation. + +--- + +## 1. Logical Model + +A PCB1 parameter block is a two-part payload: + +``` +PCB1 { + slot_manifest : list + slot_data : bytes +} + +Slot { + index : uint32 + name : string + type : enum + digest : bytes[32] +} +``` + +Slots MUST appear in ascending `index` order. The `slot_data` is a packed byte +sequence that respects the manifest ordering. + +--- + +## 2. Canonical Encoding + +The canonical byte encoding is defined by DDS §7.3: + +* COR/1 envelope with `MAGIC="PCB1"`, `VERSION=0x01`, `FLAGS=0x00`. +* Strict TLV order; no padding. +* Required TLVs: + * `slot_manifest (0x50)` — BCF/1 list of slot descriptors. + * `slot_data (0x51)` — packed bytes aligned with the manifest. + +--- + +## 3. Validation Rules + +Implementations MUST reject PCB1 payloads that violate any of the following: + +1. TLVs are not in strict ascending order, or appear more than once. +2. `slot_manifest` entries are not sorted by `index`. +3. Slot digest mismatches (`ERR_PCB_DIGEST_MISMATCH`). +4. Non-deterministic or duplicated manifest ordering (`ERR_PCB_MANIFEST_ORDER`). +5. Arity mismatch between PCB1 slot count and the enclosing FCS/1 descriptor + (`ERR_PCB_ARITY_MISMATCH`). + +--- + +## 4. Conformance + +An implementation is **PCB1–conformant** if it: + +* Emits the exact canonical encoding in DDS §7.3. +* Enforces the validation rules in §3. diff --git a/tier1/pel-1-surf.md b/tier1/pel-1-surf.md index 1165f57..9fe5384 100644 --- a/tier1/pel-1-surf.md +++ b/tier1/pel-1-surf.md @@ -186,6 +186,27 @@ as defined in `ASL/1-STORE`. The store MAY expose more operations, but `PEL/1-SU All `program_ref`, `input_refs`, `params_ref`, and `result_ref` values are interpreted relative to the `StoreConfig` used by the StoreInstance. +### 2.3 ExecutionContext (metadata surface) + +`PEL/1-SURF` execution is snapshot-bound. The **execution context metadata** +surface is intentionally minimal to preserve determinism: + +```text +ExecutionContext { + snapshot_id : uint64 // opaque store-assigned snapshot ID + execution_config_ref : optional Reference // immutable config artifact +} +``` + +Rules: + +* `snapshot_id` is the snapshot identity associated with the execution. If no + snapshot exists, callers MUST use `0`. +* `execution_config_ref`, when present, MUST refer to an immutable Artifact that + is **snapshot-scoped** (its meaning is fixed for the snapshot) and **deterministic**. +* The context is **metadata only** and MUST NOT change the semantics of `Exec_s` + beyond selecting deterministic, snapshot-scoped configuration. + --- ## 3. Surface Execution Model @@ -209,7 +230,22 @@ run( All references are interpreted relative to `store`’s `StoreConfig`. -### 3.2 Relationship to PEL/1-CORE +### 3.2 Determinism and context constraints + +To preserve determinism across replay and federation: + +* PEL execution MUST depend only on: + * the resolved Artifacts for `program_ref`, `input_refs`, and `params_ref`, + * the `ExecutionContext.snapshot_id`, and + * the Artifact referenced by `execution_config_ref` (if provided). +* PEL execution MUST NOT read storage or layout metadata, including but not + limited to: block IDs, segment IDs, physical offsets, storage stats, GC state, + or backend-specific layout details. +* Implementations MUST NOT call store APIs beyond `get` and `put` to derive + execution semantics. Index scans, log reads, or layout introspection are + forbidden for `PEL/1-SURF` determinism. + +### 3.3 Relationship to PEL/1-CORE For a supported scheme identified by `scheme_ref`, there exists a pure function `Exec_s` per `PEL/1-CORE`: diff --git a/tier1/pel-output-policy-1.md b/tier1/pel-output-policy-1.md new file mode 100644 index 0000000..c69bab5 --- /dev/null +++ b/tier1/pel-output-policy-1.md @@ -0,0 +1,99 @@ +# PEL/OUTPUT-POLICY/1 — Materialization & Promotion Policy + +Status: Draft +Owner: Architecture +Version: 0.1.0 +SoT: No +Last Updated: 2026-02-21 +Tags: [pel, outputs, promotion, retention] + +**Document ID:** `PEL/OUTPUT-POLICY/1` +**Layer:** L2 — Output handling policy (no execution semantics) + +**Depends on (normative):** + +* `PEL/1-CORE` — execution semantics +* `ASL/LOG/1` — visibility and log ordering +* `ASL/1-CORE-INDEX` — visibility and tombstone semantics + +**Informative references:** + +* `ASL/SYSTEM/1` — promotion/publication/retention separation + +--- + +## 0. Conventions + +The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, and **MAY** are +to be interpreted as in RFC 2119. + +This document defines **policy** for handling PEL outputs. It does not change +PEL execution semantics. + +--- + +## 1. Policy Dimensions + +Each output produced by PEL is subject to three independent policy decisions: + +### 1.1 Materialization + +Controls whether output bytes are stored, cached, or virtualized. + +* `store` — persist in the content index (authoritative). +* `cache` — persist in materialization cache only (performance hint). +* `virtualize` — compute in-memory only; do not persist. + +### 1.2 Promotion + +Controls whether an output is promoted from a derived view to **truth**. + +* `promote` — output becomes eligible for truth/published workflows. +* `no_promote` — output remains derived and must not be treated as truth. + +Promotion is **explicit**; it MUST NOT be inferred from execution. + +### 1.3 Publication + +Controls visibility (internal vs published) encoded in index metadata. + +* `internal` — visible only to the local domain. +* `published` — visible to federation/replay. + +Publication is distinct from promotion; a promoted output may still be internal. + +--- + +## 2. Policy Hooks (Normative) + +Core implementations MUST expose a policy hook that can be invoked **per +output** to decide the three dimensions above. + +The hook MUST be deterministic and MUST depend only on: + +* the execution inputs (program, params, inputs, snapshot state) +* explicit policy configuration supplied by the caller + +The hook MUST NOT read storage layout or non-snapshot metadata. + +--- + +## 3. Default Policy + +If no hook is supplied, the default policy is: + +* `materialization = store` +* `promotion = no_promote` +* `publication = internal` + +--- + +## 4. API Layer Mapping (Informative) + +API layers should expose explicit controls for: + +* promotion (`publish` / `promote` flags) +* visibility (`internal` vs `published`) +* materialization (`store` vs `cache` vs `virtualize`) + +The API MUST NOT implicitly promote outputs without an explicit policy choice. diff --git a/tier1/pel-program-dag-1.md b/tier1/pel-program-dag-1.md index 8321564..17cdb45 100644 --- a/tier1/pel-program-dag-1.md +++ b/tier1/pel-program-dag-1.md @@ -364,6 +364,23 @@ At the ASL/1 layer: This scheme assumes such an encoding exists and is canonical; it does not define the layout itself. +### 2.8 ProgramDescriptor (explicit executable artifacts) + +To prevent **implicit execution** of non-program artifacts (including receipts), +this scheme defines **ProgramDescriptor** semantics: + +* Only **explicitly tagged Program Artifacts** are executable. +* An artifact is executable **only if** its `type_tag` equals + `TYPE_TAG_PEL_PROGRAM_DAG_1` and its `bytes` decode as a `Program` under + `ENC/PEL-PROGRAM-DAG/1`. +* Artifacts with any other `type_tag` (including receipt or evidence tags such + as `TYPE_TAG_FER1_RECEIPT_1`) **MUST NOT** be interpreted as programs, even if + their bytes happen to be decodable as a `Program`. +* Artifacts with **no** `type_tag` are **not** executable. + +These rules ensure that only explicitly designated program artifacts can be +executed, eliminating accidental execution of receipt payloads or other data. + --- ## 3. Structural Validity diff --git a/tier1/srs.md b/tier1/srs.md index 561336c..9a8fd44 100644 --- a/tier1/srs.md +++ b/tier1/srs.md @@ -326,6 +326,28 @@ coverage `TV-MPR-001…003` (hash triple, missing weights, signature domain) and `TV-IER-001…004` (ok, missing seed, fingerprint mismatch, log digest mismatch) respectively with scenario summaries in accompanying `summary.md` files. +### FR-031A FLS/1 Ordering & Error Semantics + +FLS/1 envelopes and payloads MUST follow deterministic ordering and reject +malformed linkage metadata. Implementations SHALL: + +* Enforce strict TLV ordering and reject duplicates per DDS §7.8.1. +* Enforce channel catalog ordering (`channel_id` ascending) and uniqueness. +* Enforce message sequence bounds against the channel catalog. +* Enforce termination ledger ordering and bounds. + +Violations MUST map to ADR-006 error codes (non-exhaustive): + +* `ERR_FLS_TAG_ORDER`, `ERR_FLS_DUPLICATE_TAG`, `ERR_FLS_UNKNOWN_TAG` +* `ERR_FLS_CID_LENGTH`, `ERR_FLS_TIMESTAMP_LENGTH` +* `ERR_FLS_SESSION_ID_LENGTH`, `ERR_FLS_CHANNEL_ORDER`, `ERR_FLS_CHANNEL_DUPLICATE` +* `ERR_FLS_CHANNEL_RANGE`, `ERR_FLS_SEQ_RANGE` +* `ERR_FLS_SIGNATURE_DOMAIN`, `ERR_FLS_SIGNATURE_LENGTH`, `ERR_FLS_SIGNATURE` +* `ERR_FLS_PAYLOAD_CID_MISMATCH`, `ERR_FLS_PAYLOAD_KIND` + +Deterministic ordering and error mapping are required for replay stability and +federated transport interoperability. + ### FR-032 CT/1 Deterministic Replay (D1) Given identical AC/1 + DTF/1 + topology inputs, executing the runtime twice in @@ -475,6 +497,15 @@ Errors MUST be emitted as stable symbolic codes including but not limited to: * Requirements link to tests/defects in Phase Packs * ADRs reference affected FR/NFR IDs +### 10.1 Traceability Register (PH04) + +Traceability register entries anchor evidence and lineage tracking for Phase 04 +surfaces. + +| Entry ID | Surface | Phase | Evidence Path | Notes | +| --- | --- | --- | --- | --- | +| PH04-TR-FLS-001 | FLS/1 | PH04 | `/amduat/logs/ph04/evidence/fls1/PH04-EV-FLS-001/` | FLS/1 linkage envelopes + session/message ordering + error mapping coverage. | + --- ## 11. Future Phases diff --git a/tier1/tgk-store-1.md b/tier1/tgk-store-1.md index 559d464..68177c8 100644 --- a/tier1/tgk-store-1.md +++ b/tier1/tgk-store-1.md @@ -28,10 +28,15 @@ Tags: [traceability, execution] * `PEL/1-SURF v0.2.x` — store-backed execution surface (producer of many EdgeArtifacts) * `CIL/1`, `FER/1`, `FCT/1`, `OI/1` — provenance and fact profiles that consume graph queries * (future) `TGK/PROV/1` — provenance / trace operators over `ProvenanceGraph` +* `ADR-031` — ASL + TGK index separation boundary > **Normativity note** > `TGK/STORE/1` is a near-core **graph store profile**, not a kernel surface. It introduces no new identity scheme. It defines how to expose and query the `ProvenanceGraph` from `TGK/1-CORE` over Artifacts reachable via `ASL/1-STORE` or equivalent feeds, and constrains graph indexes and query semantics. +> **Index separation note** +> `TGK/STORE/1` assumes **separate** ASL and TGK indexes with explicit +> cross-layer invariants (ADR-031). A merged index is intentionally rejected. + © 2025 Niklas Rydberg. ## License @@ -115,6 +120,23 @@ In this document: * **ArtifactRef**, **Node**, and **EdgeRef** are all ASL/1 `Reference` values. * **ExecutionEnvironment** is as in `TGK/1-CORE`: an abstract context with a finite Artifact set and a fixed TGK profile set “in effect” at a snapshot. +--- + +## 0.1 ASL + TGK Index Boundary (Normative) + +TGK indexes are **derived** from ASL visibility and MUST respect the following +invariants (see ADR-031): + +1. TGK indexes MUST only include artifacts visible under ASL replay at the + same `(snapshot_id, log_position)`. +2. TGK indexes MUST enforce ASL tombstones and tombstone lifts by `logseq`. +3. TGK indexes MUST NOT introduce new identity schemes; all nodes/edges are + ASL `Reference` values. +4. TGK index updates MUST be deterministically derived from ASL-admitted + segments and log records. No side-channel writes. +5. Replaying ASL log + sealed segments MUST yield the same TGK index state + (up to encoding) as an online build. + Additional terms introduced here: ```text