trow AI at it.
This commit is contained in:
parent
7b3ef3eab4
commit
3473b19425
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
||||
---
|
||||
|
|
|
|||
50
docs/adr-031-asl-tgk-index-separation.md
Normal file
50
docs/adr-031-asl-tgk-index-separation.md
Normal file
|
|
@ -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.
|
||||
28
include/amduat/asl/identity.h
Normal file
28
include/amduat/asl/identity.h
Normal file
|
|
@ -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 <stdint.h>
|
||||
|
||||
#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 */
|
||||
137
include/amduat/bytestore.h
Normal file
137
include/amduat/bytestore.h
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#ifndef AMDUAT_BYTESTORE_H
|
||||
#define AMDUAT_BYTESTORE_H
|
||||
|
||||
#include "amduat/asl/core.h"
|
||||
#include "amduat/hash/asl1.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
52
include/amduat/enc/fcs1.h
Normal file
52
include/amduat/enc/fcs1.h
Normal file
|
|
@ -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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
38
include/amduat/enc/pcb1.h
Normal file
38
include/amduat/enc/pcb1.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef AMDUAT_ENC_PCB1_H
|
||||
#define AMDUAT_ENC_PCB1_H
|
||||
|
||||
#include "amduat/asl/core.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
61
include/amduat/fps.h
Normal file
61
include/amduat/fps.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#ifndef AMDUAT_FPS_H
|
||||
#define AMDUAT_FPS_H
|
||||
|
||||
#include "amduat/asl/core.h"
|
||||
#include "amduat/hash/asl1.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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/<name>.jsonl` manifest. Each line is one entry.
|
||||
The manifest is the source of truth for codegen and documentation tables.
|
||||
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
43
registry/manifest.schema.md
Normal file
43
registry/manifest.schema.md
Normal file
|
|
@ -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.<domain>.<registry>.<kind>.<id>@<version>
|
||||
```
|
||||
|
||||
`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`).
|
||||
18
src/near_core/asl/identity.c
Normal file
18
src/near_core/asl/identity.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
235
src/near_core/bytestore.c
Normal file
235
src/near_core/bytestore.c
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#include "amduat/bytestore.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
294
src/near_core/enc/fcs1.c
Normal file
294
src/near_core/enc/fcs1.c
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
#include "amduat/enc/fcs1.h"
|
||||
|
||||
#include "amduat/enc/asl1_core_codec.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
256
src/near_core/enc/pcb1.c
Normal file
256
src/near_core/enc/pcb1.c
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
#include "amduat/enc/pcb1.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
202
src/near_core/fps.c
Normal file
202
src/near_core/fps.c
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
#include "amduat/fps.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ static void usage(FILE *out) {
|
|||
" --backend <name|all> Backend to run (repeatable)\n"
|
||||
" --workdir <path> Workdir for backend state\n"
|
||||
" --fixtures <path> Fixtures root (default: tests/fixtures/conformance)\n"
|
||||
" --json Emit machine-readable JSON summary\n"
|
||||
" --json-out <path> 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;
|
||||
}
|
||||
|
|
|
|||
139
tests/asl/test_asl_error_taxonomy.c
Normal file
139
tests/asl/test_asl_error_taxonomy.c
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#include "amduat/asl/store.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
86
tests/asl/test_asl_log_encoding.c
Normal file
86
tests/asl/test_asl_log_encoding.c
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include "amduat/enc/asl_log.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
236
tests/enc/test_fcs_pcb1.c
Normal file
236
tests/enc/test_fcs_pcb1.c
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
#include "amduat/enc/fcs1.h"
|
||||
#include "amduat/enc/asl1_core_codec.h"
|
||||
#include "amduat/fps.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
626
tests/enc/test_fps.c
Normal file
626
tests/enc/test_fps.c
Normal file
|
|
@ -0,0 +1,626 @@
|
|||
#include "amduat/fps.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
22
tests/fixtures/conformance/README.md
vendored
Normal file
22
tests/fixtures/conformance/README.md
vendored
Normal file
|
|
@ -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.
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
BIN
tests/fixtures/conformance/asl_tgk_exec_plan/artifacts/plan0.bin
vendored
Normal file
BIN
tests/fixtures/conformance/asl_tgk_exec_plan/artifacts/plan0.bin
vendored
Normal file
Binary file not shown.
12
tests/fixtures/conformance/asl_tgk_exec_plan/manifest.json
vendored
Normal file
12
tests/fixtures/conformance/asl_tgk_exec_plan/manifest.json
vendored
Normal file
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
65
tests/fixtures/conformance/fps_1/manifest.json
vendored
Normal file
65
tests/fixtures/conformance/fps_1/manifest.json
vendored
Normal file
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
62
tests/fixtures/conformance/generate_manifest.py
vendored
Executable file
62
tests/fixtures/conformance/generate_manifest.py
vendored
Executable file
|
|
@ -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 <root>/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())
|
||||
99
tests/fixtures/conformance/manifest.schema.md
vendored
Normal file
99
tests/fixtures/conformance/manifest.schema.md
vendored
Normal file
|
|
@ -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.
|
||||
103
tests/tgk/test_tgk_error_taxonomy.c
Normal file
103
tests/tgk/test_tgk_error_taxonomy.c
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#include "amduat/tgk/store.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
83
tier1/bytestore-1.md
Normal file
83
tier1/bytestore-1.md
Normal file
|
|
@ -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
|
||||
98
tier1/dds.md
98
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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
95
tier1/fcs-1.md
Normal file
95
tier1/fcs-1.md
Normal file
|
|
@ -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.
|
||||
183
tier1/fer-1.md
Normal file
183
tier1/fer-1.md
Normal file
|
|
@ -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<Reference> // 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<Reference>
|
||||
parity_entries : list<ParityEntry>
|
||||
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.
|
||||
137
tier1/fps-1.md
Normal file
137
tier1/fps-1.md
Normal file
|
|
@ -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.
|
||||
203
tier1/ms-1.md
Normal file
203
tier1/ms-1.md
Normal file
|
|
@ -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<ConceptRef, Value>
|
||||
predicates: Map<PredicateRef, Value> // 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": { <RefBytesHex> : <Value> ... },
|
||||
"predicates": { <RefBytesHex> : <Value> ... } // 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": <RefBytesHex>}`.
|
||||
* `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": <RefBytesHex>,
|
||||
"context_ref": <RefBytesHex>,
|
||||
"missing_keys": [<RefBytesHex>...],
|
||||
"missing_predicates": [<RefBytesHex>...]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Ambiguity
|
||||
|
||||
```
|
||||
{
|
||||
"concept_ref": <RefBytesHex>,
|
||||
"context_ref": <RefBytesHex>,
|
||||
"ambiguous_keys": [
|
||||
{ "key": <RefBytesHex>, "candidates": [<RefBytesHex>...] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
* 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
|
||||
82
tier1/pcb1.md
Normal file
82
tier1/pcb1.md
Normal file
|
|
@ -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>
|
||||
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.
|
||||
|
|
@ -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`:
|
||||
|
||||
|
|
|
|||
99
tier1/pel-output-policy-1.md
Normal file
99
tier1/pel-output-policy-1.md
Normal file
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
31
tier1/srs.md
31
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue