Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Keep gitcrawl-store hydration checkpoints in portable exports without treating local cursor changes as new source data.
- Add explicit cloud archive admission with immutable source, integrity, enrichment, and warning evidence while preserving strict publication defaults. Thanks @vincentkoc.
- Compatibility: report portable publication time as `last_export_at`; `last_sync_at` now describes retained successful sync runs instead of old repository scan checkpoints. Thanks @obviyus.
- Stop REST pagination when GitHub returns repeated or cyclic next links, preventing repeated requests and incomplete sync results.
Expand Down
7 changes: 6 additions & 1 deletion docs/portable-stores.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,14 +369,19 @@ tables and columns are skipped safely. The `current-state-semantic-v1` policy is

| Action | Tables or columns |
| --- | --- |
| Delete local-only tables when present | `observation_schema_convergence`, `repo_pipeline_state`, `repo_sync_state`, `sqlite_stat1`, `sqlite_stat4`, `thread_observation_sequence`, `thread_child_observation_reservations`, `workflow_run_observation_reservations`, `pull_request_review_thread_syncs` |
| Delete local-only tables when present | `gitcrawl_store_hydration_progress`, `observation_schema_convergence`, `repo_pipeline_state`, `repo_sync_state`, `sqlite_stat1`, `sqlite_stat4`, `thread_observation_sequence`, `thread_child_observation_reservations`, `workflow_run_observation_reservations`, `pull_request_review_thread_syncs` |
| Clear repository ingestion time | `repositories.updated_at` |
| Clear thread ingestion/order fields | `threads.first_pulled_at`, `last_pulled_at`, `updated_at`, `observation_sequence`, `evidence_observation_sequence`, `evidence_source_updated_at` |
| Clear revision/fingerprint record bookkeeping | `thread_revisions.observation_sequence`, `thread_revisions.created_at`, `thread_fingerprints.created_at` |
| Clear membership ordering but retain membership | `thread_child_observation_memberships.observation_sequence` becomes `1`; `member_ids_json` is retained |
| Clear PR/workflow fetch and local record times | `pull_request_details.fetched_at` and `updated_at`; `pull_request_files.fetched_at`; `pull_request_commits.fetched_at`; `pull_request_checks.fetched_at`; `pull_request_review_threads.fetched_at`; `pull_request_review_thread_revisions.fetched_at` and `recorded_at`; `github_workflow_runs.fetched_at` |
| Preserve tombstone state without local observation time | Non-NULL `threads.closed_at_local` and `deleted_at` values on comments, PR commits, review threads, and review-thread revisions become an empty non-NULL marker; NULL remains NULL |

`gitcrawl_store_hydration_progress` belongs to the gitcrawl-store producer.
Its checkpoints remain in runtime caches and generated portable SQLite files.
Cursor-only changes do not require publishing a new archive, so a publisher's
semantic no-op can leave the published checkpoint behind the runtime cache.

The policy also removes the named observation-convergence triggers associated
with the deleted allocator/reservation state and normalizes SQLite's transient
schema cookie before compaction. To prevent insertion-order-only hidden rowids
Expand Down
3 changes: 3 additions & 0 deletions internal/portable/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ var currentStateSemanticPolicy = artifactIdentityPolicy{
"observation_convergence_allocator_delete",
},
DroppedTables: []string{
// gitcrawl-store owns these resumable hydration cursors, not source facts.
// Retain them in exports but do not republish unchanged data for cursor churn.
"gitcrawl_store_hydration_progress",
"observation_schema_convergence",
"repo_pipeline_state",
"repo_sync_state",
Expand Down
65 changes: 65 additions & 0 deletions internal/portable/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/binary"
"errors"
"fmt"
"os"
"path/filepath"
"slices"
Expand Down Expand Up @@ -133,6 +134,7 @@ func TestSemanticArtifactIdentityIncludesMeaningfulState(t *testing.T) {
{name: "workflow public state", sql: `update github_workflow_runs set status = 'in_progress'`},
{name: "public timestamp", sql: `update threads set updated_at_gh = '2026-08-09T00:00:00Z'`},
{name: "repository identity", sql: `update repositories set full_name = 'openclaw/renamed'`},
{name: "unknown future table", sql: `create table gitcrawl_store_public_facts(value text); insert into gitcrawl_store_public_facts values('retained')`},
{name: "unknown future column", sql: `alter table threads add column future_public_value text; update threads set future_public_value = 'retained'`},
}
for _, mutation := range mutations {
Expand All @@ -156,6 +158,68 @@ func TestSemanticArtifactIdentityIncludesMeaningfulState(t *testing.T) {
}
}

func TestSemanticArtifactIdentityRetainsStoreHydrationProgressInExport(t *testing.T) {
ctx := context.Background()
dir := t.TempDir()
sourcePath := filepath.Join(dir, "source.db")
st := seedExportSource(t, ctx, sourcePath)
defer st.Close()
if _, err := st.DB().ExecContext(ctx, `
create table gitcrawl_store_hydration_progress(
repo_id integer not null references repositories(id) on delete cascade,
phase text not null,
cursor integer not null,
primary key(repo_id, phase)
);
insert into gitcrawl_store_hydration_progress values(1, 'historical-pr-details', 10);
insert into gitcrawl_store_hydration_progress values(1, 'active-pr-context', 20);
`); err != nil {
t.Fatal(err)
}
before, err := Export(ctx, testExportOptions(sourcePath, filepath.Join(dir, "before")))
if err != nil {
t.Fatal(err)
}
for _, cursor := range []int{30, 40} {
if _, err := st.DB().ExecContext(ctx, `update gitcrawl_store_hydration_progress set cursor = ? where phase = 'historical-pr-details'`, cursor); err != nil {
t.Fatal(err)
}
result, err := Export(ctx, testExportOptions(sourcePath, filepath.Join(dir, fmt.Sprintf("cursor-%d", cursor))))
if err != nil {
t.Fatal(err)
}
if result.ArtifactID != before.ArtifactID {
t.Fatalf("hydration progress changed semantic identity: got %s, want %s", result.ArtifactID, before.ArtifactID)
}
if result.SHA256 == before.SHA256 {
t.Fatal("checkpoint-bearing export retained the previous exact file hash")
}
exported := openRawDB(t, result.DatabasePath)
var historical, active int
err = exported.QueryRowContext(ctx, `select cursor from gitcrawl_store_hydration_progress where repo_id = 1 and phase = 'historical-pr-details'`).Scan(&historical)
if err == nil {
err = exported.QueryRowContext(ctx, `select cursor from gitcrawl_store_hydration_progress where repo_id = 1 and phase = 'active-pr-context'`).Scan(&active)
}
closeErr := exported.Close()
if err != nil || closeErr != nil {
t.Fatalf("read exported hydration progress: %v; close: %v", err, closeErr)
}
if historical != cursor || active != 20 {
t.Fatalf("exported hydration progress = (%d, %d), want (%d, 20)", historical, active, cursor)
}
}
if _, err := st.DB().ExecContext(ctx, `update threads set title = 'changed source fact' where id = 1`); err != nil {
t.Fatal(err)
}
changed, err := Export(ctx, testExportOptions(sourcePath, filepath.Join(dir, "source-change")))
if err != nil {
t.Fatal(err)
}
if changed.ArtifactID == before.ArtifactID {
t.Fatal("hydration progress table hid a meaningful source change")
}
}

func TestSemanticArtifactIdentityCanonicalizesCompositeKeyRowids(t *testing.T) {
ctx := context.Background()
dir := t.TempDir()
Expand Down Expand Up @@ -388,6 +452,7 @@ func TestCurrentStateSemanticPolicyIsExplicitAndValid(t *testing.T) {
t.Fatalf("current semantic identity policy: %v", err)
}
wantDropped := []string{
"gitcrawl_store_hydration_progress",
"observation_schema_convergence",
"pull_request_review_thread_syncs",
"repo_pipeline_state",
Expand Down