Skip to content
Open
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
20 changes: 14 additions & 6 deletions internal/store/source_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ func sourceMetadataGenerationID(source, contract, fingerprint, checksum string)
}

// PublishSourceMetadata validates and atomically publishes one immutable
// generation. Retrying the identical publication is idempotent.
// generation. Retrying the identical publication is idempotent; an older
// stored generation cannot move the active head backward.
func (s *Store) PublishSourceMetadata(
ctx context.Context, sourceSHA256, extractorFingerprint string, canonical []byte,
) (SourceMetadataGeneration, error) {
Expand All @@ -102,13 +103,19 @@ func (s *Store) PublishSourceMetadata(
generation.GenerationID = sourceMetadataGenerationID(sourceSHA256, metadata.ContractVersion,
extractorFingerprint, checksum)
err = s.withStorageTx(ctx, func(tx *sql.Tx) error {
if _, execErr := tx.ExecContext(ctx, `INSERT INTO source_metadata_generations(
result, execErr := tx.ExecContext(ctx, `INSERT INTO source_metadata_generations(
generation_id,source_sha256,contract_version,extractor_fingerprint,canonical_json,checksum,created_at
) VALUES(?,?,?,?,?,?,?) ON CONFLICT(source_sha256,contract_version,extractor_fingerprint) DO NOTHING`,
generation.GenerationID, sourceSHA256, metadata.ContractVersion, extractorFingerprint,
canonical, checksum, generation.CreatedAt); execErr != nil {
canonical, checksum, generation.CreatedAt)
if execErr != nil {
return fmt.Errorf("recording source metadata: %w", execErr)
}
inserted, rowsErr := result.RowsAffected()
if rowsErr != nil {
return fmt.Errorf("recording source metadata: %w", rowsErr)
}
recorded := inserted > 0
var stored SourceMetadataGeneration
if scanErr := tx.QueryRowContext(ctx, `SELECT generation_id,source_sha256,contract_version,
extractor_fingerprint,canonical_json,checksum,created_at FROM source_metadata_generations
Expand All @@ -123,10 +130,11 @@ func (s *Store) PublishSourceMetadata(
return errors.New("source metadata extractor identity already has different evidence")
}
generation = stored
_, execErr := tx.ExecContext(ctx, `INSERT INTO source_metadata_heads(source_sha256,generation_id,published_at)
_, execErr = tx.ExecContext(ctx, `INSERT INTO source_metadata_heads(source_sha256,generation_id,published_at)
VALUES(?,?,?) ON CONFLICT(source_sha256) DO UPDATE SET
generation_id=excluded.generation_id,published_at=excluded.published_at`,
sourceSHA256, generation.GenerationID, nowRFC3339())
generation_id=excluded.generation_id,published_at=excluded.published_at
WHERE ? OR source_metadata_heads.generation_id=excluded.generation_id`,
sourceSHA256, generation.GenerationID, nowRFC3339(), recorded)
return execErr
})
return generation, err
Expand Down
71 changes: 71 additions & 0 deletions internal/store/source_metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,77 @@ func TestSourceMetadataGenerationsAreImmutableAndAttachmentFactsStayJoined(t *te
require.Error(t, err, "a head must not select another original's evidence")
}

// Coverage guard: this exercises the immutable-insert provenance used by the
// source-metadata head conflict predicate.
func TestSourceMetadataHeadAdvancesOnFirstRecordingAndHoldsOnRepublication(t *testing.T) {
s := newTestStore(t)
ctx := t.Context()
sourceSHA256 := fakeHash("a1")
_, err := s.CreateFile(ctx, s.RootID(), "source-a.pdf", sourceSHA256, 1, "application/pdf")
require.NoError(t, err)
metadataA := document.SourceMetadataV1{ContractVersion: document.SourceMetadataContractV1,
Fields: []document.SourceMetadataFieldV1{{Key: "title", Namespace: "pdf.info", SourceField: "Title",
Value: document.SourceMetadataValueV1{Kind: document.SourceMetadataString, String: new("A")}}}}
canonicalA, _, err := document.MarshalSourceMetadataV1(metadataA)
require.NoError(t, err)
generationA, err := s.PublishSourceMetadata(ctx, sourceSHA256, fakeHash("f1"), canonicalA)
require.NoError(t, err)

metadataB := document.SourceMetadataV1{ContractVersion: document.SourceMetadataContractV1,
Fields: []document.SourceMetadataFieldV1{{Key: "title", Namespace: "pdf.info", SourceField: "Title",
Value: document.SourceMetadataValueV1{Kind: document.SourceMetadataString, String: new("B")}}}}
canonicalB, _, err := document.MarshalSourceMetadataV1(metadataB)
require.NoError(t, err)
generationB, err := s.PublishSourceMetadata(ctx, sourceSHA256, fakeHash("f2"), canonicalB)
require.NoError(t, err)

active, activeMetadata, err := s.ActiveSourceMetadata(ctx, sourceSHA256)
require.NoError(t, err)
assert.Equal(t, generationB.GenerationID, active.GenerationID)
assert.Equal(t, "B", *activeMetadata.Fields[0].Value.String)

currentRetry, err := s.PublishSourceMetadata(ctx, sourceSHA256, fakeHash("f2"), canonicalB)
require.NoError(t, err)
assert.Equal(t, generationB.GenerationID, currentRetry.GenerationID)

replayed, err := s.PublishSourceMetadata(ctx, sourceSHA256, fakeHash("f1"), canonicalA)
require.NoError(t, err)
assert.Equal(t, generationA.GenerationID, replayed.GenerationID)

active, activeMetadata, err = s.ActiveSourceMetadata(ctx, sourceSHA256)
require.NoError(t, err)
assert.Equal(t, generationB.GenerationID, active.GenerationID)
assert.Equal(t, "B", *activeMetadata.Fields[0].Value.String)
var generationCount int
require.NoError(t, s.db.QueryRowContext(ctx,
`SELECT COUNT(*) FROM source_metadata_generations WHERE source_sha256=?`, sourceSHA256).Scan(&generationCount))
assert.Equal(t, 2, generationCount)
}

func TestSourceMetadataHeadAppearsWhenMissingForRecordedEvidence(t *testing.T) {
s := newTestStore(t)
ctx := t.Context()
sourceSHA256 := fakeHash("a1")
_, err := s.CreateFile(ctx, s.RootID(), "source-a.pdf", sourceSHA256, 1, "application/pdf")
require.NoError(t, err)
canonical, _, err := document.MarshalSourceMetadataV1(document.SourceMetadataV1{
ContractVersion: document.SourceMetadataContractV1,
Fields: []document.SourceMetadataFieldV1{{Key: "title", Namespace: "pdf.info", SourceField: "Title",
Value: document.SourceMetadataValueV1{Kind: document.SourceMetadataString, String: new("A")}}}})
require.NoError(t, err)
generation, err := s.PublishSourceMetadata(ctx, sourceSHA256, fakeHash("f1"), canonical)
require.NoError(t, err)
_, err = s.db.ExecContext(ctx, `DELETE FROM source_metadata_heads WHERE source_sha256=?`, sourceSHA256)
require.NoError(t, err)

republished, err := s.PublishSourceMetadata(ctx, sourceSHA256, fakeHash("f1"), canonical)
require.NoError(t, err)
assert.Equal(t, generation.GenerationID, republished.GenerationID)
active, _, err := s.ActiveSourceMetadata(ctx, sourceSHA256)
require.NoError(t, err)
assert.Equal(t, generation.GenerationID, active.GenerationID)
}

func TestHistoricalSourceMetadataOmitsCurrentAttachmentFacts(t *testing.T) {
s := newTestStore(t)
ctx := t.Context()
Expand Down