From 624bd29bf9362bf8ca99ff2e40e32ec4cc718afb Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sun, 8 Feb 2026 08:55:39 +0100 Subject: [PATCH] Recover index state and stale log heads after partial repairs --- .../asl_store_index_fs/asl_store_index_fs.c | 16 ++++- src/core/asl_log_store.c | 70 ++++++++++++------- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/adapters/asl_store_index_fs/asl_store_index_fs.c b/src/adapters/asl_store_index_fs/asl_store_index_fs.c index da88c41..bb63aea 100644 --- a/src/adapters/asl_store_index_fs/asl_store_index_fs.c +++ b/src/adapters/asl_store_index_fs/asl_store_index_fs.c @@ -11,6 +11,7 @@ #include "amduat/enc/asl_core_index.h" #include "amduat/enc/asl_log.h" #include "amduat/hash/asl1.h" +#include "amduat/util/log.h" #include #include @@ -3821,9 +3822,18 @@ static bool amduat_asl_store_index_fs_current_state_impl( if (amduat_asl_store_index_fs_find_latest_snapshot_id(fs, &snapshot_id) && amduat_asl_store_index_fs_load_snapshot_manifest( fs->root_path, snapshot_id, &manifest, manifest_hash)) { - out_state->snapshot_id = manifest.snapshot_id; - if (manifest.anchor_logseq > out_state->log_position) { - out_state->log_position = manifest.anchor_logseq; + if (manifest.anchor_logseq <= last_logseq) { + out_state->snapshot_id = manifest.snapshot_id; + if (manifest.anchor_logseq > out_state->log_position) { + out_state->log_position = manifest.anchor_logseq; + } + } else { + amduat_log( + AMDUAT_LOG_WARN, + "index snapshot anchor beyond log tail; ignoring snapshot (snapshot=%" PRIu64 " anchor=%" PRIu64 " log_tail=%" PRIu64 ")", + (uint64_t)manifest.snapshot_id, + (uint64_t)manifest.anchor_logseq, + (uint64_t)last_logseq); } amduat_asl_snapshot_manifest_free(&manifest); } else if (record_count != 0u && diff --git a/src/core/asl_log_store.c b/src/core/asl_log_store.c index 8ea2b4a..5a44112 100644 --- a/src/core/asl_log_store.c +++ b/src/core/asl_log_store.c @@ -536,6 +536,7 @@ amduat_asl_store_error_t amduat_asl_log_append( for (uint32_t attempt = 0u; attempt < AMDUAT_ASL_LOG_MAX_RETRIES; ++attempt) { bool head_exists = false; + bool head_ref_missing = false; amduat_reference_t head_ref; amduat_artifact_t head_artifact; amduat_asl_log_chunk_t head_chunk; @@ -560,6 +561,10 @@ amduat_asl_store_error_t amduat_asl_log_append( store_err = amduat_asl_store_get(log_store->store, head_ref, &head_artifact); if (store_err != AMDUAT_ASL_STORE_OK) { + if (store_err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) { + /* Recover from a stale head pointer by rebasing to a fresh chunk. */ + head_ref_missing = true; + } else { char *head_ref_hex = NULL; (void)amduat_asl_ref_encode_hex(head_ref, &head_ref_hex); amduat_log(AMDUAT_LOG_ERROR, @@ -573,37 +578,50 @@ amduat_asl_store_error_t amduat_asl_log_append( amduat_reference_free(&head_ref); free(pointer_name); return store_err; + } } - if (!head_artifact.has_type_tag || - head_artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_ASL_LOG_CHUNK_1) { - amduat_reference_free(&head_ref); + if (!head_ref_missing) { + if (!head_artifact.has_type_tag || + head_artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_ASL_LOG_CHUNK_1) { + amduat_reference_free(&head_ref); + amduat_artifact_free(&head_artifact); + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + store_err = amduat_asl_log_chunk_decode_v1(head_artifact.bytes, + &head_chunk); amduat_artifact_free(&head_artifact); - free(pointer_name); - return AMDUAT_ASL_STORE_ERR_INTEGRITY; - } - store_err = amduat_asl_log_chunk_decode_v1(head_artifact.bytes, - &head_chunk); - amduat_artifact_free(&head_artifact); - if (store_err != AMDUAT_ASL_STORE_OK) { - amduat_log(AMDUAT_LOG_ERROR, - "asl_log_append: decode head chunk failed (log=%s pointer=%s attempt=%u err=%d)", + if (store_err != AMDUAT_ASL_STORE_OK) { + amduat_log(AMDUAT_LOG_ERROR, + "asl_log_append: decode head chunk failed (log=%s pointer=%s attempt=%u err=%d)", + log_name != NULL ? log_name : "(null)", + pointer_name != NULL ? pointer_name : "(null)", + (unsigned)attempt, + (int)store_err); + amduat_reference_free(&head_ref); + free(pointer_name); + return store_err; + } + if (head_chunk.base_offset > + UINT64_MAX - (uint64_t)head_chunk.entry_count) { + amduat_asl_log_chunk_free(&head_chunk); + amduat_reference_free(&head_ref); + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + base_offset = head_chunk.base_offset + head_chunk.entry_count; + amduat_asl_log_chunk_free(&head_chunk); + } else { + char *head_ref_hex = NULL; + (void)amduat_asl_ref_encode_hex(head_ref, &head_ref_hex); + amduat_log(AMDUAT_LOG_WARN, + "asl_log_append: stale head pointer missing chunk; rebasing log head (log=%s pointer=%s attempt=%u head_ref=%s)", log_name != NULL ? log_name : "(null)", pointer_name != NULL ? pointer_name : "(null)", (unsigned)attempt, - (int)store_err); - amduat_reference_free(&head_ref); - free(pointer_name); - return store_err; + head_ref_hex != NULL ? head_ref_hex : "(hex-encode-failed)"); + free(head_ref_hex); } - if (head_chunk.base_offset > - UINT64_MAX - (uint64_t)head_chunk.entry_count) { - amduat_asl_log_chunk_free(&head_chunk); - amduat_reference_free(&head_ref); - free(pointer_name); - return AMDUAT_ASL_STORE_ERR_INTEGRITY; - } - base_offset = head_chunk.base_offset + head_chunk.entry_count; - amduat_asl_log_chunk_free(&head_chunk); } { @@ -618,7 +636,7 @@ amduat_asl_store_error_t amduat_asl_log_append( new_chunk.has_timestamp = has_timestamp; new_chunk.has_actor = has_actor; new_chunk.entries = (amduat_asl_log_entry_t *)entries; - if (head_exists) { + if (head_exists && !head_ref_missing) { new_chunk.has_prev = true; new_chunk.prev_ref = head_ref; }