trow AI at it.

This commit is contained in:
Carl Niklas Rydberg 2026-02-22 00:25:56 +01:00
parent 7b3ef3eab4
commit 3473b19425
57 changed files with 4476 additions and 64 deletions

View file

@ -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

View file

@ -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`.
---

View 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.

View 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
View 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
View 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
View 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
View 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 */

View file

@ -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);

View file

@ -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.

View file

@ -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"}

View 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`).

View 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;
}

View file

@ -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
View 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;
}

View file

@ -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,

View file

@ -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
View 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
View 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
View 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;
}

View file

@ -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) {

View file

@ -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;
}

View 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;
}

View 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;
}

View file

@ -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;
}

View file

@ -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
View 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
View 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
View 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.

View file

@ -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"
}
]
}

Binary file not shown.

View 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"
}
]
}

View 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"
}
]
}

View 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())

View 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.

View 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;
}

View file

@ -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.

View file

@ -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]

View file

@ -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]

View file

@ -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:

View file

@ -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.

View file

@ -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
View 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

View file

@ -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 channels `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

View file

@ -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]

View file

@ -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)

View file

@ -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
View 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/1conformant** 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
View 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/1conformant** 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
View 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
View 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
View 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 **PCB1conformant** if it:
* Emits the exact canonical encoding in DDS §7.3.
* Enforces the validation rules in §3.

View file

@ -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`:

View 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.

View file

@ -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

View file

@ -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

View file

@ -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