From 24bd38c4bfce791103635db61d3aa182c195db15 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Sun, 25 Oct 2026 22:30:47 +0100 Subject: [PATCH] fix(store): bump the graph digest stream to version 2 The canonical graph digest now streams index_unresolved_candidates when the table exists and binds the unresolved_calls_complete column of index_coverage_meta, but its version word was still 1. A holder of an older digest, such as the test-impact receipt's graph_sha256, would then compare two different stream definitions under one version number. Bump CBM_STORE_GRAPH_DIGEST_VERSION to 2 and document what each version streams. The golden fixture has neither the table nor the column, so its stream differs from version 1 only in the version word: still 17 rows and 4038 framed bytes, with a new golden SHA-256, and the test now asserts version 2. Signed-off-by: Martin Vogel --- src/store/store_graph_digest.c | 3 ++- src/store/store_graph_digest.h | 11 +++++++++-- tests/test_store_graph_digest.c | 8 ++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/store/store_graph_digest.c b/src/store/store_graph_digest.c index 9cea8de80..6748f25a0 100644 --- a/src/store/store_graph_digest.c +++ b/src/store/store_graph_digest.c @@ -706,7 +706,8 @@ static bool gd_table_rows(gd_context_t *g, int index, bool present) { static bool gd_stream_tables(gd_context_t *g, const bool present[GD_TABLE_COUNT]) { for (int i = 0; i < GD_TABLE_COUNT; i++) { - /* Legacy stores retain their byte-for-byte canonical stream. */ + /* A store without the candidate table streams the same tables as + * version 1; only the version word differs. */ if (i == GD_UNRESOLVED_CANDIDATES && !present[i]) { continue; } diff --git a/src/store/store_graph_digest.h b/src/store/store_graph_digest.h index d7276fd33..957e6bd7d 100644 --- a/src/store/store_graph_digest.h +++ b/src/store/store_graph_digest.h @@ -7,7 +7,14 @@ #include #include -#define CBM_STORE_GRAPH_DIGEST_VERSION 1u +/* Canonical stream version, hashed right after the domain string. A bump + * changes every digest, so a holder of an older digest (the test-impact + * receipt's graph_sha256) sees a mismatch instead of comparing two different + * stream definitions. + * 1 The nine tables projects .. store_meta. + * 2 #2265: also index_unresolved_candidates (streamed only when present) and + * index_coverage_meta.unresolved_calls_complete (bound when present). */ +#define CBM_STORE_GRAPH_DIGEST_VERSION 2u #define CBM_STORE_GRAPH_DIGEST_BYTES 32u typedef enum { @@ -32,7 +39,7 @@ typedef struct { uint64_t framed_bytes; } cbm_store_graph_digest_t; -/* Borrow a live, exclusive D5 scope. Hash the version-1 canonical stream of +/* Borrow a live, exclusive D5 scope. Hash the version-2 canonical stream of * the nine legacy tables and the unresolved candidate index when present, * including their exact CREATE SQL and explicit project * identity. The database must use UTF-8. No new handler or transaction is used. diff --git a/tests/test_store_graph_digest.c b/tests/test_store_graph_digest.c index 489c0a813..64617a96e 100644 --- a/tests/test_store_graph_digest.c +++ b/tests/test_store_graph_digest.c @@ -38,7 +38,11 @@ static const char *const gd_inserts[] = { "INSERT INTO index_coverage(project,rel_path,kind,detail) VALUES(CAST(X'70' AS TEXT),CAST(X'62' AS TEXT),CAST(X'736b6970' AS TEXT),CAST(X'' AS TEXT));", "INSERT INTO index_coverage_meta(project,generation,index_mode,recorded_at,recording_status,ignored_files_stored,ignored_files_total,coverage_version,hash_records_complete) VALUES(CAST(X'70' AS TEXT),CAST(X'67656e' AS TEXT),CAST(X'66756c6c' AS TEXT),CAST(X'74' AS TEXT),CAST(X'636f6d706c657465' AS TEXT),-1,0,1,1);", }; -static const unsigned char gd_golden_sha[32] = { 0x46, 0x44, 0x71, 0x72, 0xdc, 0x9b, 0x2d, 0xa8, 0xda, 0xc8, 0x54, 0x5a, 0x56, 0x88, 0xab, 0x59, 0x1d, 0xad, 0x39, 0xe2, 0x9b, 0xe6, 0x33, 0x9c, 0x3d, 0x26, 0x49, 0x27, 0xd0, 0x94, 0xe9, 0x3e }; +/* Stream version 2: the oracle's version-1 stream (4038 framed bytes, golden + * 46447172dc9b2da8...) with only its version word set to 2. This fixture has + * no index_unresolved_candidates table and no unresolved_calls_complete + * column, so nothing else in its stream changes. */ +static const unsigned char gd_golden_sha[32] = { 0xd0, 0x51, 0xc4, 0x56, 0x11, 0x5c, 0xf7, 0x3c, 0x75, 0xe2, 0x89, 0xb5, 0xc3, 0x92, 0xa2, 0x56, 0xa5, 0x66, 0xe6, 0x62, 0xfd, 0x56, 0x66, 0x35, 0x05, 0xc6, 0x1e, 0xa5, 0x49, 0x7d, 0xa1, 0x30 }; #define GD_GOLDEN_ROWS UINT64_C(17) #define GD_GOLDEN_BYTES UINT64_C(4038) @@ -190,7 +194,7 @@ TEST(store_graph_digest_independent_canonical_golden) { gd_int(cbm_store_get_db(s),"SELECT length(CAST(properties AS BLOB)) FROM nodes WHERE id=-7",4) && gd_int(cbm_store_get_db(s),"SELECT count(*) FROM lsp_surface WHERE typeof(ref_bloom)='blob'",2); bool got=setup && gd_p(s,&out); cbm_store_close(s); - ASSERT_TRUE(setup); ASSERT_TRUE(got); ASSERT_EQ(out.version,1); + ASSERT_TRUE(setup); ASSERT_TRUE(got); ASSERT_EQ(out.version,2); ASSERT_TRUE(out.rows==GD_GOLDEN_ROWS); ASSERT_TRUE(out.framed_bytes==GD_GOLDEN_BYTES); ASSERT_TRUE(memcmp(out.sha256,gd_golden_sha,32)==0); PASS(); }