diff --git a/README.md b/README.md index 85dea82..e27db85 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Run unit tests directly in each module: (cd tools/knowledge-retrieval && go test -v -run 'Test(BM25|Dense|Current|Hybrid|Lifecycle|Paired|Situating|FineSpan)' ./...) ``` -*Note on conformance and integration tests*: Certain tests in `tools/knowledge-registry/p1-src03-acquisition-runner` and `tools/knowledge-retrieval` depend on shared monorepo schemas (`knowledge/registry/schemas/`) and test vectors (`testdata/canonical-json/v1/vectors.json`). Neither is part of this repository yet, so those tests fail here. +*Note on conformance and integration tests*: Tests in `tools/knowledge-registry/p1-src03-acquisition-runner` and `tools/knowledge-retrieval` that depend on shared registry schemas (`knowledge/registry/schemas/`) and acceptance data (`knowledge/registry/acceptance/p1-src-03/`) read them from the directory named by the `KNOWLEDGE_DATA_ROOT` environment variable. When `KNOWLEDGE_DATA_ROOT` is unset or empty, these tests skip with the fixed prefix `knowledge data root absent:` followed by the test purpose. To run with shared data, set `KNOWLEDGE_DATA_ROOT` to a directory containing the `knowledge/registry` tree (e.g. `KNOWLEDGE_DATA_ROOT=/path/to/data go test ./...`). If `KNOWLEDGE_DATA_ROOT` is set but lacks the expected files, the tests fail with the missing file path. Tests depending on test vectors (`testdata/canonical-json/v1/vectors.json`) fail when that file is absent from the repository. ### 2. Python Tests diff --git a/tools/knowledge-registry/p1-src03-acquisition-runner/data_root_test.go b/tools/knowledge-registry/p1-src03-acquisition-runner/data_root_test.go new file mode 100644 index 0000000..8dd3345 --- /dev/null +++ b/tools/knowledge-registry/p1-src03-acquisition-runner/data_root_test.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" +) + +type testingTB interface { + Helper() + Skipf(format string, args ...any) + Fatalf(format string, args ...any) +} + +func knowledgeDataRoot(tb testingTB, purpose string, expectedPaths ...string) string { + tb.Helper() + root := strings.TrimSpace(os.Getenv("KNOWLEDGE_DATA_ROOT")) + if root == "" { + tb.Skipf("knowledge data root absent: %s", purpose) + return "" + } + checks := expectedPaths + if len(checks) == 0 { + checks = []string{"knowledge/registry"} + } + for _, rel := range checks { + target := filepath.Join(root, filepath.FromSlash(rel)) + if _, err := os.Stat(target); err != nil { + tb.Fatalf("knowledge data root %s lacking expected file: %s", root, target) + return "" + } + } + return root +} + +type spyTB struct { + failed bool + skipped bool + message string +} + +func (s *spyTB) Helper() {} + +func (s *spyTB) Skipf(format string, args ...any) { + s.skipped = true + s.message = fmt.Sprintf(format, args...) +} + +func (s *spyTB) Fatalf(format string, args ...any) { + s.failed = true + s.message = fmt.Sprintf(format, args...) +} + +func TestKnowledgeDataRootPlantedUnsetSkipsWithPrefix(t *testing.T) { + for _, envVal := range []string{"", " "} { + t.Setenv("KNOWLEDGE_DATA_ROOT", envVal) + spy := &spyTB{} + got := knowledgeDataRoot(spy, "planted unset check") + if got != "" { + t.Fatalf("expected empty root on unset, got %q", got) + } + if !spy.skipped || spy.failed { + t.Fatalf("expected skip without failure, got skipped=%v failed=%v", spy.skipped, spy.failed) + } + const wantPrefix = "knowledge data root absent:" + if !strings.HasPrefix(spy.message, wantPrefix) { + t.Fatalf("skip message %q missing prefix %q", spy.message, wantPrefix) + } + if !strings.Contains(spy.message, "planted unset check") { + t.Fatalf("skip message %q missing purpose", spy.message) + } + } +} + +func TestKnowledgeDataRootPlantedEmptyTempDirFails(t *testing.T) { + emptyDir := t.TempDir() + t.Setenv("KNOWLEDGE_DATA_ROOT", emptyDir) + spy := &spyTB{} + got := knowledgeDataRoot(spy, "planted empty check", "knowledge/registry") + if got != "" { + t.Fatalf("expected empty root on failure, got %q", got) + } + if !spy.failed || spy.skipped { + t.Fatalf("expected fatal failure without skip, got failed=%v skipped=%v", spy.failed, spy.skipped) + } + if !strings.Contains(spy.message, emptyDir) { + t.Fatalf("fatal message %q missing empty dir path %s", spy.message, emptyDir) + } +} + +func TestKnowledgeDataRootPlantedTempDirWithFixturesPasses(t *testing.T) { + tempDir := t.TempDir() + fixtureRel := filepath.Join("knowledge", "registry", "acceptance", "fixture.json") + fullPath := filepath.Join(tempDir, fixtureRel) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(fullPath, []byte("{}\n"), 0o644); err != nil { + t.Fatal(err) + } + t.Setenv("KNOWLEDGE_DATA_ROOT", tempDir) + spy := &spyTB{} + got := knowledgeDataRoot(spy, "planted fixture check", fixtureRel) + if spy.failed || spy.skipped { + t.Fatalf("expected success without fail or skip, got failed=%v skipped=%v msg=%s", spy.failed, spy.skipped, spy.message) + } + if got != tempDir { + t.Fatalf("got root %q, want %q", got, tempDir) + } +} diff --git a/tools/knowledge-registry/p1-src03-acquisition-runner/runner_test.go b/tools/knowledge-registry/p1-src03-acquisition-runner/runner_test.go index 6139056..b24842e 100644 --- a/tools/knowledge-registry/p1-src03-acquisition-runner/runner_test.go +++ b/tools/knowledge-registry/p1-src03-acquisition-runner/runner_test.go @@ -878,21 +878,38 @@ func TestSharedCanonicalJSONConformanceVectors(t *testing.T) { } } +func verifyExecutionArtifact(tb testingTB, artifact executionArtifact, repositoryRoot, dataRoot string) { + tb.Helper() + path := filepath.Join(repositoryRoot, filepath.FromSlash(artifact.LogicalPath)) + if strings.HasPrefix(artifact.LogicalPath, "knowledge/registry/") { + path = filepath.Join(dataRoot, filepath.FromSlash(artifact.LogicalPath)) + } + raw, err := os.ReadFile(path) + if err != nil { + if strings.HasPrefix(artifact.LogicalPath, "knowledge/registry/") { + tb.Fatalf("missing data root file %s: %v", path, err) + } else { + tb.Fatalf("missing repository file %s: %v", path, err) + } + return + } + digest := sha256.Sum256(raw) + if got := "sha256:" + hex.EncodeToString(digest[:]); got != artifact.SHA256 || int64(len(raw)) != artifact.Bytes { + tb.Fatalf("execution artifact drifted: %s = %s/%d, want %s/%d", artifact.LogicalPath, got, len(raw), artifact.SHA256, artifact.Bytes) + return + } +} + func TestExecutionManifestBindsExactRepositoryBytes(t *testing.T) { manifest, err := exactExecutionManifest() if err != nil { t.Fatal(err) } + dataRoot := knowledgeDataRoot(t, "execution manifest exact repository bytes", + "knowledge/registry/schemas/source-acquisition-contract-v2.schema.json") repositoryRoot := filepath.Join("..", "..", "..") for _, artifact := range manifest.Artifacts { - raw, err := os.ReadFile(filepath.Join(repositoryRoot, filepath.FromSlash(artifact.LogicalPath))) - if err != nil { - t.Fatal(err) - } - digest := sha256.Sum256(raw) - if got := "sha256:" + hex.EncodeToString(digest[:]); got != artifact.SHA256 || int64(len(raw)) != artifact.Bytes { - t.Fatalf("execution artifact drifted: %s = %s/%d, want %s/%d", artifact.LogicalPath, got, len(raw), artifact.SHA256, artifact.Bytes) - } + verifyExecutionArtifact(t, artifact, repositoryRoot, dataRoot) } gate, err := exactExecutionGateBinding() if err != nil { @@ -922,6 +939,73 @@ func TestExecutionManifestBindsExactRepositoryBytes(t *testing.T) { } } +func syntheticPlantedArtifact() (executionArtifact, []byte) { + content := []byte("synthetic planted artifact payload\n") + digest := sha256.Sum256(content) + return executionArtifact{ + LogicalPath: "tools/planted/absent.txt", + Role: "planted-test", + SHA256: "sha256:" + hex.EncodeToString(digest[:]), + Bytes: int64(len(content)), + }, content +} + +func TestExecutionManifestPlantedRepositoryArtifactMissingFails(t *testing.T) { + artifact, content := syntheticPlantedArtifact() + tempRepoRoot := t.TempDir() + tempDataRoot := t.TempDir() + dataCopyPath := filepath.Join(tempDataRoot, filepath.FromSlash(artifact.LogicalPath)) + if err := os.MkdirAll(filepath.Dir(dataCopyPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(dataCopyPath, content, 0o644); err != nil { + t.Fatal(err) + } + spy := &spyTB{} + verifyExecutionArtifact(spy, artifact, tempRepoRoot, tempDataRoot) + if !spy.failed { + t.Fatalf("expected missing repository artifact %s to fail, but read from data root succeeded", artifact.LogicalPath) + } + expectedRepoPath := filepath.Join(tempRepoRoot, filepath.FromSlash(artifact.LogicalPath)) + if !strings.Contains(spy.message, expectedRepoPath) { + t.Fatalf("expected failure message %q to name repository path %s", spy.message, expectedRepoPath) + } +} + +func TestExecutionManifestPlantedRepositoryArtifactPresentOrDrifted(t *testing.T) { + artifact, content := syntheticPlantedArtifact() + tempRepoRoot := t.TempDir() + tempDataRoot := t.TempDir() + repoPath := filepath.Join(tempRepoRoot, filepath.FromSlash(artifact.LogicalPath)) + if err := os.MkdirAll(filepath.Dir(repoPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(repoPath, content, 0o644); err != nil { + t.Fatal(err) + } + + spyPass := &spyTB{} + verifyExecutionArtifact(spyPass, artifact, tempRepoRoot, tempDataRoot) + if spyPass.failed { + t.Fatalf("expected matching repository artifact to pass, got: %s", spyPass.message) + } + + corrupted := append([]byte(nil), content...) + corrupted[0] ^= 0xff + if err := os.WriteFile(repoPath, corrupted, 0o644); err != nil { + t.Fatal(err) + } + + spyDrift := &spyTB{} + verifyExecutionArtifact(spyDrift, artifact, tempRepoRoot, tempDataRoot) + if !spyDrift.failed { + t.Fatal("expected wrong digest to fail with drift line") + } + if !strings.Contains(spyDrift.message, "execution artifact drifted:") { + t.Fatalf("expected failure message to report drift, got: %s", spyDrift.message) + } +} + func TestRunnerContractDigestIsDeterministicAndManifestBound(t *testing.T) { contract, _, err := exactContract() if err != nil { @@ -962,37 +1046,17 @@ func TestEmitSchemaValidationFixtures(t *testing.T) { } } -func TestP1SRC02RunnerAndSchemasRemainByteIdentical(t *testing.T) { - expected := map[string]string{ - "../source-acquisition-runner/README.md": "f7da4d8a2f555970203d33d5750cef97c2dc383c77867bf7d5aa333ce2c8646c", - "../source-acquisition-runner/cas.go": "93721cb78aa49acc8377a209f3df6b6368056c276744dc49e0338f5887ddea75", - "../source-acquisition-runner/contract.go": "aef2405575f8fee962611f8fee95a852483337fd75636afc71d81f0969f2ff15", - "../source-acquisition-runner/github.go": "37c48cfbe1efe4932ee126271eb8958fd8f138714f02710d8afa648e6e2e80e3", - "../source-acquisition-runner/go.mod": "793c7f899940cc686f170ff89215d9b8eee48f4ebfaf793726b70b1be10c32de", - "../source-acquisition-runner/main.go": "a99cd8e28f21f325fd2ef057169e477a8ff30a04302726e0be3470f1e2335127", - "../source-acquisition-runner/runner_test.go": "de66639d35f46611ceff59b4a9a01f7883c90ec29c316d8b47cf38d16a062fe8", - "../../../knowledge/registry/schemas/source-acquisition-receipt.schema.json": "ed1577e9741674b0a5ad036b949b811293b82579c635c6c63bc5e561eab1eeb1", - "../../../knowledge/registry/schemas/source-inventory-proposal.schema.json": "a9197f0a7ac704f92650354153e35ad90734b99feaa99ebb7bb38123932f12ac", - "../../../knowledge/registry/schemas/source-acquisition-output-common.schema.json": "0a6b54bfab2af8ec7400de5b439d34d6f9f05c8d914deb479585dbe73ee92549", - "../../../knowledge/registry/schemas/source-acquisition-plan.schema.json": "a89767e5b9689da4242bfbc2d22160baad204f6596ad61aab6b41fd00c50358d", - } - for path, want := range expected { - raw, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - digest := sha256.Sum256(raw) - if got := hex.EncodeToString(digest[:]); got != want { - t.Fatalf("P1-SRC-02 frozen artifact changed: %s = %s, want %s", path, got, want) - } - } - for _, identity := range []string{ +func verifyRunnerIdentities(t *testing.T) { + t.Helper() + identities := []string{ "source-acquisition-plan:sha256:7b06d690d2edfe08e1fd05060116b3ca08a2a8f82763976f4d4fe1232c844aad", "accepted-record-artifact-bundle:sha256:61310bc368840c0202b0bd6e6d828c140aba26b7a389922d9b1fdbb836aecba6", "sha256:6306df08e525472da822d10477ea12a9ec3929d14d9d322f842ac88b76401fdb", - } { + } + paths := []string{"../source-acquisition-runner/contract.go", "../source-acquisition-runner/github.go", "../source-acquisition-runner/README.md"} + for _, identity := range identities { found := false - for _, path := range []string{"../source-acquisition-runner/contract.go", "../source-acquisition-runner/github.go", "../source-acquisition-runner/README.md"} { + for _, path := range paths { raw, err := os.ReadFile(path) if err != nil { t.Fatal(err) @@ -1005,6 +1069,51 @@ func TestP1SRC02RunnerAndSchemasRemainByteIdentical(t *testing.T) { } } +func TestP1SRC02RunnerAndSchemasRemainByteIdentical(t *testing.T) { + runnerFiles := map[string]string{ + "../source-acquisition-runner/README.md": "f7da4d8a2f555970203d33d5750cef97c2dc383c77867bf7d5aa333ce2c8646c", + "../source-acquisition-runner/cas.go": "93721cb78aa49acc8377a209f3df6b6368056c276744dc49e0338f5887ddea75", + "../source-acquisition-runner/contract.go": "aef2405575f8fee962611f8fee95a852483337fd75636afc71d81f0969f2ff15", + "../source-acquisition-runner/github.go": "37c48cfbe1efe4932ee126271eb8958fd8f138714f02710d8afa648e6e2e80e3", + "../source-acquisition-runner/go.mod": "793c7f899940cc686f170ff89215d9b8eee48f4ebfaf793726b70b1be10c32de", + "../source-acquisition-runner/main.go": "a99cd8e28f21f325fd2ef057169e477a8ff30a04302726e0be3470f1e2335127", + "../source-acquisition-runner/runner_test.go": "de66639d35f46611ceff59b4a9a01f7883c90ec29c316d8b47cf38d16a062fe8", + } + for path, want := range runnerFiles { + raw, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + digest := sha256.Sum256(raw) + if got := hex.EncodeToString(digest[:]); got != want { + t.Fatalf("P1-SRC-02 frozen artifact changed: %s = %s, want %s", path, got, want) + } + } + verifyRunnerIdentities(t) + + schemaFiles := map[string]string{ + "knowledge/registry/schemas/source-acquisition-receipt.schema.json": "ed1577e9741674b0a5ad036b949b811293b82579c635c6c63bc5e561eab1eeb1", + "knowledge/registry/schemas/source-inventory-proposal.schema.json": "a9197f0a7ac704f92650354153e35ad90734b99feaa99ebb7bb38123932f12ac", + "knowledge/registry/schemas/source-acquisition-output-common.schema.json": "0a6b54bfab2af8ec7400de5b439d34d6f9f05c8d914deb479585dbe73ee92549", + "knowledge/registry/schemas/source-acquisition-plan.schema.json": "a89767e5b9689da4242bfbc2d22160baad204f6596ad61aab6b41fd00c50358d", + } + t.Run("schemas", func(t *testing.T) { + dataRoot := knowledgeDataRoot(t, "P1-SRC-02 schema byte identity", + "knowledge/registry/schemas/source-acquisition-receipt.schema.json") + for rel, want := range schemaFiles { + path := filepath.Join(dataRoot, filepath.FromSlash(rel)) + raw, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read schema %s: %v", path, err) + } + digest := sha256.Sum256(raw) + if got := hex.EncodeToString(digest[:]); got != want { + t.Fatalf("P1-SRC-02 frozen schema changed: %s = %s, want %s", path, got, want) + } + } + }) +} + func TestGetJSONRejectsRepositoryOutsideContract(t *testing.T) { contract, _, err := exactContract() if err != nil { diff --git a/tools/knowledge-retrieval/data_root_test.go b/tools/knowledge-retrieval/data_root_test.go new file mode 100644 index 0000000..8dd3345 --- /dev/null +++ b/tools/knowledge-retrieval/data_root_test.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" +) + +type testingTB interface { + Helper() + Skipf(format string, args ...any) + Fatalf(format string, args ...any) +} + +func knowledgeDataRoot(tb testingTB, purpose string, expectedPaths ...string) string { + tb.Helper() + root := strings.TrimSpace(os.Getenv("KNOWLEDGE_DATA_ROOT")) + if root == "" { + tb.Skipf("knowledge data root absent: %s", purpose) + return "" + } + checks := expectedPaths + if len(checks) == 0 { + checks = []string{"knowledge/registry"} + } + for _, rel := range checks { + target := filepath.Join(root, filepath.FromSlash(rel)) + if _, err := os.Stat(target); err != nil { + tb.Fatalf("knowledge data root %s lacking expected file: %s", root, target) + return "" + } + } + return root +} + +type spyTB struct { + failed bool + skipped bool + message string +} + +func (s *spyTB) Helper() {} + +func (s *spyTB) Skipf(format string, args ...any) { + s.skipped = true + s.message = fmt.Sprintf(format, args...) +} + +func (s *spyTB) Fatalf(format string, args ...any) { + s.failed = true + s.message = fmt.Sprintf(format, args...) +} + +func TestKnowledgeDataRootPlantedUnsetSkipsWithPrefix(t *testing.T) { + for _, envVal := range []string{"", " "} { + t.Setenv("KNOWLEDGE_DATA_ROOT", envVal) + spy := &spyTB{} + got := knowledgeDataRoot(spy, "planted unset check") + if got != "" { + t.Fatalf("expected empty root on unset, got %q", got) + } + if !spy.skipped || spy.failed { + t.Fatalf("expected skip without failure, got skipped=%v failed=%v", spy.skipped, spy.failed) + } + const wantPrefix = "knowledge data root absent:" + if !strings.HasPrefix(spy.message, wantPrefix) { + t.Fatalf("skip message %q missing prefix %q", spy.message, wantPrefix) + } + if !strings.Contains(spy.message, "planted unset check") { + t.Fatalf("skip message %q missing purpose", spy.message) + } + } +} + +func TestKnowledgeDataRootPlantedEmptyTempDirFails(t *testing.T) { + emptyDir := t.TempDir() + t.Setenv("KNOWLEDGE_DATA_ROOT", emptyDir) + spy := &spyTB{} + got := knowledgeDataRoot(spy, "planted empty check", "knowledge/registry") + if got != "" { + t.Fatalf("expected empty root on failure, got %q", got) + } + if !spy.failed || spy.skipped { + t.Fatalf("expected fatal failure without skip, got failed=%v skipped=%v", spy.failed, spy.skipped) + } + if !strings.Contains(spy.message, emptyDir) { + t.Fatalf("fatal message %q missing empty dir path %s", spy.message, emptyDir) + } +} + +func TestKnowledgeDataRootPlantedTempDirWithFixturesPasses(t *testing.T) { + tempDir := t.TempDir() + fixtureRel := filepath.Join("knowledge", "registry", "acceptance", "fixture.json") + fullPath := filepath.Join(tempDir, fixtureRel) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(fullPath, []byte("{}\n"), 0o644); err != nil { + t.Fatal(err) + } + t.Setenv("KNOWLEDGE_DATA_ROOT", tempDir) + spy := &spyTB{} + got := knowledgeDataRoot(spy, "planted fixture check", fixtureRel) + if spy.failed || spy.skipped { + t.Fatalf("expected success without fail or skip, got failed=%v skipped=%v msg=%s", spy.failed, spy.skipped, spy.message) + } + if got != tempDir { + t.Fatalf("got root %q, want %q", got, tempDir) + } +} diff --git a/tools/knowledge-retrieval/evaluation_binding_closure_test.go b/tools/knowledge-retrieval/evaluation_binding_closure_test.go index 783c155..ae0084b 100644 --- a/tools/knowledge-retrieval/evaluation_binding_closure_test.go +++ b/tools/knowledge-retrieval/evaluation_binding_closure_test.go @@ -10,18 +10,18 @@ import ( ) const ( - closureAcceptanceDir = "../../knowledge/registry/acceptance/p1-src-03" - closureEvaluationTime = "2026-07-20T13:00:00Z" - frozenRequirementsID = "retrieval-binding-requirements:sha256:8c58e1e9cfe2cef672f104cfc88ab0229ba666e829d94188cda277b6a4e87f49" + closureAcceptanceRelDir = "knowledge/registry/acceptance/p1-src-03" + closureEvaluationTime = "2026-07-20T13:00:00Z" + frozenRequirementsID = "retrieval-binding-requirements:sha256:8c58e1e9cfe2cef672f104cfc88ab0229ba666e829d94188cda277b6a4e87f49" ) // closureFixture copies the committed admitted-artifacts envelope and its nine // referenced manifests into a writable temp tree so negative cases can tamper // freely without touching the committed evidence. -func closureFixture(t *testing.T) (admittedPath, root string) { +func closureFixture(t *testing.T, acceptanceDir string) (admittedPath, root string) { t.Helper() root = t.TempDir() - raw, err := os.ReadFile(filepath.Join(closureAcceptanceDir, "admitted-artifacts.json")) + raw, err := os.ReadFile(filepath.Join(acceptanceDir, "admitted-artifacts.json")) if err != nil { t.Fatalf("read admitted-artifacts: %v", err) } @@ -31,7 +31,7 @@ func closureFixture(t *testing.T) (admittedPath, root string) { } for _, a := range env.Authorities { for _, art := range []MultidomainBindingClosureAdmittedArtifact{a.SnapshotManifest, a.EvidenceManifest, a.AdmissionPolicy} { - b, err := os.ReadFile(filepath.Join(closureAcceptanceDir, art.Path)) + b, err := os.ReadFile(filepath.Join(acceptanceDir, art.Path)) if err != nil { t.Fatalf("read artifact %s: %v", art.Path, err) } @@ -85,41 +85,59 @@ func rewriteAdmitted(t *testing.T, admittedPath string, mutate func(*Multidomain } } -func TestMultidomainBindingClosureDeterministicNineClose(t *testing.T) { - admitted, _ := closureFixture(t) - freezeBefore, err := os.ReadFile(multidomainFreezePath) - if err != nil { - t.Fatal(err) - } +func matchesExpectedGapCounts(m MultidomainBindingClosureManifest) bool { + return m.ClosedGapCount == 9 && m.BlockingGapCount == 29 && m.GapCount == 38 +} - first, firstPath := materializeClosure(t, admitted) +func isBlockedState(m MultidomainBindingClosureManifest) bool { + return m.State == "blocked" && !m.Promotable && !m.ExecutionAuthorized && !m.Ready +} + +func hasAnyCapability(c MultidomainBindingClosureCapabilities) bool { + return c.RetrievalAuthorized || c.EvaluationAuthorized || c.SigningAuthorized || + c.GenerationAuthorized || c.PointerWriteAuthorized || c.ServingAuthorized +} - if first.ClosedGapCount != 9 || first.BlockingGapCount != 29 || first.GapCount != 38 { +func verifyClosureGapsAndCapabilities(t *testing.T, m MultidomainBindingClosureManifest) { + t.Helper() + if !matchesExpectedGapCounts(m) { t.Fatalf("gaps = %d closed / %d blocking / %d total; want 9/29/38", - first.ClosedGapCount, first.BlockingGapCount, first.GapCount) + m.ClosedGapCount, m.BlockingGapCount, m.GapCount) } closed := 0 - for _, g := range first.Gaps { - if g.Blocking { - continue - } - closed++ - if g.Scope != "source_authority" { - t.Fatalf("non-blocking gap has scope %q; only source_authority may close", g.Scope) + for _, g := range m.Gaps { + if !g.Blocking { + closed++ + if g.Scope != "source_authority" { + t.Fatalf("non-blocking gap has scope %q; only source_authority may close", g.Scope) + } } } if closed != 9 { t.Fatalf("%d gaps flipped non-blocking; want exactly 9", closed) } - if first.State != "blocked" || first.Promotable || first.ExecutionAuthorized || first.Ready { + if !isBlockedState(m) { t.Fatalf("state=%q promotable=%v execution=%v ready=%v; want blocked/false/false/false", - first.State, first.Promotable, first.ExecutionAuthorized, first.Ready) + m.State, m.Promotable, m.ExecutionAuthorized, m.Ready) } - c := first.Capabilities - if c.RetrievalAuthorized || c.EvaluationAuthorized || c.SigningAuthorized || - c.GenerationAuthorized || c.PointerWriteAuthorized || c.ServingAuthorized { - t.Fatalf("closure granted an authority capability: %+v", c) + if hasAnyCapability(m.Capabilities) { + t.Fatalf("closure granted an authority capability: %+v", m.Capabilities) } +} + +func TestMultidomainBindingClosureDeterministicNineClose(t *testing.T) { + dataRoot := knowledgeDataRoot(t, "multidomain binding closure deterministic nine close", + filepath.Join(closureAcceptanceRelDir, "admitted-artifacts.json")) + acceptanceDir := filepath.Join(dataRoot, filepath.FromSlash(closureAcceptanceRelDir)) + admitted, _ := closureFixture(t, acceptanceDir) + freezeBefore, err := os.ReadFile(multidomainFreezePath) + if err != nil { + t.Fatal(err) + } + + first, firstPath := materializeClosure(t, admitted) + verifyClosureGapsAndCapabilities(t, first) + if !strings.HasPrefix(first.ManifestID, "retrieval-binding-requirements:sha256:") { t.Fatalf("manifest id %q lacks the expected prefix", first.ManifestID) } @@ -152,9 +170,13 @@ func TestMultidomainBindingClosureDeterministicNineClose(t *testing.T) { } func TestMultidomainBindingClosureFailsClosed(t *testing.T) { + dataRoot := knowledgeDataRoot(t, "multidomain binding closure fails closed", + filepath.Join(closureAcceptanceRelDir, "admitted-artifacts.json")) + acceptanceDir := filepath.Join(dataRoot, filepath.FromSlash(closureAcceptanceRelDir)) + // wrong frozen requirements target t.Run("wrong_requirements_target", func(t *testing.T) { - admitted, _ := closureFixture(t) + admitted, _ := closureFixture(t, acceptanceDir) rewriteAdmitted(t, admitted, func(e *MultidomainBindingClosureAdmittedArtifacts) { e.RequirementsManifestID = "retrieval-binding-requirements:sha256:" + strings.Repeat("0", 64) }) @@ -166,7 +188,7 @@ func TestMultidomainBindingClosureFailsClosed(t *testing.T) { // only two authorities: cannot collapse to fewer than the three frozen authorities t.Run("collapsed_authorities", func(t *testing.T) { - admitted, _ := closureFixture(t) + admitted, _ := closureFixture(t, acceptanceDir) rewriteAdmitted(t, admitted, func(e *MultidomainBindingClosureAdmittedArtifacts) { e.Authorities = e.Authorities[:2] }) @@ -176,9 +198,13 @@ func TestMultidomainBindingClosureFailsClosed(t *testing.T) { } }) + testClosureFailsClosedPinsAndMutations(t, acceptanceDir) +} + +func testClosureFailsClosedPinsAndMutations(t *testing.T, acceptanceDir string) { // bad freeze caller pin (missing sha256: prefix) t.Run("invalid_freeze_pin", func(t *testing.T) { - admitted, _ := closureFixture(t) + admitted, _ := closureFixture(t, acceptanceDir) bare := strings.TrimPrefix(p1MultidomainFreezeDocumentSHA256, "sha256:") if _, _, err := MaterializeMultidomainBindingClosure(multidomainCorePath, multidomainFreezePath, bare, multidomainFixtureRoot, t.TempDir(), admitted, closureEvaluationTime, ""); err == nil { @@ -188,7 +214,7 @@ func TestMultidomainBindingClosureFailsClosed(t *testing.T) { // a mutated admitted manifest no longer matches its pinned document sha256 t.Run("mutated_admitted_artifact_bytes", func(t *testing.T) { - admitted, root := closureFixture(t) + admitted, root := closureFixture(t, acceptanceDir) var env MultidomainBindingClosureAdmittedArtifacts raw, _ := os.ReadFile(admitted) if err := json.Unmarshal(raw, &env); err != nil { @@ -201,10 +227,8 @@ func TestMultidomainBindingClosureFailsClosed(t *testing.T) { } manifest, _, err := MaterializeMultidomainBindingClosure(multidomainCorePath, multidomainFreezePath, p1MultidomainFreezeDocumentSHA256, multidomainFixtureRoot, t.TempDir(), admitted, closureEvaluationTime, "") - // Fail-closed: a mismatched document sha256 must not close its gap — either - // the run errors or the tampered authority's gap stays blocking (closed < 9). if err == nil && manifest.ClosedGapCount >= 9 { - t.Fatalf("tampered artifact still closed all 9 gaps (closed=%d); a mismatched document sha256 must leave its gap blocking", manifest.ClosedGapCount) + t.Fatalf("tampered artifact still closed all 9 gaps (closed=%d)", manifest.ClosedGapCount) } }) } diff --git a/tools/knowledge-retrieval/fusion_discrimination_test.go b/tools/knowledge-retrieval/fusion_discrimination_test.go index 6d04b7e..5dbe5b7 100644 --- a/tools/knowledge-retrieval/fusion_discrimination_test.go +++ b/tools/knowledge-retrieval/fusion_discrimination_test.go @@ -247,13 +247,11 @@ func TestSeparatingShapesAtRealCorpusSize(t *testing.T) { // and selectLexicalTopK for lexical, denseDotProduct plus the dense comparator // for dense. No scoring arithmetic is restated here. const ( - goldRankAcceptanceDir = "../../knowledge/registry/acceptance/p1-src-03" - goldRankStoreRoot = goldRankAcceptanceDir + "/retrieval" - goldRankCrosswalkPath = goldRankAcceptanceDir + "/crosswalk.json" - goldRankFreezePath = multidomainFixtureRoot + "/suite-freeze.core.json" + goldRankAcceptanceRelDir = "knowledge/registry/acceptance/p1-src-03" + goldRankFreezePath = multidomainFixtureRoot + "/suite-freeze.core.json" // The suite named by the most recent committed evaluation receipt // (multidomain-evaluation-receipts/.../e92c6086, evaluated 2026-07-20). - goldRankSuitePath = goldRankStoreRoot + "/multidomain-evaluation-suites/sha256/78/" + + goldRankSuiteRelPath = goldRankAcceptanceRelDir + "/retrieval/multidomain-evaluation-suites/sha256/78/" + "783779b3a446f26e02642c0db536f112a0ba03cd549272cb910984eef9959b69.json" goldRankSuiteID = "multidomain-evaluation-suite:sha256:" + "783779b3a446f26e02642c0db536f112a0ba03cd549272cb910984eef9959b69" @@ -388,17 +386,18 @@ func unitSpanIDFor(ref EvidenceRef) string { // on load, so both the gold-rank measurement and the case-mining search below // read exactly the artifacts the committed receipt was produced from. type goldRankCorpus struct { - suite MultidomainEvaluationSuite - index LexicalIndex - profile string - dense DenseVectorArtifact - window int + suite MultidomainEvaluationSuite + index LexicalIndex + profile string + dense DenseVectorArtifact + window int + storeRoot string + acceptanceDir string } -func loadGoldRankCorpus(t *testing.T) goldRankCorpus { +func loadGoldSuite(t *testing.T, suitePath string) MultidomainEvaluationSuite { t.Helper() - - suiteBytes, err := readStableFile(goldRankSuitePath, "multidomain evaluation suite", maxMultidomainSuiteBytes) + suiteBytes, err := readStableFile(suitePath, "multidomain evaluation suite", maxMultidomainSuiteBytes) if err != nil { t.Fatalf("read suite: %v", err) } @@ -409,8 +408,18 @@ func loadGoldRankCorpus(t *testing.T) goldRankCorpus { if suite.SuiteID != goldRankSuiteID { t.Fatalf("suite id %q; want the receipt-pinned %q", suite.SuiteID, goldRankSuiteID) } + return suite +} - indexBytes, err := verifyFileDigest(filepath.Join(goldRankStoreRoot, filepath.FromSlash(suite.LexicalIndex.ObjectPath)), +func loadGoldRankCorpus(t *testing.T, purpose string) goldRankCorpus { + t.Helper() + dataRoot := knowledgeDataRoot(t, purpose, goldRankSuiteRelPath) + acceptanceDir := filepath.Join(dataRoot, filepath.FromSlash(goldRankAcceptanceRelDir)) + storeRoot := filepath.Join(acceptanceDir, "retrieval") + suitePath := filepath.Join(dataRoot, filepath.FromSlash(goldRankSuiteRelPath)) + suite := loadGoldSuite(t, suitePath) + + indexBytes, err := verifyFileDigest(filepath.Join(storeRoot, filepath.FromSlash(suite.LexicalIndex.ObjectPath)), suite.LexicalIndex.DocumentSHA256, suite.LexicalIndex.ByteSize) if err != nil { t.Fatalf("lexical index does not match its suite pin: %v", err) @@ -419,7 +428,7 @@ func loadGoldRankCorpus(t *testing.T) goldRankCorpus { if err := decodeStrictCanonical(indexBytes, &index); err != nil { t.Fatalf("decode lexical index: %v", err) } - candidateBytes, err := verifyFileDigest(filepath.Join(goldRankStoreRoot, filepath.FromSlash(suite.LexicalCandidate.ObjectPath)), + candidateBytes, err := verifyFileDigest(filepath.Join(storeRoot, filepath.FromSlash(suite.LexicalCandidate.ObjectPath)), suite.LexicalCandidate.DocumentSHA256, suite.LexicalCandidate.ByteSize) if err != nil { t.Fatalf("lexical candidate does not match its suite pin: %v", err) @@ -432,7 +441,7 @@ func loadGoldRankCorpus(t *testing.T) goldRankCorpus { if !isBM25FProfile(profile) { t.Fatalf("lexical profile %q is not BM25F; this harness drives the BM25F scorers", profile) } - denseBytes, err := verifyFileDigest(filepath.Join(goldRankStoreRoot, filepath.FromSlash(suite.DenseArtifact.ObjectPath)), + denseBytes, err := verifyFileDigest(filepath.Join(storeRoot, filepath.FromSlash(suite.DenseArtifact.ObjectPath)), suite.DenseArtifact.DocumentSHA256, suite.DenseArtifact.ByteSize) if err != nil { t.Fatalf("dense artifact does not match its suite pin: %v", err) @@ -446,11 +455,19 @@ func loadGoldRankCorpus(t *testing.T) goldRankCorpus { t.Logf("corpus: lexical index document_count=%d documents=%d terms=%d; dense vectors=%d; fusion window=%d", index.DocumentCount, len(index.Documents), len(index.Terms), len(dense.Documents), window) - return goldRankCorpus{suite: suite, index: index, profile: profile, dense: dense, window: window} + return goldRankCorpus{ + suite: suite, + index: index, + profile: profile, + dense: dense, + window: window, + storeRoot: storeRoot, + acceptanceDir: acceptanceDir, + } } func TestGoldSpanLaneRanks(t *testing.T) { - corpus := loadGoldRankCorpus(t) + corpus := loadGoldRankCorpus(t, "gold span lane ranks") suite, index, profile, dense, window := corpus.suite, corpus.index, corpus.profile, corpus.dense, corpus.window freezeBytes, err := readStableFile(goldRankFreezePath, "scorer-blind freeze core", maxScorerBlindFreezeBytes) @@ -465,7 +482,7 @@ func TestGoldSpanLaneRanks(t *testing.T) { t.Fatalf("freeze carries %d cases; the frozen suite is eleven", len(freeze.Cases)) } - crosswalkBytes, err := readStableFile(goldRankCrosswalkPath, "crosswalk", maxManifestBytes) + crosswalkBytes, err := readStableFile(filepath.Join(corpus.acceptanceDir, "crosswalk.json"), "crosswalk", maxManifestBytes) if err != nil { t.Fatalf("read crosswalk: %v", err) } @@ -538,7 +555,7 @@ func TestGoldSpanLaneRanks(t *testing.T) { continue } - signalPath := filepath.Join(goldRankStoreRoot, filepath.FromSlash(suiteCase.Signals.Baseline.ObjectPath)) + signalPath := filepath.Join(corpus.storeRoot, filepath.FromSlash(suiteCase.Signals.Baseline.ObjectPath)) signalBytes, err := verifyFileDigest(signalPath, suiteCase.Signals.Baseline.DocumentSHA256, suiteCase.Signals.Baseline.ByteSize) if err != nil { unresolved(fmt.Sprintf("query signal does not match its suite pin: %v", err)) @@ -731,7 +748,7 @@ func formatLaneRanks(ranks laneRanks) string { } func TestMineSeparatingCaseShapes(t *testing.T) { - corpus := loadGoldRankCorpus(t) + corpus := loadGoldRankCorpus(t, "mine separating case shapes") suite, index, window := corpus.suite, corpus.index, corpus.window champion := championProfile(window) @@ -760,7 +777,7 @@ func TestMineSeparatingCaseShapes(t *testing.T) { } loaded := make([]QuerySignalArtifact, 0, len(arms)) for _, arm := range arms { - signalPath := filepath.Join(goldRankStoreRoot, filepath.FromSlash(arm.binding.ObjectPath)) + signalPath := filepath.Join(corpus.storeRoot, filepath.FromSlash(arm.binding.ObjectPath)) signalBytes, err := verifyFileDigest(signalPath, arm.binding.DocumentSHA256, arm.binding.ByteSize) if err != nil { t.Fatalf("%s %s query signal does not match its suite pin: %v", suiteCase.CaseID, arm.name, err)