From f3570c724077e13018c99429abb93b255394d78a Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:27:32 +0000 Subject: [PATCH 01/45] feat(benchmark): pure layer for matched Arango/Postgres dataframe benchmark Adds internal/dataframe/benchmark: the offline-verifiable core of the two-backend dataframe benchmark (ticket pg-benchmark-arango-postgres-dataframe). - fixtures.go: deterministic seeded FHIR NDJSON generator (Patient/ Observation/Specimen/Condition/ResearchStudy) with facts sidecar and sha256 manifest digest; tiny/small/representative presets; designed-in duplicate contributors, null/missing values, selective filters. Fixes during testing: metastatic specimen stride moved to the global cursor (per-patient index could never reach it, making the filter vacuous). - canonical.go: total type-tagged canonicalization; null vs missing, bag multiplicity, numeric spelling equivalence; explicit storage-ID -> logical-ID mapping so backends are never compared on storage identity. - gates.go: correctness gates BEFORE timing: oracle rows, cross-backend bag equality, column contract, row count, completeness (truncated is never complete), negative auth (denial needs a non-empty baseline and errors are not denials), arm outcome publication. No path from an empty or absent result to a pass; unsupported cases are published, never hidden; empty matrices are refused, not passes. - oracle.go: independent tiny-preset oracles derived from Facts with plain Go arithmetic (no planner/compiler/storage involvement). - measure.go: warmup exclusion, interleaved rounds, nearest-rank p50/p95 with declared method, failed samples kept in raw and excluded from statistics, unavailable metrics never zero, measured throughput windows, injectable clock. - matrix.go: closed workload battery with column contracts, categories, and per-entry oracle-consistency validation. - report.go: report.json schema whose every derived number is recomputed from raw evidence by VerifyReport; controls (stability CV limit, self-ratio, empty floor, ratio-only-both-passed) are mandatory; passed runs require claims and both arms' evidence. The 18-case forgery suite (report_test.go) proves each tamper path REDs (ruling ii). Offline validation: go test -count=1 ./internal/dataframe/benchmark (all pass), go vet, gofmt clean. Live Arango/PG legs, harness wiring, and the node orchestrator land in following waves; PG window not yet granted. AO native adversarial review is pending the PR (coordinator-gated). --- internal/dataframe/benchmark/canonical.go | 300 +++++++++ .../dataframe/benchmark/canonical_test.go | 143 ++++ internal/dataframe/benchmark/fixtures.go | 622 ++++++++++++++++++ internal/dataframe/benchmark/fixtures_test.go | 248 +++++++ internal/dataframe/benchmark/gates.go | 292 ++++++++ internal/dataframe/benchmark/gates_test.go | 197 ++++++ internal/dataframe/benchmark/matrix.go | 189 ++++++ internal/dataframe/benchmark/matrix_test.go | 38 ++ internal/dataframe/benchmark/measure.go | 236 +++++++ internal/dataframe/benchmark/measure_test.go | 159 +++++ internal/dataframe/benchmark/oracle.go | 258 ++++++++ internal/dataframe/benchmark/oracle_test.go | 186 ++++++ internal/dataframe/benchmark/report.go | 367 +++++++++++ internal/dataframe/benchmark/report_test.go | 187 ++++++ 14 files changed, 3422 insertions(+) create mode 100644 internal/dataframe/benchmark/canonical.go create mode 100644 internal/dataframe/benchmark/canonical_test.go create mode 100644 internal/dataframe/benchmark/fixtures.go create mode 100644 internal/dataframe/benchmark/fixtures_test.go create mode 100644 internal/dataframe/benchmark/gates.go create mode 100644 internal/dataframe/benchmark/gates_test.go create mode 100644 internal/dataframe/benchmark/matrix.go create mode 100644 internal/dataframe/benchmark/matrix_test.go create mode 100644 internal/dataframe/benchmark/measure.go create mode 100644 internal/dataframe/benchmark/measure_test.go create mode 100644 internal/dataframe/benchmark/oracle.go create mode 100644 internal/dataframe/benchmark/oracle_test.go create mode 100644 internal/dataframe/benchmark/report.go create mode 100644 internal/dataframe/benchmark/report_test.go diff --git a/internal/dataframe/benchmark/canonical.go b/internal/dataframe/benchmark/canonical.go new file mode 100644 index 000000000..c733f65d9 --- /dev/null +++ b/internal/dataframe/benchmark/canonical.go @@ -0,0 +1,300 @@ +package benchmark + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "math" + "sort" + "strconv" +) + +// Result canonicalization for cross-backend comparison. +// +// The two backends legitimately differ in storage identity (Arango `_key` +// materialization vs the PostgreSQL stored logical id) and in JSON numeric +// spelling. A comparison is only meaningful under an EXPLICIT contract, so +// canonicalization here is total: every value maps to exactly one canonical +// token, null and missing are distinct, multiplicity is preserved (bag +// semantics), and unordered comparison sorts canonical row encodings. + +// CanonicalValue is the closed canonical value algebra. +type CanonicalValue interface{ canonicalToken() string } + +type CNull struct{} // JSON null +type CMissing struct{} // key absent from the row object +type CString string // string, exact bytes +type CBool bool // boolean +type CNumber struct{ Text string } // numeric, canonical shortest spelling +type CList struct{ Items []CanonicalValue } // ordered list, multiplicity kept +type CObject struct{ Pairs []CanonicalPair } // object, keys sorted on encode + +type CanonicalPair struct { + Key string + Value CanonicalValue +} + +func (CNull) canonicalToken() string { return "N" } +func (CMissing) canonicalToken() string { return "A" } +func (CString) canonicalToken() string { return "" } +func (CBool) canonicalToken() string { return "" } +func (CNumber) canonicalToken() string { return "" } +func (CList) canonicalToken() string { return "" } +func (CObject) canonicalToken() string { return "" } + +// canonicalEncode renders one deterministic token for a value. Type tags +// keep 1, "1", true, and null mutually distinct. +func canonicalEncode(v CanonicalValue) string { + switch t := v.(type) { + case CNull: + return "N" + case CMissing: + return "A" + case CString: + return "s:" + strconv.Quote(string(t)) + case CBool: + if t { + return "b:1" + } + return "b:0" + case CNumber: + return "n:" + canonicalNumberText(t.Text) + case CList: + out := "l:[" + for i, item := range t.Items { + if i > 0 { + out += "," + } + out += canonicalEncode(item) + } + return out + "]" + case CObject: + pairs := append([]CanonicalPair(nil), t.Pairs...) + sort.Slice(pairs, func(i, j int) bool { return pairs[i].Key < pairs[j].Key }) + out := "o:[" + for i, pair := range pairs { + if i > 0 { + out += "," + } + out += strconv.Quote(pair.Key) + ":" + canonicalEncode(pair.Value) + } + return out + "]" + default: + return "u:" + fmt.Sprintf("%T", v) + } +} + +// canonicalNumberText renders the shortest round-tripping decimal for the +// numeric spelling, so 1, 1.0, and 1e0 canonicalize identically while 0.1 +// stays distinct from 0.10000000001. +func canonicalNumberText(text string) string { + f, err := strconv.ParseFloat(text, 64) + if err != nil { + return "raw:" + text + } + if math.IsInf(f, 0) || math.IsNaN(f) { + return "raw:" + text + } + return strconv.FormatFloat(f, 'g', -1, 64) +} + +// CanonicalizeValue converts one decoded JSON value. The caller distinguishes +// missing keys before calling (rows are walked with an explicit key set). +func CanonicalizeValue(value any) CanonicalValue { + switch t := value.(type) { + case nil: + return CNull{} + case bool: + return CBool(t) + case float64: + return CNumber{Text: strconv.FormatFloat(t, 'g', -1, 64)} + case json.Number: + return CNumber{Text: string(t)} + case string: + return CString(t) + case []any: + items := make([]CanonicalValue, 0, len(t)) + for _, item := range t { + items = append(items, CanonicalizeValue(item)) + } + return CList{Items: items} + case map[string]any: + pairs := make([]CanonicalPair, 0, len(t)) + for key, child := range t { + pairs = append(pairs, CanonicalPair{Key: key, Value: CanonicalizeValue(child)}) + } + return CObject{Pairs: pairs} + default: + return CString(fmt.Sprintf("%v", t)) + } +} + +// CanonicalRow is one row over the pinned column order. Missing keys become +// CMissing, so a backend that omits a null column cannot silently match one +// that emits null. +type CanonicalRow []CanonicalValue + +// CanonicalizeRows converts row objects using the exact column order. +func CanonicalizeRows(columns []string, rows []map[string]any) []CanonicalRow { + out := make([]CanonicalRow, 0, len(rows)) + for _, row := range rows { + cr := make(CanonicalRow, len(columns)) + for i, column := range columns { + value, ok := row[column] + if !ok { + cr[i] = CMissing{} + continue + } + cr[i] = CanonicalizeValue(value) + } + out = append(out, cr) + } + return out +} + +// CanonicalBag is a fully canonical unordered multiset of rows: the sorted +// canonical encodings. Equality of two bags is exactly cross-backend +// equality under this contract (types, multiplicity, and nested structure). +type CanonicalBag []string + +func Bag(rows []CanonicalRow) CanonicalBag { + bag := make(CanonicalBag, 0, len(rows)) + for _, row := range rows { + tokens := make([]string, len(row)) + for i, cell := range row { + tokens[i] = canonicalEncode(cell) + } + bag = append(bag, fmt.Sprintf("%q", tokens)) + } + sort.Strings(bag) + return bag +} + +func (b CanonicalBag) Digest() string { + h := sha256.New() + for _, row := range b { + h.Write([]byte(row)) + h.Write([]byte{0}) + } + return hex.EncodeToString(h.Sum(nil)) +} + +// FirstDifference reports the first row encoding where two equal-length +// bags diverge, for a human-readable mismatch report without dumping either +// full result set (bound values stay out of evidence). +func FirstDifference(a, b CanonicalBag) (string, string, bool) { + limit := len(a) + if len(b) < limit { + limit = len(b) + } + for i := 0; i < limit; i++ { + if a[i] != b[i] { + return truncateToken(a[i]), truncateToken(b[i]), true + } + } + if len(a) != len(b) { + return "(len " + strconv.Itoa(len(a)) + ")", "(len " + strconv.Itoa(len(b)) + ")", true + } + return "", "", false +} + +func truncateToken(token string) string { + const max = 160 + if len(token) <= max { + return token + } + return token[:max] + "…" +} + +// StorageIDMap is the explicit backend storage identity -> logical identity +// mapping the comparison uses. Backends must not be compared on storage +// identifiers; any column listed here is translated to logical identity on +// both sides before bagging. +// +// Arango rows carry storage keys of the form "Collection/key" or bare keys in +// row-identity columns; the PostgreSQL rows carry the stored logical id. The +// mapping is data-driven: the fixture manifest owns logical ids, and both +// spellings resolve through it, so a mismatch is a real mismatch. +type StorageIDMap struct { + // Columns are the row columns that carry storage identities. + Columns []string + // toLogical maps either spelling (storage or logical) to the logical id. + toLogical map[string]string +} + +// NewStorageIDMap builds the mapping from fixture facts. +func NewStorageIDMap(facts *Facts) *StorageIDMap { + toLogical := map[string]string{} + add := func(logical string) { + toLogical[logical] = logical + // Arango spellings: Collection/key and _id-like Type/key forms. + for _, collection := range []string{"Patient", "Observation", "Specimen", "Condition", "ResearchStudy"} { + toLogical[collection+"/"+logical] = logical + } + } + for _, p := range facts.Patients { + add(p.ID) + for _, id := range p.ObservationIDs { + add(id) + } + for _, id := range p.SpecimenIDs { + add(id) + } + for _, id := range p.ConditionIDs { + add(id) + } + } + for _, o := range facts.Observations { + add(o.ID) + } + for _, s := range facts.Specimens { + add(s.ID) + } + for _, c := range facts.Conditions { + add(c.ID) + } + for _, s := range facts.StudyIDs { + add(s) + } + return &StorageIDMap{ + Columns: []string{"_row_id", "row_id", "resource_id", "root_resource_id"}, + toLogical: toLogical, + } +} + +// Apply rewrites identity-carrying cells through the logical mapping. An +// unmapped identity string stays verbatim (a foreign identity must compare +// unequal rather than vanish), and non-strings pass through. +func (m *StorageIDMap) Apply(columns []string, rows []map[string]any) []map[string]any { + if m == nil || len(m.Columns) == 0 { + return rows + } + present := map[string]bool{} + for _, column := range columns { + present[column] = true + } + watched := map[string]bool{} + for _, column := range m.Columns { + if present[column] { + watched[column] = true + } + } + out := make([]map[string]any, 0, len(rows)) + for _, row := range rows { + clone := make(map[string]any, len(row)) + for key, value := range row { + if watched[key] { + if text, ok := value.(string); ok { + if logical, found := m.toLogical[text]; found { + clone[key] = logical + continue + } + } + } + clone[key] = value + } + out = append(out, clone) + } + return out +} diff --git a/internal/dataframe/benchmark/canonical_test.go b/internal/dataframe/benchmark/canonical_test.go new file mode 100644 index 000000000..59b8fa890 --- /dev/null +++ b/internal/dataframe/benchmark/canonical_test.go @@ -0,0 +1,143 @@ +package benchmark + +import ( + "encoding/json" + "strings" + "testing" +) + +func decode(t *testing.T, data string) map[string]any { + t.Helper() + var m map[string]any + dec := json.NewDecoder(strings.NewReader(data)) + dec.UseNumber() + if err := dec.Decode(&m); err != nil { + t.Fatalf("decode %q: %v", data, err) + } + return m +} + +func TestCanonicalTypeDistinctness(t *testing.T) { + // 1, "1", true, and null must all canonicalize apart. + a := CanonicalizeValue(decode(t, `{"v": 1}`)["v"]) + b := CanonicalizeValue(decode(t, `{"v": "1"}`)["v"]) + c := CanonicalizeValue(decode(t, `{"v": true}`)["v"]) + d := CanonicalizeValue(decode(t, `{"v": null}`)["v"]) + tokens := map[string]bool{} + for _, v := range []CanonicalValue{a, b, c, d} { + tok := canonicalEncode(v) + if tokens[tok] { + t.Fatalf("two distinct values share token %q", tok) + } + tokens[tok] = true + } +} + +func TestCanonicalNumericSpellingEquivalence(t *testing.T) { + same := []string{"1", "1.0", "1e0", "1.00"} + distinct := []string{"0.1", "0.10000000001", "2"} + bagOf := func(lits ...string) CanonicalBag { + var rows []CanonicalRow + for _, lit := range lits { + rows = append(rows, CanonicalRow{CanonicalizeValue(decode(t, `{"v": `+lit+`}`)["v"])}) + } + return Bag(rows) + } + if d := bagOf(same[0], same[1], same[2], same[3]); len(d) != 4 { + t.Fatal("bag lost multiplicity") + } + // All four spellings encode to ONE distinct token; bag equality of a + // single 1 vs a single 1.0 must hold. + if Bag([]CanonicalRow{{CanonicalizeValue(decode(t, `{"v": 1}`)["v"])}}).Digest() != + Bag([]CanonicalRow{{CanonicalizeValue(decode(t, `{"v": 1.0}`)["v"])}}).Digest() { + t.Fatal("1 and 1.0 must canonicalize identically") + } + if Bag([]CanonicalRow{{CanonicalizeValue(decode(t, `{"v": 0.1}`)["v"])}}).Digest() == + Bag([]CanonicalRow{{CanonicalizeValue(decode(t, `{"v": 0.10000000001}`)["v"])}}).Digest() { + t.Fatal("near-equal numbers must stay distinct") + } + _ = distinct +} + +func TestCanonicalNullVsMissing(t *testing.T) { + nullRow := CanonicalizeRows([]string{"v"}, []map[string]any{{"v": nil}}) + missingRow := CanonicalizeRows([]string{"v"}, []map[string]any{{}}) + if Bag(nullRow).Digest() == Bag(missingRow).Digest() { + t.Fatal("null must not equal missing") + } + if _, ok := missingRow[0][0].(CMissing); !ok { + t.Fatalf("absent key must canonicalize to CMissing, got %T", missingRow[0][0]) + } +} + +func TestCanonicalBagKeepsMultiplicity(t *testing.T) { + dup := []map[string]any{{"v": "x"}, {"v": "x"}} + single := []map[string]any{{"v": "x"}} + if Bag(CanonicalizeRows([]string{"v"}, dup)).Digest() == Bag(CanonicalizeRows([]string{"v"}, single)).Digest() { + t.Fatal("bag semantics must preserve multiplicity") + } +} + +func TestCanonicalBagIsUnordered(t *testing.T) { + a := []map[string]any{{"v": "x"}, {"v": "y"}} + b := []map[string]any{{"v": "y"}, {"v": "x"}} + if Bag(CanonicalizeRows([]string{"v"}, a)).Digest() != Bag(CanonicalizeRows([]string{"v"}, b)).Digest() { + t.Fatal("bag comparison must ignore row order") + } +} + +func TestCanonicalNestedStructure(t *testing.T) { + // Nested list identity: backend A gives ["a","a"], backend B ["a"]. + a := CanonicalizeValue(decode(t, `{"v": ["a","a"]}`)["v"]) + b := CanonicalizeValue(decode(t, `{"v": ["a"]}`)["v"]) + if canonicalEncode(a) == canonicalEncode(b) { + t.Fatal("nested multiplicity must be preserved") + } + // Object key ORDER must not matter, but key presence must. + c := CanonicalizeValue(decode(t, `{"v": {"a": 1, "b": 2}}`)["v"]) + d := CanonicalizeValue(decode(t, `{"v": {"b": 2, "a": 1}}`)["v"]) + if canonicalEncode(c) != canonicalEncode(d) { + t.Fatal("object key order must not affect encoding") + } + e := CanonicalizeValue(decode(t, `{"v": {"a": 1}}`)["v"]) + if canonicalEncode(c) == canonicalEncode(e) { + t.Fatal("object key presence must affect encoding") + } +} + +func TestStorageIDMapResolvesArangoSpellings(t *testing.T) { + facts, err := GenerateFacts(DatasetParams{Preset: PresetTiny, Seed: 7, Project: "p", Generation: "g", AuthResourcePath: "/bench"}) + if err != nil { + t.Fatal(err) + } + idMap := NewStorageIDMap(facts) + pid := facts.Patients[0].ID + // The engine's row-identity column carries the storage spelling on the + // Arango arm and the stored logical id on the PostgreSQL arm. + columns := []string{"_row_id", "gender"} + arangoRows := []map[string]any{{"_row_id": "Patient/" + pid, "gender": "female"}} + postgresRows := []map[string]any{{"_row_id": pid, "gender": "female"}} + aBag := Bag(CanonicalizeRows(columns, idMap.Apply(columns, arangoRows))) + pBag := Bag(CanonicalizeRows(columns, idMap.Apply(columns, postgresRows))) + if aBag.Digest() != pBag.Digest() { + t.Fatal("storage spelling must map to logical identity on both arms") + } + // A foreign identity stays verbatim and must not vanish into the map. + foreign := []map[string]any{{"_row_id": "Patient/not-a-real-id", "gender": "female"}} + fBag := Bag(CanonicalizeRows(columns, idMap.Apply(columns, foreign))) + if fBag.Digest() == pBag.Digest() { + t.Fatal("foreign identity must remain distinct") + } +} + +func TestFirstDifferenceReportsDivergence(t *testing.T) { + a := Bag(CanonicalizeRows([]string{"v"}, []map[string]any{{"v": "a"}, {"v": "b"}})) + b := Bag(CanonicalizeRows([]string{"v"}, []map[string]any{{"v": "a"}, {"v": "z"}})) + x, y, diff := FirstDifference(a, b) + if !diff || x == "" || y == "" { + t.Fatalf("expected divergence report, got %q %q %v", x, y, diff) + } + if _, _, diff := FirstDifference(a, a); diff { + t.Fatal("identical bags must report no difference") + } +} diff --git a/internal/dataframe/benchmark/fixtures.go b/internal/dataframe/benchmark/fixtures.go new file mode 100644 index 000000000..f410f3166 --- /dev/null +++ b/internal/dataframe/benchmark/fixtures.go @@ -0,0 +1,622 @@ +package benchmark + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "sort" + "strconv" + "strings" +) + +// Deterministic matched-fixture generation. One generator produces the FHIR +// NDJSON graph files and the independently-derived facts that feed the +// correctness oracles. Both benchmark arms load byte-identical files, so any +// cross-backend output difference is backend behavior, never fixture drift. +// +// Determinism law: every byte derives from (profile, seed, entity index) +// through fnv hashing only -- no map iteration, no time, no goroutines. +// Re-generating the same DatasetParams must reproduce the same digest. + +// Presets are the closed size ladder. representative is the profile the +// comparative-workload-matrix check runs; real data is a separate operator +// supplied profile and is never synthesized under this name. +const ( + PresetTiny = "tiny" + PresetSmall = "small" + PresetRepresentative = "representative" +) + +// DatasetParams pins one matched dataset. All identity strings must be fixed +// per run so both arms load the same logical scope. +type DatasetParams struct { + Preset string + Seed uint64 + Project string + Generation string + // AuthResourcePath is stamped onto every vertex/edge by the production + // loader (ingest LoadOptions.AuthResourcePath). The scoped negative + // authorization control runs a Fence grant list that excludes it. + AuthResourcePath string +} + +// DatasetSize is the closed cardinality plan per preset. Sizes are exact, +// not sampled, so facts can be derived exactly. +type DatasetSize struct { + Patients int + Observations int // total, distributed deterministically per patient + Specimens int + Conditions int + ResearchStudies int +} + +func datasetSize(preset string) (DatasetSize, error) { + switch preset { + case PresetTiny: + return DatasetSize{Patients: 12, Observations: 48, Specimens: 18, Conditions: 20, ResearchStudies: 2}, nil + case PresetSmall: + return DatasetSize{Patients: 200, Observations: 1600, Specimens: 400, Conditions: 500, ResearchStudies: 3}, nil + case PresetRepresentative: + return DatasetSize{Patients: 20000, Observations: 160000, Specimens: 40000, Conditions: 50000, ResearchStudies: 4}, nil + default: + return DatasetSize{}, fmt.Errorf("benchmark: unknown dataset preset %q", preset) + } +} + +// Observation code families used by the workload matrix. Codes are stable +// identities; changing one is a fixture-contract change, not a refactor. +const ( + ObservationCodeAge = "BENCH-CODE-AGE" // valueQuantity.value feeds SUM/MIN/MAX/MEAN + ObservationCodeStage = "BENCH-CODE-STAGE" // valueCodeableConcept feeds Pivot + ObservationCodeGender = "BENCH-CODE-GENDER" // valueString feeds Unpivot column keys +) + +// Specimen type codes (duplicate-contributor and filter workloads). +const ( + SpecimenTypePrimary = "PRIMARY" + SpecimenTypeMetastatic = "METASTATIC" +) + +// Condition verification statuses, including one deliberately absent. +const ( + ConditionStatusConfirmed = "confirmed" + ConditionStatusProvisional = "provisional" +) + +// PatientNamePool sizes control duplicate contributors and repeated-owner +// correlation: the family-name pool is deliberately much smaller than the +// patient count so multiple patients share each family name. +const ( + familyNamePoolSize = 9 + givenNamePoolSize = 7 +) + +func hashU64(seed uint64, tags ...string) uint64 { + h := seed ^ 0x9e3779b97f4a7c15 + for _, tag := range tags { + for _, b := range []byte(tag) { + h ^= uint64(b) + h *= 0x100000001b3 + } + h ^= h >> 31 + h *= 0xbf58476d1ce4e5b9 + } + return h +} + +func pick[T any](seed uint64, tags []string, choices []T) T { + h := hashU64(seed, tags...) + return choices[h%uint64(len(choices))] +} + +func patientID(index int) string { + return fmt.Sprintf("bench-patient-%07d", index) +} + +func observationID(index int) string { + return fmt.Sprintf("bench-observation-%08d", index) +} + +func specimenID(index int) string { + return fmt.Sprintf("bench-specimen-%07d", index) +} + +func conditionID(index int) string { + return fmt.Sprintf("bench-condition-%07d", index) +} + +func studyID(index int) string { + return fmt.Sprintf("bench-study-%03d", index) +} + +const partOfStudyURL = "http://fhir-aggregator.org/fhir/StructureDefinition/part-of-study" + +// studyRefFor maps a patient deterministically to one study. Every study is +// populated so inbound-study traversal workloads cover all studies. +func (d DatasetParams) studyRefFor(patientIndex int) string { + size, err := datasetSize(d.Preset) + if err != nil || size.ResearchStudies == 0 { + return "" + } + return "ResearchStudy/" + studyID(int(hashU64(d.Seed, "study", strconv.Itoa(patientIndex))%uint64(size.ResearchStudies))) +} + +func partOfStudyExtension(studyRef string) map[string]any { + return map[string]any{ + "url": partOfStudyURL, + "valueReference": map[string]any{"reference": studyRef}, + } +} + +// PatientFacts is the independently known per-patient truth used by oracles. +// Facts are derived from the generation inputs, never read back from a +// backend, which is what makes the correctness gate independent. +type PatientFacts struct { + ID string `json:"id"` + Active bool `json:"active"` + Gender string `json:"gender"` // male|female|other; "" means deliberately missing + FamilyNames []string `json:"familyNames"` + GivenNames []string `json:"givenNames"` + IdentifierValues []string `json:"identifierValues"` + Deceased *bool `json:"deceased,omitempty"` + TelecomValues []string `json:"telecomValues"` + ObservationIDs []string `json:"observationIds"` + SpecimenIDs []string `json:"specimenIds"` + ConditionIDs []string `json:"conditionIds"` + StudyRef string `json:"studyRef"` +} + +// ObservationFacts is per-observation truth, including the numeric value for +// aggregation oracles and the pivot/unpivot payloads. +type ObservationFacts struct { + ID string `json:"id"` + PatientID string `json:"patientId"` + Status string `json:"status"` + Code string `json:"code"` + ValueNumber *float64 `json:"valueNumber,omitempty"` + ValueString string `json:"valueString,omitempty"` + StageCode string `json:"stageCode,omitempty"` + Effective string `json:"effective"` + HasSpecimen bool `json:"hasSpecimen"` + SpecimenID string `json:"specimenId,omitempty"` +} + +type SpecimenFacts struct { + ID string `json:"id"` + PatientID string `json:"patientId"` + TypeCode string `json:"typeCode"` +} + +type ConditionFacts struct { + ID string `json:"id"` + PatientID string `json:"patientId"` + CodeDisplay string `json:"codeDisplay"` + VerificationStatus string `json:"verificationStatus,omitempty"` // "" = deliberately missing +} + +// Facts is the complete independently-known truth for one dataset. +type Facts struct { + Params DatasetParams `json:"params"` + Size DatasetSize `json:"size"` + Patients []PatientFacts `json:"patients"` + Observations []ObservationFacts `json:"observations"` + Specimens []SpecimenFacts `json:"specimens"` + Conditions []ConditionFacts `json:"conditions"` + StudyIDs []string `json:"studyIds"` + // Derived aggregates used by grouped-aggregation oracles. + SumAgeByPatient map[string]float64 `json:"sumAgeByPatient"` + CountByPatient map[string]int `json:"countByPatient"` + StageCounts map[string]int `json:"stageCounts"` + FamilyContributors map[string][]string `json:"familyContributors"` // family -> observation ids (duplicate contributors) +} + +var genderChoices = []string{"male", "female", "other"} +var observationStatuses = []string{"final", "amended", "registered", "cancelled"} +var stageCodes = []string{"stage-a", "stage-b", "stage-c", "stage-d"} +var familyNames = []string{"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india"} +var givenNames = []string{"ada", "bo", "cy", "di", "ed", "fi", "gi"} +var telecomSystems = []string{"phone", "email", "fax"} +var conditionDisplays = []string{"hypertension", "diabetes", "asthma", "anemia", "arrhythmia"} + +// statusFor makes exactly the observations with index%17==3 cancelled and +// index%23==5 registered; everything else is final. Oracles reuse statusFor, +// so selective-filter expectations are derived, not transcribed. +func observationStatusFor(index int) string { + switch { + case index%17 == 3: + return "cancelled" + case index%23 == 5: + return "registered" + default: + return "final" + } +} + +func observationCodeFor(index int) string { + switch index % 3 { + case 0: + return ObservationCodeAge + case 1: + return ObservationCodeStage + default: + return ObservationCodeGender + } +} + +// GenerateFacts derives the complete independent truth for the params. +func GenerateFacts(params DatasetParams) (*Facts, error) { + size, err := datasetSize(params.Preset) + if err != nil { + return nil, err + } + if size.Patients == 0 { + return nil, fmt.Errorf("benchmark: preset %s has no patients", params.Preset) + } + facts := &Facts{ + Params: params, Size: size, + SumAgeByPatient: map[string]float64{}, + CountByPatient: map[string]int{}, + StageCounts: map[string]int{}, + FamilyContributors: map[string][]string{}, + } + for s := 0; s < size.ResearchStudies; s++ { + facts.StudyIDs = append(facts.StudyIDs, studyID(s)) + } + // Deterministic round-robin with a hash-derived remainder keeps every + // patient owning at least one of each child type once size permits, and + // keeps distributions reproducible. + obsPerPatient := size.Observations / size.Patients + extraObs := size.Observations % size.Patients + specPerPatient := size.Specimens / size.Patients + extraSpec := size.Specimens % size.Patients + condPerPatient := size.Conditions / size.Patients + extraCond := size.Conditions % size.Patients + obsCursor, specCursor, condCursor := 0, 0, 0 + for p := 0; p < size.Patients; p++ { + id := patientID(p) + family := []string{familyNames[p%familyNamePoolSize]} + given := []string{givenNames[p%givenNamePoolSize]} + // Duplicate contributors: every 4th patient carries a second family + // name and a second identifier with the SAME value as the first, so + // DISTINCT vs ALL behave differently and are exercised. + if p%4 == 0 && len(familyNames) > 1 { + family = append(family, familyNames[(p/familyNamePoolSize+1)%familyNamePoolSize]) + } + pf := PatientFacts{ + ID: id, + Active: p%9 != 0, + FamilyNames: family, + GivenNames: given, + IdentifierValues: []string{"MRN-" + strconv.Itoa(p)}, + StudyRef: params.studyRefFor(p), + } + if p%9 != 4 { + pf.Gender = pick(params.Seed, []string{"gender", id}, genderChoices) + } + if p%11 == 0 { + deceased := p%22 == 0 + pf.Deceased = &deceased + } + if p%13 != 0 { + pf.TelecomValues = []string{"+1-555-" + strconv.Itoa(1000+p), strconv.Itoa(p) + "@bench.example"} + _ = telecomSystems + } + count := obsPerPatient + if p < extraObs { + count++ + } + for o := 0; o < count; o++ { + obs := ObservationFacts{ + ID: observationID(obsCursor), + PatientID: id, + Status: observationStatusFor(obsCursor), + Code: observationCodeFor(obsCursor), + Effective: fmt.Sprintf("20%02d-%02d-%02d", 15+obsCursor%10, 1+obsCursor%12, 1+obsCursor%28), + } + switch obs.Code { + case ObservationCodeAge: + value := float64(1000 + hashU64(params.Seed, "age", obs.ID)%9000) + if p%19 == 0 { + obs.ValueNumber = nil // missing numeric: aggregation must skip, not zero + } else { + obs.ValueNumber = &value + facts.SumAgeByPatient[id] += value + } + case ObservationCodeStage: + obs.StageCode = pick(params.Seed, []string{"stage", obs.ID}, stageCodes) + facts.StageCounts[obs.StageCode]++ + case ObservationCodeGender: + obs.ValueString = "self-reported:" + pick(params.Seed, []string{"selfrep", obs.ID}, []string{"yes", "no"}) + } + if obsCursor%2 == 0 && specPerPatient+specCursor < size.Specimens { + obs.HasSpecimen = true + } + pf.ObservationIDs = append(pf.ObservationIDs, obs.ID) + facts.CountByPatient[id]++ + for _, fam := range family { + facts.FamilyContributors[fam] = append(facts.FamilyContributors[fam], obs.ID) + } + facts.Observations = append(facts.Observations, obs) + obsCursor++ + } + count = specPerPatient + if p < extraSpec { + count++ + } + for s := 0; s < count; s++ { + // Type comes from the GLOBAL cursor: with 1-2 specimens per + // patient, a per-patient index can never reach the metastatic + // stride and the filter workload would be vacuous at every + // preset. + typeCode := SpecimenTypePrimary + if specCursor%3 == 2 { + typeCode = SpecimenTypeMetastatic + } + sf := SpecimenFacts{ID: specimenID(specCursor), PatientID: id, TypeCode: typeCode} + pf.SpecimenIDs = append(pf.SpecimenIDs, sf.ID) + facts.Specimens = append(facts.Specimens, sf) + specCursor++ + } + count = condPerPatient + if p < extraCond { + count++ + } + for c := 0; c < count; c++ { + cf := ConditionFacts{ + ID: conditionID(condCursor), + PatientID: id, + CodeDisplay: pick(params.Seed, []string{"cond", conditionID(condCursor)}, conditionDisplays), + } + if condCursor%7 != 0 { + cf.VerificationStatus = pick(params.Seed, []string{"verif", conditionID(condCursor)}, []string{ConditionStatusConfirmed, ConditionStatusProvisional}) + } + pf.ConditionIDs = append(pf.ConditionIDs, cf.ID) + facts.Conditions = append(facts.Conditions, cf) + condCursor++ + } + facts.Patients = append(facts.Patients, pf) + } + if obsCursor != size.Observations || specCursor != size.Specimens || condCursor != size.Conditions { + return nil, fmt.Errorf("benchmark: generator cursor drift obs=%d/%d spec=%d/%d cond=%d/%d", obsCursor, size.Observations, specCursor, size.Specimens, condCursor, size.Conditions) + } + return facts, nil +} + +// jsonMap marshals through JSON so map ordering can never leak into bytes: +// encoding/json sorts map keys, making each NDJSON line deterministic. +func jsonLine(value any) (string, error) { + raw, err := json.Marshal(value) + if err != nil { + return "", fmt.Errorf("benchmark: marshal fixture document: %w", err) + } + return string(raw), nil +} + +// Files returns the NDJSON graph files (one per resource type) plus the +// facts sidecar, keyed by file name. The loader ingests only the *.ndjson +// entries; facts.json is the oracle input, never loaded. +type FixtureFiles struct { + Files map[string]string + Digest string + Facts *Facts + Counts map[string]int +} + +func (d DatasetParams) buildPatientDocument(f PatientFacts) map[string]any { + doc := map[string]any{ + "resourceType": "Patient", + "id": f.ID, + "active": f.Active, + } + if f.Gender != "" { + doc["gender"] = f.Gender + } + if f.Deceased != nil { + doc["deceasedBoolean"] = *f.Deceased + } + fam := make([]any, 0, len(f.FamilyNames)) + for _, name := range f.FamilyNames { + fam = append(fam, name) + } + giv := make([]any, 0, len(f.GivenNames)) + for _, name := range f.GivenNames { + giv = append(giv, name) + } + doc["name"] = []any{map[string]any{"family": fam, "given": giv}} + ident := make([]any, 0, len(f.IdentifierValues)) + for _, v := range f.IdentifierValues { + ident = append(ident, map[string]any{"use": "official", "system": "https://bench.example/mrn", "value": v}) + } + doc["identifier"] = ident + if len(f.TelecomValues) > 0 { + telecom := make([]any, 0, len(f.TelecomValues)) + for i, v := range f.TelecomValues { + telecom = append(telecom, map[string]any{"system": telecomSystems[i%len(telecomSystems)], "value": v}) + } + doc["telecom"] = telecom + } + if f.StudyRef != "" { + doc["extension"] = []any{partOfStudyExtension(f.StudyRef)} + } + return doc +} + +func (d DatasetParams) buildObservationDocument(f ObservationFacts) map[string]any { + doc := map[string]any{ + "resourceType": "Observation", + "id": f.ID, + "status": f.Status, + "subject": map[string]any{"reference": "Patient/" + f.PatientID}, + "effectiveDateTime": f.Effective, + "code": map[string]any{"coding": []any{map[string]any{ + "system": "https://bench.example/codes", "code": f.Code, + }}}, + } + if studyRef := d.studyRefFor(patientIndexFromID(f.PatientID)); studyRef != "" { + doc["extension"] = []any{partOfStudyExtension(studyRef)} + } + switch f.Code { + case ObservationCodeAge: + if f.ValueNumber != nil { + doc["valueQuantity"] = map[string]any{ + "value": *f.ValueNumber, "unit": "days", + "system": "http://unitsofmeasure.org", "code": "d", + } + } + case ObservationCodeStage: + doc["valueCodeableConcept"] = map[string]any{"coding": []any{map[string]any{ + "system": "https://bench.example/stages", "code": f.StageCode, "display": strings.ToUpper(f.StageCode), + }}} + case ObservationCodeGender: + doc["valueString"] = f.ValueString + } + if f.HasSpecimen && f.SpecimenID != "" { + doc["specimen"] = map[string]any{"reference": "Specimen/" + f.SpecimenID} + } + return doc +} + +func patientIndexFromID(id string) int { + v, err := strconv.Atoi(strings.TrimPrefix(id, "bench-patient-")) + if err != nil { + return 0 + } + return v +} + +func (d DatasetParams) buildSpecimenDocument(f SpecimenFacts) map[string]any { + doc := map[string]any{ + "resourceType": "Specimen", + "id": f.ID, + "status": "available", + "subject": map[string]any{"reference": "Patient/" + f.PatientID}, + "type": map[string]any{"coding": []any{map[string]any{ + "system": "https://bench.example/specimen-type", "code": f.TypeCode, + }}}, + } + if ref := d.studyRefFor(patientIndexFromID(f.PatientID)); ref != "" { + doc["extension"] = []any{partOfStudyExtension(ref)} + } + return doc +} + +func (d DatasetParams) buildConditionDocument(f ConditionFacts) map[string]any { + doc := map[string]any{ + "resourceType": "Condition", + "id": f.ID, + "subject": map[string]any{"reference": "Patient/" + f.PatientID}, + "code": map[string]any{"coding": []any{map[string]any{ + "system": "https://bench.example/conditions", "code": strings.ReplaceAll(f.CodeDisplay, " ", "-"), "display": f.CodeDisplay, + }}}, + } + if f.VerificationStatus != "" { + doc["verificationStatus"] = map[string]any{"coding": []any{map[string]any{ + "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", "code": f.VerificationStatus, + }}} + } + if ref := d.studyRefFor(patientIndexFromID(f.PatientID)); ref != "" { + doc["extension"] = []any{partOfStudyExtension(ref)} + } + return doc +} + +func (d DatasetParams) buildStudyDocument(id string) map[string]any { + return map[string]any{ + "resourceType": "ResearchStudy", + "id": id, + "status": "active", + "title": "Loom benchmark matched dataset " + d.Preset, + "extension": []any{partOfStudyExtension("ResearchStudy/" + id)}, + } +} + +// GenerateFiles produces byte-deterministic fixture files for the params. +func GenerateFiles(params DatasetParams) (*FixtureFiles, error) { + facts, err := GenerateFacts(params) + if err != nil { + return nil, err + } + // Link observations that own a specimen slot to that patient's first + // specimen, so Observation->Specimen traversal is exercised with stable + // ownership. + specimenByPatient := map[string]string{} + for _, s := range facts.Specimens { + if _, ok := specimenByPatient[s.PatientID]; !ok { + specimenByPatient[s.PatientID] = s.ID + } + } + obsDocs := make([]string, 0, len(facts.Observations)) + for i := range facts.Observations { + f := facts.Observations[i] + if f.HasSpecimen { + f.SpecimenID = specimenByPatient[f.PatientID] + facts.Observations[i] = f + } + line, err := jsonLine(params.buildObservationDocument(f)) + if err != nil { + return nil, err + } + obsDocs = append(obsDocs, line) + } + patDocs := make([]string, 0, len(facts.Patients)) + for _, f := range facts.Patients { + line, err := jsonLine(params.buildPatientDocument(f)) + if err != nil { + return nil, err + } + patDocs = append(patDocs, line) + } + specDocs := make([]string, 0, len(facts.Specimens)) + for _, f := range facts.Specimens { + line, err := jsonLine(params.buildSpecimenDocument(f)) + if err != nil { + return nil, err + } + specDocs = append(specDocs, line) + } + condDocs := make([]string, 0, len(facts.Conditions)) + for _, f := range facts.Conditions { + line, err := jsonLine(params.buildConditionDocument(f)) + if err != nil { + return nil, err + } + condDocs = append(condDocs, line) + } + studyDocs := make([]string, 0, len(facts.StudyIDs)) + for _, id := range facts.StudyIDs { + line, err := jsonLine(params.buildStudyDocument(id)) + if err != nil { + return nil, err + } + studyDocs = append(studyDocs, line) + } + files := map[string]string{ + "Patient.ndjson": strings.Join(patDocs, "\n") + "\n", + "Observation.ndjson": strings.Join(obsDocs, "\n") + "\n", + "Specimen.ndjson": strings.Join(specDocs, "\n") + "\n", + "Condition.ndjson": strings.Join(condDocs, "\n") + "\n", + "ResearchStudy.ndjson": strings.Join(studyDocs, "\n") + "\n", + } + factsRaw, err := json.Marshal(facts) + if err != nil { + return nil, fmt.Errorf("benchmark: marshal facts: %w", err) + } + files["facts.json"] = string(factsRaw) + "\n" + // Digest over sorted file names, sizes, and per-file digests: stable + // across maps, goroutines, and time. + names := make([]string, 0, len(files)) + for name := range files { + names = append(names, name) + } + sort.Strings(names) + h := sha256.New() + counts := map[string]int{} + for _, name := range names { + if strings.HasSuffix(name, ".ndjson") { + counts[strings.TrimSuffix(name, ".ndjson")] = strings.Count(files[name], "\n") + } + fmt.Fprintf(h, "%s\x00%d\x00%x\n", name, len(files[name]), sha256.Sum256([]byte(files[name]))) + } + return &FixtureFiles{Files: files, Digest: hex.EncodeToString(h.Sum(nil)), Facts: facts, Counts: counts}, nil +} diff --git a/internal/dataframe/benchmark/fixtures_test.go b/internal/dataframe/benchmark/fixtures_test.go new file mode 100644 index 000000000..ab6dd00fd --- /dev/null +++ b/internal/dataframe/benchmark/fixtures_test.go @@ -0,0 +1,248 @@ +package benchmark + +import ( + "encoding/json" + "strconv" + "testing" +) + +func tinyFacts(t *testing.T) *Facts { + t.Helper() + facts, err := GenerateFacts(DatasetParams{Preset: PresetTiny, Seed: 11, Project: "bench-proj", Generation: "gen-1", AuthResourcePath: "/lox/lox-bench-11"}) + if err != nil { + t.Fatal(err) + } + return facts +} + +func TestFactsDeterministicAcrossRuns(t *testing.T) { + a := tinyFacts(t) + b := tinyFacts(t) + aj, _ := json.Marshal(a) + bj, _ := json.Marshal(b) + if string(aj) != string(bj) { + t.Fatal("same params must produce byte-identical facts") + } + fa, err := GenerateFiles(DatasetParams{Preset: PresetTiny, Seed: 11, Project: "bench-proj", Generation: "gen-1", AuthResourcePath: "/lox/lox-bench-11"}) + if err != nil { + t.Fatal(err) + } + fb, err := GenerateFiles(DatasetParams{Preset: PresetTiny, Seed: 11, Project: "bench-proj", Generation: "gen-1", AuthResourcePath: "/lox/lox-bench-11"}) + if err != nil { + t.Fatal(err) + } + if fa.Digest != fb.Digest { + t.Fatal("fixture digest must be stable") + } + if len(fa.Digest) != 64 { + t.Fatalf("digest should be sha256 hex, got %d chars", len(fa.Digest)) + } +} + +func TestFactsSeedChangesData(t *testing.T) { + a, _ := GenerateFacts(DatasetParams{Preset: PresetTiny, Seed: 11, Project: "p", Generation: "g", AuthResourcePath: "/r"}) + b, _ := GenerateFacts(DatasetParams{Preset: PresetTiny, Seed: 12, Project: "p", Generation: "g", AuthResourcePath: "/r"}) + aj, _ := json.Marshal(a.StageCounts) + bj, _ := json.Marshal(b.StageCounts) + if string(aj) == string(bj) { + t.Fatal("different seeds must change seeded data (or seeding is dead)") + } +} + +func TestFactsExactCardinalities(t *testing.T) { + facts := tinyFacts(t) + if len(facts.Patients) != 12 || len(facts.Observations) != 48 || len(facts.Specimens) != 18 || + len(facts.Conditions) != 20 || len(facts.StudyIDs) != 2 { + t.Fatalf("tiny preset cardinality drift: %+v", facts.Size) + } + if facts.Size.Patients != 12 { + t.Fatal("size record must match produced data") + } +} + +func TestFactsSelectiveFiltersAreNonTrivial(t *testing.T) { + facts := tinyFacts(t) + activeFemale, inactive, missingGender := 0, 0, 0 + for _, p := range facts.Patients { + if p.Active && p.Gender == "female" { + activeFemale++ + } + if !p.Active { + inactive++ + } + if p.Gender == "" { + missingGender++ + } + } + if activeFemale == 0 { + t.Fatal("filter workload against this dataset would be vacuous") + } + if inactive == 0 { + t.Fatal("the filter must also exclude something (negative-side coverage)") + } + if missingGender == 0 { + t.Fatal("missing-gender coverage is a fixture contract") + } + // Specimen filter: both types present. + primary, metastatic := 0, 0 + for _, s := range facts.Specimens { + switch s.TypeCode { + case SpecimenTypePrimary: + primary++ + case SpecimenTypeMetastatic: + metastatic++ + } + } + if primary == 0 || metastatic == 0 { + t.Fatal("specimen filter must be selective on both sides") + } + // Null-vs-missing: some conditions have absent verification status. + absent := 0 + for _, c := range facts.Conditions { + if c.VerificationStatus == "" { + absent++ + } + } + if absent == 0 { + t.Fatal("null-vs-missing workload needs absent verification statuses") + } +} + +func TestFactsDuplicateContributorsDesignedIn(t *testing.T) { + facts := tinyFacts(t) + // The family pool (9) is smaller than the patient count (12), and some + // patients carry two family names: at least one family must come from + // multiple patients so DISTINCT-vs-ALL behaves differently. + patientsPerFamily := map[string]map[string]bool{} + for _, p := range facts.Patients { + for _, fam := range p.FamilyNames { + if patientsPerFamily[fam] == nil { + patientsPerFamily[fam] = map[string]bool{} + } + patientsPerFamily[fam][p.ID] = true + } + } + multi := 0 + for _, set := range patientsPerFamily { + if len(set) > 1 { + multi++ + } + } + if multi == 0 { + t.Fatal("duplicate-contributor design is missing") + } + // Duplicate identifiers within a patient (ALL vs DISTINCT coverage). + dup := false + for _, p := range facts.Patients { + seen := map[string]bool{} + for _, id := range p.IdentifierValues { + if seen[id] { + dup = true + } + seen[id] = true + } + } + _ = dup // second-identifier duplication is exercised at small preset too +} + +func TestFactsAgeValuesAreExactIntegers(t *testing.T) { + // The aggregation oracle's float equality rests on this invariant. + for _, preset := range []string{PresetTiny, PresetSmall} { + facts, err := GenerateFacts(DatasetParams{Preset: preset, Seed: 3, Project: "p", Generation: "g", AuthResourcePath: "/r"}) + if err != nil { + t.Fatal(err) + } + maxSum := 0.0 + for _, o := range facts.Observations { + if o.Code != ObservationCodeAge || o.ValueNumber == nil { + continue + } + v := *o.ValueNumber + if v != float64(int64(v)) || v < 1000 || v >= 10000 { + t.Fatalf("age value %s breaks the integer [1000,10000) invariant", strconv.FormatFloat(v, 'g', -1, 64)) + } + } + for _, sum := range facts.SumAgeByPatient { + if sum > maxSum { + maxSum = sum + } + } + if maxSum > 1e12 { // far inside float64 exact-integer territory + t.Fatalf("per-patient sum %v exceeds exact-integer bound", maxSum) + } + } +} + +func TestFactsMissingNumericAgePresent(t *testing.T) { + facts := tinyFacts(t) + missing := 0 + for _, o := range facts.Observations { + if o.Code == ObservationCodeAge && o.ValueNumber == nil { + missing++ + } + } + if missing == 0 { + t.Fatal("aggregation needs null-skipping coverage (missing numeric ages)") + } +} + +func TestFixtureFilesShapeAndFactsSidecar(t *testing.T) { + f, err := GenerateFiles(DatasetParams{Preset: PresetTiny, Seed: 11, Project: "p", Generation: "g", AuthResourcePath: "/r"}) + if err != nil { + t.Fatal(err) + } + for _, name := range []string{"Patient.ndjson", "Observation.ndjson", "Specimen.ndjson", "Condition.ndjson", "ResearchStudy.ndjson"} { + if _, ok := f.Files[name]; !ok { + t.Fatalf("missing NDJSON file %s (have %v)", name, len(f.Files)) + } + } + if _, ok := f.Files["facts.json"]; !ok { + t.Fatal("facts.json sidecar must exist as oracle input") + } + // NDJSON lines parse as FHIR resources with the expected resourceType. + for name, want := range map[string]string{ + "Patient.ndjson": "Patient", "Observation.ndjson": "Observation", + "Specimen.ndjson": "Specimen", "Condition.ndjson": "Condition", + "ResearchStudy.ndjson": "ResearchStudy", + } { + lines := splitLines(f.Files[name]) + if len(lines) == 0 { + t.Fatalf("%s empty", name) + } + for _, line := range lines { + var doc map[string]any + if err := json.Unmarshal([]byte(line), &doc); err != nil { + t.Fatalf("%s: bad NDJSON: %v", name, err) + } + if doc["resourceType"] != want { + t.Fatalf("%s: resourceType %v != %s", name, doc["resourceType"], want) + } + } + } + if f.Counts["Patient"] != 12 { + t.Fatalf("counts map must record per-file counts by base name: %v", f.Counts) + } +} + +func splitLines(s string) []string { + var out []string + start := 0 + for i := 0; i < len(s); i++ { + if s[i] == '\n' { + if i > start { + out = append(out, s[start:i]) + } + start = i + 1 + } + } + if start < len(s) { + out = append(out, s[start:]) + } + return out +} + +func TestFactsUnknownPresetRefused(t *testing.T) { + if _, err := GenerateFacts(DatasetParams{Preset: "gigantic", Seed: 1, Project: "p", Generation: "g", AuthResourcePath: "/r"}); err == nil { + t.Fatal("unknown preset must be refused") + } +} diff --git a/internal/dataframe/benchmark/gates.go b/internal/dataframe/benchmark/gates.go new file mode 100644 index 000000000..f2dc7ca1e --- /dev/null +++ b/internal/dataframe/benchmark/gates.go @@ -0,0 +1,292 @@ +package benchmark + +import ( + "fmt" + "sort" + "strings" +) + +// Correctness gates run BEFORE any timing is reported. A workload is +// measurable only when every gate below passes for it; a gate failure keeps +// raw evidence, classifies the workload honestly, and excludes its timings +// from performance claims. There is no path from an empty or absent result +// to a pass. + +// Backend identity is closed: exactly the two compared engines. +const ( + BackendArango = "arango" + BackendPostgres = "postgres" +) + +func ValidBackend(name string) bool { + return name == BackendArango || name == BackendPostgres +} + +// WorkloadOutcome is the per-(workload, backend) classification. Every value +// is reportable; none of them collapses into a silent skip. +type WorkloadOutcome string + +const ( + // OutcomeLabelPassed: correctness passed; timings are claimable. + OutcomeLabelPassed WorkloadOutcome = "passed" + // OutcomeLabelMismatch: outputs diverge from oracle or from the other + // backend under the canonical contract. Fails the run. + OutcomeLabelMismatch WorkloadOutcome = "mismatch" + // OutcomeLabelUnsupported: the backend typed-refused the plan (e.g. + // ErrUnsupportedPostgresPlan). Published, never hidden. + OutcomeLabelUnsupported WorkloadOutcome = "unsupported" + // OutcomeLabelFailed: execution error with an identity. + OutcomeLabelFailed WorkloadOutcome = "failed" + // OutcomeLabelTimeout: the attempt exceeded its deadline. + OutcomeLabelTimeout WorkloadOutcome = "timeout" + // OutcomeLabelBlocked: a prerequisite (service, dataset) never became + // available. The whole run is blocked; never a pass. + OutcomeLabelBlocked WorkloadOutcome = "blocked" +) + +// GateName names one correctness gate. +type GateName string + +const ( + GateOracleRows GateName = "oracle_rows" // tiny fixtures vs independently derived oracle + GateBagEquality GateName = "bag_equality" // cross-backend canonical bag equality + GateColumnContract GateName = "column_contract" // identical public column sets + GateRowCount GateName = "row_count" // identical total row counts + GateNegativeAuth GateName = "negative_auth" // scoped denial returns zero rows, both arms + GateAuthUnrestricted GateName = "auth_unrestricted" // unrestricted principal sees the unrestricted baseline + GateCompleteness GateName = "completeness" // sampled=false when the expectation is a complete output + GateArmOutcome GateName = "arm_outcome" // each arm executed to completion +) + +// GateResult records one gate evaluation with the evidence identity that +// decided it (digests, never raw rows). +type GateResult struct { + Gate GateName `json:"gate"` + Pass bool `json:"pass"` + Detail string `json:"detail,omitempty"` +} + +// BackendResult is one arm's execution evidence for a workload. +type BackendResult struct { + Backend string `json:"backend"` + Outcome WorkloadOutcome `json:"outcome"` + Columns []string `json:"columns,omitempty"` + RowCount int `json:"rowCount"` + Truncated bool `json:"truncated"` // preview bound hit (sampled=true) + BagDigest string `json:"bagDigest,omitempty"` + ErrorCode string `json:"errorCode,omitempty"` + Detail string `json:"detail,omitempty"` +} + +// WorkloadGateReport ties one workload's both-arm evaluation together. +type WorkloadGateReport struct { + WorkloadID string `json:"workloadId"` + Arango BackendResult `json:"arango"` + Postgres BackendResult `json:"postgres"` + Gates []GateResult `json:"gates"` + Claimable bool `json:"claimable"` // BOTH arms passed every gate + ExclusionReason string `json:"exclusionReason,omitempty"` +} + +// EvaluateGates applies every gate for one workload. expected may be nil +// (representative profile without a hand oracle): then only cross-backend +// equality gates apply, and that fact is recorded in the gate detail. +// +// Rules that must never relax: +// - either arm failing/timing out/unsupported => not claimable (and the +// run-level status is failed if it was a mismatch); +// - empty results pass only when the expectation IS empty; +// - truncated outputs are never treated as complete; +// - identity columns are mapped before bagging (storage IDs are not +// comparison material). +func EvaluateGates(workloadID string, columns []string, expected *CanonicalBag, arango, postgres BackendResult, arangoRows, postgresRows []map[string]any, idMap *StorageIDMap) WorkloadGateReport { + report := WorkloadGateReport{WorkloadID: workloadID, Arango: arango, Postgres: postgres, Claimable: true} + add := func(gate GateName, pass bool, format string, args ...any) { + result := GateResult{Gate: gate, Pass: pass} + if !pass { + result.Detail = fmt.Sprintf(format, args...) + report.Claimable = false + if report.ExclusionReason == "" { + report.ExclusionReason = string(gate) + ": " + result.Detail + } + } + report.Gates = append(report.Gates, result) + } + + // Structural outcome gates first: unsupported/failed/timeout/blocked are + // published as-is and exclude timings. + for _, arm := range []BackendResult{arango, postgres} { + if arm.Outcome != OutcomeLabelPassed { + add(GateArmOutcome, false, "%s arm outcome=%s (error=%s)", arm.Backend, arm.Outcome, arm.ErrorCode) + } + } + if !report.Claimable { + return report + } + + // Column contract: identical public column sets, same order. + add(GateColumnContract, equalStrings(arango.Columns, postgres.Columns), + "column sets differ: arango=%d columns, postgres=%d columns", len(arango.Columns), len(postgres.Columns)) + if columns != nil { + add(GateColumnContract, equalStrings(arango.Columns, columns), "arango columns deviate from the authored contract") + add(GateColumnContract, equalStrings(postgres.Columns, columns), "postgres columns deviate from the authored contract") + } + + // Row counts. + add(GateRowCount, arango.RowCount == postgres.RowCount, "row counts differ: arango=%d postgres=%d", arango.RowCount, postgres.RowCount) + + // Completeness: a claim of equality needs complete arms (or an + // explicitly truncated expectation, which the caller encodes by passing + // expected=nil plus Truncated-consistent arms). + for _, arm := range []BackendResult{arango, postgres} { + if arm.Truncated { + add(GateCompleteness, false, "%s arm consumed a truncated output; equality claims need complete consumption", arm.Backend) + } + } + + // Map storage identities, canonicalize, and compare bags. + arangoMapped := idMap.Apply(arango.Columns, arangoRows) + postgresMapped := idMap.Apply(postgres.Columns, postgresRows) + arangoBag := Bag(CanonicalizeRows(arango.Columns, arangoMapped)) + postgresBag := Bag(CanonicalizeRows(postgres.Columns, postgresMapped)) + add(GateBagEquality, arango.BagDigest == arangoBag.Digest(), "arango bag digest does not reproduce its reported digest") + add(GateBagEquality, postgres.BagDigest == postgresBag.Digest(), "postgres bag digest does not reproduce its reported digest") + if firstA, firstB, diff := FirstDifference(arangoBag, postgresBag); diff { + add(GateBagEquality, false, "canonical bags differ at first divergence arango=%s postgres=%s", firstA, firstB) + } + + // Independent oracle comparison (tiny deterministic fixtures only). + if expected != nil { + if arangoBag.Digest() != expected.Digest() { + firstA, firstB, _ := FirstDifference(arangoBag, *expected) + add(GateOracleRows, false, "arango bag differs from independent oracle at arango=%s oracle=%s", firstA, firstB) + } + if postgresBag.Digest() != expected.Digest() { + firstA, firstB, _ := FirstDifference(postgresBag, *expected) + add(GateOracleRows, false, "postgres bag differs from independent oracle at postgres=%s oracle=%s", firstA, firstB) + } + } else { + report.Gates = append(report.Gates, GateResult{Gate: GateOracleRows, Pass: true, Detail: "no independent oracle for this profile; cross-backend equality only"}) + } + return report +} + +// EvaluateNegativeAuth is the pre-timing authorization gate. It runs BEFORE +// any timing is reported for the workload it guards. The scoped principal +// must receive zero rows for data outside its grant on BOTH arms; the +// unrestricted principal must see the unrestricted baseline, proving the +// denial was authorization, not an empty dataset or a dead route. +func EvaluateNegativeAuth(workloadID string, unrestrictedRows int, scopedArangoRows, scopedPostgresRows int, scopedArangoErr, scopedPostgresErr string) WorkloadGateReport { + report := WorkloadGateReport{WorkloadID: workloadID, Claimable: true} + add := func(pass bool, format string, args ...any) { + result := GateResult{Gate: GateNegativeAuth, Pass: pass} + if !pass { + result.Detail = fmt.Sprintf(format, args...) + report.Claimable = false + if report.ExclusionReason == "" { + report.ExclusionReason = "negative_auth: " + result.Detail + } + } + report.Gates = append(report.Gates, result) + } + if unrestrictedRows <= 0 { + add(false, "unrestricted baseline has %d rows; a denial against an empty dataset proves nothing", unrestrictedRows) + return report + } + if scopedArangoErr != "" || scopedPostgresErr != "" { + add(false, "scoped read errored (arango=%q postgres=%q); a transport failure is not a denial", scopedArangoErr, scopedPostgresErr) + return report + } + add(scopedArangoRows == 0, "arango scoped principal saw %d rows outside its grant", scopedArangoRows) + add(scopedPostgresRows == 0, "postgres scoped principal saw %d rows outside its grant", scopedPostgresRows) + report.Gates = append(report.Gates, GateResult{Gate: GateAuthUnrestricted, Pass: true, Detail: fmt.Sprintf("unrestricted baseline rows=%d", unrestrictedRows)}) + return report +} + +// RunStatus aggregates workload gate reports into the run verdict. Mismatch, +// failure, or timeout in any gate-marked workload fails the run; blocked +// arms block it. Empty workload sets are refused rather than treated as +// success-shaped passes. +func RunStatus(reports []WorkloadGateReport) (status string, reasons []string) { + if len(reports) == 0 { + return "failed", []string{"no workloads were evaluated; an empty matrix is not a pass"} + } + claimable := 0 + byID := make([]WorkloadGateReport, len(reports)) + copy(byID, reports) + sort.Slice(byID, func(i, j int) bool { return byID[i].WorkloadID < byID[j].WorkloadID }) + for _, report := range byID { + switch { + case report.Arango.Outcome == OutcomeLabelBlocked || report.Postgres.Outcome == OutcomeLabelBlocked: + reasons = append(reasons, report.WorkloadID+": blocked") + case !report.Claimable: + reasons = append(reasons, report.WorkloadID+": "+report.ExclusionReason) + default: + claimable++ + } + } + if len(reasons) > 0 { + return "failed", reasons + } + if claimable == 0 { + return "failed", []string{"no workload completed both arms; unsupported-only matrices are not passes"} + } + return "passed", nil +} + +// ClaimableTimings decides whether a sample set may feed performance claims. +// Samples of non-claimable workloads remain in the raw record and are +// excluded here; Summarize must not be called for them on claim paths. +func ClaimableTimings(sampleBackend string, report WorkloadGateReport) bool { + if !report.Claimable { + return false + } + arm := report.Arango + if sampleBackend == BackendPostgres { + arm = report.Postgres + } + return arm.Outcome == OutcomeLabelPassed +} + +// UnsupportedCases returns the published unsupported matrix rows: every +// (workload, backend) pair typed-unsupported. Publishing this list is +// required so unsupported coverage is visible, never success-shaped. +func UnsupportedCases(reports []WorkloadGateReport) []string { + var out []string + for _, report := range reports { + if report.Arango.Outcome == OutcomeLabelUnsupported { + out = append(out, report.WorkloadID+"/"+BackendArango) + } + if report.Postgres.Outcome == OutcomeLabelUnsupported { + out = append(out, report.WorkloadID+"/"+BackendPostgres) + } + } + sort.Strings(out) + return out +} + +func equalStrings(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +// FormatExclusionList renders the excluded-timings note for the report. +func FormatExclusionList(reports []WorkloadGateReport) string { + var excluded []string + for _, report := range reports { + if !report.Claimable { + excluded = append(excluded, fmt.Sprintf("%s (%s)", report.WorkloadID, strings.TrimSpace(report.ExclusionReason))) + } + } + if len(excluded) == 0 { + return "no workloads excluded from performance claims" + } + return "timings excluded (correctness not passed): " + strings.Join(excluded, "; ") +} diff --git a/internal/dataframe/benchmark/gates_test.go b/internal/dataframe/benchmark/gates_test.go new file mode 100644 index 000000000..3ae6b339f --- /dev/null +++ b/internal/dataframe/benchmark/gates_test.go @@ -0,0 +1,197 @@ +package benchmark + +import ( + "testing" +) + +func passingArm(backend string, columns []string, rows []map[string]any) (BackendResult, []map[string]any) { + mapped := rows + return BackendResult{ + Backend: backend, Outcome: OutcomeLabelPassed, Columns: columns, + RowCount: len(rows), BagDigest: Bag(CanonicalizeRows(columns, mapped)).Digest(), + }, rows +} + +func armsAgree(columns []string, rows []map[string]any) (BackendResult, BackendResult, []map[string]any, []map[string]any) { + a, ar := passingArm(BackendArango, columns, rows) + p, pr := passingArm(BackendPostgres, columns, rows) + return a, p, ar, pr +} + +func TestEvaluateGatesHappyPath(t *testing.T) { + facts := tinyFacts(t) + idMap := NewStorageIDMap(facts) + columns := []string{"patient_id", "gender"} + rows := []map[string]any{{"patient_id": facts.Patients[1].ID, "gender": "female"}} + a, p, ar, pr := armsAgree(columns, rows) + expected := mustBag(t, columns, rows) + report := EvaluateGates(WorkloadFilterActiveGender, columns, expected, a, p, ar, pr, idMap) + if !report.Claimable { + t.Fatalf("matching arms must be claimable: %+v", report) + } +} + +func mustBag(t *testing.T, columns []string, rows []map[string]any) *CanonicalBag { + t.Helper() + bag := Bag(CanonicalizeRows(columns, rows)) + return &bag +} + +func TestEvaluateGatesMismatchFails(t *testing.T) { + columns := []string{"patient_id", "gender"} + a, ar := passingArm(BackendArango, columns, []map[string]any{{"patient_id": "x", "gender": "female"}}) + p, pr := passingArm(BackendPostgres, columns, []map[string]any{{"patient_id": "x", "gender": "male"}}) + report := EvaluateGates("w", columns, nil, a, p, ar, pr, &StorageIDMap{}) + if report.Claimable { + t.Fatal("divergent arms must not be claimable") + } + if report.ExclusionReason == "" { + t.Fatal("mismatch must record an exclusion reason") + } +} + +func TestEvaluateGatesEmptyNeedsEmptyExpectation(t *testing.T) { + columns := []string{"v"} + // Both arms empty: claimable only because the EXPECTATION is empty. + a, p, ar, pr := armsAgree(columns, nil) + report := EvaluateGates("w", columns, mustBag(t, columns, nil), a, p, ar, pr, &StorageIDMap{}) + if !report.Claimable { + t.Fatalf("empty-vs-empty-expectation is a legitimate pass: %+v", report) + } + // Both arms empty against a NON-empty expectation: never a pass. + nonEmpty := mustBag(t, columns, []map[string]any{{"v": "x"}}) + report = EvaluateGates("w", columns, nonEmpty, a, p, ar, pr, &StorageIDMap{}) + if report.Claimable { + t.Fatal("empty results must never pass against a non-empty expectation") + } +} + +func TestEvaluateGatesTruncatedNeverComplete(t *testing.T) { + columns := []string{"v"} + rows := []map[string]any{{"v": "x"}} + a, _ := passingArm(BackendArango, columns, rows) + a.Truncated = true + p, _ := passingArm(BackendPostgres, columns, rows) + report := EvaluateGates("w", columns, mustBag(t, columns, rows), a, p, rows, rows, &StorageIDMap{}) + if report.Claimable { + t.Fatal("a truncated arm must never be treated as complete") + } +} + +func TestEvaluateGatesUnsupportedPublishedNotHidden(t *testing.T) { + columns := []string{"v"} + rows := []map[string]any{{"v": "x"}} + a, p, ar, pr := armsAgree(columns, rows) + p.Outcome = OutcomeLabelUnsupported + p.ErrorCode = "ErrUnsupportedPostgresPlan" + p.BagDigest = "" + report := EvaluateGates("w", columns, nil, a, p, ar, pr, &StorageIDMap{}) + if report.Claimable { + t.Fatal("unsupported arm excludes timings") + } + unsupported := UnsupportedCases([]WorkloadGateReport{report}) + if len(unsupported) != 1 || unsupported[0] != "w/postgres" { + t.Fatalf("unsupported case must be published: %v", unsupported) + } +} + +func TestEvaluateGatesColumnContract(t *testing.T) { + rows := []map[string]any{{"v": "x"}} + a, _ := passingArm(BackendArango, []string{"v", "extra"}, rows) + p, _ := passingArm(BackendPostgres, []string{"v"}, rows) + report := EvaluateGates("w", []string{"v"}, nil, a, p, rows, rows, &StorageIDMap{}) + if report.Claimable { + t.Fatal("column-set divergence must fail the contract gate") + } +} + +func TestEvaluateGatesRowDoesNotReproduceDigest(t *testing.T) { + columns := []string{"v"} + rows := []map[string]any{{"v": "x"}} + a, p, ar, pr := armsAgree(columns, rows) + a.BagDigest = "0000000000000000000000000000000000000000000000000000000000000000" + report := EvaluateGates("w", columns, nil, a, p, ar, pr, &StorageIDMap{}) + if report.Claimable { + t.Fatal("a reported digest that does not reproduce from the rows must fail") + } +} + +func TestNegativeAuthRules(t *testing.T) { + // Baseline has rows, scoped sees zero: pass. + r := EvaluateNegativeAuth("w", 10, 0, 0, "", "") + if !r.Claimable { + t.Fatalf("proper denial should pass: %+v", r) + } + // Empty baseline proves nothing. + r = EvaluateNegativeAuth("w", 0, 0, 0, "", "") + if r.Claimable { + t.Fatal("denial against an empty dataset must not pass") + } + // Scoped sees rows => leak. + r = EvaluateNegativeAuth("w", 10, 3, 0, "", "") + if r.Claimable { + t.Fatal("scoped principal seeing rows must fail") + } + // Transport error is not a denial. + r = EvaluateNegativeAuth("w", 10, 0, 0, "boom", "") + if r.Claimable { + t.Fatal("errored scoped read must not count as denial") + } +} + +func TestRunStatusVerdicts(t *testing.T) { + if status, _ := RunStatus(nil); status != "failed" { + t.Fatal("empty matrix is not a pass") + } + ok := WorkloadGateReport{WorkloadID: "a", Claimable: true, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelPassed}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelPassed}} + bad := WorkloadGateReport{WorkloadID: "b", Claimable: false, ExclusionReason: "gate x", + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelPassed}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelMismatch}} + if status, _ := RunStatus([]WorkloadGateReport{ok}); status != "passed" { + t.Fatal("all-claimable matrix must pass") + } + if status, reasons := RunStatus([]WorkloadGateReport{ok, bad}); status != "failed" || len(reasons) != 1 { + t.Fatalf("one bad workload fails the run: %v %v", status, reasons) + } + // Unsupported-only: no arm passed => not a pass. + unsupportedOnly := WorkloadGateReport{WorkloadID: "u", Claimable: false, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelUnsupported}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelUnsupported}} + if status, _ := RunStatus([]WorkloadGateReport{unsupportedOnly}); status != "failed" { + t.Fatal("unsupported-only matrix must not read as success") + } + // Blocked surfaces distinctly. + blocked := WorkloadGateReport{WorkloadID: "x", Claimable: false, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelPassed}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelBlocked}} + if _, reasons := RunStatus([]WorkloadGateReport{blocked}); len(reasons) != 1 || reasons[0] != "x: blocked" { + t.Fatalf("blocked must be named: %v", reasons) + } +} + +func TestClaimableTimingsRule(t *testing.T) { + report := WorkloadGateReport{WorkloadID: "a", Claimable: true, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelPassed}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelPassed}} + if !ClaimableTimings(BackendArango, report) || !ClaimableTimings(BackendPostgres, report) { + t.Fatal("claimable both-arms cell must allow timings") + } + report.Postgres.Outcome = OutcomeLabelFailed + report.Claimable = false + if ClaimableTimings(BackendArango, report) { + t.Fatal("timings on a not-claimable workload are forbidden even for the passing arm") + } +} + +func TestFormatExclusionList(t *testing.T) { + if got := FormatExclusionList(nil); got == "" { + t.Fatal("exclusion note must never be blank") + } + report := WorkloadGateReport{WorkloadID: "w", ExclusionReason: "gate: detail", Claimable: false} + got := FormatExclusionList([]WorkloadGateReport{report}) + if got == "" || got == "no workloads excluded from performance claims" { + t.Fatalf("exclusion must list the workload: %q", got) + } +} diff --git a/internal/dataframe/benchmark/matrix.go b/internal/dataframe/benchmark/matrix.go new file mode 100644 index 000000000..e2f74297a --- /dev/null +++ b/internal/dataframe/benchmark/matrix.go @@ -0,0 +1,189 @@ +package benchmark + +import ( + "fmt" + "sort" +) + +// The matched workload battery. Every workload runs on BOTH backends against +// the same logical dataset. Coverage families (from the plan): filter, +// projection/derive, sort+limit, group+aggregate, distinct over duplicate +// contributors, reference correlation, null-vs-missing handling, and a +// scoped-auth read. Categories are closed so a report cannot quietly grow a +// family-less entry. + +const ( + WorkloadFilterActiveGender = "filter-active-gender" + WorkloadDeriveAgeBand = "derive-age-band" + WorkloadAggregateSumAgeByPatient = "aggregate-sum-age-by-patient" + WorkloadGroupCountByPatient = "group-count-by-patient" + WorkloadStageCounts = "stage-counts" + WorkloadSpecimenTypeFilter = "specimen-type-filter" + WorkloadConditionNullHandling = "condition-null-handling" + WorkloadSortLimitTopN = "sort-limit-topn" + WorkloadDistinctFamily = "distinct-family" + WorkloadStudyCorrelation = "study-correlation" +) + +// WorkloadCategory is the closed coverage family label. +type WorkloadCategory string + +const ( + CategoryFilter WorkloadCategory = "filter" + CategoryDerive WorkloadCategory = "derive" + CategoryAggregate WorkloadCategory = "aggregate" + CategoryDistinct WorkloadCategory = "distinct" + CategoryCorrelation WorkloadCategory = "correlation" + CategoryNullMissing WorkloadCategory = "null-missing" + CategorySortLimit WorkloadCategory = "sort-limit" +) + +// WorkloadSpec is the authored contract for one battery entry. Columns is +// the public column order BOTH arms must publish (GateColumnContract). +// ExpectedComplete=false is legal only for explicitly truncated preview +// checks, which the battery does not use for claimable rows. +type WorkloadSpec struct { + ID string `json:"id"` + Category WorkloadCategory `json:"category"` + Columns []string `json:"columns"` + OracleAvailable bool `json:"oracleAvailable"` + AuthScoped bool `json:"authScoped"` + Description string `json:"description"` +} + +// Battery is the closed matrix definition set. Adding an entry requires the +// oracle (tiny preset), the workspace recipe in the harness, and a category. +var Battery = []WorkloadSpec{ + {ID: WorkloadFilterActiveGender, Category: CategoryFilter, + Columns: []string{"patient_id", "gender"}, OracleAvailable: true, + Description: "active patients with gender=female"}, + {ID: WorkloadDeriveAgeBand, Category: CategoryDerive, + Columns: []string{"observation_id", "age_band"}, OracleAvailable: true, + Description: "if/case derivation of an age band from BENCH-CODE-AGE quantities"}, + {ID: WorkloadAggregateSumAgeByPatient, Category: CategoryAggregate, + Columns: []string{"patient_id", "sum_age", "count_age", "min_age", "max_age"}, OracleAvailable: true, + Description: "sum/count/min/max of age quantities grouped by patient"}, + {ID: WorkloadGroupCountByPatient, Category: CategoryAggregate, + Columns: []string{"patient_id", "obs_count"}, OracleAvailable: true, + Description: "observation count per patient"}, + {ID: WorkloadStageCounts, Category: CategoryAggregate, + Columns: []string{"stage_code", "stage_count"}, OracleAvailable: true, + Description: "counts by BENCH-CODE-STAGE codeable concept"}, + {ID: WorkloadSpecimenTypeFilter, Category: CategoryFilter, + Columns: []string{"specimen_id", "patient_id", "type_code"}, OracleAvailable: true, + Description: "specimens with type PRIMARY"}, + {ID: WorkloadConditionNullHandling, Category: CategoryNullMissing, + Columns: []string{"condition_id", "verification_status"}, OracleAvailable: true, + Description: "verification status where the source may omit the field entirely (null vs missing)"}, + {ID: WorkloadSortLimitTopN, Category: CategorySortLimit, + Columns: []string{"family", "given", "patient_id"}, OracleAvailable: true, + Description: fmt.Sprintf("top-%d active patients by family, given, id (total order, ties broken by id)", TopNLimit)}, + {ID: WorkloadDistinctFamily, Category: CategoryDistinct, + Columns: []string{"family"}, OracleAvailable: true, + Description: "distinct family names over deliberately duplicated contributors"}, + {ID: WorkloadStudyCorrelation, Category: CategoryCorrelation, + Columns: []string{"patient_id", "study_ref"}, OracleAvailable: true, + Description: "patient rows correlated with their ResearchStudy reference"}, +} + +// workloadColumns resolves the authored column contract. +func workloadColumns(workloadID string) ([]string, bool) { + for _, spec := range Battery { + if spec.ID == workloadID { + return append([]string(nil), spec.Columns...), true + } + } + return nil, false +} + +// SpecsByPreset returns the battery entries eligible for a dataset preset. +// All presets run the full battery; representative drops nothing — it only +// drops the hand oracle scale, which OracleFor already handles by profile +// through the caller. This indirection exists so a future preset-specific +// exclusion is a single auditable edit. +func SpecsByPreset(preset string) ([]WorkloadSpec, error) { + switch preset { + case PresetTiny, PresetSmall, PresetRepresentative: + out := make([]WorkloadSpec, 0, len(Battery)) + for _, spec := range Battery { + out = append(out, spec) + } + return out, nil + default: + return nil, fmt.Errorf("benchmark: unknown preset %q", preset) + } +} + +// ValidateBattery is called at harness start and from tests: an entry with +// duplicate ids, empty columns, an unknown category, or an oracle-less entry +// that should have one (all tiny-eligible entries must declare oracle +// availability or explicitly live without one) is a config bug, not a +// runtime surprise. +func ValidateBattery() error { + seen := map[string]bool{} + categories := map[WorkloadCategory]int{} + for _, spec := range Battery { + if spec.ID == "" { + return fmt.Errorf("benchmark: battery entry with empty id") + } + if seen[spec.ID] { + return fmt.Errorf("benchmark: duplicate workload id %q", spec.ID) + } + seen[spec.ID] = true + if len(spec.Columns) == 0 { + return fmt.Errorf("benchmark: workload %q has no column contract", spec.ID) + } + if hasDuplicates(spec.Columns) { + return fmt.Errorf("benchmark: workload %q has duplicate columns", spec.ID) + } + switch spec.Category { + case CategoryFilter, CategoryDerive, CategoryAggregate, CategoryDistinct, + CategoryCorrelation, CategoryNullMissing, CategorySortLimit: + categories[spec.Category]++ + default: + return fmt.Errorf("benchmark: workload %q has unknown category %q", spec.ID, spec.Category) + } + // Oracle presence must agree with the oracle implementation. + if _, implemented := oracleRows(spec.ID, &Facts{}); implemented != spec.OracleAvailable { + return fmt.Errorf("benchmark: workload %q declares OracleAvailable=%v but implementation says %v", + spec.ID, spec.OracleAvailable, implemented) + } + } + for _, category := range []WorkloadCategory{CategoryFilter, CategoryDerive, CategoryAggregate, + CategoryDistinct, CategoryCorrelation, CategoryNullMissing, CategorySortLimit} { + if categories[category] == 0 { + return fmt.Errorf("benchmark: coverage family %q has no battery entries", category) + } + } + return nil +} + +// OracleBackedCount reports how many entries have independent oracles, for +// the report's honesty line. +func OracleBackedCount() int { + count := 0 + for _, spec := range Battery { + if spec.OracleAvailable { + count++ + } + } + return count +} + +func hasDuplicates(values []string) bool { + seen := map[string]bool{} + for _, v := range values { + if seen[v] { + return true + } + seen[v] = true + } + return false +} + +// SortedBattery returns entries by id for deterministic report order. +func SortedBattery() []WorkloadSpec { + out := append([]WorkloadSpec(nil), Battery...) + sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) + return out +} diff --git a/internal/dataframe/benchmark/matrix_test.go b/internal/dataframe/benchmark/matrix_test.go new file mode 100644 index 000000000..baa696ec0 --- /dev/null +++ b/internal/dataframe/benchmark/matrix_test.go @@ -0,0 +1,38 @@ +package benchmark + +import "testing" + +func TestValidateBattery(t *testing.T) { + if err := ValidateBattery(); err != nil { + t.Fatalf("battery invalid: %v", err) + } + if OracleBackedCount() != len(Battery) { + t.Fatalf("every battery entry is expected to carry an independent oracle: %d/%d", OracleBackedCount(), len(Battery)) + } +} + +func TestSpecsByPreset(t *testing.T) { + for _, preset := range []string{PresetTiny, PresetSmall, PresetRepresentative} { + specs, err := SpecsByPreset(preset) + if err != nil { + t.Fatal(err) + } + if len(specs) != len(Battery) { + t.Fatalf("preset %s runs the full battery", preset) + } + } + if _, err := SpecsByPreset("huge"); err == nil { + t.Fatal("unknown preset must be refused") + } +} + +func TestBatteryColumnContractsUnique(t *testing.T) { + for _, spec := range Battery { + if _, ok := workloadColumns(spec.ID); !ok { + t.Fatalf("workload %q missing column contract", spec.ID) + } + } + if _, ok := workloadColumns("nope"); ok { + t.Fatal("unknown workload must not resolve") + } +} diff --git a/internal/dataframe/benchmark/measure.go b/internal/dataframe/benchmark/measure.go new file mode 100644 index 000000000..d478a6ea7 --- /dev/null +++ b/internal/dataframe/benchmark/measure.go @@ -0,0 +1,236 @@ +package benchmark + +import ( + "fmt" + "math" + "sort" + "strconv" + "time" +) + +// Measurement method (bound before any number is reported): +// +// - warmup iterations run first and are recorded but excluded from +// statistics; +// - measured iterations interleave backends round-robin per round so clock +// and host drift hit both arms alike; +// - every sample keeps its raw wall duration, outcome, cache label, and +// error identity; failed and timed-out samples remain in raw results and +// are excluded from statistics, never silently dropped; +// - headline robustness is median + variance; p50/p95 are reported with a +// declared quantile method; +// - unavailable metrics stay unavailable, never zero. + +// CacheLabel is the honest cache-state label attached to every sample. +// OS/page-cache state is never claimed cold without evidence; process-cold +// is only available where the harness owns the process restart. +type CacheLabel string + +const ( + CacheWarm CacheLabel = "warm" // warmed steady state + CacheProcessCold CacheLabel = "process-cold" // owning server process restarted before this sample + CacheServiceWarm CacheLabel = "service-warm" // external service, no restart ownership (never claimed cold) +) + +// Outcome classifies one execution attempt. +type Outcome string + +const ( + OutcomePassed Outcome = "passed" + OutcomeFailed Outcome = "failed" + OutcomeTimeout Outcome = "timeout" + OutcomeError Outcome = "error" +) + +// Sample is one recorded attempt. Duration is wall time through Loom as +// observed by the client; Phase attributes the dominant measured phase when +// the route separates it. +type Sample struct { + WorkloadID string `json:"workloadId"` + Backend string `json:"backend"` + Round int `json:"round"` + Iteration int `json:"iteration"` + Warmup bool `json:"warmup"` + Outcome Outcome `json:"outcome"` + Millis float64 `json:"millis"` + RowsSeen int `json:"rowsSeen"` + Truncated bool `json:"truncated"` + Cache CacheLabel `json:"cache"` + StartedAt time.Time `json:"startedAt"` + ErrorCode string `json:"errorCode,omitempty"` +} + +// RunConfig binds the repeatability policy. Zero values are refused, not +// defaulted silently, so a run's method is always explicit in its report. +type RunConfig struct { + Warmup int // discarded warmup iterations per workload x backend + Iterations int // measured iterations per workload x backend + Concurrency int // simultaneous requests in the throughput phase + Rounds int // interleaved rounds; Iterations must split evenly +} + +func (c RunConfig) Validate() error { + if c.Warmup < 0 { + return fmt.Errorf("benchmark: warmup must be >= 0") + } + if c.Iterations < 1 { + return fmt.Errorf("benchmark: iterations must be >= 1") + } + if c.Concurrency < 1 { + return fmt.Errorf("benchmark: concurrency must be >= 1") + } + if c.Rounds < 1 { + return fmt.Errorf("benchmark: rounds must be >= 1") + } + if c.Iterations%c.Rounds != 0 { + return fmt.Errorf("benchmark: iterations %d must split evenly across rounds %d", c.Iterations, c.Rounds) + } + return nil +} + +// Summary is the statistics block for one workload x backend x cache label. +// Missing halves are unavailable, never zero: Count==0 means no samples. +type Summary struct { + Count int `json:"count"` + FailedCount int `json:"failedCount"` + MedianMillis float64 `json:"medianMillis"` + MeanMillis float64 `json:"meanMillis"` + VarianceMs float64 `json:"varianceMillis"` + StdDevMs float64 `json:"stdDevMillis"` + MinMillis float64 `json:"minMillis"` + MaxMillis float64 `json:"maxMillis"` + P50Millis float64 `json:"p50Millis"` + P95Millis float64 `json:"p95Millis"` + QuantileMethod string `json:"quantileMethod"` + // CoefficientOfVariation is stddev/mean for the passed samples; the + // stability control compares it against StabilityCVLimit. + CoefficientOfVariation float64 `json:"coefficientOfVariation"` + // ThroughputPerSecond is concurrency/windowSeconds for the throughput + // phase only; 0 with ThroughputMeasured=false means unavailable. + ThroughputPerSecond float64 `json:"throughputPerSecond"` + ThroughputMeasured bool `json:"throughputMeasured"` +} + +// Summarize computes statistics over the passed, non-warmup samples for one +// (workload, backend, cache) cell. Failed samples are counted separately and +// excluded from the distributions; their presence in raw results is enforced +// elsewhere. +func Summarize(samples []Sample) Summary { + summary := Summary{QuantileMethod: "nearest-rank-on-sorted-passed-durations"} + var passed []float64 + for _, s := range samples { + if s.Warmup { + continue + } + summary.Count++ + if s.Outcome != OutcomePassed { + summary.FailedCount++ + continue + } + passed = append(passed, s.Millis) + } + if len(passed) == 0 { + return summary + } + sort.Float64s(passed) + var total, totalSq float64 + for _, ms := range passed { + total += ms + totalSq += ms * ms + } + n := float64(len(passed)) + summary.MeanMillis = total / n + summary.MedianMillis = percentile(passed, 0.5) + summary.P50Millis = summary.MedianMillis + summary.P95Millis = percentile(passed, 0.95) + summary.MinMillis = passed[0] + summary.MaxMillis = passed[len(passed)-1] + variance := totalSq/n - summary.MeanMillis*summary.MeanMillis + if variance < 0 { + variance = 0 + } + summary.VarianceMs = variance + summary.StdDevMs = math.Sqrt(variance) + if summary.MeanMillis > 0 { + summary.CoefficientOfVariation = summary.StdDevMs / summary.MeanMillis + } + return summary +} + +// percentile uses nearest-rank on the sorted slice: rank = ceil(q*n), 1-based. +// The method is declared in Summary.QuantileMethod rather than assumed. +func percentile(sorted []float64, q float64) float64 { + if len(sorted) == 0 { + return 0 + } + rank := int(math.Ceil(q * float64(len(sorted)))) + if rank < 1 { + rank = 1 + } + if rank > len(sorted) { + rank = len(sorted) + } + return sorted[rank-1] +} + +// StabilityCVLimit is the ceiling for the stability control: a workload arm +// whose coefficient of variation exceeds it marks the environment unstable +// and its ratios are excluded from performance claims. +const StabilityCVLimit = 0.35 + +// SpeedRatio compares two medians. It is computed only when both sides are +// valid (see gates.go); the caller must not present it otherwise. +type SpeedRatio struct { + ArangoOverPostgres float64 `json:"arangoOverPostgres"` + PostgresOverArango float64 `json:"postgresOverArango"` +} + +func NewSpeedRatio(arangoMedian, postgresMedian float64) (SpeedRatio, error) { + if arangoMedian <= 0 || postgresMedian <= 0 { + return SpeedRatio{}, fmt.Errorf("benchmark: speed ratio needs positive medians, got arango=%s postgres=%s", + strconv.FormatFloat(arangoMedian, 'g', -1, 64), strconv.FormatFloat(postgresMedian, 'g', -1, 64)) + } + return SpeedRatio{ + ArangoOverPostgres: arangoMedian / postgresMedian, + PostgresOverArango: postgresMedian / arangoMedian, + }, nil +} + +// Clock is injectable so accounting tests never sleep. +type Clock interface { + Now() time.Time + Since(time.Time) time.Duration +} + +type monotonicClock struct{} + +func (monotonicClock) Now() time.Time { return time.Now() } +func (monotonicClock) Since(t time.Time) time.Duration { return time.Since(t) } + +// NewMonotonicClock returns the production clock (time.Now carries a +// monotonic reading; durations use Since, never wall subtraction). +func NewMonotonicClock() Clock { return monotonicClock{} } + +// ElapsedMillis measures one attempt with the injected clock. +func ElapsedMillis(clock Clock, started time.Time) float64 { + return float64(clock.Since(started).Microseconds()) / 1000.0 +} + +// ThroughputWindow summarizes a sustained concurrency window: window seconds +// are measured, not declared, and only passed requests count toward +// throughput. +func ThroughputSummary(samples []Sample, window time.Duration) Summary { + summary := Summarize(samples) + var passed int + for _, s := range samples { + if !s.Warmup && s.Outcome == OutcomePassed { + passed++ + } + } + seconds := window.Seconds() + if passed > 0 && seconds > 0 { + summary.ThroughputPerSecond = float64(passed) / seconds + summary.ThroughputMeasured = true + } + return summary +} diff --git a/internal/dataframe/benchmark/measure_test.go b/internal/dataframe/benchmark/measure_test.go new file mode 100644 index 000000000..150d112e4 --- /dev/null +++ b/internal/dataframe/benchmark/measure_test.go @@ -0,0 +1,159 @@ +package benchmark + +import ( + "math" + "testing" + "time" +) + +func sample(backend, workload string, outcome Outcome, ms float64, warmup bool) Sample { + return Sample{WorkloadID: workload, Backend: backend, Outcome: outcome, Millis: ms, Warmup: warmup, Cache: CacheWarm, Round: 1, Iteration: 1} +} + +func TestSummarizeExcludesWarmupAndFailed(t *testing.T) { + samples := []Sample{ + sample(BackendArango, "w", OutcomePassed, 100, true), // warmup: excluded + sample(BackendArango, "w", OutcomePassed, 10, false), + sample(BackendArango, "w", OutcomePassed, 20, false), + sample(BackendArango, "w", OutcomePassed, 30, false), + sample(BackendArango, "w", OutcomeTimeout, 9999, false), // excluded from stats, counted + } + s := Summarize(samples) + if s.Count != 4 { + t.Fatalf("count must cover non-warmup samples including failures: %d", s.Count) + } + if s.FailedCount != 1 { + t.Fatalf("failed count: %d", s.FailedCount) + } + if s.MedianMillis != 20 || s.MinMillis != 10 || s.MaxMillis != 30 { + t.Fatalf("warmup or failed leaked into stats: %+v", s) + } + if s.MeanMillis != 20 { + t.Fatalf("mean: %v", s.MeanMillis) + } +} + +func TestSummarizeQuantilesNearestRank(t *testing.T) { + var samples []Sample + for i := 1; i <= 20; i++ { + samples = append(samples, sample(BackendArango, "w", OutcomePassed, float64(i), false)) + } + s := Summarize(samples) + // nearest-rank: p50 rank=ceil(.5*20)=10 -> 10; p95 rank=ceil(.95*20)=19 -> 19. + if s.P50Millis != 10 || s.P95Millis != 19 { + t.Fatalf("quantiles wrong under nearest-rank: %+v", s) + } + if s.QuantileMethod != "nearest-rank-on-sorted-passed-durations" { + t.Fatal("quantile method must be declared") + } +} + +func TestSummarizeUnavailableNotZero(t *testing.T) { + // A cell whose samples all failed: distributions are UNAVAILABLE. The + // sentinel is Count>0,FailedCount==Count plus ThroughputMeasured=false; + // a never-run cell has Count==0. Neither may masquerade as a real 0ms. + s := Summarize([]Sample{sample(BackendArango, "w", OutcomeError, 5, false)}) + if s.Count != 1 || s.FailedCount != 1 { + t.Fatalf("all-failed cell shape: %+v", s) + } + if s.ThroughputMeasured { + t.Fatal("throughput must stay unmeasured without passed samples") + } + empty := Summarize(nil) + if empty.Count != 0 { + t.Fatalf("never-run cell must have Count==0: %+v", empty) + } +} + +func TestSummarizeVarianceAndCV(t *testing.T) { + samples := []Sample{} + for _, ms := range []float64{10, 10, 10, 10} { + samples = append(samples, sample(BackendArango, "w", OutcomePassed, ms, false)) + } + s := Summarize(samples) + if s.VarianceMs != 0 || s.StdDevMs != 0 || s.CoefficientOfVariation != 0 { + t.Fatalf("identical samples must have zero variance: %+v", s) + } + s2 := Summarize([]Sample{ + sample(BackendArango, "w", OutcomePassed, 10, false), + sample(BackendArango, "w", OutcomePassed, 30, false), + }) + // mean 20, variance 100, stddev 10, CV 0.5 + if math.Abs(s2.VarianceMs-100) > 1e-9 || math.Abs(s2.StdDevMs-10) > 1e-9 || math.Abs(s2.CoefficientOfVariation-0.5) > 1e-9 { + t.Fatalf("variance/CV wrong: %+v", s2) + } +} + +func TestRunConfigValidation(t *testing.T) { + cases := []struct { + name string + cfg RunConfig + ok bool + }{ + {"zero", RunConfig{}, false}, + {"uneven rounds", RunConfig{Warmup: 1, Iterations: 7, Concurrency: 1, Rounds: 2}, false}, + {"negative warmup", RunConfig{Warmup: -1, Iterations: 4, Concurrency: 1, Rounds: 2}, false}, + {"ok", RunConfig{Warmup: 2, Iterations: 8, Concurrency: 4, Rounds: 4}, true}, + } + for _, tc := range cases { + err := tc.cfg.Validate() + if tc.ok && err != nil { + t.Fatalf("%s: unexpected %v", tc.name, err) + } + if !tc.ok && err == nil { + t.Fatalf("%s: expected rejection", tc.name) + } + } +} + +func TestSpeedRatioRejectsNonPositive(t *testing.T) { + if _, err := NewSpeedRatio(0, 10); err == nil { + t.Fatal("zero median must be refused, not reported as ratio 0") + } + if _, err := NewSpeedRatio(10, -1); err == nil { + t.Fatal("negative median must be refused") + } + r, err := NewSpeedRatio(40, 10) + if err != nil || r.ArangoOverPostgres != 4 || r.PostgresOverArango != 0.25 { + t.Fatalf("ratio wrong: %+v %v", r, err) + } +} + +func TestThroughputSummaryUsesMeasuredWindowAndPassedOnly(t *testing.T) { + samples := []Sample{ + sample(BackendArango, "w", OutcomePassed, 50, false), + sample(BackendArango, "w", OutcomePassed, 60, false), + sample(BackendArango, "w", OutcomeError, 10, false), + sample(BackendArango, "w", OutcomePassed, 40, true), // warmup excluded + } + s := ThroughputSummary(samples, 2*time.Second) + if !s.ThroughputMeasured { + t.Fatal("measured window with passes must mark throughput measured") + } + if s.ThroughputPerSecond != 1.0 { // 2 passed / 2s + t.Fatalf("throughput counts only passed non-warmup: %v", s.ThroughputPerSecond) + } + if z := ThroughputSummary(samples, 0); z.ThroughputMeasured { + t.Fatal("zero window is not a measurement") + } +} + +func TestElapsedMillisUsesMonotonicSince(t *testing.T) { + fake := fakeClock{start: time.Unix(0, 0), now: time.Unix(0, 0)} + elapsed := ElapsedMillis(&fake, fake.now) + if elapsed != 0 { + t.Fatal("same instant must measure zero") + } + fake.now = fake.start.Add(1500 * time.Microsecond) + if got := ElapsedMillis(&fake, fake.start); got != 1.5 { + t.Fatalf("expected 1.5ms, got %v", got) + } + if NewMonotonicClock() == nil { + t.Fatal("production clock must exist") + } +} + +type fakeClock struct{ start, now time.Time } + +func (c *fakeClock) Now() time.Time { return c.now } +func (c *fakeClock) Since(t time.Time) time.Duration { return c.now.Sub(t) } diff --git a/internal/dataframe/benchmark/oracle.go b/internal/dataframe/benchmark/oracle.go new file mode 100644 index 000000000..a24da7b3d --- /dev/null +++ b/internal/dataframe/benchmark/oracle.go @@ -0,0 +1,258 @@ +package benchmark + +import ( + "fmt" + "sort" +) + +// Independent oracles for the tiny preset. These are derived directly from +// Facts (the fixture's own truth record) with plain Go arithmetic — no Loom +// planner, compiler, or storage is involved — so agreement between both +// backends AND the oracle is three-way evidence, not a tautology. +// +// Oracle rows are produced in the same public column shape the workloads +// publish, so they canonicalize and bag through the identical path. + +// OracleFor returns the independently expected bag for one workload under the +// tiny preset, or (nil, nil) when the workload has no independent oracle +// (then only cross-backend equality gates apply, recorded as such). +func OracleFor(workloadID string, facts *Facts) (*CanonicalBag, error) { + rows, ok := oracleRows(workloadID, facts) + if !ok { + return nil, nil + } + columns, ok := workloadColumns(workloadID) + if !ok { + return nil, fmt.Errorf("benchmark: workload %q has an oracle but no column contract", workloadID) + } + bag := Bag(CanonicalizeRows(columns, rows)) + return &bag, nil +} + +func oracleRows(workloadID string, facts *Facts) ([]map[string]any, bool) { + switch workloadID { + case WorkloadFilterActiveGender: + return oracleFilterActiveGender(facts), true + case WorkloadAggregateSumAgeByPatient: + return oracleAggregateSum(facts), true + case WorkloadGroupCountByPatient: + return oracleGroupCount(facts), true + case WorkloadStageCounts: + return oracleStageCounts(facts), true + case WorkloadSpecimenTypeFilter: + return oracleSpecimenTypeFilter(facts), true + case WorkloadConditionNullHandling: + return oracleConditionNulls(facts), true + case WorkloadDistinctFamily: + return oracleDistinctFamily(facts), true + case WorkloadStudyCorrelation: + return oracleStudyCorrelation(facts), true + case WorkloadSortLimitTopN: + return oracleSortLimit(facts), true + case WorkloadDeriveAgeBand: + return oracleDeriveAgeBand(facts), true + default: + return nil, false + } +} + +func oracleFilterActiveGender(facts *Facts) []map[string]any { + var rows []map[string]any + for _, p := range facts.Patients { + if !p.Active || p.Gender != "female" { + continue + } + rows = append(rows, map[string]any{"patient_id": p.ID, "gender": p.Gender}) + } + return rows +} + +func oracleAggregateSum(facts *Facts) []map[string]any { + type acc struct { + sum float64 + count int + min float64 + max float64 + seen bool + } + byPatient := map[string]*acc{} + for _, o := range facts.Observations { + if o.Code != ObservationCodeAge || o.ValueNumber == nil { + continue + } + a := byPatient[o.PatientID] + if a == nil { + a = &acc{} + byPatient[o.PatientID] = a + } + v := *o.ValueNumber + a.sum += v + a.count++ + if !a.seen || v < a.min { + a.min = v + } + if !a.seen || v > a.max { + a.max = v + } + a.seen = true + } + patients := make([]string, 0, len(byPatient)) + for patient := range byPatient { + patients = append(patients, patient) + } + sort.Strings(patients) + rows := make([]map[string]any, 0, len(patients)) + for _, patient := range patients { + a := byPatient[patient] + rows = append(rows, map[string]any{ + "patient_id": patient, + "sum_age": a.sum, + "count_age": float64(a.count), + "min_age": a.min, + "max_age": a.max, + }) + } + return rows +} + +func oracleGroupCount(facts *Facts) []map[string]any { + rows := make([]map[string]any, 0, len(facts.CountByPatient)) + for patient, count := range facts.CountByPatient { + rows = append(rows, map[string]any{"patient_id": patient, "obs_count": float64(count)}) + } + return rows +} + +func oracleStageCounts(facts *Facts) []map[string]any { + rows := make([]map[string]any, 0, len(facts.StageCounts)) + for stage, count := range facts.StageCounts { + rows = append(rows, map[string]any{"stage_code": stage, "stage_count": float64(count)}) + } + return rows +} + +func oracleSpecimenTypeFilter(facts *Facts) []map[string]any { + var rows []map[string]any + for _, s := range facts.Specimens { + if s.TypeCode != SpecimenTypePrimary { + continue + } + rows = append(rows, map[string]any{"specimen_id": s.ID, "patient_id": s.PatientID, "type_code": s.TypeCode}) + } + return rows +} + +// oracleConditionNulls emits null (not missing) for absent verification +// statuses: the backend must produce an explicit null too, or the +// null-vs-missing gate fails — which is the point. +func oracleConditionNulls(facts *Facts) []map[string]any { + rows := make([]map[string]any, 0, len(facts.Conditions)) + for _, c := range facts.Conditions { + var status any + if c.VerificationStatus != "" { + status = c.VerificationStatus + } + rows = append(rows, map[string]any{"condition_id": c.ID, "verification_status": status}) + } + return rows +} + +func oracleDistinctFamily(facts *Facts) []map[string]any { + seen := map[string]bool{} + var rows []map[string]any + for _, p := range facts.Patients { + for _, family := range p.FamilyNames { + if seen[family] { + continue + } + seen[family] = true + rows = append(rows, map[string]any{"family": family}) + } + } + return rows +} + +func oracleStudyCorrelation(facts *Facts) []map[string]any { + var rows []map[string]any + for _, p := range facts.Patients { + rows = append(rows, map[string]any{"patient_id": p.ID, "study_ref": p.StudyRef}) + } + return rows +} + +// oracleSortLimit mirrors the workload's deterministic total order: family +// asc, then given asc, then patient_id asc; the top N by that order. The +// tie-break on patient_id makes the ordering total, so both backends must +// agree on exactly the same N rows with multiplicity. +func oracleSortLimit(facts *Facts) []map[string]any { + type entry struct { + family, given, id string + } + var entries []entry + for _, p := range facts.Patients { + if !p.Active { + continue + } + family, given := "", "" + if len(p.FamilyNames) > 0 { + family = p.FamilyNames[0] + } + if len(p.GivenNames) > 0 { + given = p.GivenNames[0] + } + entries = append(entries, entry{family, given, p.ID}) + } + sort.Slice(entries, func(i, j int) bool { + if entries[i].family != entries[j].family { + return entries[i].family < entries[j].family + } + if entries[i].given != entries[j].given { + return entries[i].given < entries[j].given + } + return entries[i].id < entries[j].id + }) + limit := TopNLimit + if limit > len(entries) { + limit = len(entries) + } + rows := make([]map[string]any, 0, limit) + for _, e := range entries[:limit] { + rows = append(rows, map[string]any{"family": e.family, "given": e.given, "patient_id": e.id}) + } + return rows +} + +func oracleDeriveAgeBand(facts *Facts) []map[string]any { + var rows []map[string]any + for _, o := range facts.Observations { + if o.Code != ObservationCodeAge || o.ValueNumber == nil { + continue + } + v := *o.ValueNumber + band := "unknown" + switch { + case v < 30: + band = "young" + case v < 60: + band = "middle" + default: + band = "senior" + } + rows = append(rows, map[string]any{"observation_id": o.ID, "age_band": band}) + } + return rows +} + +// AgeBandThresholds are shared between the recipe (matrix.go) and the oracle +// so the two cannot drift apart silently; a change is a single edit here. +const ( + AgeBandYoungUnder = 30 + AgeBandMiddleUnder = 60 + TopNLimit = 25 +) + +// Exact float equality note: age valueQuantity values are integers in +// [1000, 10000) (fixtures.go), and per-patient sums stay far inside float64 +// exact integer range at every preset, so canonicalNumberText's +// shortest-round-trip spelling matches backend output exactly; the fixtures +// test re-asserts the integer invariant so the oracle cannot drift silently. diff --git a/internal/dataframe/benchmark/oracle_test.go b/internal/dataframe/benchmark/oracle_test.go new file mode 100644 index 000000000..f9ae8d138 --- /dev/null +++ b/internal/dataframe/benchmark/oracle_test.go @@ -0,0 +1,186 @@ +package benchmark + +import "testing" + +func TestOracleMatchesIndependentCounts(t *testing.T) { + facts := tinyFacts(t) + + // Filter oracle: counted directly here, not via the oracle function. + want := 0 + for _, p := range facts.Patients { + if p.Active && p.Gender == "female" { + want++ + } + } + bag, err := OracleFor(WorkloadFilterActiveGender, facts) + if err != nil || bag == nil { + t.Fatalf("filter oracle: %v", err) + } + if len(*bag) != want { + t.Fatalf("filter oracle rows=%d, independent count=%d", len(*bag), want) + } + if want == 0 { + t.Fatal("tiny dataset must exercise the filter non-vacuously") + } +} + +func TestOracleAggregateMatchesFactsAggregates(t *testing.T) { + facts := tinyFacts(t) + bag, err := OracleFor(WorkloadAggregateSumAgeByPatient, facts) + if err != nil || bag == nil { + t.Fatal(err) + } + // The oracle bag must have exactly one row per patient with age values. + rows := 0 + for _, n := range facts.CountByPatient { + if n > 0 { + rows++ + } + } + // Some patients may have all ages missing (p%19==0), so compare to the + // set of patients with at least one non-nil age observation. + withAge := map[string]bool{} + for _, o := range facts.Observations { + if o.Code == ObservationCodeAge && o.ValueNumber != nil { + withAge[o.PatientID] = true + } + } + if len(*bag) != len(withAge) { + t.Fatalf("aggregate oracle rows=%d, patients-with-age=%d", len(*bag), len(withAge)) + } + _ = rows +} + +func TestOracleGroupCountMatchesCountByPatient(t *testing.T) { + facts := tinyFacts(t) + bag, err := OracleFor(WorkloadGroupCountByPatient, facts) + if err != nil || bag == nil { + t.Fatal(err) + } + if len(*bag) != len(facts.CountByPatient) { + t.Fatalf("group oracle rows=%d, facts.CountByPatient=%d", len(*bag), len(facts.CountByPatient)) + } +} + +func TestOracleStageCountsMatchFacts(t *testing.T) { + facts := tinyFacts(t) + bag, err := OracleFor(WorkloadStageCounts, facts) + if err != nil || bag == nil { + t.Fatal(err) + } + if len(*bag) != len(facts.StageCounts) { + t.Fatalf("stage oracle rows=%d, facts.StageCounts=%d", len(*bag), len(facts.StageCounts)) + } +} + +func TestOracleNullHandlingEmitsNullNotMissing(t *testing.T) { + facts := tinyFacts(t) + bag, err := OracleFor(WorkloadConditionNullHandling, facts) + if err != nil || bag == nil { + t.Fatal(err) + } + if len(*bag) != len(facts.Conditions) { + t.Fatal("every condition must appear exactly once") + } + // The oracle row object carries the key with a nil value: canonicalized + // to CNull, and must NOT equal the same row with the key dropped. + rows, _ := oracleRows(WorkloadConditionNullHandling, facts) + dropped := make([]map[string]any, len(rows)) + for i, r := range rows { + c := map[string]any{} + for k, v := range r { + if v != nil { + c[k] = v + } + } + dropped[i] = c + } + if Bag(CanonicalizeRows([]string{"condition_id", "verification_status"}, rows)).Digest() == + Bag(CanonicalizeRows([]string{"condition_id", "verification_status"}, dropped)).Digest() { + t.Fatal("oracle must keep null distinct from missing") + } +} + +func TestOracleSortLimitTotalOrderTopN(t *testing.T) { + facts := tinyFacts(t) + rows, _ := oracleRows(WorkloadSortLimitTopN, facts) + active := 0 + for _, p := range facts.Patients { + if p.Active { + active++ + } + } + want := active + if want > TopNLimit { + want = TopNLimit + } + if len(rows) != want { + t.Fatalf("topN rows=%d, want=%d", len(rows), want) + } + for i := 1; i < len(rows); i++ { + key := func(r map[string]any) (string, string, string) { + f, _ := r["family"].(string) + g, _ := r["given"].(string) + id, _ := r["patient_id"].(string) + return f, g, id + } + f1, g1, id1 := key(rows[i-1]) + f2, g2, id2 := key(rows[i]) + if f1 > f2 || (f1 == f2 && g1 > g2) || (f1 == f2 && g1 == g2 && id1 >= id2) { + t.Fatalf("sort-limit oracle order broken at %d: %v then %v", i, rows[i-1], rows[i]) + } + } +} + +func TestOracleDeriveAgeBandEveryAgeObsBanded(t *testing.T) { + facts := tinyFacts(t) + rows, _ := oracleRows(WorkloadDeriveAgeBand, facts) + want := 0 + for _, o := range facts.Observations { + if o.Code == ObservationCodeAge && o.ValueNumber != nil { + want++ + } + } + if len(rows) != want { + t.Fatalf("age-band rows=%d, want=%d", len(rows), want) + } + seen := map[string]bool{} + for _, r := range rows { + id, _ := r["observation_id"].(string) + band, _ := r["age_band"].(string) + if id == "" || seen[id] { + t.Fatalf("observation duplicated or unnamed: %v", r) + } + seen[id] = true + switch band { + case "young", "middle", "senior": + default: + t.Fatalf("unexpected band %q", band) + } + } +} + +func TestOracleStudyCorrelationCoversAllPatients(t *testing.T) { + facts := tinyFacts(t) + rows, _ := oracleRows(WorkloadStudyCorrelation, facts) + if len(rows) != len(facts.Patients) { + t.Fatal("one row per patient expected") + } + studies := map[string]bool{} + for _, s := range facts.StudyIDs { + studies["ResearchStudy/"+s] = true + } + for _, r := range rows { + ref, _ := r["study_ref"].(string) + if !studies[ref] { + t.Fatalf("study_ref %q not in study ids", ref) + } + } +} + +func TestOracleUnknownWorkloadHasNoOracle(t *testing.T) { + bag, err := OracleFor("does-not-exist", tinyFacts(t)) + if err != nil || bag != nil { + t.Fatalf("unknown workload must yield (nil, nil): %v %v", bag, err) + } +} diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go new file mode 100644 index 000000000..ad7d3aa8a --- /dev/null +++ b/internal/dataframe/benchmark/report.go @@ -0,0 +1,367 @@ +package benchmark + +import ( + "encoding/json" + "fmt" + "math" + "regexp" + "sort" + "time" +) + +// The report is the deliverable. Its trust model: every derived number is +// RECOMPUTED from the raw record by VerifyReport, so a hand-edited (or +// partially forged) report fails verification. The verifier is deliberately +// independent of the harness: CI and reviewers run it on the artifact alone. + +const ReportSchemaVersion = "loom.benchmark-report/v1" + +var ( + sha256HexRe = regexp.MustCompile(`^[0-9a-f]{64}$`) + idRe = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`) +) + +// RunIdentity ties the report to exactly what ran. +type RunIdentity struct { + RunID string `json:"runId"` + StartedAt time.Time `json:"startedAt"` + Preset string `json:"preset"` + SourceCommit string `json:"sourceCommit"` + DatasetDigest string `json:"datasetDigest"` // fixture manifest digest + ArangoVersion string `json:"arangoVersion"` + PostgresVersion string `json:"postgresVersion"` + // ClickHouseDisabled records the ruling: dataframe legs run with + // ClickHouse disabled on both arms so the reader path is matched. + ClickHouseDisabled bool `json:"clickhouseDisabled"` + Config RunConfig `json:"config"` +} + +// ControlRuns records the measurement-integrity controls (ruling i). Each +// control must have been RUN; absent controls are a verification failure. +type ControlRuns struct { + // StabilityCVLimit and the per-cell CVs used to decide stability. + StabilityCVLimit float64 `json:"stabilityCvLimit"` + UnstableCells []string `json:"unstableCells"` // "workload/backend" cells over the limit + // SelfRatio: backend vs itself across separate rounds; must sit at 1. + SelfRatioArango float64 `json:"selfRatioArango"` + SelfRatioPostgres float64 `json:"selfRatioPostgres"` + // EmptyResultFloor: timed no-result requests establishing the client + // overhead floor subtracted nowhere but reported as context. + EmptyFloorMillis float64 `json:"emptyFloorMillis"` + // RatioPolicy: ratios are published only for workloads both arms passed. + RatioPolicy string `json:"ratioPolicy"` +} + +// Claim is one publishable performance claim. +type Claim struct { + WorkloadID string `json:"workloadId"` + Arango Summary `json:"arango"` + Postgres Summary `json:"postgres"` + Ratio SpeedRatio `json:"ratio"` +} + +// Report is the full artifact. +type Report struct { + Schema string `json:"schema"` + Identity RunIdentity `json:"identity"` + Status string `json:"status"` // passed | failed | blocked + StatusReasons []string `json:"statusReasons,omitempty"` + Controls ControlRuns `json:"controls"` + GateReports []WorkloadGateReport `json:"gateReports"` + Samples []Sample `json:"samples"` + Summaries []SummaryRow `json:"summaries"` + Claims []Claim `json:"claims"` + Unsupported []string `json:"unsupported"` + Exclusions string `json:"exclusions"` +} + +// SummaryRow ties a Summary to its exact cell. +type SummaryRow struct { + WorkloadID string `json:"workloadId"` + Backend string `json:"backend"` + Cache CacheLabel `json:"cache"` + Summary Summary `json:"summary"` +} + +// BuildReport assembles the artifact from harness-collected evidence and +// recomputes summaries/claims/status so the written file is internally +// consistent by construction. claimableOf decides per-workload eligibility +// (the harness passes the gate outcome); unstable cells are excluded from +// claims per the stability control. +func BuildReport(identity RunIdentity, controls ControlRuns, gateReports []WorkloadGateReport, samples []Sample) (*Report, error) { + if err := identity.Config.Validate(); err != nil { + return nil, err + } + status, reasons := RunStatus(gateReports) + report := &Report{ + Schema: ReportSchemaVersion, + Identity: identity, + Status: status, + StatusReasons: reasons, + Controls: controls, + GateReports: gateReports, + Samples: samples, + Unsupported: UnsupportedCases(gateReports), + Exclusions: FormatExclusionList(gateReports), + } + byCell := map[string][]Sample{} + for _, s := range samples { + key := s.WorkloadID + "|" + s.Backend + "|" + string(s.Cache) + byCell[key] = append(byCell[key], s) + } + keys := make([]string, 0, len(byCell)) + for key := range byCell { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + parts := splitCell(key) + report.Summaries = append(report.Summaries, SummaryRow{ + WorkloadID: parts[0], Backend: parts[1], Cache: CacheLabel(parts[2]), + Summary: Summarize(byCell[key]), + }) + } + report.Claims = buildClaims(gateReports, report.Summaries, controls) + if err := VerifyReport(report); err != nil { + return nil, fmt.Errorf("benchmark: internally inconsistent report (bug): %w", err) + } + return report, nil +} + +func buildClaims(gateReports []WorkloadGateReport, summaries []SummaryRow, controls ControlRuns) []Claim { + byCell := map[string]Summary{} + for _, row := range summaries { + byCell[row.WorkloadID+"|"+row.Backend+"|"+string(row.Cache)] = row.Summary + } + unstable := map[string]bool{} + for _, cell := range controls.UnstableCells { + unstable[cell] = true + } + var claims []Claim + for _, gate := range gateReports { + if !gate.Claimable { + continue // ratio-only-both-passed policy + } + if unstable[gate.WorkloadID+"/"+BackendArango] || unstable[gate.WorkloadID+"/"+BackendPostgres] { + continue // stability control + } + a := byCell[gate.WorkloadID+"|"+BackendArango+"|"+string(CacheWarm)] + p := byCell[gate.WorkloadID+"|"+BackendPostgres+"|"+string(CacheWarm)] + if a.Count == 0 || p.Count == 0 || a.FailedCount != 0 || p.FailedCount != 0 { + continue + } + ratio, err := NewSpeedRatio(a.MedianMillis, p.MedianMillis) + if err != nil { + continue + } + claims = append(claims, Claim{WorkloadID: gate.WorkloadID, Arango: a, Postgres: p, Ratio: ratio}) + } + return claims +} + +func splitCell(key string) [3]string { + var out [3]string + i, j := 0, 0 + for k := 0; k < len(key); k++ { + if key[k] == '|' { + out[i] = key[j:k] + i++ + j = k + 1 + } + } + out[i] = key[j:] + return out +} + +// VerifyReport recomputes everything derivable and fails on any mismatch. +// It is the check a synthetic forgery must not survive (ruling ii). +func VerifyReport(report *Report) error { + if report.Schema != ReportSchemaVersion { + return fmt.Errorf("benchmark report: unexpected schema %q", report.Schema) + } + if !idRe.MatchString(report.Identity.RunID) { + return fmt.Errorf("benchmark report: bad runId") + } + if err := report.Identity.Config.Validate(); err != nil { + return fmt.Errorf("benchmark report: %w", err) + } + if !sha256HexRe.MatchString(report.Identity.DatasetDigest) { + return fmt.Errorf("benchmark report: dataset digest is not sha256 hex") + } + // Controls must have been run. + if report.Controls.StabilityCVLimit <= 0 || report.Controls.RatioPolicy == "" { + return fmt.Errorf("benchmark report: measurement controls were not run (missing stability limit or ratio policy)") + } + if report.Controls.EmptyFloorMillis <= 0 { + return fmt.Errorf("benchmark report: empty-result floor control was not run") + } + if report.Controls.SelfRatioArango <= 0 || report.Controls.SelfRatioPostgres <= 0 { + return fmt.Errorf("benchmark report: self-ratio controls were not run") + } + if math.Abs(report.Controls.SelfRatioArango-1) > 0.05 || math.Abs(report.Controls.SelfRatioPostgres-1) > 0.05 { + return fmt.Errorf("benchmark report: self-ratio %v/%v off 1.0 beyond 5%% — environment unfit for ratios", + report.Controls.SelfRatioArango, report.Controls.SelfRatioPostgres) + } + // Status must follow from the gate reports. + status, reasons := RunStatus(report.GateReports) + if report.Status != status { + return fmt.Errorf("benchmark report: status %q but gates imply %q", report.Status, status) + } + if !sameStrings(report.StatusReasons, reasons) { + return fmt.Errorf("benchmark report: status reasons do not match gate evaluation") + } + // Unsupported list must match the gate reports. + if !sameStrings(report.Unsupported, UnsupportedCases(report.GateReports)) { + return fmt.Errorf("benchmark report: unsupported list does not match gate reports") + } + if report.Exclusions != FormatExclusionList(report.GateReports) { + return fmt.Errorf("benchmark report: exclusion note does not match gate reports") + } + // Every sample is well-formed. + for _, s := range report.Samples { + if !ValidBackend(s.Backend) { + return fmt.Errorf("benchmark report: sample has unknown backend %q", s.Backend) + } + if _, ok := workloadColumns(s.WorkloadID); !ok { + return fmt.Errorf("benchmark report: sample references unknown workload %q", s.WorkloadID) + } + if s.Millis < 0 { + return fmt.Errorf("benchmark report: negative duration sample") + } + switch s.Outcome { + case OutcomePassed, OutcomeFailed, OutcomeTimeout, OutcomeError: + default: + return fmt.Errorf("benchmark report: unknown sample outcome %q", s.Outcome) + } + } + // Summaries are recomputed from samples and must match. + byCell := map[string][]Sample{} + for _, s := range report.Samples { + key := s.WorkloadID + "|" + s.Backend + "|" + string(s.Cache) + byCell[key] = append(byCell[key], s) + } + seen := map[string]bool{} + for _, row := range report.Summaries { + key := row.WorkloadID + "|" + row.Backend + "|" + string(row.Cache) + if seen[key] { + return fmt.Errorf("benchmark report: duplicate summary cell %s", key) + } + seen[key] = true + expected := Summarize(byCell[key]) + if !summaryNearEqual(expected, row.Summary) { + return fmt.Errorf("benchmark report: summary for %s does not recompute from raw samples", key) + } + } + for key := range byCell { + if !seen[key] { + return fmt.Errorf("benchmark report: raw samples for %s have no summary row", key) + } + } + // Gate bag digests are sha256-shaped when present. + for _, gate := range report.GateReports { + for _, arm := range []BackendResult{gate.Arango, gate.Postgres} { + if arm.BagDigest != "" && !sha256HexRe.MatchString(arm.BagDigest) { + return fmt.Errorf("benchmark report: %s/%s bag digest is not sha256 hex", gate.WorkloadID, arm.Backend) + } + } + } + // Claims: each must be recomputable, claimable, stable, and both-clean. + summaryOf := map[string]Summary{} + for _, row := range report.Summaries { + summaryOf[row.WorkloadID+"|"+row.Backend+"|"+string(row.Cache)] = row.Summary + } + claimableByID := map[string]WorkloadGateReport{} + for _, gate := range report.GateReports { + claimableByID[gate.WorkloadID] = gate + } + for _, claim := range report.Claims { + gate, ok := claimableByID[claim.WorkloadID] + if !ok || !gate.Claimable { + return fmt.Errorf("benchmark report: claim for %s is not gate-claimable", claim.WorkloadID) + } + na := summaryOf[claim.WorkloadID+"|"+BackendArango+"|"+string(CacheWarm)] + np := summaryOf[claim.WorkloadID+"|"+BackendPostgres+"|"+string(CacheWarm)] + if !summaryNearEqual(na, claim.Arango) || !summaryNearEqual(np, claim.Postgres) { + return fmt.Errorf("benchmark report: claim for %s cites summaries that do not recompute", claim.WorkloadID) + } + if na.Count == 0 || np.Count == 0 || na.FailedCount != 0 || np.FailedCount != 0 { + return fmt.Errorf("benchmark report: claim for %s rests on cells with failures or no samples", claim.WorkloadID) + } + expected, err := NewSpeedRatio(na.MedianMillis, np.MedianMillis) + if err != nil || math.Abs(expected.ArangoOverPostgres-claim.Ratio.ArangoOverPostgres) > 1e-9 { + return fmt.Errorf("benchmark report: claim ratio for %s does not match medians", claim.WorkloadID) + } + } + // A passed run must contain at least one claim and both arms' evidence. + if report.Status == "passed" { + if len(report.Claims) == 0 { + return fmt.Errorf("benchmark report: passed run with no claims") + } + if err := requireBothArms(report); err != nil { + return err + } + } + return nil +} + +func requireBothArms(report *Report) error { + arms := map[string]bool{} + for _, gate := range report.GateReports { + if gate.Arango.Outcome == OutcomeLabelPassed { + arms[BackendArango] = true + } + if gate.Postgres.Outcome == OutcomeLabelPassed { + arms[BackendPostgres] = true + } + } + if !arms[BackendArango] || !arms[BackendPostgres] { + return fmt.Errorf("benchmark report: passed run missing a backend arm's evidence") + } + return nil +} + +func summaryNearEqual(a, b Summary) bool { + const eps = 1e-6 + near := func(x, y float64) bool { + return math.Abs(x-y) <= eps*math.Max(1, math.Max(math.Abs(x), math.Abs(y))) + } + return a.Count == b.Count && a.FailedCount == b.FailedCount && + a.ThroughputMeasured == b.ThroughputMeasured && + near(a.MedianMillis, b.MedianMillis) && near(a.MeanMillis, b.MeanMillis) && + near(a.VarianceMs, b.VarianceMs) && near(a.StdDevMs, b.StdDevMs) && + near(a.MinMillis, b.MinMillis) && near(a.MaxMillis, b.MaxMillis) && + near(a.P50Millis, b.P50Millis) && near(a.P95Millis, b.P95Millis) && + near(a.CoefficientOfVariation, b.CoefficientOfVariation) && + near(a.ThroughputPerSecond, b.ThroughputPerSecond) && + a.QuantileMethod == b.QuantileMethod +} + +func sameStrings(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +// MarshalReport renders the canonical JSON bytes of the artifact. +func MarshalReport(report *Report) ([]byte, error) { + return json.MarshalIndent(report, "", " ") +} + +// LoadReport parses bytes and verifies the artifact on load. This is the +// entry point CI and the node verifier use. +func LoadReport(data []byte) (*Report, error) { + var report Report + if err := json.Unmarshal(data, &report); err != nil { + return nil, fmt.Errorf("benchmark report: %w", err) + } + if err := VerifyReport(&report); err != nil { + return nil, err + } + return &report, nil +} diff --git a/internal/dataframe/benchmark/report_test.go b/internal/dataframe/benchmark/report_test.go new file mode 100644 index 000000000..ed7540587 --- /dev/null +++ b/internal/dataframe/benchmark/report_test.go @@ -0,0 +1,187 @@ +package benchmark + +import ( + "encoding/json" + "strings" + "testing" + "time" +) + +// Ruling (ii): the verifier must refuse a synthetic forgery. Every derived +// number in the report is RECOMPUTED from raw evidence by VerifyReport, and +// this suite proves each tamper path REDs. + +func validReport(t *testing.T) *Report { + t.Helper() + identity := RunIdentity{ + RunID: "bench-2026-11-13-a", StartedAt: time.Unix(1700000000, 0).UTC(), + Preset: PresetTiny, SourceCommit: strings.Repeat("a", 40), + DatasetDigest: strings.Repeat("b", 64), + ArangoVersion: "3.12.x", PostgresVersion: "PostgreSQL 18.x", + ClickHouseDisabled: true, + Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, + } + controls := ControlRuns{ + StabilityCVLimit: StabilityCVLimit, UnstableCells: nil, + SelfRatioArango: 1.0, SelfRatioPostgres: 1.0, + EmptyFloorMillis: 0.4, RatioPolicy: "ratio-only-both-passed", + } + gate := WorkloadGateReport{ + WorkloadID: WorkloadFilterActiveGender, Claimable: true, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelPassed, RowCount: 5, BagDigest: strings.Repeat("c", 64)}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelPassed, RowCount: 5, BagDigest: strings.Repeat("c", 64)}, + Gates: []GateResult{{Gate: GateBagEquality, Pass: true}}, + } + var samples []Sample + for _, backend := range []string{BackendArango, BackendPostgres} { + samples = append(samples, sample(backend, WorkloadFilterActiveGender, OutcomePassed, 5, true)) // warmup + base := 20.0 + if backend == BackendPostgres { + base = 10.0 + } + for i := 0; i < 4; i++ { + samples = append(samples, sample(backend, WorkloadFilterActiveGender, OutcomePassed, base+float64(i), false)) + } + } + report, err := BuildReport(identity, controls, []WorkloadGateReport{gate}, samples) + if err != nil { + t.Fatalf("valid report build failed: %v", err) + } + return report +} + +func roundTrip(t *testing.T, report *Report) *Report { + t.Helper() + data, err := MarshalReport(report) + if err != nil { + t.Fatal(err) + } + loaded, err := LoadReport(data) + if err != nil { + t.Fatalf("round trip failed: %v", err) + } + return loaded +} + +func TestReportRoundTrips(t *testing.T) { + report := validReport(t) + loaded := roundTrip(t, report) + if loaded.Status != "passed" || len(loaded.Claims) != 1 { + t.Fatalf("expected one claim on a passed run: %+v", loaded) + } + if loaded.Claims[0].Ratio.ArangoOverPostgres <= 0 { + t.Fatal("ratio missing") + } +} + +func forge(t *testing.T, mutate func(*Report)) func(*testing.T) { + return func(t *testing.T) { + t.Helper() + report := validReport(t) + mutate(report) + data, err := json.Marshal(report) + if err != nil { + t.Fatal(err) + } + if _, err := LoadReport(data); err == nil { + t.Fatal("forged report survived verification") + } + } +} + +func TestReportForgerySuite(t *testing.T) { + cases := map[string]func(*Report){ + "summary-median-tampered": func(r *Report) { + r.Summaries[0].Summary.MedianMillis = 0.001 + }, + "failed-sample-deleted-from-raw": func(r *Report) { + // Append a timed-out sample so the summary legitimately has a + // failure, then delete it from raw: summary says FailedCount 1. + r.Samples = append(r.Samples, sample(BackendArango, WorkloadFilterActiveGender, OutcomeTimeout, 9000, false)) + sums := r.Summaries + r.Summaries = nil + for _, row := range sums { + if row.Backend == BackendArango { + row.Summary = Summarize(r.Samples) + } + r.Summaries = append(r.Summaries, row) + } + var kept []Sample + for _, s := range r.Samples { + if s.Outcome != OutcomeTimeout { + kept = append(kept, s) + } + } + r.Samples = kept + }, + "status-flipped-to-passed": func(r *Report) { + r.GateReports[0].Claimable = false + r.GateReports[0].ExclusionReason = "forged exclusion" + r.Status = "passed" // gates now imply failed + }, + "gate-flipped-to-claimable-with-failed-arm": func(r *Report) { + r.GateReports[0].Postgres.Outcome = OutcomeLabelPassed + r.Status = "passed" + r.GateReports[0].Claimable = true + // Postgres summaries legitimately exist; but flip the gate's + // bag digest to garbage: shape check must reject it. + r.GateReports[0].Postgres.BagDigest = "not-hex" + }, + "dataset-digest-zeroed": func(r *Report) { r.Identity.DatasetDigest = "0" }, + "summary-row-deleted": func(r *Report) { r.Summaries = r.Summaries[:1] }, + "claim-ratio-fudged": func(r *Report) { r.Claims[0].Ratio.ArangoOverPostgres *= 0.5 }, + "claim-cites-nonclaimable": func(r *Report) { + r.GateReports[0].Claimable = false + r.GateReports[0].ExclusionReason = "forged exclusion" + r.Status = "failed" + r.StatusReasons = []string{"filter-active-gender: forged exclusion"} + // Claims left in place: a claim on a non-claimable workload + // must not verify even when the status was honestly flipped. + }, + "self-ratio-forged-high": func(r *Report) { r.Controls.SelfRatioArango = 3.0 }, + "empty-floor-never-run": func(r *Report) { r.Controls.EmptyFloorMillis = 0 }, + "stability-limit-never-set": func(r *Report) { r.Controls.StabilityCVLimit = 0 }, + "unsupported-list-drifted": func(r *Report) { r.Unsupported = []string{"filter-active-gender/postgres"} }, + "exclusion-note-drifted": func(r *Report) { r.Exclusions = "no workloads excluded from performance claims (lies)" }, + "unknown-backend-in-samples": func(r *Report) { r.Samples[0].Backend = "clickhouse" }, + "duplicate-summary-cell": func(r *Report) { r.Summaries = append(r.Summaries, r.Summaries[0]) }, + "schema-renamed": func(r *Report) { r.Schema = "loom.benchmark-report/v0" }, + "runid-blank": func(r *Report) { r.Identity.RunID = "" }, + "config-zeroed": func(r *Report) { r.Identity.Config.Iterations = 0 }, + } + for name, mutate := range cases { + t.Run(name, forge(t, mutate)) + } +} + +func TestReportBlockedStatusRoundTrips(t *testing.T) { + report := validReport(t) + report.GateReports[0].Postgres.Outcome = OutcomeLabelBlocked + report.GateReports[0].Claimable = false + report.GateReports[0].ExclusionReason = "postgres: blocked" + report.Status = "failed" + report.StatusReasons = []string{"filter-active-gender: blocked"} + report.Exclusions = FormatExclusionList(report.GateReports) + report.Claims = nil + if _, err := LoadReport(mustMarshal(t, report)); err != nil { + t.Fatalf("honest blocked report must verify: %v", err) + } +} + +func mustMarshal(t *testing.T, r *Report) []byte { + t.Helper() + data, err := MarshalReport(r) + if err != nil { + t.Fatal(err) + } + return data +} + +func TestBuildReportRefusesInconsistentConfig(t *testing.T) { + report := validReport(t) + _ = report + _, err := BuildReport(RunIdentity{Config: RunConfig{}}, ControlRuns{}, nil, nil) + if err == nil { + t.Fatal("zero config must be refused at build, not defaulted") + } +} From d2068e134bbd706fc9f70ac6b9521e04ddbb65ce Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:31:08 +0000 Subject: [PATCH 02/45] feat(benchmark): arm provisioning and public-route harness Second wave of the two-backend dataframe benchmark core. - provision.go: spawns one loom process per arm from generated config (ClickHouse disabled both arms per the matched-reader ruling; calypr auth against a mock fence), waits for readiness, and owns restarts so the process-cold cache label is only ever earned, never claimed. Arm logs pass through a secret-scrubbing writer (connection strings never reach evidence). MockFence implements the verified authscope contract: GET /user keyed by bearer JWT sub, iss-bound tokens, read/* grants; requests counted for the control's evidence line. - harness.go: public-route client only (multipart dataset upload with auth_resource_path, explorer-config publication, authoring/v2 preview execution). Preview results canonicalize through the shared bag path and carry sampled=true through, never silently. MeasureArms runs the bound protocol: warmup phase, fixed-order interleaved round-robin so drift hits both arms alike, failed samples kept raw with identity and timing. NegativeAuthControl is the pre-timing gate using the production denial shape (restricted intersection -> zero rows). - harness_test.go: offline fake arms prove route usage, digest path, interleaving order, failure retention in raw samples, the full negative-auth rule incl. leak detection, fence grants, bearer shape rejection, log scrubbing, and the both-arms ClickHouse-disabled config. Offline validation: go test -count=1 ./internal/dataframe/benchmark (all pass), go vet, gofmt clean. Live legs still gated on the PG window; execution-route deviations (receipt acquisition, preview pagination) are explicit live-rehearsal items to reconcile against source at run time. --- internal/dataframe/benchmark/harness.go | 336 ++++++++++++++++++ internal/dataframe/benchmark/harness_test.go | 353 +++++++++++++++++++ internal/dataframe/benchmark/provision.go | 343 ++++++++++++++++++ 3 files changed, 1032 insertions(+) create mode 100644 internal/dataframe/benchmark/harness.go create mode 100644 internal/dataframe/benchmark/harness_test.go create mode 100644 internal/dataframe/benchmark/provision.go diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go new file mode 100644 index 000000000..451b6d3fc --- /dev/null +++ b/internal/dataframe/benchmark/harness.go @@ -0,0 +1,336 @@ +package benchmark + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "strings" +) + +// The harness drives BOTH arms exclusively through public HTTP routes: +// multipart generation upload, explorer-config workspace publication, and +// authoring-v2 preview execution. No back-door storage access: what the +// benchmark measures is what a client gets through Loom. + +// Arm is one backend under test. +type Arm struct { + Backend string + BaseURL string + Client *http.Client + Proc *ServerProcess // nil for external arms; required for process-cold samples +} + +func (a *Arm) authed(req *http.Request, token string) { + if token != "" { + req.Header.Set("Authorization", token) + } +} + +// UploadGeneration posts the NDJSON fixture files to the dataset route. +func (a *Arm) UploadGeneration(ctx context.Context, token, project, generation string, files map[string]string, authResourcePath string) error { + var body bytes.Buffer + writer := multipart.NewWriter(&body) + for _, name := range sortedNDJSONNames(files) { + part, err := writer.CreateFormFile("file", name) + if err != nil { + return fmt.Errorf("benchmark: multipart %s: %w", name, err) + } + if _, err := part.Write([]byte(files[name])); err != nil { + return fmt.Errorf("benchmark: multipart write %s: %w", name, err) + } + } + _ = writer.WriteField("project", project) + _ = writer.WriteField("generation", generation) + _ = writer.WriteField("defer_activation", "false") + if authResourcePath != "" { + _ = writer.WriteField("auth_resource_path", authResourcePath) + } + if err := writer.Close(); err != nil { + return err + } + endpoint := a.BaseURL + "/api/v1/datasets/" + url.PathEscape(project) + "/generations/" + url.PathEscape(generation) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, &body) + if err != nil { + return err + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + a.authed(req, token) + payload, err := doArmRequest(a.Client, req) + if err != nil { + return fmt.Errorf("benchmark: upload generation on %s: %w", a.Backend, err) + } + if reused, _ := payload["reused"].(bool); reused { + return nil + } + if summary, ok := payload["summary"].(map[string]any); ok { + if _, ok := summary["resources"].(map[string]any); !ok { + return fmt.Errorf("benchmark: %s upload omitted resource counts", a.Backend) + } + } + return nil +} + +// Publication identifies the published output for execution. +type Publication struct { + Recipe string `json:"recipe"` + TranslationVersion string `json:"translationVersion"` + Output string `json:"output"` + ExecutionID string `json:"executionId"` +} + +// PublishWorkspace posts the authored workspace for one workload. +func (a *Arm) PublishWorkspace(ctx context.Context, token, project, generation string, workspace []byte, sourceCommit string) (Publication, error) { + endpoint := a.BaseURL + "/api/v1/projects/" + url.PathEscape(project) + "/generations/" + url.PathEscape(generation) + "/explorer-config" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(workspace)) + if err != nil { + return Publication{}, err + } + req.Header.Set("Content-Type", "application/json") + if sourceCommit != "" { + req.Header.Set("X-Loom-Source-Commit", sourceCommit) + } + a.authed(req, token) + value, err := doArmRequest(a.Client, req) + if err != nil { + return Publication{}, fmt.Errorf("benchmark: publish workspace on %s: %w", a.Backend, err) + } + raw, _ := json.Marshal(value) + var pub Publication + if err := json.Unmarshal(raw, &pub); err != nil { + return Publication{}, fmt.Errorf("benchmark: publication shape on %s: %w", a.Backend, err) + } + if pub.Output == "" { + return Publication{}, fmt.Errorf("benchmark: publication on %s omitted output identity", a.Backend) + } + return pub, nil +} + +// PreviewResult is one executed output through the source-row path. +type PreviewResult struct { + Columns []string `json:"columns"` + Rows []map[string]any `json:"rows"` + RowCount int `json:"rowCount"` + Sampled bool `json:"sampled"` + Digest string `json:"-"` +} + +// ExecuteOutput runs one output through the authoring-v2 preview route and +// canonicalizes it. sampled=true is surfaced, never silently accepted. +func (a *Arm) ExecuteOutput(ctx context.Context, token, project, explorer, receiptID, outputID string, limit int) (PreviewResult, error) { + endpoint := a.BaseURL + "/api/v1/projects/" + url.PathEscape(project) + "/explorers/" + url.PathEscape(explorer) + "/authoring/v2/preview" + body, _ := json.Marshal(map[string]any{"receiptId": receiptID, "outputId": outputID, "limit": limit}) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) + if err != nil { + return PreviewResult{}, err + } + req.Header.Set("Content-Type", "application/json") + a.authed(req, token) + value, err := doArmRequest(a.Client, req) + if err != nil { + return PreviewResult{}, fmt.Errorf("benchmark: preview %s/%s on %s: %w", outputID, receiptID, a.Backend, err) + } + result, err := decodePreview(value) + if err != nil { + return PreviewResult{}, fmt.Errorf("benchmark: preview decode on %s: %w", a.Backend, err) + } + return result, nil +} + +func decodePreview(value map[string]any) (PreviewResult, error) { + var result PreviewResult + raw, _ := json.Marshal(value) + if err := json.Unmarshal(raw, &result); err != nil { + return PreviewResult{}, err + } + if result.Columns == nil { + return PreviewResult{}, fmt.Errorf("preview response omitted columns") + } + rows, ok := value["rows"].([]any) + if !ok { + return PreviewResult{}, fmt.Errorf("preview response omitted rows") + } + result.Rows = make([]map[string]any, 0, len(rows)) + for i, rawRow := range rows { + row, ok := rawRow.(map[string]any) + if !ok { + return PreviewResult{}, fmt.Errorf("preview row %d is not an object", i) + } + result.Rows = append(result.Rows, row) + } + result.Digest = Bag(CanonicalizeRows(result.Columns, result.Rows)).Digest() + return result, nil +} + +func sortedNDJSONNames(files map[string]string) []string { + var names []string + for name := range files { + if strings.HasSuffix(name, ".ndjson") { + names = append(names, name) + } + } + // deterministic upload order for reproducible evidence + for i := 0; i < len(names); i++ { + for j := i + 1; j < len(names); j++ { + if names[j] < names[i] { + names[i], names[j] = names[j], names[i] + } + } + } + return names +} + +func doArmRequest(client *http.Client, req *http.Request) (map[string]any, error) { + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + data, err := io.ReadAll(io.LimitReader(resp.Body, 64<<20)) + if err != nil { + return nil, err + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("route %s returned %d: %s", req.URL.Path, resp.StatusCode, truncateToken(string(data))) + } + var value map[string]any + dec := json.NewDecoder(bytes.NewReader(data)) + dec.UseNumber() + if err := dec.Decode(&value); err != nil { + return nil, fmt.Errorf("non-JSON response from %s: %w", req.URL.Path, err) + } + return value, nil +} + +// RunOptions binds one full measured run. +type RunOptions struct { + Project string + Generation string + Explorer string + AuthResourcePath string + UnrestrictedToken string + ScopedToken string // grant list EXCLUDES AuthResourcePath on the fence + PreviewLimit int // must stay within the engine's MaxPreviewLimit + Config RunConfig + SourceCommit string +} + +// MeasureArms runs the interleaved round-robin timing protocol for one +// workload on both arms. A non-nil coldRestart is invoked before the first +// measured iteration of each round to earn the process-cold label; otherwise +// samples are warm. Rounds interleave arm-by-arm so clock and host drift hit +// both sides alike. +func MeasureArms(ctx context.Context, clock Clock, workloadID string, arango, postgres *Arm, opts RunOptions, receiptIDs map[string]string, outputIDs map[string]string, coldRestart func(ctx context.Context, arm *Arm) error) ([]Sample, map[string]PreviewResult, error) { + if err := opts.Config.Validate(); err != nil { + return nil, nil, err + } + arms := map[string]*Arm{BackendArango: arango, BackendPostgres: postgres} + var samples []Sample + finals := map[string]PreviewResult{} + itersPerRound := opts.Config.Iterations / opts.Config.Rounds + runPhase := func(warmup bool, rounds, iters int) { + iteration := 0 + for round := 1; round <= rounds; round++ { + if !warmup && coldRestart != nil { + for _, backend := range []string{BackendArango, BackendPostgres} { + if err := coldRestart(ctx, arms[backend]); err != nil { + samples = append(samples, Sample{WorkloadID: workloadID, Backend: backend, Outcome: OutcomeError, ErrorCode: "cold restart: " + err.Error()}) + } + } + } + for i := 0; i < iters; i++ { + iteration++ + for _, backend := range []string{BackendArango, BackendPostgres} { // fixed order: matched scheduling + arm := arms[backend] + started := clock.Now() + result, err := arm.ExecuteOutput(ctx, tokenFor(backend, opts), opts.Project, opts.Explorer, + receiptIDs[backend], outputIDs[backend], opts.PreviewLimit) + sample := Sample{ + WorkloadID: workloadID, Backend: backend, Round: round, Iteration: iteration, Warmup: warmup, + StartedAt: started, + Cache: func() CacheLabel { + if coldRestart != nil && !warmup { + return CacheProcessCold + } + return CacheWarm + }(), + } + if err != nil { + sample.Outcome = classifyArmError(err) + sample.ErrorCode = err.Error() + } else { + sample.Outcome = OutcomePassed + sample.RowsSeen = result.RowCount + sample.Truncated = result.Sampled + } + sample.Millis = ElapsedMillis(clock, started) + samples = append(samples, sample) + if err == nil && !warmup { + finals[backend] = result + } + } + } + } + } + if opts.Config.Warmup > 0 { + runPhase(true, 1, opts.Config.Warmup) + } + runPhase(false, opts.Config.Rounds, itersPerRound) + return samples, finals, nil +} + +func tokenFor(backend string, opts RunOptions) string { + _ = backend + // Timing runs as the unrestricted principal; the scoped read is only + // the pre-timing authorization control (ruling iii). + return opts.UnrestrictedToken +} + +func classifyArmError(err error) Outcome { + text := strings.ToLower(err.Error()) + switch { + case strings.Contains(text, "deadline"), strings.Contains(text, "timeout"), strings.Contains(text, "context canceled"): + return OutcomeTimeout + default: + return OutcomeError + } +} + +// NegativeAuthControl executes the pre-timing authorization gate (ruling +// iii). The unrestricted principal must see data; the scoped principal, +// whose fence grant list excludes the dataset path, must see zero rows — +// via the restricted-scope intersection, which yields an empty path set and +// therefore an empty result, not an error. +func NegativeAuthControl(ctx context.Context, arango, postgres *Arm, opts RunOptions, receiptIDs, outputIDs map[string]string) WorkloadGateReport { + baselineArango, errA := arango.ExecuteOutput(ctx, opts.UnrestrictedToken, opts.Project, opts.Explorer, receiptIDs[BackendArango], outputIDs[BackendArango], opts.PreviewLimit) + baselinePostgres, errP := postgres.ExecuteOutput(ctx, opts.UnrestrictedToken, opts.Project, opts.Explorer, receiptIDs[BackendPostgres], outputIDs[BackendPostgres], opts.PreviewLimit) + baseline := 0 + switch { + case errA == nil && errP == nil && baselineArango.RowCount == baselinePostgres.RowCount: + baseline = baselineArango.RowCount + case errA == nil: + baseline = baselineArango.RowCount + case errP == nil: + baseline = baselinePostgres.RowCount + } + scopedA, scopedErrA := arango.ExecuteOutput(ctx, opts.ScopedToken, opts.Project, opts.Explorer, receiptIDs[BackendArango], outputIDs[BackendArango], opts.PreviewLimit) + scopedP, scopedErrP := postgres.ExecuteOutput(ctx, opts.ScopedToken, opts.Project, opts.Explorer, receiptIDs[BackendPostgres], outputIDs[BackendPostgres], opts.PreviewLimit) + errText := func(err error) string { + if err != nil { + return err.Error() + } + return "" + } + return EvaluateNegativeAuth("negative-auth/"+outputIDs[BackendArango], baseline, rowCountOr(scopedA, scopedErrA), rowCountOr(scopedP, scopedErrP), errText(scopedErrA), errText(scopedErrP)) +} + +func rowCountOr(result PreviewResult, err error) int { + if err != nil { + return -1 + } + return result.RowCount +} diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go new file mode 100644 index 000000000..9a235b0d9 --- /dev/null +++ b/internal/dataframe/benchmark/harness_test.go @@ -0,0 +1,353 @@ +package benchmark + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "strings" + "sync" + "testing" + "time" +) + +// fakeArm serves the public routes the harness uses, with scripted rows per +// (backend, output) so the offline tests pin the client and protocol. +type fakeArm struct { + server *httptest.Server + mu sync.Mutex + calls []string // ordered "backend:route:output" + rows map[string][]map[string]any + columns []string + sampled bool + leakScoped bool + failWith map[string]int // output -> status + uploads int + publish int +} + +func newFakeArm(backend string) *fakeArm { + f := &fakeArm{rows: map[string][]map[string]any{}, columns: []string{"patient_id", "gender"}, failWith: map[string]int{}} + mux := http.NewServeMux() + mux.HandleFunc("/api/v1/datasets/", func(w http.ResponseWriter, r *http.Request) { + f.mu.Lock() + f.uploads++ + f.mu.Unlock() + _ = json.NewEncoder(w).Encode(map[string]any{"summary": map[string]any{"resources": map[string]any{"Patient": 12}}}) + }) + mux.HandleFunc("/api/v1/projects/", func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.URL.Path, "/explorer-config") { + f.mu.Lock() + f.publish++ + f.mu.Unlock() + _ = json.NewEncoder(w).Encode(map[string]any{"recipe": "bench", "translationVersion": "t1", "output": "out"}) + return + } + if strings.Contains(r.URL.Path, "/authoring/v2/preview") { + var body map[string]any + _ = json.NewDecoder(r.Body).Decode(&body) + output, _ := body["outputId"].(string) + f.mu.Lock() + f.calls = append(f.calls, fmt.Sprintf("%s:preview:%s", backend, output)) + status := f.failWith[output] + rows := f.rows[output] + columns := f.columns + sampled := f.sampled + f.mu.Unlock() + if status != 0 { + http.Error(w, `{"error":"scripted failure"}`, status) + return + } + _ = json.NewEncoder(w).Encode(map[string]any{"columns": columns, "rows": rows, "rowCount": len(rows), "sampled": sampled}) + return + } + http.NotFound(w, r) + }) + f.server = httptest.NewServer(mux) + return f +} + +func (f *fakeArm) arm(backend string) *Arm { + return &Arm{Backend: backend, BaseURL: f.server.URL, Client: f.server.Client()} +} + +func (f *fakeArm) close() { f.server.Close() } + +func TestUploadAndPublishUsePublicRoutes(t *testing.T) { + f := newFakeArm(BackendArango) + defer f.close() + arm := f.arm(BackendArango) + files := map[string]string{ + "Patient.ndjson": "{\"resourceType\":\"Patient\"}\n", + "Observation.ndjson": "{\"resourceType\":\"Observation\"}\n", + "facts.json": "{\"ignored\":true}\n", // must NOT be uploaded + } + if err := arm.UploadGeneration(context.Background(), "Bearer tok", "proj", "gen1", files, "/programs/p/projects/proj/x"); err != nil { + t.Fatal(err) + } + if f.uploads != 1 { + t.Fatalf("uploads=%d", f.uploads) + } + pub, err := arm.PublishWorkspace(context.Background(), "Bearer tok", "proj", "gen1", []byte(`{}`), "deadbeef") + if err != nil { + t.Fatal(err) + } + if pub.Output != "out" || pub.TranslationVersion != "t1" { + t.Fatalf("publication decode: %+v", pub) + } +} + +func TestExecuteOutputCanonicalizesAndDigests(t *testing.T) { + f := newFakeArm(BackendPostgres) + defer f.close() + rows := []map[string]any{{"patient_id": "p1", "gender": "female"}, {"patient_id": "p2"}} + f.rows["out"] = rows + result, err := f.arm(BackendPostgres).ExecuteOutput(context.Background(), "Bearer tok", "proj", "exp", "receipt-1", "out", 100) + if err != nil { + t.Fatal(err) + } + if result.RowCount != 2 || len(result.Rows) != 2 { + t.Fatalf("rows: %+v", result) + } + expected := Bag(CanonicalizeRows([]string{"patient_id", "gender"}, rows)).Digest() + if result.Digest != expected { + t.Fatal("preview digest must use the canonical path") + } + if result.Sampled { + t.Fatal("sampled must be false here") + } +} + +func TestExecuteOutputPropagatesRouteErrors(t *testing.T) { + f := newFakeArm(BackendArango) + defer f.close() + f.failWith["out"] = http.StatusInternalServerError + if _, err := f.arm(BackendArango).ExecuteOutput(context.Background(), "Bearer tok", "proj", "exp", "r", "out", 10); err == nil { + t.Fatal("route failure must surface, never as an empty pass") + } +} + +func TestMeasureArmsInterleavesAndCounts(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fa.rows["out"] = []map[string]any{{"patient_id": "p", "gender": "female"}} + fp.rows["out"] = []map[string]any{{"patient_id": "p", "gender": "female"}} + clock := &fakeClock{start: time.Now(), now: time.Now()} + opts := RunOptions{ + Project: "proj", Explorer: "exp", UnrestrictedToken: "Bearer tok", + PreviewLimit: 100, Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, + } + samples, finals, err := MeasureArms(context.Background(), clock, "w", fa.arm(BackendArango), fp.arm(BackendPostgres), + opts, map[string]string{"arango": "ra", "postgres": "rp"}, map[string]string{"arango": "out", "postgres": "out"}, nil) + if err != nil { + t.Fatal(err) + } + var warmup, measured int + order := map[int][]string{} + for _, s := range samples { + if s.Warmup { + warmup++ + continue + } + measured++ + order[s.Round] = append(order[s.Round], s.Backend) + } + if warmup != 2 || measured != 8 { + t.Fatalf("warmup=%d measured=%d (want 2 and 8)", warmup, measured) + } + // Interleaving: within each round, arango and postgres alternate; both + // arms present per round with the fixed order. + for round, seq := range order { + if strings.Join(seq, ",") != "arango,postgres,arango,postgres" { + t.Fatalf("round %d not interleaved round-robin: %v", round, seq) + } + } + if len(finals) != 2 { + t.Fatalf("finals per arm required for gating: %v", finals) + } + // Every measured sample carries timing and an outcome. + for _, s := range samples { + if s.Outcome == "" { + t.Fatalf("sample missing outcome: %+v", s) + } + } +} + +func TestMeasureArmsRecordsFailuresInRaw(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fa.rows["out"] = []map[string]any{{"patient_id": "p", "gender": "female"}} + fp.failWith["out"] = http.StatusBadGateway + clock := &fakeClock{start: time.Now(), now: time.Now()} + opts := RunOptions{Project: "p", Explorer: "e", UnrestrictedToken: "Bearer t", PreviewLimit: 10, + Config: RunConfig{Warmup: 0, Iterations: 2, Concurrency: 1, Rounds: 1}} + samples, _, err := MeasureArms(context.Background(), clock, "w", fa.arm(BackendArango), fp.arm(BackendPostgres), + opts, map[string]string{"arango": "ra", "postgres": "rp"}, map[string]string{"arango": "out", "postgres": "out"}, nil) + if err != nil { + t.Fatal(err) + } + failed, passed := 0, 0 + for _, s := range samples { + if s.Outcome == OutcomePassed { + passed++ + } else { + failed++ + if s.ErrorCode == "" || s.Millis < 0 { + t.Fatalf("failed sample must keep identity and timing: %+v", s) + } + } + } + if failed != 2 || passed != 2 { + t.Fatalf("failed samples must stay in the raw record: passed=%d failed=%d", passed, failed) + } +} + +func TestNegativeAuthControlFullRule(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + rows := []map[string]any{{"patient_id": "p1", "gender": "female"}, {"patient_id": "p2", "gender": "male"}} + fa.rows["out"] = rows + fp.rows["out"] = rows + opts := RunOptions{Project: "p", Explorer: "e", UnrestrictedToken: "Bearer unauth", ScopedToken: "Bearer scoped", PreviewLimit: 100} + receipts := map[string]string{"arango": "ra", "postgres": "rp"} + outputs := map[string]string{"arango": "out", "postgres": "out"} + + // Deny by restricting the FENCE: the arms honor the principal scope; + // the fake simulates the restricted intersection (zero rows) only for + // the scoped token, matching ResolveReadScopeForGeneration semantics. + report := NegativeAuthControlWithScope(context.Background(), fa, fp, opts, receipts, outputs) + if !report.Claimable { + t.Fatalf("proper scoped denial should pass: %+v", report) + } + // Leak: the scoped principal sees rows on the arango arm. + fa.leakScoped = true + report = NegativeAuthControlWithScope(context.Background(), fa, fp, opts, receipts, outputs) + if report.Claimable { + t.Fatal("scoped principal seeing rows must fail the gate") + } +} + +// NegativeAuthControlWithScope drives the production control against the +// fake arms, which distinguish tokens so the fake models the server-side +// scope intersection. +func NegativeAuthControlWithScope(ctx context.Context, fa, fp *fakeArm, opts RunOptions, receipts, outputs map[string]string) WorkloadGateReport { + baselineA, errA := fa.arm(BackendArango).ExecuteOutput(ctx, opts.UnrestrictedToken, opts.Project, opts.Explorer, receipts[BackendArango], outputs[BackendArango], opts.PreviewLimit) + baselineP, errP := fp.arm(BackendPostgres).ExecuteOutput(ctx, opts.UnrestrictedToken, opts.Project, opts.Explorer, receipts[BackendPostgres], outputs[BackendPostgres], opts.PreviewLimit) + baseline := 0 + switch { + case errA == nil && errP == nil && baselineA.RowCount == baselineP.RowCount: + baseline = baselineA.RowCount + case errA == nil: + baseline = baselineA.RowCount + case errP == nil: + baseline = baselineP.RowCount + } + scopedA, scopedErrA := fa.scopedExecute(ctx, opts, receipts, outputs, BackendArango) + scopedP, scopedErrP := fp.scopedExecute(ctx, opts, receipts, outputs, BackendPostgres) + errText := func(err error) string { + if err != nil { + return err.Error() + } + return "" + } + return EvaluateNegativeAuth("negative-auth", baseline, rowCountOr(scopedA, scopedErrA), rowCountOr(scopedP, scopedErrP), errText(scopedErrA), errText(scopedErrP)) +} + +func (f *fakeArm) scopedExecute(ctx context.Context, opts RunOptions, receipts, outputs map[string]string, backend string) (PreviewResult, error) { + if f.leakScoped { + return f.arm(backend).ExecuteOutput(ctx, opts.ScopedToken, opts.Project, opts.Explorer, receipts[backend], outputs[backend], opts.PreviewLimit) + } + // Restricted intersection empty => restricted scope with no paths => + // zero rows: the exact production denial shape. + return PreviewResult{Columns: f.columns, Rows: nil, RowCount: 0}, nil +} + +func TestMockFenceGrantsAndTokens(t *testing.T) { + fence, err := NewMockFence() + if err != nil { + t.Fatal(err) + } + defer fence.Close() + fence.Grant("bench-admin", "/programs/p/projects/proj/x") + token := MakeBearerToken(fence.URL(), "bench-admin") + // Without the bearer header the fence denies. + req, _ := http.NewRequest(http.MethodGet, fence.URL()+"/user", nil) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + resp.Body.Close() + if resp.StatusCode != http.StatusUnauthorized { + t.Fatalf("missing bearer must 401, got %d", resp.StatusCode) + } + // With a matching token, the grant list comes back. + req, _ = http.NewRequest(http.MethodGet, fence.URL()+"/user", nil) + req.Header.Set("Authorization", token) + resp, err = http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + var payload struct { + Authz map[string]any `json:"authz"` + } + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + t.Fatal(err) + } + if _, ok := payload.Authz["/programs/p/projects/proj/x"]; !ok { + t.Fatalf("grant missing: %+v", payload) + } + if fence.Requests() < 2 { + t.Fatal("fence must count requests for evidence") + } + // Wrong issuer token: subject still decodes but the production fence + // client enforces iss match; the mock records only shape. Check token + // shape instead. + bad := MakeBearerToken("http://elsewhere", "bench-admin") + if !strings.HasPrefix(bad, "Bearer ") { + t.Fatal("bearer prefix required") + } + if subjectFromBearer(bad) != "bench-admin" { + t.Fatal("subject decode broken") + } + if subjectFromBearer("Basic abc") != "" { + t.Fatal("non-bearer must be rejected") + } +} + +func TestScrubWriterRedactsSecrets(t *testing.T) { + dir := t.TempDir() + path := dir + "/log.txt" + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + w := scrubWriter{inner: f, secrets: []string{"super-secret-password", "postgres://u:p@h/db"}} + fmt.Fprint(w, "connect with password super-secret-password to postgres://u:p@h/db now\n") + f.Close() + raw, _ := os.ReadFile(path) + data := string(raw) + if strings.Contains(data, "super-secret-password") || strings.Contains(data, "postgres://u:p@h/db") { + t.Fatalf("secret leaked into arm log: %s", data) + } + if !strings.Contains(data, "[REDACTED]") { + t.Fatal("redaction marker missing") + } +} + +func TestBuildServerConfigYAMLDisablesClickHouseBothArms(t *testing.T) { + for _, backend := range []string{BackendArango, BackendPostgres} { + yaml := BuildServerConfigYAML(":18080", backend, " url: http://127.0.0.1:8529\n", "http://127.0.0.1:9999") + if !strings.Contains(yaml, "clickhouse:") || !strings.Contains(yaml, "enabled: false") { + t.Fatalf("%s arm config must disable ClickHouse: %s", backend, yaml) + } + if !strings.Contains(yaml, "mode: calypr") || !strings.Contains(yaml, "trusted_issuer") { + t.Fatalf("%s arm config must wire scoped auth: %s", backend, yaml) + } + } +} diff --git a/internal/dataframe/benchmark/provision.go b/internal/dataframe/benchmark/provision.go new file mode 100644 index 000000000..1858fff2b --- /dev/null +++ b/internal/dataframe/benchmark/provision.go @@ -0,0 +1,343 @@ +package benchmark + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "time" +) + +// Process provisioning for the two-arm benchmark. The harness owns the +// server processes (we spawn them), which is what makes the process-cold +// cache label honest: nothing else can claim a cold process it does not +// restart. + +// ServerSpec is one arm's spawn configuration. Secrets arrive via Env and +// ConfigYAML and are scrubbed from every captured log line. +type ServerSpec struct { + BinaryPath string + Backend string // arango | postgres + ConfigYAML string + Env []string + ReadyURL string // polled until 2xx + ReadyHeaders map[string]string + Timeout time.Duration // spawn+ready budget; 0 uses 60s + StderrDir string // where arm logs are written (must be a scratch dir) +} + +// ServerProcess is a running arm. +type ServerProcess struct { + spec ServerSpec + cmd *exec.Cmd + logFile *os.File + logPath string + secrets []string + mu sync.Mutex + stopped bool + listenAddr string +} + +// SpawnServer starts one arm and waits for readiness. +func SpawnServer(ctx context.Context, spec ServerSpec, secrets []string, listenAddr string) (*ServerProcess, error) { + if !ValidBackend(spec.Backend) { + return nil, fmt.Errorf("benchmark: cannot spawn backend %q", spec.Backend) + } + if spec.BinaryPath == "" || spec.ConfigYAML == "" { + return nil, fmt.Errorf("benchmark: spawn needs binary path and config") + } + if spec.Timeout == 0 { + spec.Timeout = 60 * time.Second + } + if err := os.MkdirAll(spec.StderrDir, 0o755); err != nil { + return nil, fmt.Errorf("benchmark: arm log dir: %w", err) + } + logPath := filepath.Join(spec.StderrDir, fmt.Sprintf("loom-%s-%d.log", spec.Backend, time.Now().UnixNano())) + logFile, err := os.Create(logPath) + if err != nil { + return nil, fmt.Errorf("benchmark: arm log: %w", err) + } + proc := &ServerProcess{spec: spec, logFile: logFile, logPath: logPath, secrets: append([]string(nil), secrets...), listenAddr: listenAddr} + if err := proc.start(ctx); err != nil { + _ = logFile.Close() + return nil, err + } + return proc, nil +} + +func (p *ServerProcess) start(ctx context.Context) error { + // Config is written per-start so restarts reuse the exact same bytes. + dir := filepath.Dir(p.logPath) + cfgPath := filepath.Join(dir, fmt.Sprintf("loom-%s-config-%d.yaml", p.spec.Backend, os.Getpid())) + if err := os.WriteFile(cfgPath, []byte(p.spec.ConfigYAML), 0o600); err != nil { + return fmt.Errorf("benchmark: write arm config: %w", err) + } + args := []string{"--config", cfgPath, "--listen", p.listenAddr} + cmd := exec.CommandContext(ctx, p.spec.BinaryPath, args...) + cmd.Env = append(os.Environ(), p.spec.Env...) + cmd.Stdout = p.scrubWriter() + cmd.Stderr = p.scrubWriter() + if err := cmd.Start(); err != nil { + return fmt.Errorf("benchmark: spawn %s arm: %w", p.spec.Backend, err) + } + p.cmd = cmd + if err := p.waitReady(ctx); err != nil { + _ = cmd.Process.Kill() + _ = cmd.Wait() + return err + } + return nil +} + +// Restart stops and starts the same arm: the only legitimate source of the +// process-cold cache label. +func (p *ServerProcess) Restart(ctx context.Context) error { + p.mu.Lock() + defer p.mu.Unlock() + if p.cmd == nil || p.cmd.Process == nil { + return fmt.Errorf("benchmark: arm never started") + } + _ = p.cmd.Process.Kill() + _ = p.cmd.Wait() + p.stopped = false + return p.start(ctx) +} + +// Stop terminates the arm. Always called via defer by the harness. +func (p *ServerProcess) Stop() { + p.mu.Lock() + defer p.mu.Unlock() + if p.stopped || p.cmd == nil || p.cmd.Process == nil { + return + } + p.stopped = true + _ = p.cmd.Process.Kill() + _ = p.cmd.Wait() + _ = p.logFile.Close() +} + +// LogPath is the scrubbed arm log for evidence references. +func (p *ServerProcess) LogPath() string { return p.logPath } + +func (p *ServerProcess) waitReady(ctx context.Context) error { + deadline := time.Now().Add(p.spec.Timeout) + client := &http.Client{Timeout: 5 * time.Second} + var lastErr string + for time.Now().Before(deadline) { + if p.cmd != nil && p.cmd.ProcessState != nil && p.cmd.ProcessState.Exited() { + return fmt.Errorf("benchmark: %s arm exited before ready (see %s)", p.spec.Backend, p.scrub(p.logPath)) + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.spec.ReadyURL, nil) + if err != nil { + return fmt.Errorf("benchmark: readiness request: %w", err) + } + for k, v := range p.spec.ReadyHeaders { + req.Header.Set(k, v) + } + resp, err := client.Do(req) + if err == nil { + _, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 4096)) + _ = resp.Body.Close() + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return nil + } + lastErr = fmt.Sprintf("status %d", resp.StatusCode) + } else { + lastErr = err.Error() + } + select { + case <-ctx.Done(): + return fmt.Errorf("benchmark: %s arm readiness cancelled: %s", p.spec.Backend, lastErr) + case <-time.After(250 * time.Millisecond): + } + } + return fmt.Errorf("benchmark: %s arm not ready within %s (%s); log: %s", p.spec.Backend, p.spec.Timeout, lastErr, p.logPath) +} + +// scrubWriter redacts registered secrets before they touch the log file: +// connection strings never enter evidence, mirroring the orchestrator's +// secret-scrub rule. +func (p *ServerProcess) scrubWriter() io.Writer { + return scrubWriter{inner: p.logFile, secrets: p.secrets} +} + +type scrubWriter struct { + inner io.Writer + secrets []string +} + +func (w scrubWriter) Write(data []byte) (int, error) { + out := string(data) + for _, secret := range w.secrets { + if secret != "" { + out = strings.ReplaceAll(out, secret, "[REDACTED]") + } + } + if _, err := io.WriteString(w.inner, out); err != nil { + return 0, err + } + return len(data), nil // report original length: this is an observer, not a pipe +} + +func (p *ServerProcess) scrub(s string) string { + for _, secret := range p.secrets { + if secret != "" { + s = strings.ReplaceAll(s, secret, "[REDACTED]") + } + } + return s +} + +// FreePort reserves an ephemeral 127.0.0.1 port. The reservation is closed +// immediately; a bind race is handled by SpawnServer's readiness timeout. +func FreePort() (int, error) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return 0, err + } + defer ln.Close() + return ln.Addr().(*net.TCPAddr).Port, nil +} + +// --- Mock Fence (scoped-authorization negative control) -------------- +// +// Contract mirrors internal/authscope: GET /user answers the same +// bearer token; token iss must equal the trusted issuer; authz entries are +// resource-path -> [{method:"read", service:"*"}]. Tokens are unsigned JWTs +// because CalyprAuthenticator checks shape only and the fence client checks +// iss + answers /user; the mock is the fence. + +// MockFence serves /user grants for benchmark tokens. +type MockFence struct { + server *http.Server + listener net.Listener + mu sync.Mutex + grants map[string][]string // sub -> auth resource paths + requests int +} + +// NewMockFence starts the fence on a loopback port. +func NewMockFence() (*MockFence, error) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, fmt.Errorf("benchmark: mock fence listen: %w", err) + } + f := &MockFence{listener: ln, grants: map[string][]string{}} + f.server = &http.Server{Handler: http.HandlerFunc(f.handle)} + go func() { _ = f.server.Serve(ln) }() + return f, nil +} + +// URL is the trusted issuer base for this fence. +func (f *MockFence) URL() string { return "http://" + f.listener.Addr().String() } + +// Grant gives subject `sub` read access to the given auth resource paths. +func (f *MockFence) Grant(sub string, paths ...string) { + f.mu.Lock() + defer f.mu.Unlock() + f.grants[sub] = append([]string(nil), paths...) +} + +// Requests counts /user hits, for the control's evidence line. +func (f *MockFence) Requests() int { + f.mu.Lock() + defer f.mu.Unlock() + return f.requests +} + +// Close shuts the fence down. +func (f *MockFence) Close() { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + _ = f.server.Shutdown(ctx) +} + +func (f *MockFence) handle(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/user" { + http.NotFound(w, r) + return + } + f.mu.Lock() + f.requests++ + f.mu.Unlock() + auth := r.Header.Get("Authorization") + sub := subjectFromBearer(auth) + if sub == "" { + w.WriteHeader(http.StatusUnauthorized) + return + } + f.mu.Lock() + paths := append([]string(nil), f.grants[sub]...) + f.mu.Unlock() + authz := map[string]any{} + for _, path := range paths { + authz[path] = []any{map[string]any{"method": "read", "service": "*"}} + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{"sub": sub, "authz": authz}) +} + +// subjectFromBearer decodes the (unsigned) JWT payload for sub/user_name, +// mirroring internal/authscope's subjectFromAuthorizationHeader shape rules +// without importing its internals. +func subjectFromBearer(auth string) string { + const prefix = "bearer " + token := strings.TrimSpace(auth) + if len(token) < len(prefix) || !strings.EqualFold(token[:len(prefix)], prefix) { + return "" + } + token = strings.TrimSpace(token[len(prefix):]) + parts := strings.Split(token, ".") + if len(parts) < 2 { + return "" + } + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return "" + } + var claims map[string]any + if err := json.Unmarshal(payload, &claims); err != nil { + return "" + } + if sub, _ := claims["sub"].(string); sub != "" { + return sub + } + if user, _ := claims["user_name"].(string); user != "" { + return user + } + return "" +} + +// MakeBearerToken builds an unsigned JWT-shaped bearer token with iss bound +// to the fence URL, satisfying the issuer-match check. +func MakeBearerToken(issuer, sub string) string { + header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none","typ":"JWT"}`)) + payloadRaw, _ := json.Marshal(map[string]any{"iss": issuer, "sub": sub}) + payload := base64.RawURLEncoding.EncodeToString(payloadRaw) + return "Bearer " + header + "." + payload + "." + base64.RawURLEncoding.EncodeToString([]byte("mock")) +} + +// BuildServerConfigYAML renders the arm config: ClickHouse disabled on both +// arms (matched reader path ruling), scoped calypr auth against the mock +// fence, backend wiring passed in verbatim from the caller. +func BuildServerConfigYAML(listen, backend, backendYAML, fenceURL string) string { + var b bytes.Buffer + b.WriteString("server:\n") + fmt.Fprintf(&b, " listen: %q\n", listen) + fmt.Fprintf(&b, " backend: %q\n", backend) + b.WriteString(" clickhouse:\n enabled: false\n") + b.WriteString(backendYAML) + b.WriteString("auth:\n mode: calypr\n calypr:\n") + fmt.Fprintf(&b, " trusted_issuer: %q\n", fenceURL) + b.WriteString(" request_timeout: 10s\n cache_ttl: 1s\n") + return b.String() +} From d66dc915c8d489ee186d09340f6639b8436c7d5a Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:31:51 +0000 Subject: [PATCH 03/45] feat(benchmark): benchcli verify/run entry point Go-side authority for the benchmark artifact (ruling ii): - verify --report re-derives every number via benchmark.LoadReport; proved end-to-end: a valid report verifies OK, a median-tampered report is REJECTED naming the cell that fails to recompute. - run fails closed (exit 3, blocked) until the granted exclusive window wave wires preflight + the live driver; no partial environment can emit a success-shaped artifact. --- .../dataframe/benchmark/cmd/benchcli/main.go | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 internal/dataframe/benchmark/cmd/benchcli/main.go diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go new file mode 100644 index 000000000..6aa2e2a76 --- /dev/null +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -0,0 +1,60 @@ +// Command benchcli is the benchmark driver/verifier entry point the node +// orchestrator invokes. It has exactly two jobs: run a live matched +// benchmark, and verify a report.json artifact. Verification authority lives +// here in Go; node unit tests can never satisfy the live checks (ruling ii). +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/calypr/loom/internal/dataframe/benchmark" +) + +func main() { + if len(os.Args) < 2 { + usage() + os.Exit(2) + } + switch os.Args[1] { + case "verify": + fs := flag.NewFlagSet("verify", flag.ExitOnError) + reportPath := fs.String("report", "", "path to report.json") + _ = fs.Parse(os.Args[2:]) + if *reportPath == "" { + fmt.Fprintln(os.Stderr, "benchcli verify: --report is required") + os.Exit(2) + } + data, err := os.ReadFile(*reportPath) + if err != nil { + fmt.Fprintf(os.Stderr, "benchcli verify: %v\n", err) + os.Exit(1) + } + report, err := benchmark.LoadReport(data) + if err != nil { + fmt.Fprintf(os.Stderr, "benchcli verify: REJECTED: %v\n", err) + os.Exit(1) + } + fmt.Printf("benchcli verify: OK run=%s status=%s claims=%d unsupported=%d\n", + report.Identity.RunID, report.Status, len(report.Claims), len(report.Unsupported)) + case "run": + // Fails closed until the exclusive window: no partial-environment + // run may produce a success-shaped artifact. The granted-window + // wave adds preflight (services reachable, budget, namespace + // guards) and the live driver here. + if os.Getenv("LOOM_BENCH_LIVE") != "1" { + fmt.Fprintln(os.Stderr, "benchcli run: BLOCKED — live driver requires the granted exclusive window (LOOM_BENCH_LIVE=1) and is not yet wired") + os.Exit(3) + } + fmt.Fprintln(os.Stderr, "benchcli run: BLOCKED — live driver not yet wired (lands with the granted PG window wave)") + os.Exit(3) + default: + usage() + os.Exit(2) + } +} + +func usage() { + fmt.Fprintln(os.Stderr, "usage: benchcli verify --report | benchcli run") +} From efc36ac4796a0308376280be2128987a5f12f9eb Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:38:52 +0000 Subject: [PATCH 04/45] feat(benchmark): battery authoring in acceptance-proven workspace shapes Author one ExplorerBuilderWorkspace v2 document per workload using only wire shapes copied from testdata/acceptance/ncpi-tcga-brca/workspace.json (field columns, aggregate COUNT/SUM/MIN/MAX/DISTINCT_VALUES with where {path,equals}, extensionByUrl, fixedFilters {column,values}). This makes the live flow four public routes with zero builder-command payloads: explorer-config publish -> create explorer from source (draft = active revision) -> reconcile (compile receipt) -> preview (source-row execution, limit <= engine max). AuthoringVerification() asks the repository's own decoder about every template before any service is contacted: rejected shapes ship as planned/not_run with the decoder error as the named capability (same honesty mechanism as the CI-targeted Arango rows); shapes without acceptance precedent are labeled unverified-shape for rehearsal classification. OutputIDFor maps workload ids to decoder-legal output ids (underscores). Offline: all 10 templates DecodeWorkspace clean incl. fixedFilters and route child shapes; full package test suite green, vet and build clean. --- internal/dataframe/benchmark/authoring.go | 338 ++++++++++++++++++ .../dataframe/benchmark/authoring_test.go | 104 ++++++ 2 files changed, 442 insertions(+) create mode 100644 internal/dataframe/benchmark/authoring.go create mode 100644 internal/dataframe/benchmark/authoring_test.go diff --git a/internal/dataframe/benchmark/authoring.go b/internal/dataframe/benchmark/authoring.go new file mode 100644 index 000000000..01c927005 --- /dev/null +++ b/internal/dataframe/benchmark/authoring.go @@ -0,0 +1,338 @@ +package benchmark + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/calypr/loom/internal/explorer/authoringv2" +) + +// Battery authoring: one workspace per workload in the exact wire shapes +// proven by testdata/acceptance/ncpi-tcga-brca/workspace.json. The live flow +// per workload is four public routes, no builder commands: +// +// POST .../generations/{g}/explorer-config (workspace JSON) +// POST .../explorers {sourceExplorerId:"default"} (draft = active revision) +// POST .../authoring/v2/reconcile (compile receipt) +// POST .../authoring/v2/preview {receiptId, outputId, limit} +// +// Every template is validated offline by the repository's own decoder +// (AuthoringVerification); anything the decoder rejects is published as +// planned/not_run with the decoder's error as the named capability, never a +// silent skip and never an approximated shape. + +// AuthoringStatus is the offline authoring verdict for one workload. +type AuthoringStatus string + +const ( + // AuthoringProven: shapes copied verbatim from the acceptance fixture. + AuthoringProven AuthoringStatus = "proven" + // AuthoringUnverified: decodes, but a shape or cast has no acceptance + // precedent; rehearsal classifies it before any claim. + AuthoringUnverified AuthoringStatus = "unverified-shape" + // AuthoringRejected: the decoder refuses it; the workload is + // planned/not_run with the decoder error as the capability note. + AuthoringRejected AuthoringStatus = "rejected" +) + +type authoringNote struct { + Status AuthoringStatus + Detail string +} + +// wire shapes, verbatim families from the acceptance fixture +type wsColumnDef struct { + Column string `json:"column"` + Label string `json:"label"` + LogicalType string `json:"logicalType"` + OccurrenceID string `json:"occurrenceId"` + Source map[string]any `json:"source"` + Table wsTable `json:"table"` +} + +type wsTable struct { + Visible bool `json:"visible"` + Order int `json:"order"` +} + +type wsChild struct { + OccurrenceID string `json:"occurrenceId"` + ResourceType string `json:"resourceType"` + Relationship string `json:"relationship"` + Children []wsChild `json:"children,omitempty"` +} + +type wsDoc struct { + Kind string `json:"kind"` + Output wsOutputDef `json:"output"` + RootResourceType string `json:"rootResourceType"` + Route wsRouteNode `json:"route"` + Columns []wsColumnDef `json:"columns"` + FixedFilters []wsFixed `json:"fixedFilters,omitempty"` +} + +type wsFixed struct { + Column string `json:"column"` + Values []string `json:"values"` +} + +type wsRouteNode struct { + OccurrenceID string `json:"occurrenceId"` + ResourceType string `json:"resourceType"` + Children []wsChild `json:"children,omitempty"` +} + +type wsOutputDef struct { + ID string `json:"id"` + Title string `json:"title"` + RowLabel string `json:"rowLabel,omitempty"` +} + +type wsWorkspaceDoc struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` + SemanticsVersion int `json:"semanticsVersion"` + Explorer wsExplorer `json:"explorer"` + Documents []wsDoc `json:"documents"` + Tabs []wsTabDef `json:"tabs"` +} + +type wsTabDef struct { + ID string `json:"id"` + Title string `json:"title"` + OutputID string `json:"outputId"` + Order int `json:"order"` + Visible bool `json:"visible"` +} + +type wsExplorer struct { + Title string `json:"title"` +} + +const ( + wsAPIVersion = "loom.calypr.org/explorer-authoring/v2" + wsKind = "ExplorerBuilderWorkspace" + wsDocKind = "ExplorerBuilderDocument" + wsSemanticsVersion = 3 // the acceptance-proven version; DecodeWorkspace owns migration +) + +// OutputIDFor maps a workload id to a decoder-legal output id +// (letters/digits/underscores only). +func OutputIDFor(workloadID string) string { + return strings.ReplaceAll(workloadID, "-", "_") +} + +func fieldCol(column, occurrence, path, mode, logical string, order int) wsColumnDef { + return wsColumnDef{ + Column: column, Label: column, LogicalType: logical, OccurrenceID: occurrence, + Source: map[string]any{"kind": "field", "field": map[string]any{"path": path, "projectionMode": mode}}, + Table: wsTable{Visible: true, Order: order}, + } +} + +func aggregateCol(column, label, logical, occurrence, operation, path, wherePath, whereEquals string, order int) wsColumnDef { + aggregate := map[string]any{"operation": operation} + if path != "" { + aggregate["path"] = path + } + if wherePath != "" { + aggregate["where"] = map[string]any{"path": wherePath, "equals": whereEquals} + } + return wsColumnDef{ + Column: column, Label: label, LogicalType: logical, OccurrenceID: occurrence, + Source: map[string]any{"kind": "aggregate", "aggregate": aggregate}, + Table: wsTable{Visible: true, Order: order}, + } +} + +func extensionCol(column, occurrence, matchURL string, order int) wsColumnDef { + return wsColumnDef{ + Column: column, Label: column, LogicalType: "string", OccurrenceID: occurrence, + Source: map[string]any{"kind": "extensionByUrl", "lookup": map[string]any{ + "match": matchURL, "path": "extension[]", "projectionMode": "FIRST", + }}, + Table: wsTable{Visible: true, Order: order}, + } +} + +func observationChildren() []wsChild { + return []wsChild{ + {OccurrenceID: "observation", ResourceType: "Observation", Relationship: "subject_Patient"}, + {OccurrenceID: "specimen", ResourceType: "Specimen", Relationship: "subject_Patient"}, + {OccurrenceID: "condition", ResourceType: "Condition", Relationship: "subject_Patient"}, + } +} + +func patientWorkspace(outputID string, columns []wsColumnDef, children []wsChild, filters []wsFixed) []byte { + if children == nil { + children = []wsChild{} + } + w := wsWorkspaceDoc{ + APIVersion: wsAPIVersion, Kind: wsKind, SemanticsVersion: wsSemanticsVersion, + Explorer: wsExplorer{Title: "Backend benchmark battery"}, + Documents: []wsDoc{{ + Kind: wsDocKind, + Output: wsOutputDef{ID: outputID, Title: outputID, RowLabel: "row"}, + RootResourceType: "Patient", + Route: wsRouteNode{OccurrenceID: "base", ResourceType: "Patient", Children: children}, + Columns: columns, + FixedFilters: filters, + }}, + Tabs: []wsTabDef{{ID: outputID + "_tab", Title: outputID, OutputID: outputID, Order: 0, Visible: true}}, + } + raw, err := json.MarshalIndent(w, "", " ") + if err != nil { // value types only; unreachable + panic(fmt.Sprintf("benchmark: workspace marshal: %v", err)) + } + return raw +} + +// BuildWorkspace returns the authored workspace JSON for one workload. +// Output ID equals workload ID: receipts, previews, gates, and samples all +// key on it. +func BuildWorkspace(workloadID string) ([]byte, error) { + switch workloadID { + case WorkloadFilterActiveGender: + return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + fieldCol("patient_id", "base", "id", "VALUE", "string", 0), + fieldCol("gender", "base", "gender", "VALUE", "string", 1), + // hidden root filter column: the fixed filter over it keeps + // inactive patients out of the output + {Column: "active", Label: "Active", LogicalType: "boolean", OccurrenceID: "base", + Source: map[string]any{"kind": "field", "field": map[string]any{"path": "active", "projectionMode": "VALUE"}}, + Table: wsTable{Visible: false, Order: 2}}, + }, nil, []wsFixed{ + {Column: "gender", Values: []string{"female"}}, + {Column: "active", Values: []string{"true"}}, + }), nil + case WorkloadGroupCountByPatient: + return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + fieldCol("patient_id", "base", "id", "VALUE", "string", 0), + aggregateCol("obs_count", "Observation count", "integer", "observation", "COUNT", "", "", "", 1), + }, observationChildren(), nil), nil + case WorkloadAggregateSumAgeByPatient: + ageWhere := []struct{ col, op, logical string }{ + {"sum_age", "SUM", "decimal"}, {"count_age", "COUNT", "integer"}, + {"min_age", "MIN", "decimal"}, {"max_age", "MAX", "decimal"}, + } + columns := []wsColumnDef{fieldCol("patient_id", "base", "id", "VALUE", "string", 0)} + for i, agg := range ageWhere { + columns = append(columns, aggregateCol(agg.col, agg.col, agg.logical, "observation", agg.op, + "valueQuantity.value", "code.coding[].code", ObservationCodeAge, i+1)) + } + return patientWorkspace(OutputIDFor(workloadID), columns, observationChildren(), nil), nil + case WorkloadStageCounts: + return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + aggregateCol("stage_code", "Stage code", "string", "observation", "DISTINCT_VALUES", + "valueCodeableConcept.coding[].code", "code.coding[].code", ObservationCodeStage, 0), + aggregateCol("stage_count", "Stage count", "integer", "observation", "COUNT", "", + "code.coding[].code", ObservationCodeStage, 1), + }, observationChildren(), nil), nil + case WorkloadSpecimenTypeFilter: + return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + aggregateCol("specimen_id", "Specimen id", "string", "specimen", "DISTINCT_VALUES", + "id", "type.coding[].code", SpecimenTypePrimary, 0), + fieldCol("patient_id", "base", "id", "VALUE", "string", 1), + aggregateCol("type_code", "Specimen type", "string", "specimen", "DISTINCT_VALUES", + "type.coding[].code", "", "", 2), + }, observationChildren(), nil), nil + case WorkloadConditionNullHandling: + return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + aggregateCol("condition_id", "Condition id", "string", "condition", "DISTINCT_VALUES", "id", "", "", 0), + aggregateCol("verification_status", "Verification status", "string", "condition", "DISTINCT_VALUES", + "verificationStatus.coding[].code", "", "", 1), + }, observationChildren(), nil), nil + case WorkloadSortLimitTopN: + // Engine order is not comparable across backends; the workload is a + // full active-row pass compared as a bag, and the oracle checks the + // deterministic total order client-side. Sort knobs stay a diagnostic. + return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + fieldCol("family", "base", "name[].family", "VALUE", "string", 0), + fieldCol("given", "base", "name[].given[]", "VALUE", "string", 1), + fieldCol("patient_id", "base", "id", "VALUE", "string", 2), + }, nil, nil), nil + case WorkloadDistinctFamily: + return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + aggregateCol("family", "Family", "string", "base", "DISTINCT_VALUES", "name[].family", "", "", 0), + }, nil, nil), nil + case WorkloadStudyCorrelation: + return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + fieldCol("patient_id", "base", "id", "VALUE", "string", 0), + extensionCol("study_ref", "base", partOfStudyURL, 1), + }, nil, nil), nil + case WorkloadDeriveAgeBand: + // No acceptance precedent for value transformations in the workspace + // wire shape: authored best-effort, decoder verdict decides; on + // rejection the workload ships planned/not_run with this reason. + return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + aggregateCol("observation_id", "Observation id", "string", "observation", "DISTINCT_VALUES", + "id", "code.coding[].code", ObservationCodeAge, 0), + {Column: "age_band", Label: "Age band", LogicalType: "string", OccurrenceID: "observation", + Source: map[string]any{"kind": "aggregate", "aggregate": map[string]any{ + "operation": "DISTINCT_VALUES", "path": "valueQuantity.value", + "where": map[string]any{"path": "code.coding[].code", "equals": ObservationCodeAge}, + }}, + Table: wsTable{Visible: true, Order: 1}}, + }, observationChildren(), nil), nil + } + return nil, fmt.Errorf("benchmark: no workspace template for workload %q", workloadID) +} + +// AuthoringVerification asks the repository's own decoder about every +// battery workspace. It runs before any service is contacted and feeds the +// report's arm plan: rejected shapes are planned/not_run, named. +func AuthoringVerification() map[string]AuthoringStatus { + out := map[string]AuthoringStatus{} + for _, spec := range Battery { + raw, err := BuildWorkspace(spec.ID) + if err != nil { + out[spec.ID] = AuthoringRejected + continue + } + if _, err := authoringv2.DecodeWorkspace(raw); err != nil { + out[spec.ID] = AuthoringRejected + continue + } + if unverifiedWorkloads[spec.ID] { + out[spec.ID] = AuthoringUnverified + continue + } + out[spec.ID] = AuthoringProven + } + return out +} + +// unverifiedWorkloads lists shapes without acceptance-fixture precedent; +// rehearsal classifies them and the outcome is recorded in the report. +var unverifiedWorkloads = map[string]bool{ + WorkloadFilterActiveGender: true, // fixedFilters over a boolean field column + WorkloadDistinctFamily: true, // DISTINCT_VALUES at the root occurrence + WorkloadDeriveAgeBand: true, // transformation intent approximated + WorkloadStudyCorrelation: true, // extensionByUrl returning a reference string + WorkloadStageCounts: true, // DISTINCT_VALUES + COUNT pair at one occurrence (per-row grain expectation) + WorkloadSortLimitTopN: false, // field-only columns: proven shape +} + +// BuildAllWorkspaces returns every battery workspace keyed by workload id. +func BuildAllWorkspaces() (map[string][]byte, error) { + out := map[string][]byte{} + for _, spec := range Battery { + raw, err := BuildWorkspace(spec.ID) + if err != nil { + return nil, err + } + out[spec.ID] = raw + } + return out, nil +} + +// batterySpec resolves one workload's contract. +func batterySpec(workloadID string) (WorkloadSpec, bool) { + for _, spec := range Battery { + if spec.ID == workloadID { + return spec, true + } + } + return WorkloadSpec{}, false +} diff --git a/internal/dataframe/benchmark/authoring_test.go b/internal/dataframe/benchmark/authoring_test.go new file mode 100644 index 000000000..c5c45a81b --- /dev/null +++ b/internal/dataframe/benchmark/authoring_test.go @@ -0,0 +1,104 @@ +package benchmark + +import ( + "strings" + "testing" + + "github.com/calypr/loom/internal/explorer/authoringv2" +) + +// Format fidelity is judged by the repository's own decoder: if +// authoringv2.DecodeWorkspace accepts a battery workspace and its validation +// passes, the live routes will see exactly this JSON. + +func TestBatteryWorkspacesDecode(t *testing.T) { + workspaces, err := BuildAllWorkspaces() + if err != nil { + t.Fatal(err) + } + if len(workspaces) != len(Battery) { + t.Fatalf("every battery workload must have a workspace template: %d/%d", len(workspaces), len(Battery)) + } + for _, spec := range Battery { + raw, ok := workspaces[spec.ID] + if !ok { + t.Fatalf("missing workspace for %s", spec.ID) + } + ws, err := authoringv2.DecodeWorkspace(raw) + if err != nil { + t.Fatalf("%s: DecodeWorkspace rejected the authored shape: %v", spec.ID, err) + } + if len(ws.Documents) != 1 { + t.Fatalf("%s: expected one document", spec.ID) + } + doc := ws.Documents[0] + if doc.Output.ID != OutputIDFor(spec.ID) { + t.Fatalf("%s: output id mismatch: %s", spec.ID, doc.Output.ID) + } + if len(doc.Columns) != len(spec.Columns) { + // hidden helper columns (e.g. a filter column excluded from the + // published contract) are allowed; the visible projection must + // equal the contract exactly. + var visible []string + for _, column := range doc.Columns { + if column.Table.Visible == nil || *column.Table.Visible { + visible = append(visible, column.Column) + } + } + if strings.Join(visible, ",") != strings.Join(spec.Columns, ",") { + t.Fatalf("%s: visible columns %v != contract %v", spec.ID, visible, spec.Columns) + } + } else { + for i, column := range spec.Columns { + if doc.Columns[i].Column != column { + t.Fatalf("%s: column %d = %q want %q", spec.ID, i, doc.Columns[i].Column, column) + } + } + } + } +} + +func TestBatteryWorkspaceRouteShape(t *testing.T) { + // Root is always Patient with base occurrence; child occurrences use + // the relationship spellings proven by the acceptance fixture. + workspaces, err := BuildAllWorkspaces() + if err != nil { + t.Fatal(err) + } + allowed := map[string]bool{"subject_Patient": true, "partOfStudy_ResearchStudy": true} + for id, raw := range workspaces { + ws, err := authoringv2.DecodeWorkspace(raw) + if err != nil { + t.Fatalf("%s: %v", id, err) + } + route := ws.Documents[0].Route + if route.OccurrenceID != "base" || route.ResourceType != "Patient" { + t.Fatalf("%s: route root: %+v", id, route) + } + var walk func(children []authoringv2.RouteNode) + walk = func(children []authoringv2.RouteNode) { + for _, child := range children { + if !allowed[child.Relationship] { + t.Fatalf("%s: unproven relationship %q (acceptance-proven set only)", id, child.Relationship) + } + walk(child.Children) + } + } + walk(route.Children) + } +} + +func TestAuthoringVerificationNoneRejected(t *testing.T) { + status := AuthoringVerification() + for _, spec := range Battery { + got, ok := status[spec.ID] + if !ok { + t.Fatalf("%s: no authoring verdict", spec.ID) + } + if got == AuthoringRejected { + // not a test failure per se: the driver would ship this row as + // planned/not_run — but during development we want to know. + t.Logf("workload %s authoring REJECTED by decoder", spec.ID) + } + } +} From 118f8739fa0341ea9b9ddd921e3dd5db6066d41d Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:41:46 +0000 Subject: [PATCH 05/45] feat(benchmark): run scope/leg plan in report schema, verifier, and markdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit postgres-only worker sessions must say so in a way the verifier enforces, not by silent absence: - RunPlan {scope, legs[], plannedNotRun[]} stored in report.json; ValidateLegPlan refuses not_run legs without a named capability, postgres-only scope with a run arango leg, and unknown scopes/modes. - VerifyReport now recomputes status via RunStatusScoped and enforces the un-run-leg contract: arm blocked in every gate report, disclosure row per workload, zero samples/summaries/claims, no version, self-ratio exactly 0 (never a forged 1.0); run legs keep live-version and 1±0.05 self-ratio requirements. Disclosure rows for a leg that ran are also refused. - A postgres-only pass is legitimate with zero speed claims (it certifies correctness+timings, not a comparison); both-arms passes still require claims and both arms' evidence. - RenderMarkdown emits report.md from verified content only: leg table with capabilities, disclosure rows, controls with plan language (ratio-only-both-passed, CV ceiling, self-ratio, empty floor), gate and timing tables, claims or the named reason there are none. - armplan_test.go: honest postgres-only round-trip + 8-case forgery suite (faked passed arm, smuggled samples, dropped disclosure, blanked capability, forged version/self-ratio, injected speed claim, scope lie) plus the converse disclosure-for-ran-leg case. All 10 battery workloads still decode; package tests, vet, build green. --- internal/dataframe/benchmark/armplan.go | 266 +++++++++++++++++++ internal/dataframe/benchmark/armplan_test.go | 197 ++++++++++++++ internal/dataframe/benchmark/report.go | 142 ++++++++-- internal/dataframe/benchmark/report_md.go | 160 +++++++++++ internal/dataframe/benchmark/report_test.go | 4 +- 5 files changed, 752 insertions(+), 17 deletions(-) create mode 100644 internal/dataframe/benchmark/armplan.go create mode 100644 internal/dataframe/benchmark/armplan_test.go create mode 100644 internal/dataframe/benchmark/report_md.go diff --git a/internal/dataframe/benchmark/armplan.go b/internal/dataframe/benchmark/armplan.go new file mode 100644 index 000000000..abb138667 --- /dev/null +++ b/internal/dataframe/benchmark/armplan.go @@ -0,0 +1,266 @@ +package benchmark + +import ( + "fmt" + "sort" + "strings" +) + +// Run scope and leg plans make "we could only run the PostgreSQL arm" an +// explicit, verified property of the artifact instead of a silent absence. +// The Arango legs are CI-targeted per the coordinator ACK: in worker +// sessions they ship as planned/not_run rows with the named missing +// capability, never as skipped-to-green rows. + +const ( + RunScopeBothArms = "both-arms" + RunScopePostgresOnly = "postgres-only" +) + +func ValidScope(scope string) bool { + return scope == RunScopeBothArms || scope == RunScopePostgresOnly +} + +// LegPlan declares one backend arm's participation in the run. +type LegPlan struct { + Backend string `json:"backend"` + Mode string `json:"mode"` // run | not_run + // Capability names what was missing for not_run rows, e.g. + // "no ArangoDB service in worker container; go-arango CI lane targets + // this leg" — required and non-empty for not_run. + Capability string `json:"capability,omitempty"` +} + +// PlannedNotRun is a per-workload disclosure row. +type PlannedNotRun struct { + WorkloadID string `json:"workloadId"` + Backend string `json:"backend"` + Reason string `json:"reason"` +} + +// RunPlan is the declared participation of each arm for a run, stored in +// the report and re-checked by VerifyReport: "we could only run the +// PostgreSQL arm" must be a verified property, never a silent absence. +type RunPlan struct { + Scope string `json:"scope"` + Legs []LegPlan `json:"legs"` + PlannedNotRun []PlannedNotRun `json:"plannedNotRun,omitempty"` +} + +// BothArmsPlan is the CI-target plan: both services execute every leg. +func BothArmsPlan() RunPlan { + return RunPlan{ + Scope: RunScopeBothArms, + Legs: []LegPlan{ + {Backend: BackendArango, Mode: "run"}, + {Backend: BackendPostgres, Mode: "run"}, + }, + } +} + +// PostgresOnlyPlan is the worker-session plan: the Arango leg ships as +// not_run with its missing capability named verbatim in every disclosure +// row, per the coordinator ACK (Arango rows stay CI-targeted). +func PostgresOnlyPlan(arangoCapability string) RunPlan { + return RunPlan{ + Scope: RunScopePostgresOnly, + Legs: []LegPlan{ + {Backend: BackendArango, Mode: "not_run", Capability: arangoCapability}, + {Backend: BackendPostgres, Mode: "run"}, + }, + } +} + +// PlannedNotRunForBackends expands one disclosure reason across every +// battery workload for each not-run leg, so no row can be quietly dropped. +func PlannedNotRunForBackends(backends []string) []PlannedNotRun { + var rows []PlannedNotRun + for _, backend := range backends { + for _, spec := range Battery { + rows = append(rows, PlannedNotRun{WorkloadID: spec.ID, Backend: backend, Reason: "leg not run"}) + } + } + return rows +} + +// ValidateLegPlan checks the plan is total, closed, and honest. +func ValidateLegPlan(scope string, legs []LegPlan) error { + if !ValidScope(scope) { + return fmt.Errorf("benchmark: unknown run scope %q", scope) + } + byBackend := map[string]LegPlan{} + for _, leg := range legs { + if !ValidBackend(leg.Backend) { + return fmt.Errorf("benchmark: leg plan has unknown backend %q", leg.Backend) + } + if leg.Mode != "run" && leg.Mode != "not_run" { + return fmt.Errorf("benchmark: leg %s has unknown mode %q", leg.Backend, leg.Mode) + } + if leg.Mode == "not_run" && strings.TrimSpace(leg.Capability) == "" { + return fmt.Errorf("benchmark: leg %s is not_run without a named capability", leg.Backend) + } + if _, dup := byBackend[leg.Backend]; dup { + return fmt.Errorf("benchmark: duplicate leg for %s", leg.Backend) + } + byBackend[leg.Backend] = leg + } + for _, backend := range []string{BackendArango, BackendPostgres} { + if _, ok := byBackend[backend]; !ok { + return fmt.Errorf("benchmark: leg plan missing %s", backend) + } + } + switch scope { + case RunScopePostgresOnly: + if byBackend[BackendPostgres].Mode != "run" { + return fmt.Errorf("benchmark: postgres-only scope requires the postgres leg to run") + } + if byBackend[BackendArango].Mode != "not_run" || strings.TrimSpace(byBackend[BackendArango].Capability) == "" { + return fmt.Errorf("benchmark: postgres-only scope requires the arango leg not_run WITH a named capability") + } + default: + for _, backend := range []string{BackendArango, BackendPostgres} { + if byBackend[backend].Mode != "run" { + return fmt.Errorf("benchmark: both-arms scope requires %s to run", backend) + } + } + } + return nil +} + +// ValidatePlannedNotRun checks disclosure rows reference real workloads +// and backends with non-empty reasons. +func ValidatePlannedNotRun(rows []PlannedNotRun) error { + for _, row := range rows { + if _, ok := batterySpec(row.WorkloadID); !ok { + return fmt.Errorf("benchmark: planned/not_run references unknown workload %q", row.WorkloadID) + } + if !ValidBackend(row.Backend) { + return fmt.Errorf("benchmark: planned/not_run references unknown backend %q", row.Backend) + } + if strings.TrimSpace(row.Reason) == "" { + return fmt.Errorf("benchmark: planned/not_run for %s/%s has no reason", row.WorkloadID, row.Backend) + } + } + return nil +} + +// NotRunBackends lists the backends whose legs did not execute. +func (p RunPlan) NotRunBackends() []string { + var out []string + for _, leg := range p.Legs { + if leg.Mode == "not_run" { + out = append(out, leg.Backend) + } + } + return out +} + +// ExpectedOutcomeForScope returns the outcome an arm must show when its leg +// did not run, so gates can distinguish "expectedly absent" from "failed". +func ExpectedOutcomeForScope(leg LegPlan) (WorkloadOutcome, string) { + if leg.Mode == "not_run" { + return OutcomeLabelBlocked, "leg not run: " + leg.Capability + } + return "", "" +} + +// RunStatusScoped aggregates gate reports under the run scope. In +// postgres-only scope the arango arm's blocked outcome is the EXPECTED +// posture (its capability is named in the leg plan) and does not fail the +// run; everything else behaves as in both-arms scope. +func RunStatusScoped(reports []WorkloadGateReport, scope string) (string, []string) { + if len(reports) == 0 { + return "failed", []string{"no workloads were evaluated; an empty matrix is not a pass"} + } + claimable := 0 + byID := append([]WorkloadGateReport(nil), reports...) + sort.Slice(byID, func(i, j int) bool { return byID[i].WorkloadID < byID[j].WorkloadID }) + var reasons []string + for _, report := range byID { + armOutcomes := []WorkloadOutcome{report.Arango.Outcome, report.Postgres.Outcome} + if scope == RunScopePostgresOnly { + // Only the PostgreSQL arm decides; the Arango arm must be the + // expected blocked posture, anything else is a forgery signal. + if report.Arango.Outcome != OutcomeLabelBlocked { + reasons = append(reasons, report.WorkloadID+": postgres-only run must show the arango arm blocked, got "+string(report.Arango.Outcome)) + continue + } + armOutcomes = []WorkloadOutcome{report.Postgres.Outcome} + } + blocked := false + for _, outcome := range armOutcomes { + if outcome == OutcomeLabelBlocked { + blocked = true + } + } + if blocked { + reasons = append(reasons, report.WorkloadID+": blocked") + continue + } + if !report.Claimable { + reasons = append(reasons, report.WorkloadID+": "+report.ExclusionReason) + continue + } + claimable++ + } + if len(reasons) > 0 { + return "failed", reasons + } + if claimable == 0 { + return "failed", []string{"no workload completed its expected arms; unsupported-only matrices are not passes"} + } + return "passed", nil +} + +// EvaluateGatesSingleArm evaluates the scope-applicable gates for one arm +// with an independent oracle (postgres-only sessions). Cross-backend gates +// cannot apply; that is recorded in the gate detail, not hidden. The other +// arm must already be published as blocked by the caller. +func EvaluateGatesSingleArm(workloadID, backend string, columns []string, expected *CanonicalBag, arm BackendResult, rows []map[string]any, idMap *StorageIDMap, blockedDetail string) WorkloadGateReport { + blocked := BackendResult{Backend: otherBackend(backend), Outcome: OutcomeLabelBlocked, Detail: blockedDetail} + report := WorkloadGateReport{WorkloadID: workloadID, Claimable: true} + if backend == BackendArango { + report.Arango, report.Postgres = arm, blocked + } else { + report.Arango, report.Postgres = blocked, arm + } + add := func(gate GateName, pass bool, format string, args ...any) { + result := GateResult{Gate: gate, Pass: pass} + if !pass { + result.Detail = fmt.Sprintf(format, args...) + report.Claimable = false + if report.ExclusionReason == "" { + report.ExclusionReason = string(gate) + ": " + result.Detail + } + } + report.Gates = append(report.Gates, result) + } + if arm.Outcome != OutcomeLabelPassed { + add(GateArmOutcome, false, "%s arm outcome=%s (error=%s)", arm.Backend, arm.Outcome, arm.ErrorCode) + return report + } + add(GateColumnContract, equalStrings(arm.Columns, columns), "%s columns deviate from the authored contract", arm.Backend) + mapped := idMap.Apply(arm.Columns, rows) + bag := Bag(CanonicalizeRows(arm.Columns, mapped)) + add(GateBagEquality, arm.BagDigest == bag.Digest(), "%s bag digest does not reproduce from its rows", arm.Backend) + if arm.Truncated { + add(GateCompleteness, false, "%s arm consumed a truncated output", arm.Backend) + } + if expected != nil { + if bag.Digest() != expected.Digest() { + firstA, firstB, _ := FirstDifference(bag, *expected) + add(GateOracleRows, false, "%s bag differs from independent oracle at arm=%s oracle=%s", arm.Backend, firstA, firstB) + } + } else { + report.Gates = append(report.Gates, GateResult{Gate: GateOracleRows, Pass: true, Detail: "profile has no hand oracle; single-arm scope publishes timings only"}) + } + report.Gates = append(report.Gates, GateResult{Gate: GateBagEquality, Pass: true, Detail: "cross-backend equality is CI-targeted under " + RunScopePostgresOnly + " scope"}) + return report +} + +func otherBackend(backend string) string { + if backend == BackendArango { + return BackendPostgres + } + return BackendArango +} diff --git a/internal/dataframe/benchmark/armplan_test.go b/internal/dataframe/benchmark/armplan_test.go new file mode 100644 index 000000000..939265297 --- /dev/null +++ b/internal/dataframe/benchmark/armplan_test.go @@ -0,0 +1,197 @@ +package benchmark + +import ( + "encoding/json" + "strings" + "testing" + "time" +) + +// The postgres-only scope is where "we only ran PostgreSQL" lives. It must +// be a verified property: the un-run arm stays blocked, disclosed per +// workload, sample-free, claim-free, version-free — every deception path +// below has to RED. + +const testArangoCapability = "no ArangoDB service in worker container; go-arango CI lane targets this leg" + +func postgresOnlyGate(workloadID string, bagDigest string) WorkloadGateReport { + return WorkloadGateReport{ + WorkloadID: workloadID, Claimable: true, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelBlocked, Detail: "leg not run: " + testArangoCapability}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelPassed, RowCount: 5, BagDigest: bagDigest}, + Gates: []GateResult{{Gate: GateOracleRows, Pass: true}, {Gate: GateBagEquality, Pass: true, Detail: "cross-backend equality is CI-targeted"}}, + } +} + +func validPostgresOnlyReport(t *testing.T) *Report { + t.Helper() + identity := RunIdentity{ + RunID: "bench-2026-11-13-pg-a", StartedAt: time.Unix(1700000000, 0).UTC(), + Preset: PresetTiny, SourceCommit: strings.Repeat("a", 40), + DatasetDigest: strings.Repeat("b", 64), + PostgresVersion: "PostgreSQL 18.x", + ClickHouseDisabled: true, + Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, + } + controls := ControlRuns{ + StabilityCVLimit: StabilityCVLimit, + // the arango self-ratio cannot exist without the leg: 0 = absent, + // never a forged 1.0 + SelfRatioArango: 0, SelfRatioPostgres: 1.0, + EmptyFloorMillis: 0.4, RatioPolicy: "ratio-only-both-passed", + } + plan := PostgresOnlyPlan(testArangoCapability) + plan.PlannedNotRun = PlannedNotRunForBackends([]string{BackendArango}) + for i := range plan.PlannedNotRun { + plan.PlannedNotRun[i].Reason = "leg not run: " + testArangoCapability + } + gate := postgresOnlyGate(WorkloadFilterActiveGender, strings.Repeat("c", 64)) + var samples []Sample + samples = append(samples, sample(BackendPostgres, WorkloadFilterActiveGender, OutcomePassed, 5, true)) + for i := 0; i < 4; i++ { + samples = append(samples, sample(BackendPostgres, WorkloadFilterActiveGender, OutcomePassed, 10+float64(i), false)) + } + report, err := BuildReport(identity, plan, controls, []WorkloadGateReport{gate}, samples) + if err != nil { + t.Fatalf("honest postgres-only report must build: %v", err) + } + return report +} + +func TestPostgresOnlyReportRoundTrips(t *testing.T) { + report := validPostgresOnlyReport(t) + loaded, err := LoadReport(mustMarshal(t, report)) + if err != nil { + t.Fatalf("honest postgres-only report must verify: %v", err) + } + if loaded.Status != "passed" { + t.Fatalf("postgres-only pass must be a pass: %+v", loaded.StatusReasons) + } + if len(loaded.Claims) != 0 { + t.Fatalf("no speed claims may exist without the arango leg: %+v", loaded.Claims) + } +} + +func TestPostgresOnlyRunStatusIgnoresExpectedBlockedArm(t *testing.T) { + status, reasons := RunStatusScoped([]WorkloadGateReport{postgresOnlyGate(WorkloadFilterActiveGender, strings.Repeat("c", 64))}, RunScopePostgresOnly) + if status != "passed" || len(reasons) != 0 { + t.Fatalf("expected pass, got %s %v", status, reasons) + } + // An unexpected arango outcome under postgres-only scope is a forgery + // signal, not a pass. + gate := postgresOnlyGate(WorkloadFilterActiveGender, strings.Repeat("c", 64)) + gate.Arango.Outcome = OutcomeLabelPassed + if status, _ := RunStatusScoped([]WorkloadGateReport{gate}, RunScopePostgresOnly); status != "failed" { + t.Fatal("arango arm passed under postgres-only scope must fail the run") + } + // Empty matrix is never a pass, even scoped. + if status, _ := RunStatusScoped(nil, RunScopePostgresOnly); status != "failed" { + t.Fatal("empty scoped matrix must not pass") + } +} + +func TestLegPlanValidation(t *testing.T) { + if err := ValidateLegPlan(RunScopePostgresOnly, []LegPlan{ + {Backend: BackendArango, Mode: "not_run"}, + {Backend: BackendPostgres, Mode: "run"}, + }); err == nil { + t.Fatal("not_run leg without capability must be refused") + } + if err := ValidateLegPlan(RunScopeBothArms, BothArmsPlan().Legs); err != nil { + t.Fatalf("both-arms plan must validate: %v", err) + } + if err := ValidateLegPlan(RunScopePostgresOnly, BothArmsPlan().Legs); err == nil { + t.Fatal("postgres-only scope must refuse an executed arango leg") + } + if err := ValidateLegPlan("everything", BothArmsPlan().Legs); err == nil { + t.Fatal("unknown scope must be refused") + } +} + +func forgeWith(t *testing.T, base func(*testing.T) *Report, mutate func(*Report)) func(*testing.T) { + return func(t *testing.T) { + t.Helper() + report := base(t) + mutate(report) + data, err := json.Marshal(report) + if err != nil { + t.Fatal(err) + } + if _, err := LoadReport(data); err == nil { + t.Fatal("forged report survived verification") + } + } +} + +func TestPostgresOnlyForgerySuite(t *testing.T) { + cases := map[string]func(*Report){ + "arango-arm-faked-passed": func(r *Report) { + r.GateReports[0].Arango.Outcome = OutcomeLabelPassed + }, + "arango-samples-smuggled": func(r *Report) { + r.Samples = append(r.Samples, sample(BackendArango, WorkloadFilterActiveGender, OutcomePassed, 20, false)) + r.Summaries = append(r.Summaries, SummaryRow{ + WorkloadID: WorkloadFilterActiveGender, Backend: BackendArango, Cache: CacheWarm, + Summary: Summarize([]Sample{sample(BackendArango, WorkloadFilterActiveGender, OutcomePassed, 20, false)}), + }) + }, + "disclosure-rows-dropped": func(r *Report) { + r.Plan.PlannedNotRun = nil + }, + "capability-blanked": func(r *Report) { + r.Plan.Legs[0].Capability = " " + }, + "arango-version-claimed-for-unrun-leg": func(r *Report) { + r.Identity.ArangoVersion = "3.12.x" + }, + "postgres-version-hidden": func(r *Report) { + r.Identity.PostgresVersion = "" + }, + "speed-claim-injected-without-arango": func(r *Report) { + r.Claims = append(r.Claims, Claim{ + WorkloadID: WorkloadFilterActiveGender, + Arango: Summarize([]Sample{sample(BackendArango, WorkloadFilterActiveGender, OutcomePassed, 40, false)}), + Postgres: Summarize([]Sample{sample(BackendPostgres, WorkloadFilterActiveGender, OutcomePassed, 10, false)}), + Ratio: SpeedRatio{ArangoOverPostgres: 4.0}, + }) + }, + "unrun-selfratio-forged-to-one": func(r *Report) { + r.Controls.SelfRatioArango = 1.0 + }, + "scope-lie-arango-leg-marked-run": func(r *Report) { + r.Plan.Legs[0].Mode = "run" + }, + } + for name, mutate := range cases { + t.Run(name, forgeWith(t, validPostgresOnlyReport, mutate)) + } + // The converse: disclosure rows for a leg that DID run are lies too. + t.Run("disclosure-for-ran-leg", forgeWith(t, validReport, func(r *Report) { + r.Plan.PlannedNotRun = []PlannedNotRun{{WorkloadID: WorkloadFilterActiveGender, Backend: BackendArango, Reason: "pretend we skipped it"}} + })) +} + +func TestRenderMarkdown(t *testing.T) { + both := RenderMarkdown(validReport(t)) + for _, want := range []string{ + "Status: passed", "Scope: `both-arms`", "ratio-only-both-passed", + "arango/postgres", "Empty-result floor", "1.000", + } { + if !strings.Contains(both, want) { + t.Fatalf("both-arms markdown missing %q", want) + } + } + only := RenderMarkdown(validPostgresOnlyReport(t)) + for _, want := range []string{ + "Scope: `postgres-only`", "not_run", testArangoCapability, + "not observed (leg not run", "no cross-backend speed claims", + "never silently skipped", + } { + if !strings.Contains(only, want) { + t.Fatalf("postgres-only markdown missing %q", want) + } + } + if strings.Contains(only, "arango/postgres |") { + t.Fatal("postgres-only markdown must not contain a claims table") + } +} diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go index ad7d3aa8a..42046c8c4 100644 --- a/internal/dataframe/benchmark/report.go +++ b/internal/dataframe/benchmark/report.go @@ -6,6 +6,7 @@ import ( "math" "regexp" "sort" + "strings" "time" ) @@ -63,6 +64,7 @@ type Claim struct { // Report is the full artifact. type Report struct { Schema string `json:"schema"` + Plan RunPlan `json:"plan"` Identity RunIdentity `json:"identity"` Status string `json:"status"` // passed | failed | blocked StatusReasons []string `json:"statusReasons,omitempty"` @@ -88,13 +90,20 @@ type SummaryRow struct { // consistent by construction. claimableOf decides per-workload eligibility // (the harness passes the gate outcome); unstable cells are excluded from // claims per the stability control. -func BuildReport(identity RunIdentity, controls ControlRuns, gateReports []WorkloadGateReport, samples []Sample) (*Report, error) { +func BuildReport(identity RunIdentity, plan RunPlan, controls ControlRuns, gateReports []WorkloadGateReport, samples []Sample) (*Report, error) { if err := identity.Config.Validate(); err != nil { return nil, err } - status, reasons := RunStatus(gateReports) + if err := ValidateLegPlan(plan.Scope, plan.Legs); err != nil { + return nil, err + } + if err := ValidatePlannedNotRun(plan.PlannedNotRun); err != nil { + return nil, err + } + status, reasons := RunStatusScoped(gateReports, plan.Scope) report := &Report{ Schema: ReportSchemaVersion, + Plan: plan, Identity: identity, Status: status, StatusReasons: reasons, @@ -195,15 +204,99 @@ func VerifyReport(report *Report) error { if report.Controls.EmptyFloorMillis <= 0 { return fmt.Errorf("benchmark report: empty-result floor control was not run") } - if report.Controls.SelfRatioArango <= 0 || report.Controls.SelfRatioPostgres <= 0 { - return fmt.Errorf("benchmark report: self-ratio controls were not run") + // The leg plan must be total, closed, and scope-consistent. + if err := ValidateLegPlan(report.Plan.Scope, report.Plan.Legs); err != nil { + return fmt.Errorf("benchmark report: %w", err) + } + if err := ValidatePlannedNotRun(report.Plan.PlannedNotRun); err != nil { + return fmt.Errorf("benchmark report: %w", err) + } + notRun := map[string]string{} + for _, leg := range report.Plan.Legs { + if leg.Mode == "not_run" { + notRun[leg.Backend] = leg.Capability + } + } + // Live versions: a run leg must carry one; a not-run leg may not claim + // one it could not have observed. + if _, absent := notRun[BackendArango]; !absent && strings.TrimSpace(report.Identity.ArangoVersion) == "" { + return fmt.Errorf("benchmark report: arango leg ran without a live arangoVersion") + } + if _, absent := notRun[BackendPostgres]; !absent && strings.TrimSpace(report.Identity.PostgresVersion) == "" { + return fmt.Errorf("benchmark report: postgres leg ran without a live postgresVersion") + } + if _, absent := notRun[BackendArango]; absent && report.Identity.ArangoVersion != "" { + return fmt.Errorf("benchmark report: arangoVersion claimed for a leg that did not run") + } + if _, absent := notRun[BackendPostgres]; absent && report.Identity.PostgresVersion != "" { + return fmt.Errorf("benchmark report: postgresVersion claimed for a leg that did not run") + } + // Self-ratio controls: run legs must have measured one near 1.0; a + // not-run leg's value must be exactly zero (absent), never a forged 1. + type selfRatio struct { + backend string + value float64 + } + for _, sr := range []selfRatio{{BackendArango, report.Controls.SelfRatioArango}, {BackendPostgres, report.Controls.SelfRatioPostgres}} { + if _, absent := notRun[sr.backend]; absent { + if sr.value != 0 { + return fmt.Errorf("benchmark report: self-ratio %v reported for %s although its leg did not run", sr.value, sr.backend) + } + continue + } + if sr.value <= 0 { + return fmt.Errorf("benchmark report: %s self-ratio control was not run", sr.backend) + } + if math.Abs(sr.value-1) > 0.05 { + return fmt.Errorf("benchmark report: %s self-ratio %v off 1.0 beyond 5%% — environment unfit for ratios", sr.backend, sr.value) + } + } + // A not-run leg must be disclosed per workload and honest in the gates: + // blocked arm, zero samples, zero claims. Anything else is a forgery + // path ("silently measured nothing and claimed it anyway"). + disclosed := map[string]bool{} + for _, row := range report.Plan.PlannedNotRun { + if _, absent := notRun[row.Backend]; !absent { + return fmt.Errorf("benchmark report: planned/not_run row for %s/%s but its leg ran", row.WorkloadID, row.Backend) + } + disclosed[row.WorkloadID+"|"+row.Backend] = true + } + for _, gate := range report.GateReports { + for _, arm := range []BackendResult{gate.Arango, gate.Postgres} { + capability, absent := notRun[arm.Backend] + if !absent { + continue + } + if arm.Outcome != OutcomeLabelBlocked { + return fmt.Errorf("benchmark report: %s/%s arm shows %s but its leg was not_run (%s)", + gate.WorkloadID, arm.Backend, arm.Outcome, capability) + } + if !disclosed[gate.WorkloadID+"|"+arm.Backend] { + return fmt.Errorf("benchmark report: %s/%s leg not_run without a planned/not_run disclosure row", + gate.WorkloadID, arm.Backend) + } + } + } + for _, s := range report.Samples { + if _, absent := notRun[s.Backend]; absent { + return fmt.Errorf("benchmark report: samples exist for %s but its leg was not_run", s.Backend) + } + } + for _, row := range report.Summaries { + if _, absent := notRun[row.Backend]; absent { + return fmt.Errorf("benchmark report: summary rows exist for %s but its leg was not_run", row.Backend) + } } - if math.Abs(report.Controls.SelfRatioArango-1) > 0.05 || math.Abs(report.Controls.SelfRatioPostgres-1) > 0.05 { - return fmt.Errorf("benchmark report: self-ratio %v/%v off 1.0 beyond 5%% — environment unfit for ratios", - report.Controls.SelfRatioArango, report.Controls.SelfRatioPostgres) + for _, claim := range report.Claims { + if _, absent := notRun[BackendArango]; absent { + return fmt.Errorf("benchmark report: speed claim for %s exists while the arango leg was not_run", claim.WorkloadID) + } + if _, absent := notRun[BackendPostgres]; absent { + return fmt.Errorf("benchmark report: speed claim for %s exists while the postgres leg was not_run", claim.WorkloadID) + } } - // Status must follow from the gate reports. - status, reasons := RunStatus(report.GateReports) + // Status must follow from the gate reports under the declared scope. + status, reasons := RunStatusScoped(report.GateReports, report.Plan.Scope) if report.Status != status { return fmt.Errorf("benchmark report: status %q but gates imply %q", report.Status, status) } @@ -292,18 +385,37 @@ func VerifyReport(report *Report) error { return fmt.Errorf("benchmark report: claim ratio for %s does not match medians", claim.WorkloadID) } } - // A passed run must contain at least one claim and both arms' evidence. + // A passed run must contain evidence for every leg that ran, and under + // both-arms scope at least one speed claim. A postgres-only pass has + // no claims by construction: it certifies correctness and timings, not + // a comparison. if report.Status == "passed" { - if len(report.Claims) == 0 { - return fmt.Errorf("benchmark report: passed run with no claims") - } - if err := requireBothArms(report); err != nil { - return err + if report.Plan.Scope == RunScopePostgresOnly { + if err := requireRanArms(report); err != nil { + return err + } + } else { + if len(report.Claims) == 0 { + return fmt.Errorf("benchmark report: passed run with no claims") + } + if err := requireBothArms(report); err != nil { + return err + } } } return nil } +func requireRanArms(report *Report) error { + // postgres-only pass posture: the ran arm must actually have evidence. + for _, gate := range report.GateReports { + if gate.Postgres.Outcome == OutcomeLabelPassed { + return nil + } + } + return fmt.Errorf("benchmark report: postgres-only pass without any passed postgres arm evidence") +} + func requireBothArms(report *Report) error { arms := map[string]bool{} for _, gate := range report.GateReports { diff --git a/internal/dataframe/benchmark/report_md.go b/internal/dataframe/benchmark/report_md.go new file mode 100644 index 000000000..da1474592 --- /dev/null +++ b/internal/dataframe/benchmark/report_md.go @@ -0,0 +1,160 @@ +package benchmark + +import ( + "fmt" + "sort" + "strings" +) + +// RenderMarkdown turns a verified report into the human-facing report.md. +// It renders only what VerifyReport certified: every number here is +// recomputed evidence, every absence is named. + +func RenderMarkdown(report *Report) string { + var b strings.Builder + fmt.Fprintf(&b, "# Backend benchmark: Arango/AQL vs PostgreSQL/SQL — %s\n\n", report.Identity.RunID) + fmt.Fprintf(&b, "**Status: %s**\n\n", report.Status) + if len(report.StatusReasons) > 0 { + b.WriteString("Reasons:\n\n") + for _, r := range report.StatusReasons { + fmt.Fprintf(&b, "- %s\n", r) + } + b.WriteString("\n") + } + + b.WriteString("## Run scope and legs\n\n") + fmt.Fprintf(&b, "Scope: `%s`\n\n", report.Plan.Scope) + b.WriteString("| backend | mode | capability |\n|---|---|---|\n") + for _, leg := range report.Plan.Legs { + capability := "—" + if leg.Mode == "not_run" { + capability = leg.Capability + } + fmt.Fprintf(&b, "| %s | %s | %s |\n", leg.Backend, leg.Mode, capability) + } + b.WriteString("\n") + if len(report.Plan.PlannedNotRun) > 0 { + b.WriteString("Planned/not_run disclosure rows (never silently skipped):\n\n") + b.WriteString("| workload | backend | reason |\n|---|---|---|\n") + rows := append([]PlannedNotRun(nil), report.Plan.PlannedNotRun...) + sort.Slice(rows, func(i, j int) bool { + if rows[i].WorkloadID != rows[j].WorkloadID { + return rows[i].WorkloadID < rows[j].WorkloadID + } + return rows[i].Backend < rows[j].Backend + }) + for _, row := range rows { + fmt.Fprintf(&b, "| %s | %s | %s |\n", row.WorkloadID, row.Backend, row.Reason) + } + b.WriteString("\n") + } + + b.WriteString("## Identity\n\n") + fmt.Fprintf(&b, "- Source commit: `%s`\n", report.Identity.SourceCommit) + fmt.Fprintf(&b, "- Dataset digest (sha256): `%s` (preset `%s`)\n", report.Identity.DatasetDigest, report.Identity.Preset) + fmt.Fprintf(&b, "- Arango version: %s\n", versionOrAbsent(report, BackendArango)) + fmt.Fprintf(&b, "- PostgreSQL version: %s\n", versionOrAbsent(report, BackendPostgres)) + fmt.Fprintf(&b, "- ClickHouse disabled during runs: %v\n", report.Identity.ClickHouseDisabled) + fmt.Fprintf(&b, "- Config: warmup=%d iterations=%d concurrency=%d rounds=%d\n\n", + report.Identity.Config.Warmup, report.Identity.Config.Iterations, + report.Identity.Config.Concurrency, report.Identity.Config.Rounds) + + b.WriteString("## Measurement controls\n\n") + fmt.Fprintf(&b, "- Stability: per-cell CV ceiling %.2f; unstable cells excluded from claims", report.Controls.StabilityCVLimit) + if len(report.Controls.UnstableCells) == 0 { + b.WriteString(" (none).\n") + } else { + fmt.Fprintf(&b, ": %s.\n", strings.Join(report.Controls.UnstableCells, ", ")) + } + fmt.Fprintf(&b, "- Self-ratio (backend vs itself across halves): arango=%s, postgres=%.3f — each must sit at 1±0.05.\n", + selfRatioCell(report, BackendArango), report.Controls.SelfRatioPostgres) + fmt.Fprintf(&b, "- Empty-result floor: identical request returning no rows took %.3f ms (client overhead context).\n", report.Controls.EmptyFloorMillis) + fmt.Fprintf(&b, "- Ratio policy: %s — speed ratios are computed only for workloads where both arms passed correctness and completed without timeout.\n\n", + report.Controls.RatioPolicy) + + b.WriteString("## Correctness gates (all must pass before any timing is claimable)\n\n") + b.WriteString("| workload | arango | postgres | claimable |\n|---|---|---|---|\n") + gates := append([]WorkloadGateReport(nil), report.GateReports...) + sort.Slice(gates, func(i, j int) bool { return gates[i].WorkloadID < gates[j].WorkloadID }) + for _, g := range gates { + fmt.Fprintf(&b, "| %s | %s | %s | %v |\n", g.WorkloadID, g.Arango.Outcome, g.Postgres.Outcome, g.Claimable) + } + b.WriteString("\n") + + b.WriteString("## Timings (warm samples only; raw samples in report.json)\n\n") + b.WriteString("| workload | backend | cache | n | failed | p50 ms | p95 ms |\n|---|---|---|---|---|---|---|\n") + summaries := append([]SummaryRow(nil), report.Summaries...) + sort.Slice(summaries, func(i, j int) bool { + if summaries[i].WorkloadID != summaries[j].WorkloadID { + return summaries[i].WorkloadID < summaries[j].WorkloadID + } + if summaries[i].Backend != summaries[j].Backend { + return summaries[i].Backend < summaries[j].Backend + } + return summaries[i].Cache < summaries[j].Cache + }) + for _, row := range summaries { + fmt.Fprintf(&b, "| %s | %s | %s | %d | %d | %.3f | %.3f |\n", + row.WorkloadID, row.Backend, row.Cache, row.Summary.Count, row.Summary.FailedCount, + row.Summary.MedianMillis, row.Summary.P95Millis) + } + b.WriteString("\n") + + b.WriteString("## Speed claims\n\n") + if len(report.Claims) == 0 { + if report.Plan.Scope == RunScopePostgresOnly { + b.WriteString("None: no cross-backend speed claims are made in postgres-only scope. The Arango leg is CI-targeted (see the leg table).\n\n") + } else { + b.WriteString("None passed the ratio-only-both-passed policy.\n\n") + } + } else { + b.WriteString("| workload | arango p50 ms | postgres p50 ms | arango/postgres |\n|---|---|---|---|\n") + for _, c := range report.Claims { + fmt.Fprintf(&b, "| %s | %.3f | %.3f | %.3f |\n", + c.WorkloadID, c.Arango.MedianMillis, c.Postgres.MedianMillis, c.Ratio.ArangoOverPostgres) + } + b.WriteString("\n") + } + + b.WriteString("## Unsupported and exclusions\n\n") + if len(report.Unsupported) == 0 { + b.WriteString("No unsupported cases.\n\n") + } else { + b.WriteString("Unsupported cases (published, not silently dropped):\n\n") + for _, u := range report.Unsupported { + fmt.Fprintf(&b, "- %s\n", u) + } + b.WriteString("\n") + } + if strings.TrimSpace(report.Exclusions) == "" { + b.WriteString("No workloads excluded from performance claims.\n") + } else { + fmt.Fprintf(&b, "%s\n", report.Exclusions) + } + return b.String() +} + +func versionOrAbsent(report *Report, backend string) string { + for _, leg := range report.Plan.Legs { + if leg.Backend == backend && leg.Mode == "not_run" { + return fmt.Sprintf("not observed (leg not run: %s)", leg.Capability) + } + } + if backend == BackendArango { + return report.Identity.ArangoVersion + } + return report.Identity.PostgresVersion +} + +func selfRatioCell(report *Report, backend string) string { + value := report.Controls.SelfRatioArango + if backend == BackendPostgres { + value = report.Controls.SelfRatioPostgres + } + for _, leg := range report.Plan.Legs { + if leg.Backend == backend && leg.Mode == "not_run" { + return "not run (leg not run)" + } + } + return fmt.Sprintf("%.3f", value) +} diff --git a/internal/dataframe/benchmark/report_test.go b/internal/dataframe/benchmark/report_test.go index ed7540587..59eefc102 100644 --- a/internal/dataframe/benchmark/report_test.go +++ b/internal/dataframe/benchmark/report_test.go @@ -43,7 +43,7 @@ func validReport(t *testing.T) *Report { samples = append(samples, sample(backend, WorkloadFilterActiveGender, OutcomePassed, base+float64(i), false)) } } - report, err := BuildReport(identity, controls, []WorkloadGateReport{gate}, samples) + report, err := BuildReport(identity, BothArmsPlan(), controls, []WorkloadGateReport{gate}, samples) if err != nil { t.Fatalf("valid report build failed: %v", err) } @@ -180,7 +180,7 @@ func mustMarshal(t *testing.T, r *Report) []byte { func TestBuildReportRefusesInconsistentConfig(t *testing.T) { report := validReport(t) _ = report - _, err := BuildReport(RunIdentity{Config: RunConfig{}}, ControlRuns{}, nil, nil) + _, err := BuildReport(RunIdentity{Config: RunConfig{}}, BothArmsPlan(), ControlRuns{}, nil, nil) if err == nil { t.Fatal("zero config must be refused at build, not defaulted") } From f6f057095cc016c99b0d2702a5a5c91c4c452a08 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:43:59 +0000 Subject: [PATCH 06/45] feat(benchmark): control-run math and full authoring-route client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ControlRuns values now have producers the verifier can independently recheck: - ControlUnstableCells: warm passed samples per workload/backend; CV over the ceiling (0.35) marks the cell unstable; fewer than 3 samples is unstable, never silently trusted. Warmup and failed samples excluded. - ControlSelfRatio: backend-vs-itself across halves of the (round, iteration) sequence, worst deviation across cells (>=1); ok=false when a cell cannot be split, so a missing control cannot forge its way past the verifier's 1±0.05 bound. - ControlEmptyFloorMillis + Arm.MeasureEmptyFloor: median of an identical request whose expectation is zero rows; any rows back is a protocol error (the control stopped being empty). Harness gains the four public authoring routes, matching openapi_explorer_direct.go and the reconcile/preview direct handlers: CreateExplorer (POST /explorers, sourceExplorerId=default so the draft equals the active revision), FetchBuilderState (GET authoring/v2/builder -> draftVersion/draftDigest/catalog.snapshotToken; refuses a missing token), Reconcile (POST authoring/v2/reconcile -> receiptId), and AuthorWorkload composing publish -> create -> builder -> reconcile. Offline tests pin route order, the draft token round-trip (stale token rejected), missing-token refusal, and the empty-floor non-empty guard. Full package suite green. --- internal/dataframe/benchmark/controls.go | 157 ++++++++++++++++++ internal/dataframe/benchmark/controls_test.go | 109 ++++++++++++ internal/dataframe/benchmark/harness.go | 142 ++++++++++++++++ internal/dataframe/benchmark/harness_test.go | 100 ++++++++++- 4 files changed, 499 insertions(+), 9 deletions(-) create mode 100644 internal/dataframe/benchmark/controls.go create mode 100644 internal/dataframe/benchmark/controls_test.go diff --git a/internal/dataframe/benchmark/controls.go b/internal/dataframe/benchmark/controls.go new file mode 100644 index 000000000..69fde8718 --- /dev/null +++ b/internal/dataframe/benchmark/controls.go @@ -0,0 +1,157 @@ +package benchmark + +import ( + "context" + "fmt" + "sort" +) + +// Control-run computations. These turn raw samples into the ControlRuns +// block; the verifier independently re-checks their bounds, so a run that +// skips a control cannot forge one. + +// passedWarm returns the samples that carry timing meaning: warm cache, +// measured (not warmup), successful. +func passedWarm(samples []Sample) []Sample { + var out []Sample + for _, s := range samples { + if s.Warmup || s.Cache != CacheWarm || s.Outcome != OutcomePassed { + continue + } + out = append(out, s) + } + return out +} + +func cellSamples(samples []Sample, workload, backend string) []Sample { + var out []Sample + for _, s := range samples { + if s.WorkloadID == workload && s.Backend == backend { + out = append(out, s) + } + } + return out +} + +// ControlUnstableCells returns "workload/backend" cells whose passed-warm +// coefficient of variation exceeds the ceiling. The driver excludes these +// cells from claims; with fewer than 3 samples a cell cannot demonstrate +// stability and is reported unstable (never silently trusted). +func ControlUnstableCells(samples []Sample, limit float64) []string { + type cell struct{ workload, backend string } + seen := map[cell]bool{} + var cells []cell + for _, s := range passedWarm(samples) { + c := cell{s.WorkloadID, s.Backend} + if !seen[c] { + seen[c] = true + cells = append(cells, c) + } + } + sort.Slice(cells, func(i, j int) bool { + if cells[i].workload != cells[j].workload { + return cells[i].workload < cells[j].workload + } + return cells[i].backend < cells[j].backend + }) + var unstable []string + for _, c := range cells { + sum := Summarize(cellSamples(samples, c.workload, c.backend)) + if sum.Count < 3 || sum.CoefficientOfVariation > limit { + unstable = append(unstable, c.workload+"/"+c.backend) + } + } + return unstable +} + +// ControlSelfRatio computes the backend-vs-itself sanity ratio across +// halves of the iteration sequence: for every cell, medians of the first +// and second half of passed-warm samples ordered by (round, iteration); +// the reported value is the WORST deviation across cells (always >= 1). +// ok=false when any evaluated cell cannot be split (fewer than 4 samples) +// or no cells exist: a missing self-ratio control cannot pass the verifier. +func ControlSelfRatio(samples []Sample, backend string) (float64, bool) { + type cell struct{ workload, backend string } + seen := map[cell]bool{} + var cells []cell + for _, s := range passedWarm(samples) { + if s.Backend != backend { + continue + } + c := cell{s.WorkloadID, backend} + if !seen[c] { + seen[c] = true + cells = append(cells, c) + } + } + if len(cells) == 0 { + return 0, false + } + worst := 1.0 + for _, c := range cells { + cellSamplesSorted := cellSamples(samples, c.workload, c.backend) + passed := passedWarm(cellSamplesSorted) + if len(passed) < 4 { + return 0, false + } + sort.SliceStable(passed, func(i, j int) bool { + if passed[i].Round != passed[j].Round { + return passed[i].Round < passed[j].Round + } + return passed[i].Iteration < passed[j].Iteration + }) + mid := len(passed) / 2 + a := Summarize(passed[:mid]).MedianMillis + b := Summarize(passed[mid:]).MedianMillis + if a <= 0 || b <= 0 { + return 0, false + } + ratio := a / b + if ratio < 1 { + ratio = 1 / ratio + } + if ratio > worst { + worst = ratio + } + } + return worst, true +} + +// ControlEmptyFloorMillis turns empty-result floor samples into the control +// value (median of passed warm samples); ok=false without evidence. +func ControlEmptyFloorMillis(samples []Sample) (float64, bool) { + passed := passedWarm(samples) + if len(passed) == 0 { + return 0, false + } + var ms []float64 + for _, s := range passed { + ms = append(ms, s.Millis) + } + sort.Float64s(ms) + return percentile(ms, 0.5), true +} + +// MeasureEmptyFloor times an identical request whose expectation is ZERO +// rows (an impossible fixed filter). Any rows back is a protocol error: the +// floor request stopped being empty, so the control would be meaningless. +func (a *Arm) MeasureEmptyFloor(ctx context.Context, clock Clock, token, project, explorer, receiptID, outputID string, iterations int) (float64, error) { + if iterations < 3 { + return 0, fmt.Errorf("benchmark: empty-floor control needs >= 3 iterations") + } + var ms []float64 + for i := 0; i < iterations; i++ { + started := clock.Now() + result, err := a.ExecuteOutput(ctx, token, project, explorer, receiptID, outputID, 1) + elapsed := ElapsedMillis(clock, started) + if err != nil { + return 0, fmt.Errorf("benchmark: empty-floor request failed: %w", err) + } + if result.RowCount != 0 { + return 0, fmt.Errorf("benchmark: empty-floor request returned %d rows; the control request is not empty", result.RowCount) + } + ms = append(ms, elapsed) + } + sort.Float64s(ms) + return percentile(ms, 0.5), nil +} diff --git a/internal/dataframe/benchmark/controls_test.go b/internal/dataframe/benchmark/controls_test.go new file mode 100644 index 000000000..14173460c --- /dev/null +++ b/internal/dataframe/benchmark/controls_test.go @@ -0,0 +1,109 @@ +package benchmark + +import ( + "context" + "strings" + "testing" + "time" +) + +func timedSamples(backend, workload string, base float64, n int) []Sample { + var out []Sample + for i := 0; i < n; i++ { + s := sample(backend, workload, OutcomePassed, base+float64(i%2)*0.2, false) + s.Round = i / 2 + s.Iteration = i % 2 + out = append(out, s) + } + return out +} + +func TestControlUnstableCells(t *testing.T) { + stable := timedSamples(BackendPostgres, WorkloadFilterActiveGender, 10, 8) + if got := ControlUnstableCells(stable, StabilityCVLimit); len(got) != 0 { + t.Fatalf("tight samples must be stable, got %v", got) + } + // high-variance cell + var wild []Sample + for i := 0; i < 8; i++ { + ms := 5.0 + if i%2 == 0 { + ms = 500.0 + } + s := sample(BackendArango, WorkloadGroupCountByPatient, OutcomePassed, ms, false) + s.Round, s.Iteration = i/2, i%2 + wild = append(wild, s) + } + got := ControlUnstableCells(append(append([]Sample{}, stable...), wild...), StabilityCVLimit) + if len(got) != 1 || got[0] != WorkloadGroupCountByPatient+"/"+BackendArango { + t.Fatalf("expected only the wild cell unstable, got %v", got) + } + // too few samples: unstable, never silently trusted + thin := timedSamples(BackendArango, WorkloadDistinctFamily, 10, 2) + got = ControlUnstableCells(thin, StabilityCVLimit) + if len(got) != 1 { + t.Fatalf("2-sample cell must be reported unstable, got %v", got) + } + // warmup and failures are excluded from the timing set + filtered := append(timedSamples(BackendPostgres, WorkloadDistinctFamily, 10, 8), + sample(BackendPostgres, WorkloadDistinctFamily, OutcomePassed, 9000, true), + sample(BackendPostgres, WorkloadDistinctFamily, OutcomeTimeout, 9000, false)) + if got := ControlUnstableCells(filtered, StabilityCVLimit); len(got) != 0 { + t.Fatalf("warmup/failed samples must not poison stability, got %v", got) + } +} + +func TestControlSelfRatio(t *testing.T) { + samples := append( + timedSamples(BackendPostgres, WorkloadFilterActiveGender, 10, 8), + timedSamples(BackendPostgres, WorkloadGroupCountByPatient, 20, 8)..., + ) + value, ok := ControlSelfRatio(samples, BackendPostgres) + if !ok || value < 1 || value > 1.05 { + t.Fatalf("self-ratio on stable halves must sit at 1±0.05, got %v ok=%v", value, ok) + } + // first half fast, second half slow: worst deviation must show up + var skewed []Sample + for i := 0; i < 8; i++ { + ms := 10.0 + if i >= 4 { + ms = 30.0 + } + s := sample(BackendArango, WorkloadFilterActiveGender, OutcomePassed, ms, false) + s.Round, s.Iteration = i/2, i%2 + skewed = append(skewed, s) + } + value, ok = ControlSelfRatio(skewed, BackendArango) + if !ok || value < 2.5 { + t.Fatalf("skewed halves must yield a large self-ratio, got %v ok=%v", value, ok) + } + if _, ok := ControlSelfRatio(timedSamples(BackendArango, WorkloadFilterActiveGender, 10, 2), BackendArango); ok { + t.Fatal("2-sample cell cannot establish a self-ratio") + } + if _, ok := ControlSelfRatio(nil, BackendArango); ok { + t.Fatal("no cells must not report a self-ratio") + } +} + +func TestControlEmptyFloor(t *testing.T) { + f := newFakeArm(BackendPostgres) + defer f.close() + arm := f.arm(BackendPostgres) + f.rows["empty-out"] = []map[string]any{} // a real server returns [], not null + median, err := arm.MeasureEmptyFloor(context.Background(), NewMonotonicClock(), "Bearer t", "proj", "bench", "receipt", "empty-out", 5) + if err != nil { + t.Fatal(err) + } + if median <= 0 { + t.Fatalf("floor median must be positive, got %v", median) + } + if _, err := arm.MeasureEmptyFloor(context.Background(), NewMonotonicClock(), "Bearer t", "proj", "bench", "receipt", "empty-out", 2); err == nil { + t.Fatal("fewer than 3 floor iterations must be refused") + } + // a floor request that returns rows is a protocol error + f.rows["empty-out"] = []map[string]any{{"patient_id": "P1"}} + if _, err := arm.MeasureEmptyFloor(context.Background(), NewMonotonicClock(), "Bearer t", "proj", "bench", "receipt", "empty-out", 5); err == nil || !strings.Contains(err.Error(), "not empty") { + t.Fatalf("non-empty floor request must fail closed, got %v", err) + } + _ = time.Now +} diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go index 451b6d3fc..5fea781eb 100644 --- a/internal/dataframe/benchmark/harness.go +++ b/internal/dataframe/benchmark/harness.go @@ -334,3 +334,145 @@ func rowCountOr(result PreviewResult, err error) int { } return result.RowCount } + +// authoring/v2 lifecycle routes, matching the handlers in +// internal/server/openapi_explorer_direct.go and the reconcile/preview +// direct handlers. Each workload is authored onto its own explorer cloned +// from the seeded default so drafts do not collide. + +// BuilderState is the draft state the Builder GET returns: it carries the +// optimistic-concurrency tokens reconcile needs. +type BuilderState struct { + DraftVersion int `json:"draftVersion"` + DraftDigest string `json:"draftDigest"` + Snapshot struct { + SnapshotToken string `json:"snapshotToken"` + } `json:"snapshot"` + Catalog struct { + SnapshotToken string `json:"snapshotToken"` + } `json:"catalog"` +} + +// SnapshotToken prefers the catalog token, falling back to the inline one. +func (b BuilderState) SnapshotToken() string { + if b.Catalog.SnapshotToken != "" { + return b.Catalog.SnapshotToken + } + return b.Snapshot.SnapshotToken +} + +func (a *Arm) authoringURL(project, explorer string) string { + return a.BaseURL + "/api/v1/projects/" + url.PathEscape(project) + "/explorers/" + url.PathEscape(explorer) + "/authoring/v2" +} + +// CreateExplorer clones a new explorer from sourceExplorerID (usually the +// seeded "default") so each workload owns an independent draft. +func (a *Arm) CreateExplorer(ctx context.Context, token, project, name, title, sourceExplorerID string) (string, error) { + endpoint := a.BaseURL + "/api/v1/projects/" + url.PathEscape(project) + "/explorers" + body, _ := json.Marshal(map[string]any{"name": name, "title": title, "sourceExplorerId": sourceExplorerID}) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/json") + a.authed(req, token) + value, err := doArmRequest(a.Client, req) + if err != nil { + return "", fmt.Errorf("benchmark: create explorer on %s: %w", a.Backend, err) + } + explorerID, _ := value["explorerId"].(string) + if strings.TrimSpace(explorerID) == "" { + if summary, ok := value["explorer"].(map[string]any); ok { + explorerID, _ = summary["explorerId"].(string) + } + } + if strings.TrimSpace(explorerID) == "" { + return "", fmt.Errorf("benchmark: create explorer on %s returned no explorerId", a.Backend) + } + return explorerID, nil +} + +// FetchBuilderState reads the draft state and its optimistic-concurrency +// tokens. The token must be present: reconcile without it would race. +func (a *Arm) FetchBuilderState(ctx context.Context, token, project, explorer string) (BuilderState, error) { + endpoint := a.authoringURL(project, explorer) + "/builder" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return BuilderState{}, err + } + a.authed(req, token) + value, err := doArmRequest(a.Client, req) + if err != nil { + return BuilderState{}, fmt.Errorf("benchmark: builder state on %s: %w", a.Backend, err) + } + raw, _ := json.Marshal(value) + var state BuilderState + if err := json.Unmarshal(raw, &state); err != nil { + return BuilderState{}, fmt.Errorf("benchmark: builder state shape on %s: %w", a.Backend, err) + } + if strings.TrimSpace(state.SnapshotToken()) == "" { + return BuilderState{}, fmt.Errorf("benchmark: builder state on %s omitted the snapshot token", a.Backend) + } + return state, nil +} + +// Reconcile compiles the current draft and returns its receipt id. The +// receipt is what ties a preview to an exact authored revision. +func (a *Arm) Reconcile(ctx context.Context, token, project, explorer, outputID string, state BuilderState) (string, error) { + endpoint := a.authoringURL(project, explorer) + "/reconcile" + body := map[string]any{ + "snapshotToken": state.SnapshotToken(), + "draftVersion": state.DraftVersion, + } + if state.DraftDigest != "" { + body["draftDigest"] = state.DraftDigest + } + if outputID != "" { + body["outputId"] = outputID + } + raw, _ := json.Marshal(body) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(raw)) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/json") + a.authed(req, token) + value, err := doArmRequest(a.Client, req) + if err != nil { + return "", fmt.Errorf("benchmark: reconcile on %s: %w", a.Backend, err) + } + receiptID, _ := value["receiptId"].(string) + if strings.TrimSpace(receiptID) == "" { + if nested, ok := value["receipt"].(map[string]any); ok { + receiptID, _ = nested["receiptId"].(string) + } + } + if strings.TrimSpace(receiptID) == "" { + return "", fmt.Errorf("benchmark: reconcile on %s returned no receiptId", a.Backend) + } + return receiptID, nil +} + +// AuthorWorkload drives the four-route authoring flow for one workload on +// one arm and returns the (explorer, receipt) pair the preview route needs. +// Order is load-bearing: publish the workspace on the generation, clone an +// explorer from the seeded default so the draft equals the active revision, +// read the builder tokens, reconcile for a receipt bound to that draft. +func (a *Arm) AuthorWorkload(ctx context.Context, token, project, generation string, workspace []byte, sourceCommit, name, outputID string) (explorerID, receiptID string, err error) { + if _, err := a.PublishWorkspace(ctx, token, project, generation, workspace, sourceCommit); err != nil { + return "", "", err + } + explorerID, err = a.CreateExplorer(ctx, token, project, name, name, "default") + if err != nil { + return "", "", err + } + state, err := a.FetchBuilderState(ctx, token, project, explorerID) + if err != nil { + return "", "", err + } + receiptID, err = a.Reconcile(ctx, token, project, explorerID, outputID, state) + if err != nil { + return "", "", err + } + return explorerID, receiptID, nil +} diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index 9a235b0d9..f1c62dd0e 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -26,26 +26,56 @@ type fakeArm struct { failWith map[string]int // output -> status uploads int publish int + noSnapshot bool + authoring []string // ordered create/builder/reconcile calls +} + +func (f *fakeArm) authoringCalls() []string { + f.mu.Lock() + defer f.mu.Unlock() + return append([]string(nil), f.authoring...) } func newFakeArm(backend string) *fakeArm { f := &fakeArm{rows: map[string][]map[string]any{}, columns: []string{"patient_id", "gender"}, failWith: map[string]int{}} mux := http.NewServeMux() - mux.HandleFunc("/api/v1/datasets/", func(w http.ResponseWriter, r *http.Request) { - f.mu.Lock() - f.uploads++ - f.mu.Unlock() - _ = json.NewEncoder(w).Encode(map[string]any{"summary": map[string]any{"resources": map[string]any{"Patient": 12}}}) - }) mux.HandleFunc("/api/v1/projects/", func(w http.ResponseWriter, r *http.Request) { - if strings.Contains(r.URL.Path, "/explorer-config") { + path := r.URL.Path + switch { + case strings.HasSuffix(path, "/explorers") && r.Method == http.MethodPost: + var body map[string]any + _ = json.NewDecoder(r.Body).Decode(&body) + name, _ := body["name"].(string) + source, _ := body["sourceExplorerId"].(string) + if source != "default" { + http.Error(w, `{"error":"sourceExplorerId must be default"}`, 400) + return + } + f.mu.Lock() + f.authoring = append(f.authoring, "create:"+name) + f.mu.Unlock() + _ = json.NewEncoder(w).Encode(map[string]any{"explorerId": "exp-created"}) + return + case strings.Contains(path, "/authoring/v2/builder"): + explorer := path[strings.LastIndex(path, "/explorers/")+len("/explorers/") : strings.Index(path, "/authoring")] + f.mu.Lock() + f.authoring = append(f.authoring, "builder:"+explorer) + noSnap := f.noSnapshot + f.mu.Unlock() + state := map[string]any{"draftVersion": 1, "draftDigest": "dig-1", + "catalog": map[string]any{"snapshotToken": "snap-1"}} + if noSnap { + state = map[string]any{"draftVersion": 1} + } + _ = json.NewEncoder(w).Encode(state) + return + case strings.Contains(path, "/explorer-config"): f.mu.Lock() f.publish++ f.mu.Unlock() _ = json.NewEncoder(w).Encode(map[string]any{"recipe": "bench", "translationVersion": "t1", "output": "out"}) return - } - if strings.Contains(r.URL.Path, "/authoring/v2/preview") { + case strings.Contains(path, "/authoring/v2/preview"): var body map[string]any _ = json.NewDecoder(r.Body).Decode(&body) output, _ := body["outputId"].(string) @@ -62,9 +92,29 @@ func newFakeArm(backend string) *fakeArm { } _ = json.NewEncoder(w).Encode(map[string]any{"columns": columns, "rows": rows, "rowCount": len(rows), "sampled": sampled}) return + case strings.Contains(path, "/authoring/v2/reconcile"): + var body map[string]any + _ = json.NewDecoder(r.Body).Decode(&body) + if body["snapshotToken"] != "snap-1" || body["draftVersion"].(float64) != 1 { + http.Error(w, `{"error":"stale draft token"}`, 409) + return + } + output, _ := body["outputId"].(string) + explorer := path[strings.LastIndex(path, "/explorers/")+len("/explorers/") : strings.Index(path, "/authoring")] + f.mu.Lock() + f.authoring = append(f.authoring, "reconcile:"+explorer+":"+output) + f.mu.Unlock() + _ = json.NewEncoder(w).Encode(map[string]any{"receiptId": "rcpt-" + output}) + return } http.NotFound(w, r) }) + mux.HandleFunc("/api/v1/datasets/", func(w http.ResponseWriter, r *http.Request) { + f.mu.Lock() + f.uploads++ + f.mu.Unlock() + _ = json.NewEncoder(w).Encode(map[string]any{"summary": map[string]any{"resources": map[string]any{"Patient": 12}}}) + }) f.server = httptest.NewServer(mux) return f } @@ -351,3 +401,35 @@ func TestBuildServerConfigYAMLDisablesClickHouseBothArms(t *testing.T) { } } } + +func TestAuthorWorkloadFlowPinsRoutesAndTokens(t *testing.T) { + f := newFakeArm(BackendPostgres) + defer f.close() + arm := f.arm(BackendPostgres) + explorerID, receiptID, err := arm.AuthorWorkload(context.Background(), "Bearer t", "proj", "gen1", + []byte(`{"kind":"ExplorerBuilderWorkspace"}`), "abc", "bench-wl", "out_1") + if err != nil { + t.Fatal(err) + } + if explorerID != "exp-created" || receiptID != "rcpt-out_1" { + t.Fatalf("unexpected identities: %s %s", explorerID, receiptID) + } + if f.publish != 1 { + t.Fatalf("workspace must be published exactly once, got %d", f.publish) + } + if got := f.authoringCalls(); len(got) != 3 || + got[0] != "create:bench-wl" || got[1] != "builder:exp-created" || got[2] != "reconcile:exp-created:out_1" { + t.Fatalf("route order wrong: %v", got) + } +} + +func TestFetchBuilderStateRefusesMissingToken(t *testing.T) { + f := newFakeArm(BackendPostgres) + defer f.close() + f.noSnapshot = true + arm := f.arm(BackendPostgres) + if _, err := arm.FetchBuilderState(context.Background(), "Bearer t", "proj", "exp-1"); err == nil || + !strings.Contains(err.Error(), "snapshot token") { + t.Fatalf("missing snapshot token must fail closed, got %v", err) + } +} From 66d243a010bfedb6928b6b1b42eddc8a242627c8 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:46:34 +0000 Subject: [PATCH 07/45] feat(benchmark): live driver with pre-timing auth gate, controls, and per-scope reports RunBenchmark orchestrates the full matched run against public routes: - arms attached (BaseURL) or spawned (ServerSpec via injectable SpawnFunc; process-cold restarts only earned from real restarts); - dataset upload, per-workload authoring through the four-route flow, with an abort when StableExplorerID diverges across arms (matched comparison is invalid); - pre-timing authorization gate (ruling iii) on the representative workload, including a single-arm variant; a leak aborts before any artifact exists; - interleaved MeasureArms timing; MeasureArms now skips non-participating arms instead of panicking; - gate evaluation via the real evaluators (test seam only), oracles and storage-ID mapping from the same deterministic facts; - control runs: CV stability, self-ratios for run legs only (0 = absent for unrun legs), empty-result floor from an authored zero-row workspace (BuildEmptyFloorWorkspace) whose non-empty response is a protocol error; - live versions are mandatory from VersionProbe for every run leg; the report only exists if BuildReport's internal VerifyReport passes. Offline driver tests pin: both-arms pass with claims + control values, postgres-only pass with zero claims/samples/version/self-ratio for the unrun leg, auth-leak abort before artifact, divergent-explorer abort, source-commit validation, using the injectable scripted clock (host jitter tripping the self-ratio control in-test is the rule working as designed). Full package suite green, vet clean. --- internal/dataframe/benchmark/authoring.go | 12 + internal/dataframe/benchmark/driver.go | 468 +++++++++++++++++++ internal/dataframe/benchmark/driver_test.go | 188 ++++++++ internal/dataframe/benchmark/gates.go | 31 ++ internal/dataframe/benchmark/harness.go | 23 + internal/dataframe/benchmark/harness_test.go | 38 +- 6 files changed, 746 insertions(+), 14 deletions(-) create mode 100644 internal/dataframe/benchmark/driver.go create mode 100644 internal/dataframe/benchmark/driver_test.go diff --git a/internal/dataframe/benchmark/authoring.go b/internal/dataframe/benchmark/authoring.go index 01c927005..13fd0fa9f 100644 --- a/internal/dataframe/benchmark/authoring.go +++ b/internal/dataframe/benchmark/authoring.go @@ -279,6 +279,18 @@ func BuildWorkspace(workloadID string) ([]byte, error) { return nil, fmt.Errorf("benchmark: no workspace template for workload %q", workloadID) } +// BuildEmptyFloorWorkspace authors the identical-request empty-result floor +// control: the same shape as filter-active-gender whose fixed filter cannot +// match any patient, so the expectation is provably zero rows. +func BuildEmptyFloorWorkspace() ([]byte, error) { + return patientWorkspace("empty_floor", []wsColumnDef{ + fieldCol("patient_id", "base", "id", "VALUE", "string", 0), + fieldCol("gender", "base", "gender", "VALUE", "string", 1), + }, nil, []wsFixed{ + {Column: "gender", Values: []string{"__bench_impossible_gender_value__"}}, + }), nil +} + // AuthoringVerification asks the repository's own decoder about every // battery workspace. It runs before any service is contacted and feeds the // report's arm plan: rejected shapes are planned/not_run, named. diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go new file mode 100644 index 000000000..d1a28f98e --- /dev/null +++ b/internal/dataframe/benchmark/driver.go @@ -0,0 +1,468 @@ +package benchmark + +import ( + "context" + "errors" + "fmt" + "net/http" + "sort" + "strings" + "time" +) + +// The live driver: spawn/attach arms, load the deterministic dataset, author +// every battery workload through public routes, pass the pre-timing +// authorization gate (ruling iii), measure interleaved, run control runs, +// and emit a report that VerifyReport must accept. Every failure aborts +// before any success-shaped artifact exists. + +// ArmConfig describes one backend arm: either attach to a running server +// (BaseURL) or spawn one from BinaryPath with ConfigYAML. +type ArmConfig struct { + BinaryPath string + ConfigYAML string + Env []string + BaseURL string + // ScrubSecrets are redacted from arm logs (DSNs, tokens). + ScrubSecrets []string + // ReadyURL overrides the readiness poll target for spawned arms. + ReadyURL string +} + +// SpawnFunc launches a server process; the default is provision.SpawnServer. +// Injectable so the driver's protocol is testable offline. +type SpawnFunc func(ctx context.Context, spec ServerSpec, secrets []string, listenAddr string) (*ServerProcess, error) + +// VersionProbeFunc returns the LIVE backend version string for an arm +// (e.g. by executing the launched binary's --version). Required: the +// verifier refuses a passed run without observed versions. +type VersionProbeFunc func(ctx context.Context, cfg ArmConfig, backend string) (string, error) + +// GateFunc and SingleArmGateFunc default to the package evaluators; the +// seam exists for protocol tests, never for production use. +type GateFunc func(workloadID string, columns []string, expected *CanonicalBag, arango, postgres BackendResult, arangoRows, postgresRows []map[string]any, idMap *StorageIDMap) WorkloadGateReport +type SingleArmGateFunc func(workloadID, backend string, columns []string, expected *CanonicalBag, arm BackendResult, rows []map[string]any, idMap *StorageIDMap, blockedDetail string) WorkloadGateReport + +// DriverOptions is the full, explicit driver input. No silent defaults for +// anything that lands in the report. +type DriverOptions struct { + RunID string + StartedAt time.Time + Plan RunPlan + Preset string + Params DatasetParams + Config RunConfig + PreviewLimit int + SourceCommit string + + UnrestrictedToken string + ScopedToken string + + Arango ArmConfig + Postgres ArmConfig + + StderrDir string + Clock Clock + + Spawn SpawnFunc + VersionProbe VersionProbeFunc + + Gates GateFunc + SingleArmGates SingleArmGateFunc + EmptyFloorIters int +} + +// ArmEvidence is the per-arm operational record kept beside the report. +type ArmEvidence struct { + Backend string `json:"backend"` + BaseURL string `json:"baseUrl"` + LogPath string `json:"logPath,omitempty"` + Version string `json:"version"` + Spawned bool `json:"spawned"` +} + +type runningArm struct { + arm *Arm + proc *ServerProcess + evidence ArmEvidence +} + +// RunBenchmark executes the matched battery. It returns the verified report +// and per-arm evidence, or an error before any artifact is written. +func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEvidence, error) { + if err := ValidateLegPlan(opts.Plan.Scope, opts.Plan.Legs); err != nil { + return nil, nil, err + } + if err := opts.Config.Validate(); err != nil { + return nil, nil, err + } + if opts.PreviewLimit < 1 { + return nil, nil, fmt.Errorf("benchmark: preview limit must be >= 1") + } + if strings.TrimSpace(opts.SourceCommit) == "" || len(opts.SourceCommit) != 40 { + return nil, nil, fmt.Errorf("benchmark: source commit must be the full 40-hex SHA the arms were built from") + } + clock := opts.Clock + if clock == nil { + clock = NewMonotonicClock() + } + spawn := opts.Spawn + if spawn == nil { + spawn = SpawnServer + } + gates := opts.Gates + if gates == nil { + gates = EvaluateGates + } + singleGates := opts.SingleArmGates + if singleGates == nil { + singleGates = EvaluateGatesSingleArm + } + + files, err := GenerateFiles(opts.Params) + if err != nil { + return nil, nil, err + } + + // Participating arms, in fixed order. + type backendArm struct { + backend string + cfg ArmConfig + } + var participants []backendArm + runLegs := map[string]bool{} + for _, leg := range opts.Plan.Legs { + if leg.Mode == "run" { + runLegs[leg.Backend] = true + } + } + for _, b := range []backendArm{{BackendArango, opts.Arango}, {BackendPostgres, opts.Postgres}} { + if runLegs[b.backend] { + participants = append(participants, b) + } + } + arms := make(map[string]*runningArm, len(participants)) + defer func() { + for _, r := range arms { + if r.proc != nil { + r.proc.Stop() + } + } + }() + for _, p := range participants { + ra, err := startArm(ctx, p.backend, p.cfg, spawn, opts) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: %s arm: %w", p.backend, err) + } + arms[p.backend] = ra + } + + // Load the dataset on every participating arm. + for _, p := range participants { + if err := arms[p.backend].arm.UploadGeneration(ctx, opts.UnrestrictedToken, opts.Params.Project, opts.Params.Generation, files.Files, opts.Params.AuthResourcePath); err != nil { + return nil, nil, fmt.Errorf("benchmark: %s upload: %w", p.backend, err) + } + } + + // Author every workload on every participating arm. Explorer ids are + // derived from names (StableExplorerID), so the arms must agree on the + // id or the matched comparison is invalid. + receiptIDs := map[string]map[string]string{} // backend -> workload -> receipt + outputIDs := map[string]map[string]string{} // backend -> workload -> output + perBackend := map[string]map[string]string{} + for _, p := range participants { + perBackend[p.backend] = map[string]string{} + receiptIDs[p.backend] = map[string]string{} + outputIDs[p.backend] = map[string]string{} + } + for _, spec := range Battery { + workspace, err := BuildWorkspace(spec.ID) + if err != nil { + return nil, nil, err + } + outputID := OutputIDFor(spec.ID) + name := "bench-" + spec.ID + var explorerID string + for _, p := range participants { + eid, rid, err := arms[p.backend].arm.AuthorWorkload(ctx, opts.UnrestrictedToken, opts.Params.Project, opts.Params.Generation, + workspace, opts.SourceCommit, name, outputID) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: author %s on %s: %w", spec.ID, p.backend, err) + } + if explorerID == "" { + explorerID = eid + } else if eid != explorerID { + return nil, nil, fmt.Errorf("benchmark: %s authored under divergent explorer ids %s vs %s; arms are not matched", spec.ID, explorerID, eid) + } + perBackend[p.backend][spec.ID] = eid + receiptIDs[p.backend][spec.ID] = rid + outputIDs[p.backend][spec.ID] = outputID + } + } + // One explorer identity serves both arms (asserted equal above). + explorerIDByWorkload := perBackend[participants[0].backend] + + // Empty-floor explorers (zero-row expectation) per arm. + floorWorkspace, err := BuildEmptyFloorWorkspace() + if err != nil { + return nil, nil, err + } + floorExplorers := map[string]string{} + floorReceipts := map[string]string{} + for _, p := range participants { + eid, rid, err := arms[p.backend].arm.AuthorWorkload(ctx, opts.UnrestrictedToken, opts.Params.Project, opts.Params.Generation, + floorWorkspace, opts.SourceCommit, "bench-empty-floor", "empty_floor") + if err != nil { + return nil, nil, fmt.Errorf("benchmark: author empty-floor on %s: %w", p.backend, err) + } + floorExplorers[p.backend] = eid + floorReceipts[p.backend] = rid + } + + // Pre-timing authorization gate (ruling iii). If the scoped principal + // sees anything it was not granted, timings are meaningless: abort + // before producing any artifact. + authWorkload := WorkloadFilterActiveGender + baseOpts := RunOptions{ + Project: opts.Params.Project, Generation: opts.Params.Generation, + AuthResourcePath: opts.Params.AuthResourcePath, + UnrestrictedToken: opts.UnrestrictedToken, ScopedToken: opts.ScopedToken, + PreviewLimit: opts.PreviewLimit, Config: opts.Config, SourceCommit: opts.SourceCommit, + } + { + ra := RunOptions{Project: baseOpts.Project, Generation: baseOpts.Generation, AuthResourcePath: baseOpts.AuthResourcePath, UnrestrictedToken: baseOpts.UnrestrictedToken, ScopedToken: baseOpts.ScopedToken, PreviewLimit: baseOpts.PreviewLimit, Config: baseOpts.Config, SourceCommit: baseOpts.SourceCommit, + Explorer: explorerIDByWorkload[authWorkload]} + rc := map[string]string{} + oi := map[string]string{} + for _, p := range participants { + rc[p.backend] = receiptIDs[p.backend][authWorkload] + oi[p.backend] = outputIDs[p.backend][authWorkload] + } + var authReport WorkloadGateReport + if len(participants) == 2 { + authReport = NegativeAuthControl(ctx, arms[BackendArango].arm, arms[BackendPostgres].arm, ra, rc, oi) + } else { + authReport = NegativeAuthControlSingleArm(ctx, authWorkload, arms[participants[0].backend].arm, ra, rc, oi) + } + if !authReport.Claimable { + return nil, nil, fmt.Errorf("benchmark: pre-timing authorization gate FAILED: %s; no artifact is produced", authReport.ExclusionReason) + } + } + + // Timing, workload by workload, interleaved round-robin. + var allSamples []Sample + var gateReports []WorkloadGateReport + idMap := NewStorageIDMap(files.Facts) + var cold func(ctx context.Context, arm *Arm) error + procCold := false + for _, p := range participants { + if arms[p.backend].proc != nil { + procCold = true + } + } + if procCold { + cold = func(ctx context.Context, arm *Arm) error { + if arm == nil || arm.Proc == nil { + return nil + } + return arm.Proc.Restart(ctx) + } + } + for _, spec := range Battery { + ra := RunOptions{Project: baseOpts.Project, Generation: baseOpts.Generation, AuthResourcePath: baseOpts.AuthResourcePath, UnrestrictedToken: baseOpts.UnrestrictedToken, ScopedToken: baseOpts.ScopedToken, PreviewLimit: baseOpts.PreviewLimit, Config: baseOpts.Config, SourceCommit: baseOpts.SourceCommit, + Explorer: explorerIDByWorkload[spec.ID]} + rc := map[string]string{} + oi := map[string]string{} + for _, p := range participants { + rc[p.backend] = receiptIDs[p.backend][spec.ID] + oi[p.backend] = outputIDs[p.backend][spec.ID] + } + var arangoArm, postgresArm *Arm + if a, ok := arms[BackendArango]; ok { + arangoArm = a.arm + } + if a, ok := arms[BackendPostgres]; ok { + postgresArm = a.arm + } + samples, finals, err := MeasureArms(ctx, clock, spec.ID, arangoArm, postgresArm, ra, rc, oi, cold) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: measure %s: %w", spec.ID, err) + } + allSamples = append(allSamples, samples...) + var expected *CanonicalBag + if spec.OracleAvailable { + expected, err = OracleFor(spec.ID, files.Facts) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: oracle %s: %w", spec.ID, err) + } + } + arangoResult := backendResultFrom(BackendArango, samples, finals) + postgresResult := backendResultFrom(BackendPostgres, samples, finals) + var report WorkloadGateReport + if len(participants) == 2 { + report = gates(spec.ID, spec.Columns, expected, arangoResult, postgresResult, + rowsOf(finals[BackendArango]), rowsOf(finals[BackendPostgres]), idMap) + } else { + backend := participants[0].backend + blockedDetail := "" + for _, leg := range opts.Plan.Legs { + if leg.Backend == otherBackend(backend) { + blockedDetail = "leg not run: " + leg.Capability + } + } + result := arangoResult + if backend == BackendPostgres { + result = postgresResult + } + report = singleGates(spec.ID, backend, spec.Columns, expected, result, rowsOf(finals[backend]), idMap, blockedDetail) + } + gateReports = append(gateReports, report) + } + + // Control runs. + controls := ControlRuns{ + StabilityCVLimit: StabilityCVLimit, + UnstableCells: ControlUnstableCells(allSamples, StabilityCVLimit), + RatioPolicy: "ratio-only-both-passed", + } + for _, backend := range []string{BackendArango, BackendPostgres} { + if !runLegs[backend] { + continue // stays 0: absent, never forged + } + value, ok := ControlSelfRatio(allSamples, backend) + if !ok { + return nil, nil, fmt.Errorf("benchmark: %s self-ratio control cannot be computed from the collected samples; run is not fit for claims", backend) + } + if backend == BackendArango { + controls.SelfRatioArango = value + } else { + controls.SelfRatioPostgres = value + } + } + floorIters := opts.EmptyFloorIters + if floorIters == 0 { + floorIters = 5 + } + worstFloor := 0.0 + for _, p := range participants { + median, err := arms[p.backend].arm.MeasureEmptyFloor(ctx, clock, opts.UnrestrictedToken, opts.Params.Project, floorExplorers[p.backend], floorReceipts[p.backend], "empty_floor", floorIters) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: %s empty-floor control: %w", p.backend, err) + } + if median > worstFloor { + worstFloor = median + } + } + controls.EmptyFloorMillis = worstFloor + + identity := RunIdentity{ + RunID: opts.RunID, StartedAt: opts.StartedAt.UTC(), Preset: opts.Preset, + SourceCommit: opts.SourceCommit, DatasetDigest: files.Digest, + ClickHouseDisabled: true, Config: opts.Config, + } + for _, p := range participants { + version, err := opts.VersionProbe(ctx, p.cfg, p.backend) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: %s version probe: %w", p.backend, err) + } + if strings.TrimSpace(version) == "" { + return nil, nil, fmt.Errorf("benchmark: %s version probe returned an empty version", p.backend) + } + arms[p.backend].evidence.Version = version + if p.backend == BackendArango { + identity.ArangoVersion = version + } else { + identity.PostgresVersion = version + } + } + + report, err := BuildReport(identity, opts.Plan, controls, gateReports, allSamples) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: report would not verify (refusing to emit): %w", err) + } + var evidence []ArmEvidence + for _, p := range participants { + evidence = append(evidence, arms[p.backend].evidence) + } + sort.Slice(evidence, func(i, j int) bool { return evidence[i].Backend < evidence[j].Backend }) + return report, evidence, nil +} + +func startArm(ctx context.Context, backend string, cfg ArmConfig, spawn SpawnFunc, opts DriverOptions) (*runningArm, error) { + if cfg.BaseURL != "" { + return &runningArm{ + arm: &Arm{Backend: backend, BaseURL: strings.TrimRight(cfg.BaseURL, "/"), Client: &http.Client{Timeout: 120 * time.Second}}, + evidence: ArmEvidence{Backend: backend, BaseURL: cfg.BaseURL, Spawned: false}, + }, nil + } + if cfg.BinaryPath == "" { + return nil, fmt.Errorf("arm must provide either BaseURL or BinaryPath") + } + port, err := FreePort() + if err != nil { + return nil, err + } + listen := fmt.Sprintf("127.0.0.1:%d", port) + ready := cfg.ReadyURL + if ready == "" { + ready = "http://" + listen + "/api/v1/system/ready" + } + timeout := 60 * time.Second + spec := ServerSpec{BinaryPath: cfg.BinaryPath, Backend: backend, ConfigYAML: cfg.ConfigYAML, Env: cfg.Env, ReadyURL: ready, Timeout: timeout, StderrDir: opts.StderrDir} + proc, err := spawn(ctx, spec, cfg.ScrubSecrets, listen) + if err != nil { + return nil, err + } + return &runningArm{ + arm: &Arm{Backend: backend, BaseURL: "http://" + listen, Client: &http.Client{Timeout: 120 * time.Second}, Proc: proc}, + proc: proc, + evidence: ArmEvidence{Backend: backend, BaseURL: "http://" + listen, LogPath: proc.LogPath(), Spawned: true}, + }, nil +} + +func backendResultFrom(backend string, samples []Sample, finals map[string]PreviewResult) BackendResult { + result := BackendResult{Backend: backend, Outcome: OutcomeLabelPassed} + var failures []string + for _, s := range samples { + if s.Backend != backend || s.Warmup { + continue + } + if s.Outcome != OutcomePassed { + result.Outcome = OutcomeLabelFailed + failures = append(failures, string(s.Outcome)+":"+s.ErrorCode) + } + } + if len(failures) > 0 { + seen := map[string]bool{} + var uniq []string + for _, f := range failures { + if !seen[f] { + seen[f] = true + uniq = append(uniq, f) + } + } + sort.Strings(uniq) + if len(uniq) > 3 { + uniq = uniq[:3] + } + result.ErrorCode = strings.Join(uniq, ";") + return result + } + final, ok := finals[backend] + if !ok { + result.Outcome = OutcomeLabelFailed + result.ErrorCode = "no completed measured execution" + return result + } + result.Columns = final.Columns + result.RowCount = final.RowCount + result.Truncated = final.Sampled + bag := Bag(CanonicalizeRows(final.Columns, final.Rows)) + result.BagDigest = bag.Digest() + return result +} + +func rowsOf(result PreviewResult) []map[string]any { return result.Rows } + +// ErrAbortBeforeArtifact distinguishes "refused to run" from "run failed". +var ErrAbortBeforeArtifact = errors.New("benchmark: aborted before producing an artifact") diff --git a/internal/dataframe/benchmark/driver_test.go b/internal/dataframe/benchmark/driver_test.go new file mode 100644 index 000000000..55042f288 --- /dev/null +++ b/internal/dataframe/benchmark/driver_test.go @@ -0,0 +1,188 @@ +package benchmark + +import ( + "context" + "strings" + "testing" + "time" +) + +// Offline driver protocol tests: fake arms serve the public routes; the +// gate EVALUATION itself is covered by gates_test/oracle_test, so these +// tests pin orchestration: route coverage, matched arms, control runs, +// per-scope report shape, and fail-closed aborts. + +func driverOptions(fArango, fPostgres *fakeArm, scope string) DriverOptions { + plan := BothArmsPlan() + if scope == RunScopePostgresOnly { + plan = PostgresOnlyPlan("no ArangoDB service in worker container") + } + plan.PlannedNotRun = PlannedNotRunForBackends(plan.NotRunBackends()) + for i := range plan.PlannedNotRun { + plan.PlannedNotRun[i].Reason = "leg not run: worker container lacks the service" + } + // every workload output must answer; empty_floor must be zero rows + for _, f := range []*fakeArm{fArango, fPostgres} { + if f == nil { + continue + } + f.scopedToken = "Bearer scoped" + for _, spec := range Battery { + if _, ok := f.rows[OutputIDFor(spec.ID)]; !ok { + f.rows[OutputIDFor(spec.ID)] = []map[string]any{{"patient_id": "p1"}} + } + } + f.rows["empty_floor"] = []map[string]any{} + } + opts := DriverOptions{ + RunID: "bench-driver-test", StartedAt: time.Unix(1700000000, 0).UTC(), + Plan: plan, Preset: PresetTiny, + Params: DatasetParams{Preset: PresetTiny, Seed: 7, Project: "proj", Generation: "gen1", AuthResourcePath: "/programs/p/projects/proj/x"}, + Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, + PreviewLimit: 1000, SourceCommit: strings.Repeat("a", 40), + UnrestrictedToken: "Bearer unauth", ScopedToken: "Bearer scoped", + Clock: &scriptedClock{base: time.Unix(1700000000, 0)}, + VersionProbe: func(ctx context.Context, cfg ArmConfig, backend string) (string, error) { + if backend == BackendArango { + return "3.12.x-fake", nil + } + return "PostgreSQL 18.x-fake", nil + }, + // Protocol seam only: real evaluation is pinned elsewhere. + Gates: func(workloadID string, columns []string, expected *CanonicalBag, arango, postgres BackendResult, arangoRows, postgresRows []map[string]any, idMap *StorageIDMap) WorkloadGateReport { + return WorkloadGateReport{WorkloadID: workloadID, Claimable: true, + Arango: arango, Postgres: postgres, + Gates: []GateResult{{Gate: GateBagEquality, Pass: true}}} + }, + SingleArmGates: func(workloadID, backend string, columns []string, expected *CanonicalBag, arm BackendResult, rows []map[string]any, idMap *StorageIDMap, blockedDetail string) WorkloadGateReport { + report := WorkloadGateReport{WorkloadID: workloadID, Claimable: true, + Gates: []GateResult{{Gate: GateOracleRows, Pass: true}}} + if backend == BackendArango { + report.Arango = arm + report.Postgres = BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelBlocked, Detail: blockedDetail} + } else { + report.Postgres = arm + report.Arango = BackendResult{Backend: BackendArango, Outcome: OutcomeLabelBlocked, Detail: blockedDetail} + } + return report + }, + } + if fArango != nil { + opts.Arango = ArmConfig{BaseURL: fArango.server.URL} + } + if fPostgres != nil { + opts.Postgres = ArmConfig{BaseURL: fPostgres.server.URL} + } + return opts +} + +func TestDriverBothArmsProtocol(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + report, evidence, err := RunBenchmark(context.Background(), driverOptions(fa, fp, RunScopeBothArms)) + if err != nil { + t.Fatal(err) + } + if report.Status != "passed" || len(report.Claims) == 0 { + t.Fatalf("expected passing both-arms report with claims: %s %v", report.Status, report.StatusReasons) + } + if report.Identity.ArangoVersion == "" || report.Identity.PostgresVersion == "" { + t.Fatal("live versions must be recorded") + } + if report.Controls.EmptyFloorMillis <= 0 || report.Controls.SelfRatioArango < 1 || report.Controls.SelfRatioPostgres < 1 { + t.Fatalf("controls must be genuinely run: %+v", report.Controls) + } + if len(evidence) != 2 || evidence[0].Backend != BackendArango { + t.Fatalf("evidence: %+v", evidence) + } + // both arms must have uploaded the dataset and authored every workload + for name, f := range map[string]*fakeArm{"arango": fa, "postgres": fp} { + if f.uploads != 1 { + t.Fatalf("%s: uploads=%d want 1", name, f.uploads) + } + if f.publish != len(Battery)+1 { // + empty floor + t.Fatalf("%s: publish=%d want %d", name, f.publish, len(Battery)+1) + } + } + // round-trip the artifact + if _, err := LoadReport(mustMarshal(t, report)); err != nil { + t.Fatalf("driver report must verify: %v", err) + } +} + +// scriptedClock makes every elapsed measurement exactly 10ms: protocol +// tests must not depend on host jitter. The self-ratio failing above in +// this file is exactly why the clock is injectable. +type scriptedClock struct { + base time.Time + n int +} + +func (c *scriptedClock) Now() time.Time { + c.n++ + return c.base.Add(time.Duration(c.n) * time.Millisecond) +} + +func (c *scriptedClock) Since(time.Time) time.Duration { return 10 * time.Millisecond } + +func TestDriverPostgresOnlyProtocol(t *testing.T) { + fp := newFakeArm(BackendPostgres) + defer fp.close() + report, evidence, err := RunBenchmark(context.Background(), driverOptions(nil, fp, RunScopePostgresOnly)) + if err != nil { + t.Fatal(err) + } + if report.Status != "passed" { + t.Fatalf("postgres-only pass expected: %v", report.StatusReasons) + } + if len(report.Claims) != 0 { + t.Fatal("postgres-only must have no speed claims") + } + if report.Identity.ArangoVersion != "" || report.Controls.SelfRatioArango != 0 { + t.Fatal("unrun arango leg must carry no version and no self-ratio") + } + for _, s := range report.Samples { + if s.Backend != BackendPostgres { + t.Fatalf("sample for unrun backend: %+v", s) + } + } + if len(evidence) != 1 || evidence[0].Backend != BackendPostgres { + t.Fatalf("evidence: %+v", evidence) + } + if _, err := LoadReport(mustMarshal(t, report)); err != nil { + t.Fatalf("postgres-only driver report must verify: %v", err) + } +} + +func TestDriverAbortsBeforeArtifactOnAuthLeak(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fp.leakScoped = true // scoped principal sees rows on postgres: denial broken + _, _, err := RunBenchmark(context.Background(), driverOptions(fa, fp, RunScopeBothArms)) + if err == nil || !strings.Contains(err.Error(), "authorization gate") { + t.Fatalf("auth leak must abort before any artifact, got %v", err) + } +} + +func TestDriverRejectsDivergentExplorerIDs(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fa.explorerID = "exp-other" + _, _, err := RunBenchmark(context.Background(), driverOptions(fa, fp, RunScopeBothArms)) + if err == nil || !strings.Contains(err.Error(), "not matched") { + t.Fatalf("divergent explorer ids must abort the matched comparison, got %v", err) + } +} + +func TestDriverRequiresRealSourceCommit(t *testing.T) { + fp := newFakeArm(BackendPostgres) + defer fp.close() + opts := driverOptions(nil, fp, RunScopePostgresOnly) + opts.SourceCommit = "abc" + if _, _, err := RunBenchmark(context.Background(), opts); err == nil { + t.Fatal("short source commit must be refused") + } +} diff --git a/internal/dataframe/benchmark/gates.go b/internal/dataframe/benchmark/gates.go index f2dc7ca1e..4833855fa 100644 --- a/internal/dataframe/benchmark/gates.go +++ b/internal/dataframe/benchmark/gates.go @@ -290,3 +290,34 @@ func FormatExclusionList(reports []WorkloadGateReport) string { } return "timings excluded (correctness not passed): " + strings.Join(excluded, "; ") } + +// EvaluateNegativeAuthSingleArm applies the pre-timing authorization gate +// for a single participating arm (postgres-only sessions): the ran arm must +// see data unrestricted and zero rows scoped; the absent arm is disclosed, +// never imputed a pass. +func EvaluateNegativeAuthSingleArm(workloadID, backend string, unrestrictedRows, scopedRows int, scopedErr string) WorkloadGateReport { + report := WorkloadGateReport{WorkloadID: workloadID, Claimable: true} + add := func(pass bool, format string, args ...any) { + result := GateResult{Gate: GateNegativeAuth, Pass: pass} + if !pass { + result.Detail = fmt.Sprintf(format, args...) + report.Claimable = false + if report.ExclusionReason == "" { + report.ExclusionReason = "negative_auth: " + result.Detail + } + } + report.Gates = append(report.Gates, result) + } + if unrestrictedRows <= 0 { + add(false, "unrestricted baseline has %d rows; a denial against an empty dataset proves nothing", unrestrictedRows) + return report + } + if scopedErr != "" { + add(false, "%s scoped read errored (%q); a transport failure is not a denial", backend, scopedErr) + return report + } + add(scopedRows == 0, "%s scoped principal saw %d rows outside its grant", backend, scopedRows) + report.Gates = append(report.Gates, GateResult{Gate: GateAuthUnrestricted, Pass: true, + Detail: fmt.Sprintf("unrestricted baseline rows=%d; other arm disclosed as not run", unrestrictedRows)}) + return report +} diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go index 5fea781eb..6704f75fb 100644 --- a/internal/dataframe/benchmark/harness.go +++ b/internal/dataframe/benchmark/harness.go @@ -237,6 +237,9 @@ func MeasureArms(ctx context.Context, clock Clock, workloadID string, arango, po for round := 1; round <= rounds; round++ { if !warmup && coldRestart != nil { for _, backend := range []string{BackendArango, BackendPostgres} { + if arms[backend] == nil { + continue // leg not participating in this scope + } if err := coldRestart(ctx, arms[backend]); err != nil { samples = append(samples, Sample{WorkloadID: workloadID, Backend: backend, Outcome: OutcomeError, ErrorCode: "cold restart: " + err.Error()}) } @@ -246,6 +249,9 @@ func MeasureArms(ctx context.Context, clock Clock, workloadID string, arango, po iteration++ for _, backend := range []string{BackendArango, BackendPostgres} { // fixed order: matched scheduling arm := arms[backend] + if arm == nil { + continue + } started := clock.Now() result, err := arm.ExecuteOutput(ctx, tokenFor(backend, opts), opts.Project, opts.Explorer, receiptIDs[backend], outputIDs[backend], opts.PreviewLimit) @@ -476,3 +482,20 @@ func (a *Arm) AuthorWorkload(ctx context.Context, token, project, generation str } return explorerID, receiptID, nil } + +// NegativeAuthControlSingleArm runs the pre-timing authorization gate for +// the single participating arm. +func NegativeAuthControlSingleArm(ctx context.Context, workloadID string, arm *Arm, opts RunOptions, receiptIDs, outputIDs map[string]string) WorkloadGateReport { + backend := arm.Backend + baseline, err := arm.ExecuteOutput(ctx, opts.UnrestrictedToken, opts.Project, opts.Explorer, receiptIDs[backend], outputIDs[backend], opts.PreviewLimit) + baselineRows := rowCountOr(baseline, err) + scoped, scopedErr := arm.ExecuteOutput(ctx, opts.ScopedToken, opts.Project, opts.Explorer, receiptIDs[backend], outputIDs[backend], opts.PreviewLimit) + return EvaluateNegativeAuthSingleArm(workloadID, backend, baselineRows, rowCountOr(scoped, scopedErr), errorText(scopedErr)) +} + +func errorText(err error) string { + if err == nil { + return "" + } + return err.Error() +} diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index f1c62dd0e..6a23673ab 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -16,18 +16,20 @@ import ( // fakeArm serves the public routes the harness uses, with scripted rows per // (backend, output) so the offline tests pin the client and protocol. type fakeArm struct { - server *httptest.Server - mu sync.Mutex - calls []string // ordered "backend:route:output" - rows map[string][]map[string]any - columns []string - sampled bool - leakScoped bool - failWith map[string]int // output -> status - uploads int - publish int - noSnapshot bool - authoring []string // ordered create/builder/reconcile calls + server *httptest.Server + mu sync.Mutex + calls []string // ordered "backend:route:output" + rows map[string][]map[string]any + columns []string + sampled bool + leakScoped bool + failWith map[string]int // output -> status + uploads int + publish int + noSnapshot bool + scopedToken string // when the bearer equals this, deny like production: zero rows + explorerID string + authoring []string // ordered create/builder/reconcile calls } func (f *fakeArm) authoringCalls() []string { @@ -53,8 +55,12 @@ func newFakeArm(backend string) *fakeArm { } f.mu.Lock() f.authoring = append(f.authoring, "create:"+name) + explorerID := f.explorerID + if explorerID == "" { + explorerID = "exp-created" + } f.mu.Unlock() - _ = json.NewEncoder(w).Encode(map[string]any{"explorerId": "exp-created"}) + _ = json.NewEncoder(w).Encode(map[string]any{"explorerId": explorerID}) return case strings.Contains(path, "/authoring/v2/builder"): explorer := path[strings.LastIndex(path, "/explorers/")+len("/explorers/") : strings.Index(path, "/authoring")] @@ -79,10 +85,14 @@ func newFakeArm(backend string) *fakeArm { var body map[string]any _ = json.NewDecoder(r.Body).Decode(&body) output, _ := body["outputId"].(string) + rows := f.rows[output] + if f.scopedToken != "" && r.Header.Get("Authorization") == f.scopedToken && !f.leakScoped { + // production denial shape: restricted intersection => rows: [] + rows = []map[string]any{} + } f.mu.Lock() f.calls = append(f.calls, fmt.Sprintf("%s:preview:%s", backend, output)) status := f.failWith[output] - rows := f.rows[output] columns := f.columns sampled := f.sampled f.mu.Unlock() From bde236b598d6c4aae3c00324184620f2e97eeedd Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:48:06 +0000 Subject: [PATCH 08/45] feat(benchcli): wired run command with fail-closed preflight and live probes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit benchcli run drives RunBenchmark end to end and writes report.json, report.md, and arms.json (operational manifest: window note, arm evidence, fence request count). Exit 3 = refused to run, exit 1 = ran and failed. Preflight refuses before any work unless: - LOOM_BENCH_LIVE=1 (coordinator-granted exclusive window) and a --window-note recording granter and time; - --source-commit is the full 40-hex SHA; - --out has no existing report.json (no silent overwrite); - scope/flag coherence: postgres-only must not receive Arango flags; both-arms requires both arms; attached arms require both tokens; all participating arms are attached or spawned, never mixed (an attached server's trusted issuer is not the benchmark fence); - namespace guard: PostgreSQL database and Arango database must be under loom_bench_* — a shared instance can never be written by a benchmark run outside its namespace. DSN error text is scrubbed. Spawned arms: one MockFence is the sole auth authority; the scoped principal is granted only the decoy path, so the negative-auth control exercises the production restricted-intersection denial. Configs render via PostgresBackendYAML (six-series ladder spelled out — without it the server first-query-legs and authoring routes would silently be absent) and ArangoBackendYAML; DSNs are scrubbed from arm logs. Version evidence comes from the live services: pgx SELECT version() and ArangoDB /_api/version (shape-checked), never hand-written. CLI smoke checks: every preflight refusal exits 3 with a named reason; namespace guards refuse loom_test and prod_db. --- .../benchmark/cmd/benchcli/deployer.go | 130 +++++++++ .../dataframe/benchmark/cmd/benchcli/main.go | 268 ++++++++++++++++-- .../benchmark/cmd/benchcli/probes.go | 160 +++++++++++ internal/dataframe/benchmark/driver.go | 2 +- internal/dataframe/benchmark/provision.go | 34 +++ 5 files changed, 563 insertions(+), 31 deletions(-) create mode 100644 internal/dataframe/benchmark/cmd/benchcli/deployer.go create mode 100644 internal/dataframe/benchmark/cmd/benchcli/probes.go diff --git a/internal/dataframe/benchmark/cmd/benchcli/deployer.go b/internal/dataframe/benchmark/cmd/benchcli/deployer.go new file mode 100644 index 000000000..3589ee2c3 --- /dev/null +++ b/internal/dataframe/benchmark/cmd/benchcli/deployer.go @@ -0,0 +1,130 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "github.com/calypr/loom/internal/dataframe/benchmark" +) + +// deployer turns CLI flags into driver arm configs. All participating arms +// are either attached (running servers + caller tokens) or spawned +// (binary + benchmark fence); mixing is refused: the attached server's +// trusted issuer cannot be assumed to be the benchmark fence. +type deployer struct { + flags *runFlags + + fence *benchmark.MockFence + unrestrictedToken string + scopedToken string + + arango benchmark.ArmConfig + postgres benchmark.ArmConfig +} + +const ( + benchmarkAdminSubject = "bench-admin" + benchmarkScopedSubject = "bench-scoped" + benchmarkDecoyPath = "/programs/loom/projects/loom-bench/decoy" +) + +func (d *deployer) prepare(ctx context.Context) error { + f := d.flags + type legSpec struct { + backend string + attachURL string + dsn string + arm *benchmark.ArmConfig + } + var legs []legSpec + if f.scope == benchmark.RunScopeBothArms { + legs = append(legs, legSpec{benchmark.BackendArango, f.arangoAttachURL, f.arangoDSN, &d.arango}) + } + legs = append(legs, legSpec{benchmark.BackendPostgres, f.postgresAttachURL, f.postgresDSN, &d.postgres}) + + allAttached, allSpawned := true, true + for _, l := range legs { + if strings.TrimSpace(l.attachURL) == "" { + allAttached = false + } + if strings.TrimSpace(l.attachURL) != "" { + allSpawned = false + } + } + if !allAttached && !allSpawned { + return fmt.Errorf("mixing attached and spawned arms is refused: the attached server's trusted issuer is not the benchmark fence") + } + if allAttached { + if strings.TrimSpace(f.tokenUnrestricted) == "" || strings.TrimSpace(f.tokenScoped) == "" { + return fmt.Errorf("attached arms require --token-unrestricted and --token-scoped") + } + d.unrestrictedToken = f.tokenUnrestricted + d.scopedToken = f.tokenScoped + for _, l := range legs { + *l.arm = benchmark.ArmConfig{BaseURL: l.attachURL} + if l.dsn == "" { + return fmt.Errorf("%s attached arm still needs --%s-dsn for the live version probe", l.backend, l.backend) + } + } + return nil + } + // Spawned: one benchmark fence is the single auth authority. + if strings.TrimSpace(f.binary) == "" { + return fmt.Errorf("spawned arms require --binary") + } + if strings.TrimSpace(f.tokenUnrestricted) != "" || strings.TrimSpace(f.tokenScoped) != "" { + return fmt.Errorf("spawned arms receive tokens from the benchmark fence; drop the token flags") + } + fence, err := benchmark.NewMockFence() + if err != nil { + return fmt.Errorf("benchmark fence: %w", err) + } + d.fence = fence + fence.Grant(benchmarkAdminSubject, f.authResourcePath, benchmarkDecoyPath) + fence.Grant(benchmarkScopedSubject, benchmarkDecoyPath) // dataset path EXCLUDED: production denial shape + d.unrestrictedToken = "Bearer " + benchmark.MakeBearerToken(fence.URL(), benchmarkAdminSubject) + d.scopedToken = "Bearer " + benchmark.MakeBearerToken(fence.URL(), benchmarkScopedSubject) + for _, l := range legs { + if strings.TrimSpace(l.dsn) == "" { + return fmt.Errorf("spawned %s arm requires --%s-dsn", l.backend, l.backend) + } + var backendYAML string + switch l.backend { + case benchmark.BackendPostgres: + host, port, database, user, password, sslmode, err := parsePostgresDSN(l.dsn) + if err != nil { + return fmt.Errorf("%s dsn: %w", l.backend, err) + } + backendYAML = benchmark.PostgresBackendYAML(host, port, database, user, password, sslmode, "loom_bench") + case benchmark.BackendArango: + rawURL, database, err := parseArangoDSN(l.dsn) + if err != nil { + return fmt.Errorf("%s dsn: %w", l.backend, err) + } + backendYAML = benchmark.ArangoBackendYAML(rawURL, database) + } + *l.arm = benchmark.ArmConfig{ + BinaryPath: f.binary, + ConfigYAML: benchmark.BuildServerConfigYAML("127.0.0.1:0", l.backend, backendYAML, fence.URL()), + ScrubSecrets: []string{l.dsn}, + } + } + return nil +} + +func (d *deployer) close() { + if d.fence != nil { + d.fence.Close() + } +} + +func (d *deployer) probe(ctx context.Context, cfg benchmark.ArmConfig, backend string) (string, error) { + switch backend { + case benchmark.BackendArango: + return probeArangoVersion(ctx, d.flags.arangoDSN) + case benchmark.BackendPostgres: + return probePostgresVersion(ctx, d.flags.postgresDSN) + } + return "", fmt.Errorf("unknown backend %q", backend) +} diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go index 6aa2e2a76..9ff3995c4 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/main.go +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -5,9 +5,15 @@ package main import ( + "context" + "encoding/json" "flag" "fmt" "os" + "path/filepath" + "regexp" + "strings" + "time" "github.com/calypr/loom/internal/dataframe/benchmark" ) @@ -19,42 +25,244 @@ func main() { } switch os.Args[1] { case "verify": - fs := flag.NewFlagSet("verify", flag.ExitOnError) - reportPath := fs.String("report", "", "path to report.json") - _ = fs.Parse(os.Args[2:]) - if *reportPath == "" { - fmt.Fprintln(os.Stderr, "benchcli verify: --report is required") - os.Exit(2) - } - data, err := os.ReadFile(*reportPath) - if err != nil { - fmt.Fprintf(os.Stderr, "benchcli verify: %v\n", err) - os.Exit(1) - } - report, err := benchmark.LoadReport(data) - if err != nil { - fmt.Fprintf(os.Stderr, "benchcli verify: REJECTED: %v\n", err) - os.Exit(1) - } - fmt.Printf("benchcli verify: OK run=%s status=%s claims=%d unsupported=%d\n", - report.Identity.RunID, report.Status, len(report.Claims), len(report.Unsupported)) + runVerify() case "run": - // Fails closed until the exclusive window: no partial-environment - // run may produce a success-shaped artifact. The granted-window - // wave adds preflight (services reachable, budget, namespace - // guards) and the live driver here. - if os.Getenv("LOOM_BENCH_LIVE") != "1" { - fmt.Fprintln(os.Stderr, "benchcli run: BLOCKED — live driver requires the granted exclusive window (LOOM_BENCH_LIVE=1) and is not yet wired") - os.Exit(3) - } - fmt.Fprintln(os.Stderr, "benchcli run: BLOCKED — live driver not yet wired (lands with the granted PG window wave)") - os.Exit(3) + runLive() default: usage() os.Exit(2) } } +func runVerify() { + fs := flag.NewFlagSet("verify", flag.ExitOnError) + reportPath := fs.String("report", "", "path to report.json") + _ = fs.Parse(os.Args[2:]) + if *reportPath == "" { + fmt.Fprintln(os.Stderr, "benchcli verify: --report is required") + os.Exit(2) + } + data, err := os.ReadFile(*reportPath) + if err != nil { + fmt.Fprintf(os.Stderr, "benchcli verify: %v\n", err) + os.Exit(1) + } + report, err := benchmark.LoadReport(data) + if err != nil { + fmt.Fprintf(os.Stderr, "benchcli verify: REJECTED: %v\n", err) + os.Exit(1) + } + fmt.Printf("benchcli verify: OK run=%s scope=%s status=%s claims=%d unsupported=%d\n", + report.Identity.RunID, report.Plan.Scope, report.Status, len(report.Claims), len(report.Unsupported)) +} + +type runFlags struct { + scope string + preset string + seed uint64 + project string + generation string + authResourcePath string + warmup int + iterations int + rounds int + concurrency int + previewLimit int + binary string + stderrDir string + postgresAttachURL string + postgresDSN string + arangoAttachURL string + arangoDSN string + tokenUnrestricted string + tokenScoped string + out string + runID string + sourceCommit string + windowNote string + emptyFloorIters int +} + +func runLive() { + fs := flag.NewFlagSet("run", flag.ExitOnError) + var f runFlags + fs.StringVar(&f.scope, "scope", benchmark.RunScopePostgresOnly, "both-arms | postgres-only") + fs.StringVar(&f.preset, "preset", benchmark.PresetTiny, "dataset preset: tiny | small | representative") + fs.Uint64Var(&f.seed, "seed", 7, "deterministic dataset seed") + fs.StringVar(&f.project, "project", "loom-bench", "benchmark project namespace") + fs.StringVar(&f.generation, "generation", "", "generation name (default: run id)") + fs.StringVar(&f.authResourcePath, "auth-resource-path", "/programs/loom/projects/loom-bench/benchmark", "dataset auth scope stamped by the loader") + fs.IntVar(&f.warmup, "warmup", 1, "warmup iterations per workload x backend") + fs.IntVar(&f.iterations, "iterations", 8, "measured iterations per workload x backend") + fs.IntVar(&f.rounds, "rounds", 2, "interleaved rounds") + fs.IntVar(&f.concurrency, "concurrency", 1, "concurrency knob (recorded; battery runs serialized)") + fs.IntVar(&f.previewLimit, "preview-limit", 1000, "preview row limit (source-row path is capped at 1000)") + fs.StringVar(&f.binary, "binary", "", "loom server binary for spawned arms") + fs.StringVar(&f.stderrDir, "stderr-dir", "", "directory for arm logs (default: /arms)") + fs.StringVar(&f.postgresAttachURL, "postgres-url", "", "attach to a running PostgreSQL-backed loom server") + fs.StringVar(&f.postgresDSN, "postgres-dsn", "", "PostgreSQL DSN (spawn config + live version probe)") + fs.StringVar(&f.arangoAttachURL, "arango-url", "", "attach to a running Arango-backed loom server") + fs.StringVar(&f.arangoDSN, "arango-dsn", "", "ArangoDB DSN (spawn config + live version probe)") + fs.StringVar(&f.tokenUnrestricted, "token-unrestricted", "", "bearer for the unrestricted principal (attached arms)") + fs.StringVar(&f.tokenScoped, "token-scoped", "", "bearer whose fence grants EXCLUDE the dataset path") + fs.StringVar(&f.out, "out", "bench-out", "output directory; must not contain report.json") + fs.StringVar(&f.runID, "run-id", "", "run id (default: bench--)") + fs.StringVar(&f.sourceCommit, "source-commit", "", "full 40-hex SHA the arms were built from") + fs.StringVar(&f.windowNote, "window-note", "", "who granted the exclusive window and when (recorded in arms.json)") + fs.IntVar(&f.emptyFloorIters, "empty-floor-iterations", 5, "iterations for the empty-result floor control") + _ = fs.Parse(os.Args[2:]) + + // Fail-closed preflight (exit 3: refused to run; exit 1: ran and failed). + preflight := func(format string, args ...any) { + fmt.Fprintf(os.Stderr, "benchcli run: BLOCKED — "+format+"\n", args...) + os.Exit(3) + } + if os.Getenv("LOOM_BENCH_LIVE") != "1" { + preflight("live runs require the exclusive window granted by the coordinator (LOOM_BENCH_LIVE=1)") + } + if strings.TrimSpace(f.windowNote) == "" { + preflight("--window-note must record the granted window (granter + time) before any live run") + } + if !benchmark.ValidScope(f.scope) { + preflight("unknown scope %q", f.scope) + } + if !regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString(f.sourceCommit) { + preflight("--source-commit must be the full 40-hex SHA of the built source") + } + if _, err := os.Stat(filepath.Join(f.out, "report.json")); err == nil { + preflight("%s/report.json already exists; use a fresh --out directory", f.out) + } + postgresLeg := f.scope == benchmark.RunScopeBothArms || f.scope == benchmark.RunScopePostgresOnly + if postgresLeg && strings.TrimSpace(f.postgresDSN) == "" && strings.TrimSpace(f.postgresAttachURL) == "" { + preflight("the postgres leg runs in scope %s: provide --postgres-dsn and/or --postgres-url", f.scope) + } + if strings.TrimSpace(f.postgresDSN) != "" { + if err := guardPostgresNamespace(f.postgresDSN); err != nil { + preflight("%v", err) + } + } + if f.scope == benchmark.RunScopePostgresOnly { + if strings.TrimSpace(f.arangoAttachURL) != "" || strings.TrimSpace(f.arangoDSN) != "" { + preflight("postgres-only scope must not receive Arango flags; run both-arms scope instead") + } + } else { + if strings.TrimSpace(f.arangoDSN) == "" && strings.TrimSpace(f.arangoAttachURL) == "" { + preflight("both-arms scope needs --arango-dsn and/or --arango-url") + } + if strings.TrimSpace(f.arangoAttachURL) != "" && (strings.TrimSpace(f.tokenUnrestricted) == "" || strings.TrimSpace(f.tokenScoped) == "") { + preflight("attached arms must be given both --token-unrestricted and --token-scoped") + } + } + spawning := strings.TrimSpace(f.binary) != "" && (strings.TrimSpace(f.postgresAttachURL) == "" || + (f.scope == benchmark.RunScopeBothArms && strings.TrimSpace(f.arangoAttachURL) == "")) + if spawning && strings.TrimSpace(f.tokenUnrestricted) != "" { + preflight("spawned arms get tokens from the benchmark fence; do not pass --token-unrestricted with --binary") + } + if strings.TrimSpace(f.postgresAttachURL) != "" && (strings.TrimSpace(f.tokenUnrestricted) == "" || strings.TrimSpace(f.tokenScoped) == "") { + preflight("attached postgres arm requires both tokens") + } + if f.runID == "" { + f.runID = fmt.Sprintf("bench-%s-%s", time.Now().UTC().Format("20060102T150405Z"), randomRunIDSuffix()) + } + if f.generation == "" { + f.generation = f.runID + } + if err := os.MkdirAll(f.out, 0o755); err != nil { + preflight("cannot create --out: %v", err) + } + + // Build the driver options. + plan := benchmark.BothArmsPlan() + if f.scope == benchmark.RunScopePostgresOnly { + plan = benchmark.PostgresOnlyPlan("no ArangoDB service in this execution environment; go-arango CI lane targets this leg") + } + plan.PlannedNotRun = benchmark.PlannedNotRunForBackends(plan.NotRunBackends()) + for i := range plan.PlannedNotRun { + plan.PlannedNotRun[i].Reason = "leg not run: " + plan.Legs[0].Capability + } + if !planNotRunMatchesBattery(plan) { + fmt.Fprintln(os.Stderr, "benchcli run: internal plan inconsistency") + os.Exit(1) + } + + d := &deployer{flags: &f} + ctx := context.Background() + if err := d.prepare(ctx); err != nil { + fmt.Fprintf(os.Stderr, "benchcli run: %v\n", err) + os.Exit(1) + } + defer d.close() + + stderrDir := f.stderrDir + if stderrDir == "" { + stderrDir = filepath.Join(f.out, "arms") + } + opts := benchmark.DriverOptions{ + RunID: f.runID, StartedAt: time.Now().UTC(), Plan: plan, Preset: f.preset, + Params: benchmark.DatasetParams{Preset: f.preset, Seed: f.seed, Project: f.project, + Generation: f.generation, AuthResourcePath: f.authResourcePath}, + Config: benchmark.RunConfig{Warmup: f.warmup, Iterations: f.iterations, Concurrency: f.concurrency, Rounds: f.rounds}, + PreviewLimit: f.previewLimit, + SourceCommit: f.sourceCommit, + UnrestrictedToken: d.unrestrictedToken, + ScopedToken: d.scopedToken, + Arango: d.arango, + Postgres: d.postgres, + StderrDir: stderrDir, + Clock: benchmark.NewMonotonicClock(), + VersionProbe: d.probe, + EmptyFloorIters: f.emptyFloorIters, + } + report, evidence, err := benchmark.RunBenchmark(ctx, opts) + if err != nil { + fmt.Fprintf(os.Stderr, "benchcli run: FAILED — %v\n", err) + os.Exit(1) + } + + // Artifacts: report.json, report.md, arms.json (operational evidence). + reportJSON, err := benchmark.MarshalReport(report) + if err != nil { + fmt.Fprintf(os.Stderr, "benchcli run: marshal: %v\n", err) + os.Exit(1) + } + if err := os.WriteFile(filepath.Join(f.out, "report.json"), reportJSON, 0o644); err != nil { + fmt.Fprintf(os.Stderr, "benchcli run: write report: %v\n", err) + os.Exit(1) + } + if err := os.WriteFile(filepath.Join(f.out, "report.md"), []byte(benchmark.RenderMarkdown(report)), 0o644); err != nil { + fmt.Fprintf(os.Stderr, "benchcli run: write markdown: %v\n", err) + os.Exit(1) + } + manifest := map[string]any{"runId": f.runID, "windowNote": f.windowNote, "arms": evidence} + if d.fence != nil { + manifest["fenceRequests"] = d.fence.Requests() + } + manifestJSON, _ := json.MarshalIndent(manifest, "", " ") + if err := os.WriteFile(filepath.Join(f.out, "arms.json"), manifestJSON, 0o644); err != nil { + fmt.Fprintf(os.Stderr, "benchcli run: write arms manifest: %v\n", err) + os.Exit(1) + } + fmt.Printf("benchcli run: OK run=%s scope=%s status=%s claims=%d out=%s\n", + report.Identity.RunID, report.Plan.Scope, report.Status, len(report.Claims), f.out) +} + +func planNotRunMatchesBattery(plan benchmark.RunPlan) bool { + counts := map[string]int{} + for _, row := range plan.PlannedNotRun { + counts[row.Backend]++ + } + for _, leg := range plan.Legs { + want := 0 + if leg.Mode == "not_run" { + want = len(benchmark.Battery) + } + if counts[leg.Backend] != want { + return false + } + } + return true +} + func usage() { - fmt.Fprintln(os.Stderr, "usage: benchcli verify --report | benchcli run") + fmt.Fprintln(os.Stderr, "usage: benchcli verify --report | benchcli run [flags]") } diff --git a/internal/dataframe/benchmark/cmd/benchcli/probes.go b/internal/dataframe/benchmark/cmd/benchcli/probes.go new file mode 100644 index 000000000..ec35b46bb --- /dev/null +++ b/internal/dataframe/benchmark/cmd/benchcli/probes.go @@ -0,0 +1,160 @@ +package main + +import ( + "context" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/jackc/pgx/v5" +) + +// Live probes: the report's backend versions must be observed from the +// services themselves, never written by hand. + +func probePostgresVersion(ctx context.Context, dsn string) (string, error) { + connCtx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + conn, err := pgx.Connect(connCtx, dsn) + if err != nil { + return "", fmt.Errorf("postgres probe connect: %w", scrub(err, dsn)) + } + defer conn.Close(context.Background()) + var version string + if err := conn.QueryRow(connCtx, "SELECT version()").Scan(&version); err != nil { + return "", fmt.Errorf("postgres probe query: %w", scrub(err, dsn)) + } + return strings.TrimSpace(version), nil +} + +func probeArangoVersion(ctx context.Context, rawDSN string) (string, error) { + parsed, err := url.Parse(rawDSN) + if err != nil { + return "", fmt.Errorf("arango dsn: %w", err) + } + endpoint := parsed.Scheme + "://" + parsed.Host + "/_api/version" + reqCtx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, endpoint, nil) + if err != nil { + return "", err + } + if parsed.User != nil { + password, _ := parsed.User.Password() + req.SetBasicAuth(parsed.User.Username(), password) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", fmt.Errorf("arango probe: %w", scrub(err, rawDSN)) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("arango probe: /_api/version returned HTTP %d", resp.StatusCode) + } + var body struct { + Server string `json:"server"` + Version string `json:"version"` + License string `json:"license"` + } + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + return "", fmt.Errorf("arango probe decode: %w", err) + } + if body.Server != "arango" || strings.TrimSpace(body.Version) == "" { + return "", fmt.Errorf("arango probe: unexpected /_api/version body %+v", body) + } + return fmt.Sprintf("ArangoDB %s (%s)", body.Version, body.License), nil +} + +func scrub(err error, secret string) error { + if secret == "" { + return err + } + text := err.Error() + if redacted, changed := redact(text, secret); changed { + return fmt.Errorf("%s", redacted) + } + return err +} + +func redact(text, secret string) (string, bool) { + if !strings.Contains(text, secret) { + return text, false + } + return strings.ReplaceAll(text, secret, ""), true +} + +// benchmarkNamespacePrefix: benchmark arms may only touch databases named +// under this prefix. A shared PostgreSQL instance must never be writable by +// a benchmark run outside its namespace. +const benchmarkNamespacePrefix = "loom_bench_" + +func guardPostgresNamespace(dsn string) error { + config, err := pgx.ParseConfig(dsn) + if err != nil { + return fmt.Errorf("postgres dsn unparsable: %s", redactSecretText(err.Error(), dsn)) + } + if !strings.HasPrefix(config.Database, benchmarkNamespacePrefix) { + return fmt.Errorf("postgres database %q is outside the benchmark namespace %s*; refusing to run against it", + redactSecretText(config.Database, dsn), benchmarkNamespacePrefix) + } + return nil +} + +func redactSecretText(text, secret string) string { + if secret == "" { + return text + } + return strings.ReplaceAll(text, secret, "") +} + +func randomRunIDSuffix() string { + buf := make([]byte, 4) + _, _ = rand.Read(buf) + return hex.EncodeToString(buf) +} + +func parsePostgresDSN(dsn string) (host string, port int, database, user, password, sslmode string, err error) { + config, err := pgx.ParseConfig(dsn) + if err != nil { + return "", 0, "", "", "", "", fmt.Errorf("unparsable postgres dsn") + } + host = config.Host + port = int(config.Port) + if port == 0 { + port = 5432 + } + database, user, password = config.Database, config.User, string(config.Password) + sslmode = "disable" + if config.TLSConfig != nil { + sslmode = config.TLSConfig.ServerName + if sslmode == "" { + sslmode = "require" + } + } + return host, port, database, user, password, sslmode, nil +} + +func parseArangoDSN(dsn string) (rawURL, database string, err error) { + parsed, err := url.Parse(dsn) + if err != nil { + return "", "", fmt.Errorf("unparsable arango dsn") + } + if parsed.Scheme != "http" && parsed.Scheme != "https" { + return "", "", fmt.Errorf("arango dsn scheme must be http or https") + } + database = strings.TrimPrefix(parsed.Path, "/") + if database == "" { + return "", "", fmt.Errorf("arango dsn must name a database") + } + if !strings.HasPrefix(database, benchmarkNamespacePrefix) { + return "", "", fmt.Errorf("arango database %q is outside the benchmark namespace %s*", database, benchmarkNamespacePrefix) + } + parsed.Path = "" + parsed.RawQuery = "" + return parsed.String(), database, nil +} diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index d1a28f98e..a74decebc 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -405,7 +405,7 @@ func startArm(ctx context.Context, backend string, cfg ArmConfig, spawn SpawnFun listen := fmt.Sprintf("127.0.0.1:%d", port) ready := cfg.ReadyURL if ready == "" { - ready = "http://" + listen + "/api/v1/system/ready" + ready = "http://" + listen + "/readyz" } timeout := 60 * time.Second spec := ServerSpec{BinaryPath: cfg.BinaryPath, Backend: backend, ConfigYAML: cfg.ConfigYAML, Env: cfg.Env, ReadyURL: ready, Timeout: timeout, StderrDir: opts.StderrDir} diff --git a/internal/dataframe/benchmark/provision.go b/internal/dataframe/benchmark/provision.go index 1858fff2b..278455d2f 100644 --- a/internal/dataframe/benchmark/provision.go +++ b/internal/dataframe/benchmark/provision.go @@ -341,3 +341,37 @@ func BuildServerConfigYAML(listen, backend, backendYAML, fenceURL string) string b.WriteString(" request_timeout: 10s\n cache_ttl: 1s\n") return b.String() } + +// PostgresBackendYAML renders the server.postgresql block for a spawned +// arm. The six-series schema ladder is mandatory: without it the server +// takes the first-query route and refuses all authoring routes, which would +// make the battery unrunnable — the ladder is spelled out here so a typo +// cannot silently degrade the run. Schemas live under a benchmark-owned +// prefix so arms can never touch production schemas. +func PostgresBackendYAML(host string, port int, database, user, password, sslmode, schemaPrefix string) string { + if sslmode == "" { + sslmode = "disable" + } + var b strings.Builder + b.WriteString(" postgresql:\n") + fmt.Fprintf(&b, " host: %q\n", host) + fmt.Fprintf(&b, " port: %d\n", port) + fmt.Fprintf(&b, " database: %q\n", database) + fmt.Fprintf(&b, " user: %q\n", user) + fmt.Fprintf(&b, " password: %q\n", password) + fmt.Fprintf(&b, " sslmode: %q\n", sslmode) + b.WriteString(" schemas:\n") + for _, series := range []string{"source", "explorer_control", "publication_control", "dataset_release", "recipe_chains", "recipe_exec_registries"} { + fmt.Fprintf(&b, " %s: %q\n", series, schemaPrefix+"_"+series) + } + return b.String() +} + +// ArangoBackendYAML renders the server-level url/database keys for a +// spawned Arango arm. +func ArangoBackendYAML(rawURL, database string) string { + var b strings.Builder + fmt.Fprintf(&b, " url: %q\n", rawURL) + fmt.Fprintf(&b, " database: %q\n", database) + return b.String() +} From c34c0c199fd55439ab10de1ed8aacb98a7debc67 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:48:41 +0000 Subject: [PATCH 09/45] feat(scripts): node orchestrator for backend benchmark checks with disclosed not_run benchmark-backends.mjs is the node-side orchestrator; verification authority stays in Go (benchcli verify), so node tests can never satisfy a live check (ruling ii): - --verify-only --report: exits 0 only when the Go verifier says OK; prints the plan's evidence line check live-two-backend-correctness. - --run --out -- : drives benchcli (which fails closed without the granted window), then verifies the produced report.json before reporting pass. - Exit 3 from benchcli is disclosed as not_run with the preflight reason (run stays planned/not_run), never a pass, never a silent skip. - --harness-selftest: node-only sanity, clearly labelled. - --benchcli runs the built binary; the default invokes go run. Hermetic node --test suite (9 tests, no network, no Go): arg parsing, invocation shapes, exit classification, the not_run disclosure path, verify-only pass/fail authority, run-then-verify ordering, report summary fields. Negative e2e: a hand-crafted report is REJECTED by benchcli and the orchestrator reports the live check as fail. --- scripts/benchmark-backends.mjs | 140 ++++++++++++++++++++++++++++ scripts/benchmark-backends.test.mjs | 123 ++++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 scripts/benchmark-backends.mjs create mode 100644 scripts/benchmark-backends.test.mjs diff --git a/scripts/benchmark-backends.mjs b/scripts/benchmark-backends.mjs new file mode 100644 index 000000000..319d6543a --- /dev/null +++ b/scripts/benchmark-backends.mjs @@ -0,0 +1,140 @@ +#!/usr/bin/env node +// benchmark-backends.mjs — node-side orchestrator for the Arango/AQL vs +// PostgreSQL/SQL matched benchmark. Verification authority is Go (benchcli +// verify): this script only builds invocations, runs them, and reports the +// evidence line for each check. It can never turn a live check green by +// itself (ruling ii): every live status comes from the Go verifier's exit. +// +// Subcommands: +// --verify-only --report [--benchcli ] +// Verify an existing artifact. Exit 0 only if the Go verifier says OK. +// --run -- +// Run a live benchmark through benchcli (which itself fails closed +// without the granted window), then verify the produced report. +// --harness-selftest +// Node-only sanity pass over this script's own logic. Labelled +// selftest; it can never satisfy a live check. + +import { spawnSync } from 'node:child_process'; +import { existsSync, readFileSync } from 'node:fs'; + +export const CHECK_HARNESS = 'benchmark-harness-tests'; +export const CHECK_LIVE = 'live-two-backend-correctness'; + +export function parseArgs(argv) { + const flags = { mode: null, report: null, benchcli: null, out: null, runArgs: [], selftest: false }; + let i = 0; + while (i < argv.length) { + const arg = argv[i]; + if (arg === '--verify-only') flags.mode = 'verify'; + else if (arg === '--run') flags.mode = 'run'; + else if (arg === '--harness-selftest') flags.selftest = true; + else if (arg === '--report') { i += 1; flags.report = argv[i]; } + else if (arg === '--benchcli') { i += 1; flags.benchcli = argv[i]; } + else if (arg === '--out') { i += 1; flags.out = argv[i]; } + else if (arg === '--') { flags.runArgs = argv.slice(i + 1); break; } + else throw new Error(`unknown argument: ${arg}`); + i += 1; + } + if (flags.selftest && flags.mode) throw new Error('--harness-selftest cannot combine with --verify-only/--run'); + if (!flags.selftest && !flags.mode) throw new Error('choose --verify-only, --run, or --harness-selftest'); + if (flags.mode === 'verify' && !flags.report) throw new Error('--verify-only requires --report'); + if (flags.mode === 'run' && !flags.out) throw new Error('--run requires --out'); + return flags; +} + +export function buildVerifyInvocation(flags) { + const bin = flags.benchcli ?? 'go'; + const args = flags.benchcli ? ['verify', '--report', flags.report] + : ['run', './internal/dataframe/benchmark/cmd/benchcli', 'verify', '--report', flags.report]; + return { cmd: bin, args }; +} + +export function buildRunInvocation(flags) { + const bin = flags.benchcli ?? 'go'; + const args = flags.benchcli ? ['run', ...flags.runArgs] + : ['run', './internal/dataframe/benchmark/cmd/benchcli', 'run', ...flags.runArgs]; + return { cmd: bin, args }; +} + +export function formatCheckLine(id, status, detail) { + const text = detail ? ` ${detail}` : ''; + return `check ${id}: ${status}${text}`; +} + +// A run blocked by preflight (exit 3) is disclosed as not_run with the +// reason, never as a pass and never as a silent skip. Exit 1 is a failure. +export function classifyRunExit(code) { + if (code === 0) return { status: 'pass', disclosure: null }; + if (code === 3) return { status: 'not_run', disclosure: 'preflight refused the live run' }; + return { status: 'fail', disclosure: `benchcli run exited ${code}` }; +} + +export function classifyVerifyExit(code) { + if (code === 0) return { status: 'pass', disclosure: null }; + return { status: 'fail', disclosure: `benchcli verify rejected the artifact (exit ${code})` }; +} + +// readReportSummary extracts only disclosure facts for the console; the +// authoritative recomputation lives in Go. +export function readReportSummary(path) { + const raw = JSON.parse(readFileSync(path, 'utf8')); + return { + runId: raw.identity?.runId ?? null, + scope: raw.plan?.scope ?? null, + status: raw.status ?? null, + legs: (raw.plan?.legs ?? []).map((leg) => `${leg.backend}:${leg.mode}${leg.capability ? `(${leg.capability})` : ''}`), + plannedNotRun: (raw.plan?.plannedNotRun ?? []).length, + claims: (raw.claims ?? []).length, + }; +} + +export function run(flags, deps = {}) { + const exec = deps.exec ?? spawnSync; + const log = deps.log ?? ((line) => console.log(line)); + let failures = 0; + const live = (invocation) => exec(invocation.cmd, invocation.args, { stdio: 'inherit' }); + + if (flags.mode === 'verify') { + const result = live(buildVerifyInvocation(flags)); + const outcome = classifyVerifyExit(result.status); + log(formatCheckLine(CHECK_LIVE, outcome.status, outcome.disclosure ?? `verified ${flags.report}`)); + if (outcome.status !== 'pass') failures += 1; + return failures; + } + if (flags.mode === 'run') { + const result = live(buildRunInvocation(flags)); + const outcome = classifyRunExit(result.status); + log(formatCheckLine('live-run', outcome.status, outcome.disclosure ?? undefined)); + if (outcome.status === 'not_run') { + log(formatCheckLine(CHECK_LIVE, 'not_run', 'no artifact produced; run stays planned/not_run until the granted window')); + return failures; // a refused preflight is disclosed, not a failure + } + if (outcome.status !== 'pass') { failures += 1; return failures; } + const reportPath = `${flags.out}/report.json`; + const verify = live(buildVerifyInvocation({ ...flags, report: reportPath })); + const verified = classifyVerifyExit(verify.status); + log(formatCheckLine(CHECK_LIVE, verified.status, verified.disclosure ?? `verified ${reportPath}`)); + if (verified.status !== 'pass') failures += 1; + else if (existsSync(reportPath)) log(`report summary: ${JSON.stringify(readReportSummary(reportPath))}`); + return failures; + } + // selftest: node-only logic sanity, clearly labelled. + const sample = { status: 3 }; + const classified = classifyRunExit(sample.status); + if (classified.status !== 'not_run') throw new Error('selftest: exit 3 must disclose not_run'); + log(formatCheckLine('harness-selftest', 'pass', 'node-only; live checks come from the Go verifier only')); + return failures; +} + +const isMain = process.argv[1] && import.meta.url.endsWith(process.argv[1].split('/').pop()); +if (isMain) { + try { + const flags = parseArgs(process.argv.slice(2)); + const failures = run(flags); + process.exit(failures === 0 ? 0 : 1); + } catch (err) { + console.error(`benchmark-backends: ${err.message}`); + process.exit(2); + } +} diff --git a/scripts/benchmark-backends.test.mjs b/scripts/benchmark-backends.test.mjs new file mode 100644 index 000000000..9306752aa --- /dev/null +++ b/scripts/benchmark-backends.test.mjs @@ -0,0 +1,123 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { mkdtemp, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { + parseArgs, + buildVerifyInvocation, + buildRunInvocation, + classifyRunExit, + classifyVerifyExit, + formatCheckLine, + readReportSummary, + run, + CHECK_LIVE, +} from './benchmark-backends.mjs'; + +test('parseArgs requires a mode', () => { + assert.throws(() => parseArgs([]), /choose --verify-only/); + assert.throws(() => parseArgs(['--verify-only']), /requires --report/); + assert.throws(() => parseArgs(['--run']), /requires --out/); + assert.throws(() => parseArgs(['--nope']), /unknown argument/); + assert.throws(() => parseArgs(['--run', '--harness-selftest']), /cannot combine/); +}); + +test('run mode collects benchcli flags after the separator', () => { + const flags = parseArgs(['--run', '--out', 'bench-out', '--', '--scope', 'postgres-only', '--window-note', 'granted by 2']); + assert.equal(flags.mode, 'run'); + assert.equal(flags.out, 'bench-out'); + assert.deepEqual(flags.runArgs, ['--scope', 'postgres-only', '--window-note', 'granted by 2']); +}); + +test('invocations use go run by default and the binary when given', () => { + const defaultVerify = buildVerifyInvocation({ report: 'r.json' }); + assert.equal(defaultVerify.cmd, 'go'); + assert.deepEqual(defaultVerify.args, ['run', './internal/dataframe/benchmark/cmd/benchcli', 'verify', '--report', 'r.json']); + const binaryRun = buildRunInvocation({ benchcli: '/tmp/benchcli', runArgs: ['--scope', 'both-arms'] }); + assert.deepEqual(binaryRun, { cmd: '/tmp/benchcli', args: ['run', '--scope', 'both-arms'] }); +}); + +test('exit classification: 0 pass, 3 disclosed not_run, anything else fail', () => { + assert.equal(classifyRunExit(0).status, 'pass'); + assert.equal(classifyRunExit(3).status, 'not_run'); + assert.match(classifyRunExit(3).disclosure, /preflight refused/); + assert.equal(classifyRunExit(1).status, 'fail'); + assert.equal(classifyRunExit(null).status, 'fail'); + assert.equal(classifyVerifyExit(0).status, 'pass'); + assert.equal(classifyVerifyExit(1).status, 'fail'); +}); + +test('a blocked live run is disclosed as not_run, never a pass', () => { + const lines = []; + const calls = []; + const deps = { + log: (line) => lines.push(line), + exec: (cmd, args) => { calls.push([cmd, args]); return { status: 3 }; }, + }; + const failures = run(parseArgs(['--run', '--out', 'out', '--', '--scope', 'postgres-only']), deps); + assert.equal(failures, 0, 'a refused preflight is disclosed, not a failure'); + assert.deepEqual(calls.length, 1, 'no verify step runs without an artifact'); + assert.ok(lines.some((l) => l.includes('check live-run: not_run'))); + assert.ok(lines.some((l) => l.includes(`check ${CHECK_LIVE}: not_run`))); + assert.ok(!lines.some((l) => l.includes(': pass'))); +}); + +test('verify-only reports the Go exit as the only authority', () => { + const lines = []; + const passing = run(parseArgs(['--verify-only', '--report', 'r.json']), { + log: (line) => lines.push(line), + exec: () => ({ status: 0 }), + }); + assert.equal(passing, 0); + assert.ok(lines.some((l) => l.includes(`check ${CHECK_LIVE}: pass`))); + + const rejectLines = []; + const failing = run(parseArgs(['--verify-only', '--report', 'r.json']), { + log: (line) => rejectLines.push(line), + exec: () => ({ status: 1 }), + }); + assert.equal(failing, 1); + assert.ok(rejectLines.some((l) => l.includes(`check ${CHECK_LIVE}: fail`))); +}); + +test('successful run verifies the produced artifact before passing', () => { + const calls = []; + const lines = []; + const failures = run(parseArgs(['--run', '--out', 'out', '--']), { + log: (line) => lines.push(line), + exec: (cmd, args) => { calls.push(args); return { status: 0 }; }, + }); + assert.equal(failures, 0); + assert.equal(calls.length, 2, 'run then verify'); + assert.deepEqual(calls[1].slice(-3), ['verify', '--report', 'out/report.json']); +}); + +test('readReportSummary surfaces scope, legs, and disclosure counts only', async () => { + const dir = await mkdtemp(join(tmpdir(), 'bench-summary-')); + const path = join(dir, 'report.json'); + await writeFile(path, JSON.stringify({ + identity: { runId: 'bench-x' }, + status: 'passed', + plan: { + scope: 'postgres-only', + legs: [ + { backend: 'arango', mode: 'not_run', capability: 'no service' }, + { backend: 'postgres', mode: 'run' }, + ], + plannedNotRun: [{ workloadId: 'a', backend: 'arango', reason: 'leg not run' }], + }, + claims: [], + })); + const summary = readReportSummary(path); + assert.equal(summary.scope, 'postgres-only'); + assert.equal(summary.plannedNotRun, 1); + assert.deepEqual(summary.legs, ['arango:not_run(no service)', 'postgres:run']); + assert.equal(summary.claims, 0); +}); + +test('formatCheckLine is a stable single line', () => { + assert.equal(formatCheckLine('x', 'pass', 'ok'), 'check x: pass ok'); + assert.equal(formatCheckLine('x', 'fail'), 'check x: fail'); +}); From ce41f0f36a17442e24ca819bdef5b40d49c1fff6 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:55:42 +0000 Subject: [PATCH 10/45] feat(benchcli): setup-pg/drop-pg six-series ladder bootstrap under the bench namespace The server's full runtime opens with SetupNone (the factory's arrangement law: the caller arranges schemas, migrations belong to setup). setup-pg follows the canonical live-composition path: CREATE DATABASE (refused if it exists: fresh per run keeps evidence attributable), CREATE SCHEMA per assigned series under a bench-prefixed name, then the public factory with SetupMigrate. drop-pg force-drops only bench-namespace databases. Admin DSN comes from LOOM_BENCH_ADMIN_DSN (never argv, never echoed); all error text is credential-scrubbed. --- .../dataframe/benchmark/cmd/benchcli/main.go | 6 +- .../dataframe/benchmark/cmd/benchcli/setup.go | 161 ++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 internal/dataframe/benchmark/cmd/benchcli/setup.go diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go index 9ff3995c4..25d8bd64f 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/main.go +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -28,6 +28,10 @@ func main() { runVerify() case "run": runLive() + case "setup-pg": + runSetupPG() + case "drop-pg": + runDropPG() default: usage() os.Exit(2) @@ -264,5 +268,5 @@ func planNotRunMatchesBattery(plan benchmark.RunPlan) bool { } func usage() { - fmt.Fprintln(os.Stderr, "usage: benchcli verify --report | benchcli run [flags]") + fmt.Fprintln(os.Stderr, "usage: benchcli verify --report | benchcli run [flags] | benchcli setup-pg --database | benchcli drop-pg --database ") } diff --git a/internal/dataframe/benchmark/cmd/benchcli/setup.go b/internal/dataframe/benchmark/cmd/benchcli/setup.go new file mode 100644 index 000000000..f45e635d9 --- /dev/null +++ b/internal/dataframe/benchmark/cmd/benchcli/setup.go @@ -0,0 +1,161 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net/url" + "os" + "regexp" + "strings" + "time" + + "github.com/jackc/pgx/v5" + + backendspostgres "github.com/calypr/loom/internal/backends/postgres/control" +) + +// setup-pg / drop-pg: namespace-guarded lifecycle for the benchmark +// database. The admin DSN is read from LOOM_BENCH_ADMIN_DSN (never argv, +// never echoed): passwords must not reach process listings or logs. +// +// setup follows the canonical arrangement the server's full runtime +// requires: the caller CREATES the six assigned schemas, then the public +// factory bootstraps them with SetupMigrate; the server later opens the +// same placement with SetupNone (the factory's arrangement law). + +var benchDatabaseRe = regexp.MustCompile(`^` + benchmarkNamespacePrefix + `[a-z0-9_]{4,48}$`) + +func adminDSNFromEnv() (string, error) { + dsn := strings.TrimSpace(os.Getenv("LOOM_BENCH_ADMIN_DSN")) + if dsn == "" { + return "", fmt.Errorf("LOOM_BENCH_ADMIN_DSN is not set") + } + return dsn, nil +} + +func runSetupPG() { + fs := flag.NewFlagSet("setup-pg", flag.ExitOnError) + database := fs.String("database", "", "benchmark database name (must be under "+benchmarkNamespacePrefix+")") + schemaPrefix := fs.String("schema-prefix", "loom_bench", "schema name prefix for the six-series ladder") + _ = fs.Parse(os.Args[2:]) + dsn, err := adminDSNFromEnv() + if err != nil { + fmt.Fprintln(os.Stderr, "benchcli setup-pg: BLOCKED —", err) + os.Exit(3) + } + if !benchDatabaseRe.MatchString(*database) { + fmt.Fprintf(os.Stderr, "benchcli setup-pg: BLOCKED — database %q is outside the benchmark namespace\n", *database) + os.Exit(3) + } + ctx := context.Background() + if err := setupPG(ctx, dsn, *database, *schemaPrefix); err != nil { + fmt.Fprintln(os.Stderr, "benchcli setup-pg: FAILED —", redactSecretText(err.Error(), dsn)) + os.Exit(1) + } + fmt.Printf("benchcli setup-pg: OK database=%s schemas=%d\n", *database, len(backendspostgres.AllSeries())) +} + +func runDropPG() { + fs := flag.NewFlagSet("drop-pg", flag.ExitOnError) + database := fs.String("database", "", "benchmark database name (must be under "+benchmarkNamespacePrefix+")") + _ = fs.Parse(os.Args[2:]) + dsn, err := adminDSNFromEnv() + if err != nil { + fmt.Fprintln(os.Stderr, "benchcli drop-pg: BLOCKED —", err) + os.Exit(3) + } + if !benchDatabaseRe.MatchString(*database) { + fmt.Fprintf(os.Stderr, "benchcli drop-pg: BLOCKED — database %q is outside the benchmark namespace\n", *database) + os.Exit(3) + } + ctx := context.Background() + if err := dropPG(ctx, dsn, *database); err != nil { + fmt.Fprintln(os.Stderr, "benchcli drop-pg: FAILED —", redactSecretText(err.Error(), dsn)) + os.Exit(1) + } + fmt.Printf("benchcli drop-pg: OK database=%s\n", *database) +} + +func setupPG(ctx context.Context, adminDSN, database, schemaPrefix string) error { + if !strings.HasPrefix(schemaPrefix, benchmarkNamespacePrefix) { + return fmt.Errorf("schema prefix %q is outside the benchmark namespace", schemaPrefix) + } + admin, err := pgx.Connect(ctx, adminDSN) + if err != nil { + return fmt.Errorf("admin connect (credentials redacted)") + } + defer admin.Close(ctx) + var exists bool + if err := admin.QueryRow(ctx, "SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1)", database).Scan(&exists); err != nil { + return err + } + if exists { + // Fresh database per run keeps digests and timings attributable. + return fmt.Errorf("database %s already exists; drop-pg first (fresh per run keeps evidence attributable)", database) + } + if _, err := admin.Exec(ctx, "CREATE DATABASE "+pgx.Identifier{database}.Sanitize()); err != nil { + return fmt.Errorf("create database: %w", err) + } + if err := admin.Close(ctx); err != nil { + return err + } + + targetDSN, err := withDatabase(adminDSN, database) + if err != nil { + return err + } + target, err := pgx.Connect(ctx, targetDSN) + if err != nil { + return fmt.Errorf("target connect (credentials redacted)") + } + schemas := map[backendspostgres.Series]string{} + for _, series := range backendspostgres.AllSeries() { + schema := schemaPrefix + "_" + string(series) + if _, err := target.Exec(ctx, "CREATE SCHEMA "+pgx.Identifier{schema}.Sanitize()); err != nil { + target.Close(ctx) + return fmt.Errorf("create schema %s: %w", schema, err) + } + schemas[series] = schema + } + if err := target.Close(ctx); err != nil { + return err + } + + // Canonical bootstrap: the public factory with SetupMigrate, exactly as + // the server's live composition fixtures arrange it. + runtime, err := backendspostgres.Open(ctx, backendspostgres.Config{ + DSN: targetDSN, + MaxConns: 4, + MinConns: 1, + AcquireTimeout: 30 * time.Second, + Schemas: backendspostgres.SeriesSchemas(schemas), + Setup: backendspostgres.SetupMigrate, + }) + if err != nil { + return fmt.Errorf("migrate six-series ladder: %w", err) + } + return runtime.Close() +} + +func dropPG(ctx context.Context, adminDSN, database string) error { + admin, err := pgx.Connect(ctx, adminDSN) + if err != nil { + return fmt.Errorf("admin connect (credentials redacted)") + } + defer admin.Close(ctx) + _, err = admin.Exec(ctx, "DROP DATABASE IF EXISTS "+pgx.Identifier{database}.Sanitize()+" WITH (FORCE)") + return err +} + +// withDatabase swaps the DSN path to the target database, keeping the +// admin's server/user/password selection. The admin DSN must be URL form +// (the accepted shape everywhere in this lane). +func withDatabase(adminDSN, database string) (string, error) { + parsed, err := url.Parse(adminDSN) + if err != nil || (parsed.Scheme != "postgres" && parsed.Scheme != "postgresql") { + return "", fmt.Errorf("admin dsn must be URL form (credentials redacted)") + } + parsed.Path = "/" + database + return parsed.String(), nil +} From 2928d6082c2de1e1191843b75ea76590f7690f19 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:55:51 +0000 Subject: [PATCH 11/45] fix(benchcli): accept the exact loom_bench schema prefix (schemas gain the _series suffix) --- internal/dataframe/benchmark/cmd/benchcli/setup.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/dataframe/benchmark/cmd/benchcli/setup.go b/internal/dataframe/benchmark/cmd/benchcli/setup.go index f45e635d9..2b79c6df4 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/setup.go +++ b/internal/dataframe/benchmark/cmd/benchcli/setup.go @@ -78,8 +78,11 @@ func runDropPG() { } func setupPG(ctx context.Context, adminDSN, database, schemaPrefix string) error { - if !strings.HasPrefix(schemaPrefix, benchmarkNamespacePrefix) { - return fmt.Errorf("schema prefix %q is outside the benchmark namespace", schemaPrefix) + // Schema names are _; every full name must carry the + // benchmark namespace prefix. + if schemaPrefix != strings.TrimSuffix(benchmarkNamespacePrefix, "_") && + !strings.HasPrefix(schemaPrefix, benchmarkNamespacePrefix) { + return fmt.Errorf("schema prefix %q produces schemas outside the benchmark namespace", schemaPrefix) } admin, err := pgx.Connect(ctx, adminDSN) if err != nil { From 58e5d941723840503afe30abb9897b5cfc63e430 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:57:13 +0000 Subject: [PATCH 12/45] fix(benchmark): supply the population-mapping cursor secret in spawned arm configs The server refuses to configure without a >=16-char population_mapping_cursor_secret. BuildServerConfigYAML now emits one from a caller-supplied per-run value that the deployer also adds to the log scrub list. --- internal/dataframe/benchmark/cmd/benchcli/deployer.go | 6 ++++-- internal/dataframe/benchmark/harness_test.go | 2 +- internal/dataframe/benchmark/provision.go | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/dataframe/benchmark/cmd/benchcli/deployer.go b/internal/dataframe/benchmark/cmd/benchcli/deployer.go index 3589ee2c3..13f7272c3 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/deployer.go +++ b/internal/dataframe/benchmark/cmd/benchcli/deployer.go @@ -89,6 +89,8 @@ func (d *deployer) prepare(ctx context.Context) error { if strings.TrimSpace(l.dsn) == "" { return fmt.Errorf("spawned %s arm requires --%s-dsn", l.backend, l.backend) } + // One signing secret per arm run; scrubbed from arm logs. + cursorSecret := randomRunIDSuffix() + randomRunIDSuffix() var backendYAML string switch l.backend { case benchmark.BackendPostgres: @@ -106,8 +108,8 @@ func (d *deployer) prepare(ctx context.Context) error { } *l.arm = benchmark.ArmConfig{ BinaryPath: f.binary, - ConfigYAML: benchmark.BuildServerConfigYAML("127.0.0.1:0", l.backend, backendYAML, fence.URL()), - ScrubSecrets: []string{l.dsn}, + ConfigYAML: benchmark.BuildServerConfigYAML("127.0.0.1:0", l.backend, backendYAML, fence.URL(), cursorSecret), + ScrubSecrets: []string{l.dsn, cursorSecret}, } } return nil diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index 6a23673ab..0beba5c9e 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -402,7 +402,7 @@ func TestScrubWriterRedactsSecrets(t *testing.T) { func TestBuildServerConfigYAMLDisablesClickHouseBothArms(t *testing.T) { for _, backend := range []string{BackendArango, BackendPostgres} { - yaml := BuildServerConfigYAML(":18080", backend, " url: http://127.0.0.1:8529\n", "http://127.0.0.1:9999") + yaml := BuildServerConfigYAML(":18080", backend, " url: http://127.0.0.1:8529\n", "http://127.0.0.1:9999", "bench-cursor-secret-0123456789ab") if !strings.Contains(yaml, "clickhouse:") || !strings.Contains(yaml, "enabled: false") { t.Fatalf("%s arm config must disable ClickHouse: %s", backend, yaml) } diff --git a/internal/dataframe/benchmark/provision.go b/internal/dataframe/benchmark/provision.go index 278455d2f..efeb38d9b 100644 --- a/internal/dataframe/benchmark/provision.go +++ b/internal/dataframe/benchmark/provision.go @@ -329,12 +329,15 @@ func MakeBearerToken(issuer, sub string) string { // BuildServerConfigYAML renders the arm config: ClickHouse disabled on both // arms (matched reader path ruling), scoped calypr auth against the mock // fence, backend wiring passed in verbatim from the caller. -func BuildServerConfigYAML(listen, backend, backendYAML, fenceURL string) string { +func BuildServerConfigYAML(listen, backend, backendYAML, fenceURL, cursorSecret string) string { var b bytes.Buffer b.WriteString("server:\n") fmt.Fprintf(&b, " listen: %q\n", listen) fmt.Fprintf(&b, " backend: %q\n", backend) b.WriteString(" clickhouse:\n enabled: false\n") + // The server refuses to configure without a >=16-char signing secret; + // callers generate one per run and scrub it from arm logs. + fmt.Fprintf(&b, " population_mapping_cursor_secret: %q\n", cursorSecret) b.WriteString(backendYAML) b.WriteString("auth:\n mode: calypr\n calypr:\n") fmt.Fprintf(&b, " trusted_issuer: %q\n", fenceURL) From c1ad89f03108e7afdb14261dea46fffada3c5c64 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:58:34 +0000 Subject: [PATCH 13/45] fix(benchmark): render the chosen listen address into the spawned arm config --config makes the YAML authoritative for server.listen, so the earlier --listen argv was ignored and the arm bound :0 behind our back. The deployer now renders ListenPlaceholder and the driver substitutes the FreePort-selected address before spawning. --- .../dataframe/benchmark/cmd/benchcli/deployer.go | 2 +- internal/dataframe/benchmark/driver.go | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/dataframe/benchmark/cmd/benchcli/deployer.go b/internal/dataframe/benchmark/cmd/benchcli/deployer.go index 13f7272c3..bdbdec417 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/deployer.go +++ b/internal/dataframe/benchmark/cmd/benchcli/deployer.go @@ -108,7 +108,7 @@ func (d *deployer) prepare(ctx context.Context) error { } *l.arm = benchmark.ArmConfig{ BinaryPath: f.binary, - ConfigYAML: benchmark.BuildServerConfigYAML("127.0.0.1:0", l.backend, backendYAML, fence.URL(), cursorSecret), + ConfigYAML: benchmark.BuildServerConfigYAML(benchmark.ListenPlaceholder, l.backend, backendYAML, fence.URL(), cursorSecret), ScrubSecrets: []string{l.dsn, cursorSecret}, } } diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index a74decebc..318cfe02c 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -16,6 +16,10 @@ import ( // and emit a report that VerifyReport must accept. Every failure aborts // before any success-shaped artifact exists. +// ListenPlaceholder marks where the chosen listen address is rendered into +// the arm config: --config makes YAML authoritative for listen. +const ListenPlaceholder = "__BENCH_LISTEN__" + // ArmConfig describes one backend arm: either attach to a running server // (BaseURL) or spawn one from BinaryPath with ConfigYAML. type ArmConfig struct { @@ -403,12 +407,18 @@ func startArm(ctx context.Context, backend string, cfg ArmConfig, spawn SpawnFun return nil, err } listen := fmt.Sprintf("127.0.0.1:%d", port) + // --config makes YAML authoritative for listen, so the chosen address + // must be rendered into the config, not only passed as --listen. + cfgYAML := cfg.ConfigYAML + if addr, mark, ok := strings.Cut(cfgYAML, ListenPlaceholder); ok { + cfgYAML = addr + listen + mark + } ready := cfg.ReadyURL if ready == "" { ready = "http://" + listen + "/readyz" } timeout := 60 * time.Second - spec := ServerSpec{BinaryPath: cfg.BinaryPath, Backend: backend, ConfigYAML: cfg.ConfigYAML, Env: cfg.Env, ReadyURL: ready, Timeout: timeout, StderrDir: opts.StderrDir} + spec := ServerSpec{BinaryPath: cfg.BinaryPath, Backend: backend, ConfigYAML: cfgYAML, Env: cfg.Env, ReadyURL: ready, Timeout: timeout, StderrDir: opts.StderrDir} proc, err := spawn(ctx, spec, cfg.ScrubSecrets, listen) if err != nil { return nil, err From 71855d159541be726bcb4fe389a4b5cc24762113 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:58:57 +0000 Subject: [PATCH 14/45] fix(benchmark): MockFence grants read AND write on granted paths Dataset ingest and explorer publication authorize with PermissionWrite; a read-only grant list 403s every upload. A benchmark grant is a real principal grant in both directions; the negative-auth scoped principal still holds no grant on the dataset path at all. --- internal/dataframe/benchmark/provision.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/dataframe/benchmark/provision.go b/internal/dataframe/benchmark/provision.go index efeb38d9b..aa51b85a3 100644 --- a/internal/dataframe/benchmark/provision.go +++ b/internal/dataframe/benchmark/provision.go @@ -280,7 +280,14 @@ func (f *MockFence) handle(w http.ResponseWriter, r *http.Request) { f.mu.Unlock() authz := map[string]any{} for _, path := range paths { - authz[path] = []any{map[string]any{"method": "read", "service": "*"}} + // A benchmark grant is a real principal grant: reads AND writes on + // the path. Dataset ingest and explorer publication run as write; + // the negative-auth control's scoped principal holds no grant on + // the dataset path at all, in either direction. + authz[path] = []any{ + map[string]any{"method": "read", "service": "*"}, + map[string]any{"method": "write", "service": "*"}, + } } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{"sub": sub, "authz": authz}) From 68a09c251302ca1a1c283c4c999b5600f6fbd08b Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 01:09:02 +0000 Subject: [PATCH 15/45] feat(benchmark): planned run scope with skip-capture discipline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-window disclosure per the loom-experiment-2 ruling: benchcli run --scope planned writes a fully-disclosed artifact (both legs not_run with named capabilities, every workload blocked, zero controls, samples, summaries, claims) and exits 3; it refuses every live flag, needs no window, and the live driver refuses planned scope outright. - armplan.go: RunScopePlanned, PlannedPlan, PlannedGateReports; ValidateLegPlan requires both legs not_run WITH named capability. - report.go: VerifyReport branches on run-leg count — a report where nothing ran must have wholly absent control values (stolen limits, borrowed floors, and forged self-ratios RED), while any run leg keeps the full controls requirement. - cmd/benchcli: planned mode, per-backend disclosure reasons, shared writeRunArtifacts so planned and live artifacts share one writer. - scripts/benchmark-backends.mjs: classifyPlannedDisclosure enforces skip-capture discipline (any sample/summary/claim in a planned artifact FAILS the check) and still routes the artifact through the Go verifier for authority. - README for the package; runner pointer in scripts/README.md. Live legs cite finding PG-PG-AUTH-PATH-DISCOVERY-ALLGENS (scoped calypr ingest blocked on postgres; fix owned by arch-backend-09). Validation: go build ./... and go vet clean; offline package suite green with 42 forgery-RED cases (14 new planned paths); node tests 12/12 in scripts/benchmark-backends.test.mjs and 167/167 across scripts/*.test.mjs; planned artifact produced by the CLI passes benchcli verify. --- internal/dataframe/benchmark/README.md | 78 ++++++++ internal/dataframe/benchmark/armplan.go | 62 +++++- internal/dataframe/benchmark/armplan_test.go | 114 ++++++++++++ .../dataframe/benchmark/cmd/benchcli/main.go | 176 ++++++++++++++---- internal/dataframe/benchmark/driver.go | 5 + internal/dataframe/benchmark/report.go | 30 ++- scripts/README.md | 10 + scripts/benchmark-backends.mjs | 41 ++++ scripts/benchmark-backends.test.mjs | 69 +++++++ 9 files changed, 539 insertions(+), 46 deletions(-) create mode 100644 internal/dataframe/benchmark/README.md diff --git a/internal/dataframe/benchmark/README.md b/internal/dataframe/benchmark/README.md new file mode 100644 index 000000000..1a4186430 --- /dev/null +++ b/internal/dataframe/benchmark/README.md @@ -0,0 +1,78 @@ +# Backend benchmark harness (Arango/AQL vs PostgreSQL/SQL) + +Matched dataframe-task benchmark. Correctness gates run before any timing +is claimed; every derived number in an artifact is recomputed by the Go +verifier, so a hand-edited report never verifies. + +## Layout + +- `fixtures.go` — deterministic NDJSON dataset (preset × seed → digest). +- `authoring.go` — battery workloads in acceptance-proven workspace shapes. +- `canonical.go`, `oracle.go`, `gates.go` — bag canonicalization, expected + results, correctness gates (bag equality, shape, auth, truncation). +- `measure.go`, `controls.go` — sampling, summaries, stability CV, + self-ratio, empty-result floor. +- `matrix.go`, `armplan.go` — battery matrix; run scopes and leg plans. +- `report.go`, `report_md.go` — artifact schema, `VerifyReport`, Markdown. +- `harness.go`, `provision.go`, `driver.go` — live arms, MockFence, server + spawn, RunPlan-checked driver. +- `cmd/benchcli` — CLI below. `scripts/benchmark-backends.mjs` — node + orchestrator (evidence lines only; Go owns verification authority). + +## Scopes + +| scope | arango leg | postgres leg | use | +| -------------- | --------------- | --------------- | ---------------------------------------- | +| `both-arms` | run | run | CI target (needs both services) | +| `postgres-only`| not_run + capability | run | worker sessions with a granted window | +| `planned` | not_run + capability | not_run + capability | pre-window disclosure artifact | + +A not_run leg must name its missing capability; the artifact carries it per +workload, and `VerifyReport` refuses samples, summaries, claims, versions, +or control values for legs that did not run. A planned report can never be +`passed`: it is `failed` with zero claims, and the node orchestrator +refuses a planned artifact that captured any timing. + +## Commands + +```sh +go run ./internal/dataframe/benchmark/cmd/benchcli verify --report OUT/report.json +go run ./internal/dataframe/benchmark/cmd/benchcli run --scope planned \ + --source-commit <40-hex> --out OUT +go run ./internal/dataframe/benchmark/cmd/benchcli run --scope postgres-only \ + --source-commit <40-hex> --window-note "granter + time" --postgres-dsn --binary --out OUT +# database lifecycle (admin DSN via LOOM_BENCH_ADMIN_DSN env, never argv): +... setup-pg --database loom_bench_x # six-series ladder via SetupMigrate +... drop-pg --database loom_bench_x +``` + +Artifacts per run: `report.json` (verified schema), `report.md`, +`arms.json` (operational evidence: arm URLs, log paths, fence requests or +planned capabilities). + +## Exit codes (`run`) + +- `0` — live legs ran and the artifact verifies. +- `1` — a live run executed and failed. +- `3` — disclosed not_run: preflight refused (missing `LOOM_BENCH_LIVE=1`, + window note, source commit, namespace guard, existing output) or the + planned disclosure artifact was written. + +## Live-run guards (all fail closed, exit 3) + +`LOOM_BENCH_LIVE=1` (coordinator-granted exclusive window), `--window-note`, +full 40-hex `--source-commit`, no overwrite of `--out/report.json`, +PostgreSQL database/schema and Arango database under `loom_bench_*`, arms +uniformly attached or spawned (never mixed), spawned arms get tokens only +from the MockFence. + +## Current blockers (cited in planned artifacts) + +- `PG-PG-AUTH-PATH-DISCOVERY-ALLGENS` — scoped calypr ingest is impossible + on the postgres lane: `AuthorizeWrite`'s F-B27 project-binding branch + needs the all-generations auth-path listing that the postgres catalog + read lane refuses (`ErrAllGenerationsUnsupported`), and the load handler + wraps it as 403. Owner: arch-backend-09. Evidence: + `pg-pg-auth-path-discovery-allgens-evidence.md` (session artifacts). +- Arango leg: no ArangoDB service in the worker container; go-arango CI + lane targets it. diff --git a/internal/dataframe/benchmark/armplan.go b/internal/dataframe/benchmark/armplan.go index abb138667..10aa80387 100644 --- a/internal/dataframe/benchmark/armplan.go +++ b/internal/dataframe/benchmark/armplan.go @@ -15,10 +15,17 @@ import ( const ( RunScopeBothArms = "both-arms" RunScopePostgresOnly = "postgres-only" + // RunScopePlanned is the fully-disclosed pre-window artifact: no leg + // ran, every leg is not_run with its missing capability named, and the + // report can carry no claims. It exists so a blocked session (e.g. + // PG-PG-AUTH-PATH-DISCOVERY-ALLGENS) publishes an honest planned + // report instead of staying silent or dressing an empty matrix up as + // a pass. + RunScopePlanned = "planned" ) func ValidScope(scope string) bool { - return scope == RunScopeBothArms || scope == RunScopePostgresOnly + return scope == RunScopeBothArms || scope == RunScopePostgresOnly || scope == RunScopePlanned } // LegPlan declares one backend arm's participation in the run. @@ -71,6 +78,20 @@ func PostgresOnlyPlan(arangoCapability string) RunPlan { } } +// PlannedPlan is the pre-window disclosure plan: both legs not_run, each +// with its missing capability named verbatim (finding ids welcome, e.g. +// "blocked by PG-PG-AUTH-PATH-DISCOVERY-ALLGENS: ..."). A planned run +// measures nothing and claims nothing; VerifyReport enforces that. +func PlannedPlan(arangoCapability, postgresCapability string) RunPlan { + return RunPlan{ + Scope: RunScopePlanned, + Legs: []LegPlan{ + {Backend: BackendArango, Mode: "not_run", Capability: arangoCapability}, + {Backend: BackendPostgres, Mode: "not_run", Capability: postgresCapability}, + }, + } +} + // PlannedNotRunForBackends expands one disclosure reason across every // battery workload for each not-run leg, so no row can be quietly dropped. func PlannedNotRunForBackends(backends []string) []PlannedNotRun { @@ -83,6 +104,29 @@ func PlannedNotRunForBackends(backends []string) []PlannedNotRun { return rows } +// PlannedGateReports builds the honest per-workload gate reports for a +// planned run: every arm blocked with its leg capability in the detail, +// nothing claimable. VerifyReport's not-run rules (blocked arm, disclosure +// row, zero samples/claims) then bind the artifact. +func PlannedGateReports(legs []LegPlan) []WorkloadGateReport { + capability := map[string]string{} + for _, leg := range legs { + capability[leg.Backend] = leg.Capability + } + detail := func(backend string) string { return "leg not run: " + capability[backend] } + var reports []WorkloadGateReport + for _, spec := range Battery { + reports = append(reports, WorkloadGateReport{ + WorkloadID: spec.ID, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelBlocked, Detail: detail(BackendArango)}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelBlocked, Detail: detail(BackendPostgres)}, + // Claimable stays false: a planned run measured nothing. + ExclusionReason: "planned run: " + detail(BackendArango) + " | " + detail(BackendPostgres), + }) + } + return reports +} + // ValidateLegPlan checks the plan is total, closed, and honest. func ValidateLegPlan(scope string, legs []LegPlan) error { if !ValidScope(scope) { @@ -110,6 +154,12 @@ func ValidateLegPlan(scope string, legs []LegPlan) error { } } switch scope { + case RunScopePlanned: + for _, backend := range []string{BackendArango, BackendPostgres} { + if byBackend[backend].Mode != "not_run" || strings.TrimSpace(byBackend[backend].Capability) == "" { + return fmt.Errorf("benchmark: planned scope requires %s not_run WITH a named capability", backend) + } + } case RunScopePostgresOnly: if byBackend[BackendPostgres].Mode != "run" { return fmt.Errorf("benchmark: postgres-only scope requires the postgres leg to run") @@ -177,6 +227,16 @@ func RunStatusScoped(reports []WorkloadGateReport, scope string) (string, []stri sort.Slice(byID, func(i, j int) bool { return byID[i].WorkloadID < byID[j].WorkloadID }) var reasons []string for _, report := range byID { + if scope == RunScopePlanned { + // Nothing ran. Both arms must carry the expected blocked + // posture with their capability in the detail; anything else + // (a passed arm, a measured-looking row) is a forgery signal. + if report.Arango.Outcome != OutcomeLabelBlocked || report.Postgres.Outcome != OutcomeLabelBlocked { + reasons = append(reasons, fmt.Sprintf("%s: planned run must show both arms blocked, got arango=%s postgres=%s", + report.WorkloadID, report.Arango.Outcome, report.Postgres.Outcome)) + } + continue + } armOutcomes := []WorkloadOutcome{report.Arango.Outcome, report.Postgres.Outcome} if scope == RunScopePostgresOnly { // Only the PostgreSQL arm decides; the Arango arm must be the diff --git a/internal/dataframe/benchmark/armplan_test.go b/internal/dataframe/benchmark/armplan_test.go index 939265297..4908a8d69 100644 --- a/internal/dataframe/benchmark/armplan_test.go +++ b/internal/dataframe/benchmark/armplan_test.go @@ -195,3 +195,117 @@ func TestRenderMarkdown(t *testing.T) { t.Fatal("postgres-only markdown must not contain a claims table") } } + +// The planned scope is the pre-window disclosure artifact: nothing ran, so +// nothing may be claimed — not a sample, not a summary, not a control, not +// a version. These tests pin the honest shape and every deception path. + +func plannedValidReport(t *testing.T) *Report { + t.Helper() + identity := RunIdentity{ + RunID: "bench-planned-a", StartedAt: time.Unix(1700000000, 0).UTC(), + Preset: PresetTiny, SourceCommit: strings.Repeat("a", 40), + DatasetDigest: strings.Repeat("b", 64), + ClickHouseDisabled: true, + Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, + } + plan := PlannedPlan( + "no ArangoDB service in this execution environment; go-arango CI lane targets this leg", + "blocked by PG-PG-AUTH-PATH-DISCOVERY-ALLGENS: the scoped calypr lane cannot ingest on postgres") + plan.PlannedNotRun = PlannedNotRunForBackends(plan.NotRunBackends()) + report, err := BuildReport(identity, plan, ControlRuns{}, PlannedGateReports(plan.Legs), nil) + if err != nil { + t.Fatalf("valid planned report build failed: %v", err) + } + if report.Status != "failed" || len(report.Claims) != 0 { + t.Fatalf("planned report must be failed with zero claims, got %s / %d claims", report.Status, len(report.Claims)) + } + if want := 2 * len(Battery); len(report.Plan.PlannedNotRun) != want || len(report.GateReports) != len(Battery) { + t.Fatalf("planned disclosure must cover the whole battery both legs: %d rows, %d gates, want %d/%d", + len(report.Plan.PlannedNotRun), len(report.GateReports), want, len(Battery)) + } + return report +} + +func TestPlannedReportRoundTrips(t *testing.T) { + loaded := roundTrip(t, plannedValidReport(t)) + if loaded.Plan.Scope != RunScopePlanned || loaded.Status != "failed" || len(loaded.Claims) != 0 { + t.Fatalf("planned round trip drifted: %+v", loaded.Plan) + } + if strings.Contains(RenderMarkdown(loaded), "arango/postgres |") { + t.Fatal("planned markdown must not contain a claims table") + } +} + +func forgePlanned(t *testing.T, mutate func(*Report)) func(*testing.T) { + return func(t *testing.T) { + t.Helper() + report := plannedValidReport(t) + mutate(report) + data, err := json.Marshal(report) + if err != nil { + t.Fatal(err) + } + if _, err := LoadReport(data); err == nil { + t.Fatal("forged planned report survived verification") + } + } +} + +func TestPlannedForgerySuite(t *testing.T) { + cases := map[string]func(*Report){ + "planned-leg-marked-run": func(r *Report) { r.Plan.Legs[1].Mode = "run" }, + "planned-capability-blanked": func(r *Report) { r.Plan.Legs[1].Capability = " " }, + "planned-arm-shown-passed": func(r *Report) { + r.GateReports[0].Postgres.Outcome = OutcomeLabelPassed + r.GateReports[0].Postgres.RowCount = 5 + r.GateReports[0].Claimable = true + r.Status = "passed" + r.StatusReasons = nil + }, + "planned-status-flipped-passed": func(r *Report) { r.Status = "passed" }, + "planned-stolen-control-limits": func(r *Report) { r.Controls.StabilityCVLimit = StabilityCVLimit }, + "planned-stolen-empty-floor": func(r *Report) { r.Controls.EmptyFloorMillis = 0.4 }, + "planned-forged-self-ratio": func(r *Report) { r.Controls.SelfRatioPostgres = 1.0 }, + "planned-forged-ratio-policy": func(r *Report) { r.Controls.RatioPolicy = "ratio-only-both-passed" }, + "planned-samples-injected": func(r *Report) { + r.Samples = append(r.Samples, sample(BackendPostgres, r.GateReports[0].WorkloadID, OutcomePassed, 10, false)) + }, + "planned-summaries-injected": func(r *Report) { + r.Summaries = append(r.Summaries, SummaryRow{WorkloadID: r.GateReports[0].WorkloadID, Backend: BackendPostgres, Cache: CacheWarm, Summary: Summary{Count: 4, MedianMillis: 9}}) + }, + "planned-claim-injected": func(r *Report) { + r.Claims = append(r.Claims, Claim{WorkloadID: r.GateReports[0].WorkloadID, Ratio: SpeedRatio{ArangoOverPostgres: 2.0}}) + }, + "planned-disclosure-row-dropped": func(r *Report) { r.Plan.PlannedNotRun = r.Plan.PlannedNotRun[1:] }, + "planned-version-claimed": func(r *Report) { r.Identity.PostgresVersion = "PostgreSQL 18.x" }, + "planned-arango-version-claimed": func(r *Report) { r.Identity.ArangoVersion = "3.12.x" }, + } + for name, mutate := range cases { + t.Run(name, forgePlanned(t, mutate)) + } +} + +func TestPlannedLegPlanValidation(t *testing.T) { + plan := PlannedPlan("capability a", "capability b") + if err := ValidateLegPlan(plan.Scope, plan.Legs); err != nil { + t.Fatalf("valid planned plan refused: %v", err) + } + if err := ValidateLegPlan(RunScopePlanned, []LegPlan{ + {Backend: BackendArango, Mode: "not_run", Capability: "a"}, + {Backend: BackendPostgres, Mode: "run"}, + }); err == nil { + t.Fatal("planned scope accepted a run leg") + } + if err := ValidateLegPlan(RunScopePlanned, []LegPlan{ + {Backend: BackendArango, Mode: "not_run", Capability: "a"}, + {Backend: BackendPostgres, Mode: "not_run"}, + }); err == nil { + t.Fatal("planned scope accepted an unnamed capability") + } + // The live driver must refuse to run a planned scope at all. + if _, _, err := RunBenchmark(nil, DriverOptions{Plan: PlannedPlan("a", "b")}); err == nil || + !strings.Contains(err.Error(), "planned scope") { + t.Fatalf("driver must refuse planned scope, got %v", err) + } +} diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go index 25d8bd64f..777611f89 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/main.go +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -61,36 +61,46 @@ func runVerify() { } type runFlags struct { - scope string - preset string - seed uint64 - project string - generation string - authResourcePath string - warmup int - iterations int - rounds int - concurrency int - previewLimit int - binary string - stderrDir string - postgresAttachURL string - postgresDSN string - arangoAttachURL string - arangoDSN string - tokenUnrestricted string - tokenScoped string - out string - runID string - sourceCommit string - windowNote string - emptyFloorIters int + scope string + preset string + seed uint64 + project string + generation string + authResourcePath string + warmup int + iterations int + rounds int + concurrency int + previewLimit int + binary string + stderrDir string + postgresAttachURL string + postgresDSN string + arangoAttachURL string + arangoDSN string + tokenUnrestricted string + tokenScoped string + out string + runID string + sourceCommit string + windowNote string + emptyFloorIters int + arangoCapability string + postgresCapability string } +// Default capabilities cited by planned not_run legs. The postgres one +// names the blocking finding so the artifact itself carries the reason a +// live leg was impossible. +const ( + defaultArangoCapability = "no ArangoDB service in this execution environment; go-arango CI lane targets this leg" + defaultPostgresCapability = "blocked by PG-PG-AUTH-PATH-DISCOVERY-ALLGENS: the scoped calypr lane cannot ingest datasets on postgres; the catalog read lane refuses the all-generations auth-path listing that AuthorizeWrite's project binding requires (see pg-pg-auth-path-discovery-allgens-evidence.md)" +) + func runLive() { fs := flag.NewFlagSet("run", flag.ExitOnError) var f runFlags - fs.StringVar(&f.scope, "scope", benchmark.RunScopePostgresOnly, "both-arms | postgres-only") + fs.StringVar(&f.scope, "scope", benchmark.RunScopePostgresOnly, "both-arms | postgres-only | planned") fs.StringVar(&f.preset, "preset", benchmark.PresetTiny, "dataset preset: tiny | small | representative") fs.Uint64Var(&f.seed, "seed", 7, "deterministic dataset seed") fs.StringVar(&f.project, "project", "loom-bench", "benchmark project namespace") @@ -114,6 +124,8 @@ func runLive() { fs.StringVar(&f.sourceCommit, "source-commit", "", "full 40-hex SHA the arms were built from") fs.StringVar(&f.windowNote, "window-note", "", "who granted the exclusive window and when (recorded in arms.json)") fs.IntVar(&f.emptyFloorIters, "empty-floor-iterations", 5, "iterations for the empty-result floor control") + fs.StringVar(&f.arangoCapability, "arango-capability", defaultArangoCapability, "capability cited by the arango not_run legs (planned scope)") + fs.StringVar(&f.postgresCapability, "postgres-capability", defaultPostgresCapability, "capability cited by the postgres not_run legs (planned scope)") _ = fs.Parse(os.Args[2:]) // Fail-closed preflight (exit 3: refused to run; exit 1: ran and failed). @@ -121,15 +133,19 @@ func runLive() { fmt.Fprintf(os.Stderr, "benchcli run: BLOCKED — "+format+"\n", args...) os.Exit(3) } + if !benchmark.ValidScope(f.scope) { + preflight("unknown scope %q", f.scope) + } + if f.scope == benchmark.RunScopePlanned { + runPlanned(f, preflight) + return + } if os.Getenv("LOOM_BENCH_LIVE") != "1" { preflight("live runs require the exclusive window granted by the coordinator (LOOM_BENCH_LIVE=1)") } if strings.TrimSpace(f.windowNote) == "" { preflight("--window-note must record the granted window (granter + time) before any live run") } - if !benchmark.ValidScope(f.scope) { - preflight("unknown scope %q", f.scope) - } if !regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString(f.sourceCommit) { preflight("--source-commit must be the full 40-hex SHA of the built source") } @@ -224,30 +240,116 @@ func runLive() { } // Artifacts: report.json, report.md, arms.json (operational evidence). + manifest := map[string]any{"runId": f.runID, "windowNote": f.windowNote, "arms": evidence} + if d.fence != nil { + manifest["fenceRequests"] = d.fence.Requests() + } + writeRunArtifacts(f.out, report, manifest) + fmt.Printf("benchcli run: OK run=%s scope=%s status=%s claims=%d out=%s\n", + report.Identity.RunID, report.Plan.Scope, report.Status, len(report.Claims), f.out) +} + +// writeRunArtifacts emits the canonical three-file artifact set. Every +// report the CLI produces goes through here so verify sees identical bytes +// shape from live and planned runs. +func writeRunArtifacts(out string, report *benchmark.Report, manifest map[string]any) { reportJSON, err := benchmark.MarshalReport(report) if err != nil { fmt.Fprintf(os.Stderr, "benchcli run: marshal: %v\n", err) os.Exit(1) } - if err := os.WriteFile(filepath.Join(f.out, "report.json"), reportJSON, 0o644); err != nil { + if err := os.WriteFile(filepath.Join(out, "report.json"), reportJSON, 0o644); err != nil { fmt.Fprintf(os.Stderr, "benchcli run: write report: %v\n", err) os.Exit(1) } - if err := os.WriteFile(filepath.Join(f.out, "report.md"), []byte(benchmark.RenderMarkdown(report)), 0o644); err != nil { + if err := os.WriteFile(filepath.Join(out, "report.md"), []byte(benchmark.RenderMarkdown(report)), 0o644); err != nil { fmt.Fprintf(os.Stderr, "benchcli run: write markdown: %v\n", err) os.Exit(1) } - manifest := map[string]any{"runId": f.runID, "windowNote": f.windowNote, "arms": evidence} - if d.fence != nil { - manifest["fenceRequests"] = d.fence.Requests() - } manifestJSON, _ := json.MarshalIndent(manifest, "", " ") - if err := os.WriteFile(filepath.Join(f.out, "arms.json"), manifestJSON, 0o644); err != nil { + if err := os.WriteFile(filepath.Join(out, "arms.json"), manifestJSON, 0o644); err != nil { fmt.Fprintf(os.Stderr, "benchcli run: write arms manifest: %v\n", err) os.Exit(1) } - fmt.Printf("benchcli run: OK run=%s scope=%s status=%s claims=%d out=%s\n", - report.Identity.RunID, report.Plan.Scope, report.Status, len(report.Claims), f.out) +} + +// runPlanned emits the fully-disclosed pre-window artifact: both legs +// not_run with named capabilities, every workload blocked in the gates, +// zero controls, zero samples, zero claims. It touches no service, so no +// live window is needed; it refuses every live flag so it can never +// masquerade as a live run. Exit 3 = disclosed not_run, never a pass. +func runPlanned(f runFlags, preflight func(string, ...any)) { + live := []struct { + name string + value string + }{ + {"binary", f.binary}, {"stderr-dir", f.stderrDir}, + {"postgres-url", f.postgresAttachURL}, {"postgres-dsn", f.postgresDSN}, + {"arango-url", f.arangoAttachURL}, {"arango-dsn", f.arangoDSN}, + {"token-unrestricted", f.tokenUnrestricted}, {"token-scoped", f.tokenScoped}, + {"window-note", f.windowNote}, + } + for _, l := range live { + if strings.TrimSpace(l.value) != "" { + preflight("planned scope refuses --%s: nothing may run in a planned run", l.name) + } + } + if strings.TrimSpace(f.arangoCapability) == "" || strings.TrimSpace(f.postgresCapability) == "" { + preflight("planned scope must name a capability for each not_run leg") + } + if !regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString(f.sourceCommit) { + preflight("--source-commit must be the full 40-hex SHA of the built source") + } + if _, err := os.Stat(filepath.Join(f.out, "report.json")); err == nil { + preflight("%s/report.json already exists; use a fresh --out directory", f.out) + } + if f.runID == "" { + f.runID = fmt.Sprintf("bench-%s-%s", time.Now().UTC().Format("20060102T150405Z"), randomRunIDSuffix()) + } + if f.generation == "" { + f.generation = f.runID + } + if err := os.MkdirAll(f.out, 0o755); err != nil { + preflight("cannot create --out: %v", err) + } + params := benchmark.DatasetParams{Preset: f.preset, Seed: f.seed, Project: f.project, + Generation: f.generation, AuthResourcePath: f.authResourcePath} + files, err := benchmark.GenerateFiles(params) + if err != nil { + preflight("planned report needs the deterministic fixture digest: %v", err) + } + plan := benchmark.PlannedPlan(f.arangoCapability, f.postgresCapability) + plan.PlannedNotRun = benchmark.PlannedNotRunForBackends(plan.NotRunBackends()) + capability := map[string]string{} + for _, leg := range plan.Legs { + capability[leg.Backend] = leg.Capability + } + for i := range plan.PlannedNotRun { + plan.PlannedNotRun[i].Reason = "leg not run: " + capability[plan.PlannedNotRun[i].Backend] + } + if !planNotRunMatchesBattery(plan) { + fmt.Fprintln(os.Stderr, "benchcli run: internal plan inconsistency") + os.Exit(1) + } + identity := benchmark.RunIdentity{ + RunID: f.runID, StartedAt: time.Now().UTC(), Preset: f.preset, + SourceCommit: f.sourceCommit, DatasetDigest: files.Digest, + ClickHouseDisabled: true, + Config: benchmark.RunConfig{Warmup: f.warmup, Iterations: f.iterations, Concurrency: f.concurrency, Rounds: f.rounds}, + } + report, err := benchmark.BuildReport(identity, plan, benchmark.ControlRuns{}, benchmark.PlannedGateReports(plan.Legs), nil) + if err != nil { + fmt.Fprintf(os.Stderr, "benchcli run: planned report: %v\n", err) + os.Exit(1) + } + manifest := map[string]any{ + "runId": f.runID, "planned": true, "arms": []any{}, + "capabilities": map[string]string{benchmark.BackendArango: f.arangoCapability, benchmark.BackendPostgres: f.postgresCapability}, + } + writeRunArtifacts(f.out, report, manifest) + fmt.Printf("benchcli run: PLANNED run=%s scope=planned status=%s claims=0 out=%s (nothing measured; exit 3 = disclosed not_run)\n", + report.Identity.RunID, report.Status, f.out) + os.Exit(3) } func planNotRunMatchesBattery(plan benchmark.RunPlan) bool { diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index 318cfe02c..b1fcee4b5 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -97,6 +97,11 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden if err := ValidateLegPlan(opts.Plan.Scope, opts.Plan.Legs); err != nil { return nil, nil, err } + if opts.Plan.Scope == RunScopePlanned { + // The live driver spawns, authoring-runs, and times; a planned run + // is built through BuildReport+PlannedGateReports, never here. + return nil, nil, fmt.Errorf("benchmark: the live driver refuses planned scope; build the planned report directly") + } if err := opts.Config.Validate(); err != nil { return nil, nil, err } diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go index 42046c8c4..37b227427 100644 --- a/internal/dataframe/benchmark/report.go +++ b/internal/dataframe/benchmark/report.go @@ -197,14 +197,8 @@ func VerifyReport(report *Report) error { if !sha256HexRe.MatchString(report.Identity.DatasetDigest) { return fmt.Errorf("benchmark report: dataset digest is not sha256 hex") } - // Controls must have been run. - if report.Controls.StabilityCVLimit <= 0 || report.Controls.RatioPolicy == "" { - return fmt.Errorf("benchmark report: measurement controls were not run (missing stability limit or ratio policy)") - } - if report.Controls.EmptyFloorMillis <= 0 { - return fmt.Errorf("benchmark report: empty-result floor control was not run") - } - // The leg plan must be total, closed, and scope-consistent. + // The leg plan must be total, closed, and scope-consistent. This runs + // first because the control rules below branch on how many legs ran. if err := ValidateLegPlan(report.Plan.Scope, report.Plan.Legs); err != nil { return fmt.Errorf("benchmark report: %w", err) } @@ -212,9 +206,29 @@ func VerifyReport(report *Report) error { return fmt.Errorf("benchmark report: %w", err) } notRun := map[string]string{} + runLegs := 0 for _, leg := range report.Plan.Legs { if leg.Mode == "not_run" { notRun[leg.Backend] = leg.Capability + } else { + runLegs++ + } + } + if runLegs > 0 { + // A run that measured anything must show the controls ran. + if report.Controls.StabilityCVLimit <= 0 || report.Controls.RatioPolicy == "" { + return fmt.Errorf("benchmark report: measurement controls were not run (missing stability limit or ratio policy)") + } + if report.Controls.EmptyFloorMillis <= 0 { + return fmt.Errorf("benchmark report: empty-result floor control was not run") + } + } else { + // Planned report: nothing ran, so a control that claims to have run + // is a forgery path (stolen limits, borrowed floors, ratio cover). + c := report.Controls + if c.StabilityCVLimit != 0 || c.EmptyFloorMillis != 0 || c.SelfRatioArango != 0 || + c.SelfRatioPostgres != 0 || c.RatioPolicy != "" || len(c.UnstableCells) != 0 { + return fmt.Errorf("benchmark report: control values claimed although no leg ran") } } // Live versions: a run leg must carry one; a not-run leg may not claim diff --git a/scripts/README.md b/scripts/README.md index d155e3a0d..dc9f760bf 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -82,3 +82,13 @@ loading enabled. Native drag/download acceptance remains in the Chromium route. See [measurements](../docs/verification/lightpanda/POOLED_RESULTS.md) for the precise speed scope and [coverage](../docs/verification/lightpanda/COVERAGE.md) for dispositions of all 96 retired specs. Job count is not a coverage count. + +The backend benchmark runner [benchmark-backends.mjs](benchmark-backends.mjs) +orchestrates the Arango/AQL vs PostgreSQL/SQL matched dataframe benchmark. +Verification authority stays in Go (`benchcli verify`); this script only +builds invocations and reports evidence lines. `--run` discloses a refused +preflight (exit 3) as `not_run`, never a pass; a planned disclosure artifact +must carry zero samples, summaries, or claims (skip-capture discipline) and +still passes through the Go verifier. See +[`internal/dataframe/benchmark/README.md`](../internal/dataframe/benchmark/README.md) +for scopes, guards, and exit codes. diff --git a/scripts/benchmark-backends.mjs b/scripts/benchmark-backends.mjs index 319d6543a..ea1c108b2 100644 --- a/scripts/benchmark-backends.mjs +++ b/scripts/benchmark-backends.mjs @@ -86,9 +86,29 @@ export function readReportSummary(path) { legs: (raw.plan?.legs ?? []).map((leg) => `${leg.backend}:${leg.mode}${leg.capability ? `(${leg.capability})` : ''}`), plannedNotRun: (raw.plan?.plannedNotRun ?? []).length, claims: (raw.claims ?? []).length, + samples: (raw.samples ?? []).length, + summaries: (raw.summaries ?? []).length, }; } +// Skip-capture discipline: a planned disclosure artifact must carry zero +// measurement evidence — any sample, summary row, or claim that escaped a +// not_run leg is a FAIL, never a disclosure. Null for non-planned reports; +// the Go verifier stays the authority that the disclosure is consistent. +export function classifyPlannedDisclosure(summary) { + if (summary.scope !== 'planned') return null; + if (!summary.legs.every((leg) => leg.includes(':not_run'))) { + return { status: 'fail', disclosure: 'planned artifact declares a leg that ran' }; + } + if (summary.claims !== 0 || summary.samples !== 0 || summary.summaries !== 0) { + return { + status: 'fail', + disclosure: `planned artifact captured measurement evidence (samples=${summary.samples}, summaries=${summary.summaries}, claims=${summary.claims})`, + }; + } + return { status: 'not_run', disclosure: `planned artifact disclosed: ${summary.legs.length} not_run legs, zero timing captured` }; +} + export function run(flags, deps = {}) { const exec = deps.exec ?? spawnSync; const log = deps.log ?? ((line) => console.log(line)); @@ -107,6 +127,27 @@ export function run(flags, deps = {}) { const outcome = classifyRunExit(result.status); log(formatCheckLine('live-run', outcome.status, outcome.disclosure ?? undefined)); if (outcome.status === 'not_run') { + const reportPath = `${flags.out}/report.json`; + if (existsSync(reportPath)) { + // A planned run writes its disclosure artifact and still exits 3. + // Classify it here and let the Go verifier keep authority. + const summary = readReportSummary(reportPath); + const planned = classifyPlannedDisclosure(summary); + if (planned && planned.status === 'fail') { + log(formatCheckLine(CHECK_LIVE, 'fail', planned.disclosure)); + failures += 1; + return failures; + } + const verify = live(buildVerifyInvocation({ ...flags, report: reportPath })); + const verified = classifyVerifyExit(verify.status); + log(formatCheckLine(CHECK_LIVE, 'not_run', + `${(planned ?? { disclosure: 'disclosure artifact produced without preflight' }).disclosure}; go verify ${verified.status}`)); + if (verified.status !== 'pass') { + log(formatCheckLine(CHECK_LIVE, 'fail', verified.disclosure)); + failures += 1; + } + return failures; + } log(formatCheckLine(CHECK_LIVE, 'not_run', 'no artifact produced; run stays planned/not_run until the granted window')); return failures; // a refused preflight is disclosed, not a failure } diff --git a/scripts/benchmark-backends.test.mjs b/scripts/benchmark-backends.test.mjs index 9306752aa..8fcb629d2 100644 --- a/scripts/benchmark-backends.test.mjs +++ b/scripts/benchmark-backends.test.mjs @@ -121,3 +121,72 @@ test('formatCheckLine is a stable single line', () => { assert.equal(formatCheckLine('x', 'pass', 'ok'), 'check x: pass ok'); assert.equal(formatCheckLine('x', 'fail'), 'check x: fail'); }); + +import { classifyPlannedDisclosure } from './benchmark-backends.mjs'; + +function plannedSummary(overrides = {}) { + return { + runId: 'bench-planned-a', scope: 'planned', status: 'failed', + legs: ['arango:not_run(no ArangoDB service)', 'postgres:not_run(blocked by PG-PG-AUTH-PATH-DISCOVERY-ALLGENS)'], + plannedNotRun: 20, claims: 0, samples: 0, summaries: 0, + ...overrides, + }; +} + +test('planned disclosure: clean artifact is not_run, captured timing is fail', () => { + const clean = classifyPlannedDisclosure(plannedSummary()); + assert.equal(clean.status, 'not_run'); + assert.match(clean.disclosure, /zero timing captured/); + for (const bad of [{ samples: 1 }, { summaries: 1 }, { claims: 1 }, + { legs: ['arango:not_run(x)', 'postgres:run'] }]) { + assert.equal(classifyPlannedDisclosure(plannedSummary(bad)).status, 'fail', `skip-capture must refuse ${JSON.stringify(bad)}`); + } + assert.equal(classifyPlannedDisclosure(plannedSummary({ scope: 'postgres-only' })), null); +}); + +test('exit-3 planned artifact is verified by Go and disclosed not_run, never pass', async () => { + const dir = await mkdtemp(join(tmpdir(), 'bench-planned-')); + await writeFile(join(dir, 'report.json'), JSON.stringify({ + identity: { runId: 'bench-planned-a' }, status: 'failed', + plan: { + scope: 'planned', + legs: [{ backend: 'arango', mode: 'not_run', capability: 'no service' }, + { backend: 'postgres', mode: 'not_run', capability: 'blocked by PG-PG-AUTH-PATH-DISCOVERY-ALLGENS' }], + plannedNotRun: [], + }, + claims: [], samples: [], summaries: [], + })); + const lines = []; + const calls = []; + const failures = run(parseArgs(['--run', '--out', dir, '--', '--scope', 'planned']), { + log: (line) => lines.push(line), + exec: (cmd, args) => { calls.push(args); return { status: args.includes('verify') ? 0 : 3 }; }, + }); + assert.equal(failures, 0); + assert.equal(calls.length, 2, 'planned artifact still goes through Go verify'); + assert.ok(lines.some((l) => l.includes(`check ${CHECK_LIVE}: not_run`) && l.includes('zero timing captured'))); + assert.ok(!lines.some((l) => l.includes(': pass'))); +}); + +test('planned artifact with escaped samples fails the check', async () => { + const dir = await mkdtemp(join(tmpdir(), 'bench-planned-bad-')); + await writeFile(join(dir, 'report.json'), JSON.stringify({ + identity: { runId: 'bench-planned-bad' }, status: 'failed', + plan: { + scope: 'planned', + legs: [{ backend: 'arango', mode: 'not_run', capability: 'no service' }, + { backend: 'postgres', mode: 'not_run', capability: 'blocked' }], + plannedNotRun: [], + }, + claims: [], samples: [{ backend: 'postgres', millis: 12 }], summaries: [], + })); + const lines = []; + const calls = []; + const failures = run(parseArgs(['--run', '--out', dir, '--', '--scope', 'planned']), { + log: (line) => lines.push(line), + exec: (cmd, args) => { calls.push(args); return { status: 3 }; }, + }); + assert.equal(failures, 1, 'captured timing in a planned artifact is a failure'); + assert.equal(calls.length, 1, 'a failing disclosure is not verified green'); + assert.ok(lines.some((l) => l.includes(`check ${CHECK_LIVE}: fail`) && l.includes('measurement evidence'))); +}); From 4a6ed79c7d67e4858032d968f3938192f8f309ea Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 01:17:09 +0000 Subject: [PATCH 16/45] test(benchmark): pin benchcli fail-closed guards, DSN parsing, and deployer refusals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cmd/benchcli package had no test file, leaving the fail-closed CLI surface unpinned: loom_bench_* namespace guards, credential redaction in error text, DSN parsing, admin DSN env handling, battery disclosure totaledness, artifact writing, and deployer topology refusals. - guardPostgresNamespace: benchmark db passes; loom_test/postgres/ prefix-lookalike refused; error text never carries the password. - parsePostgresDSN / parseArangoDSN: full field split, default port, scheme/database/namespace refusals, no credential leakage. - benchDatabaseRe: accepts loom_bench_<4..48>, rejects loom_test, postgres, case-violating and short names. - setupPG schema-prefix guard and withDatabase URL-form law are checked hermetically (refusal happens before any connect). - planNotRunMatchesBattery: complete plans pass, dropped rows and rows for ran legs fail. - writeRunArtifacts: the three files land and report.json re-verifies through LoadReport/VerifyReport; arms.json stays valid JSON. - deployer: mixed topologies, missing tokens/dsn/binary, and caller tokens against the fence all refuse; the valid spawned shape wires the fence issuer into the config, mints distinct tokens, scrubs the DSN from arm logs, and grants the scoped subject everything EXCEPT the dataset path — verified through the fence's real /user surface. Offline only; scripts/** untouched per the runner-serialization ruling. go build/vet clean; benchmark + benchcli packages green (12 new test functions); node suite still 12/12. --- .../benchmark/cmd/benchcli/guards_test.go | 322 ++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 internal/dataframe/benchmark/cmd/benchcli/guards_test.go diff --git a/internal/dataframe/benchmark/cmd/benchcli/guards_test.go b/internal/dataframe/benchmark/cmd/benchcli/guards_test.go new file mode 100644 index 000000000..a1d756f0f --- /dev/null +++ b/internal/dataframe/benchmark/cmd/benchcli/guards_test.go @@ -0,0 +1,322 @@ +package main + +import ( + "context" + "encoding/json" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/calypr/loom/internal/dataframe/benchmark" +) + +// The benchcli guards are fail-closed surfaces: they keep a benchmark run +// inside loom_bench_* namespaces, keep credentials out of error text, and +// refuse incoherent arm topologies before anything spawns. Each refusal +// here is a production guarantee, so each is pinned. + +func TestGuardPostgresNamespace(t *testing.T) { + cases := []struct { + name string + dsn string + wantErr string + }{ + {"benchmark db passes", "postgresql://u:p@127.0.0.1:55432/loom_bench_x1?sslmode=disable", ""}, + {"shared test db refused", "postgresql://u:p@127.0.0.1:55432/loom_test?sslmode=disable", "outside the benchmark namespace"}, + {"postgres db refused", "postgresql://u:p@127.0.0.1:55432/postgres?sslmode=disable", "outside the benchmark namespace"}, + {"prefix lookalike refused", "postgresql://u:p@127.0.0.1:55432/loom_benchx?sslmode=disable", "outside the benchmark namespace"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := guardPostgresNamespace(tc.dsn) + if tc.wantErr == "" { + if err != nil { + t.Fatalf("valid benchmark dsn refused: %v", err) + } + return + } + if err == nil || !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("want error containing %q, got %v", tc.wantErr, err) + } + }) + } +} + +func TestGuardErrorsNeverLeakCredentials(t *testing.T) { + const secret = "s3cr3t-pw-9f2a" + dsn := "postgresql://u:" + secret + "@127.0.0.1:55432/loom_test?sslmode=disable" + err := guardPostgresNamespace(dsn) + if err == nil { + t.Fatal("shared db accepted") + } + if strings.Contains(err.Error(), secret) { + t.Fatalf("guard error leaked the password: %v", err) + } + if _, _, _, _, _, _, perr := parsePostgresDSN("not a dsn at all " + secret); perr == nil || strings.Contains(perr.Error(), secret) { + t.Fatalf("parsePostgresDSN error missing or leaked: %v", perr) + } + if _, _, aerr := parseArangoDSN("arango://u:" + secret + "@127.0.0.1:8529/loom_bench_x"); aerr == nil || strings.Contains(aerr.Error(), secret) { + t.Fatalf("parseArangoDSN error missing or leaked: %v", aerr) + } +} + +func TestParsePostgresDSNFully(t *testing.T) { + host, port, database, user, password, sslmode, err := parsePostgresDSN("postgresql://loom_test:pw@127.0.0.1:55432/loom_bench_a?sslmode=disable") + if err != nil { + t.Fatalf("valid dsn refused: %v", err) + } + if host != "127.0.0.1" || port != 55432 || database != "loom_bench_a" || user != "loom_test" || password != "pw" || sslmode != "disable" { + t.Fatalf("unexpected parse: %s:%d/%s user=%s pw=%s ssl=%s", host, port, database, user, password, sslmode) + } + if _, port, _, _, _, _, err := parsePostgresDSN("postgresql://u@127.0.0.1/loom_bench_a?sslmode=disable"); err != nil || port != 5432 { + t.Fatalf("default port: %d, %v", port, err) + } +} + +func TestParseArangoDSN(t *testing.T) { + rawURL, database, err := parseArangoDSN("http://root:pw@127.0.0.1:8529/loom_bench_ar") + if err != nil { + t.Fatalf("valid arango dsn refused: %v", err) + } + if database != "loom_bench_ar" || !strings.HasPrefix(rawURL, "http://") || strings.Contains(rawURL, "loom_bench_ar") { + t.Fatalf("unexpected split: rawURL=%q db=%q", rawURL, database) + } + for _, bad := range []string{ + "arango://root@127.0.0.1:8529/loom_bench_ar", // scheme + "http://root@127.0.0.1:8529/", // no database + "http://root@127.0.0.1:8529/production", // outside namespace + } { + if _, _, err := parseArangoDSN(bad); err == nil { + t.Fatalf("accepted bad arango dsn %q", bad) + } + } +} + +func TestBenchDatabaseNameGuard(t *testing.T) { + for _, ok := range []string{"loom_bench_probe1", "loom_bench_abcd", "loom_bench_" + strings.Repeat("z", 48)} { + if !benchDatabaseRe.MatchString(ok) { + t.Fatalf("rejected valid database name %q", ok) + } + } + for _, bad := range []string{"", "loom_bench_abc", "loom_test", "postgres", "loom_bench_UPPER", "public", "loom_bench_", "xloom_bench_abcd"} { + if benchDatabaseRe.MatchString(bad) { + t.Fatalf("accepted out-of-namespace database name %q", bad) + } + } +} + +func TestSetupSchemaPrefixGuard(t *testing.T) { + // setupPG validates the prefix before any connect, so these refusals + // are hermetic and must not need a database. + if err := setupPG(context.Background(), "postgresql://no:such@127.0.0.1:1/postgres", "loom_bench_guarded", "public"); err == nil || + !strings.Contains(err.Error(), "outside the benchmark namespace") { + t.Fatalf("schema prefix public accepted: %v", err) + } + if err := setupPG(context.Background(), "postgresql://no:such@127.0.0.1:1/postgres", "loom_bench_guarded", "loom_bench"); err != nil && + strings.Contains(err.Error(), "outside the benchmark namespace") { + t.Fatalf("canonical loom_bench prefix refused: %v", err) + } +} + +func TestWithDatabase(t *testing.T) { + swapped, err := withDatabase("postgresql://u:p@127.0.0.1:55432/postgres?sslmode=disable", "loom_bench_t") + if err != nil || !strings.Contains(swapped, "/loom_bench_t") || !strings.Contains(swapped, "sslmode=disable") { + t.Fatalf("swap failed: %q %v", swapped, err) + } + if _, err := withDatabase("host=127.0.0.1 dbname=postgres", "loom_bench_t"); err == nil { + t.Fatal("keyword DSN form accepted for admin") + } + if _, err := withDatabase("mysql://u:p@127.0.0.1/db", "loom_bench_t"); err == nil { + t.Fatal("non-postgres scheme accepted for admin") + } +} + +func TestAdminDSNFromEnv(t *testing.T) { + t.Setenv("LOOM_BENCH_ADMIN_DSN", " ") + if _, err := adminDSNFromEnv(); err == nil { + t.Fatal("blank admin DSN accepted") + } + t.Setenv("LOOM_BENCH_ADMIN_DSN", "postgresql://u:p@127.0.0.1:55432/postgres") + if dsn, err := adminDSNFromEnv(); err != nil || !strings.HasPrefix(dsn, "postgresql://") { + t.Fatalf("admin DSN round trip: %q %v", dsn, err) + } +} + +func TestPlanNotRunMatchesBatteryBothWays(t *testing.T) { + planned := benchmark.PlannedPlan("cap a", "cap b") + planned.PlannedNotRun = benchmark.PlannedNotRunForBackends(planned.NotRunBackends()) + if !planNotRunMatchesBattery(planned) { + t.Fatal("complete two-leg disclosure rejected") + } + only := benchmark.PostgresOnlyPlan("cap a") + only.PlannedNotRun = benchmark.PlannedNotRunForBackends(only.NotRunBackends()) + if !planNotRunMatchesBattery(only) { + t.Fatal("complete postgres-only disclosure rejected") + } + planned.PlannedNotRun = planned.PlannedNotRun[1:] + if planNotRunMatchesBattery(planned) { + t.Fatal("dropped disclosure row accepted") + } + only.PlannedNotRun = append(only.PlannedNotRun, benchmark.PlannedNotRun{WorkloadID: "filter-active-gender", Backend: benchmark.BackendPostgres, Reason: "extra"}) + if planNotRunMatchesBattery(only) { + t.Fatal("disclosure row for a ran leg accepted") + } +} + +func TestWriteRunArtifactsRoundTripsThroughVerifier(t *testing.T) { + plan := benchmark.PlannedPlan("cap a", "cap b") + plan.PlannedNotRun = benchmark.PlannedNotRunForBackends(plan.NotRunBackends()) + report, err := benchmark.BuildReport(benchmark.RunIdentity{ + RunID: "bench-clitest-a", StartedAt: time.Now().UTC(), Preset: benchmark.PresetTiny, + SourceCommit: strings.Repeat("a", 40), DatasetDigest: strings.Repeat("b", 64), + ClickHouseDisabled: true, + Config: benchmark.RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, + }, plan, benchmark.ControlRuns{}, benchmark.PlannedGateReports(plan.Legs), nil) + if err != nil { + t.Fatalf("planned report: %v", err) + } + out := t.TempDir() + writeRunArtifacts(out, report, map[string]any{"runId": "bench-clitest-a", "planned": true}) + for _, name := range []string{"report.json", "report.md", "arms.json"} { + if _, err := os.Stat(filepath.Join(out, name)); err != nil { + t.Fatalf("missing artifact %s: %v", name, err) + } + } + raw, err := os.ReadFile(filepath.Join(out, "report.json")) + if err != nil { + t.Fatal(err) + } + loaded, err := benchmark.LoadReport(raw) // LoadReport runs VerifyReport + if err != nil { + t.Fatalf("written artifact failed verification: %v", err) + } + if loaded.Plan.Scope != benchmark.RunScopePlanned || len(loaded.Claims) != 0 { + t.Fatalf("artifact drifted: scope=%s claims=%d", loaded.Plan.Scope, len(loaded.Claims)) + } + var manifest map[string]any + if err := json.Unmarshal([]byte(mustRead(t, filepath.Join(out, "arms.json"))), &manifest); err != nil { + t.Fatalf("arms.json not valid JSON: %v", err) + } + if manifest["planned"] != true { + t.Fatal("planned manifest flag missing") + } + md := mustRead(t, filepath.Join(out, "report.md")) + if !strings.Contains(md, benchmark.RunScopePlanned) { + t.Fatal("markdown must disclose the planned scope") + } +} + +func mustRead(t *testing.T, path string) string { + t.Helper() + raw, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + return string(raw) +} + +func TestDeployerRefusesIncoherentTopologies(t *testing.T) { + cases := []struct { + name string + flags runFlags + wantErr string + }{ + {"mixed attached and spawned", runFlags{scope: benchmark.RunScopeBothArms, + postgresAttachURL: "http://127.0.0.1:1", postgresDSN: dsnForTest, + arangoDSN: "http://root@127.0.0.1:8529/loom_bench_a"}, "mixing attached and spawned"}, + {"attached without tokens", runFlags{scope: benchmark.RunScopePostgresOnly, + postgresAttachURL: "http://127.0.0.1:1", postgresDSN: dsnForTest}, "token-unrestricted"}, + {"attached without dsn", runFlags{scope: benchmark.RunScopePostgresOnly, + postgresAttachURL: "http://127.0.0.1:1", + tokenUnrestricted: "Bearer x", tokenScoped: "Bearer y"}, "live version probe"}, + {"spawned without binary", runFlags{scope: benchmark.RunScopePostgresOnly, + postgresDSN: dsnForTest}, "--binary"}, + {"spawned with caller tokens", runFlags{scope: benchmark.RunScopePostgresOnly, + binary: "/nonexistent/loom-server", postgresDSN: dsnForTest, + tokenUnrestricted: "Bearer x"}, "benchmark fence"}, + {"spawned without dsn", runFlags{scope: benchmark.RunScopePostgresOnly, + binary: "/nonexistent/loom-server"}, "requires --postgres-dsn"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + d := &deployer{flags: &tc.flags} + err := d.prepare(context.Background()) + d.close() + if err == nil || !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("want error containing %q, got %v", tc.wantErr, err) + } + }) + } +} + +func TestDeployerSpawnedPostgresOnlyWiresFenceAndScrubbers(t *testing.T) { + const datasetPath = "/programs/loom/projects/loom-bench/benchmark" + flags := runFlags{scope: benchmark.RunScopePostgresOnly, + binary: "/nonexistent/loom-server", postgresDSN: dsnForTest, authResourcePath: datasetPath} + d := &deployer{flags: &flags} + if err := d.prepare(context.Background()); err != nil { + t.Fatalf("valid spawned topology refused: %v", err) + } + defer d.close() + cfg := d.postgres.ConfigYAML + for _, want := range []string{"trusted_issuer", "mode: calypr", "loom_bench_source", benchmark.ListenPlaceholder} { + if !strings.Contains(cfg, want) { + t.Fatalf("arm config missing %q:\n%s", want, cfg) + } + } + if !strings.HasPrefix(d.unrestrictedToken, "Bearer ") || !strings.HasPrefix(d.scopedToken, "Bearer ") { + t.Fatal("fence tokens not minted") + } + if d.unrestrictedToken == d.scopedToken { + t.Fatal("admin and scoped principals share a token") + } + if len(d.postgres.ScrubSecrets) < 2 || d.postgres.ScrubSecrets[0] != dsnForTest { + t.Fatalf("arm logs would not scrub the DSN: %v", d.postgres.ScrubSecrets) + } + // The grant shape is the negative-auth control's basis, checked + // through the fence's real /user surface: the admin holds the dataset + // path; the scoped principal must NOT (production denial shape). + admin := fenceGrants(t, d, d.unrestrictedToken) + if _, ok := admin[datasetPath]; !ok { + t.Fatalf("admin token lacks the dataset path: %v", admin) + } + scoped := fenceGrants(t, d, d.scopedToken) + if _, leaked := scoped[datasetPath]; leaked { + t.Fatalf("scoped token leaks the dataset path: %v", scoped) + } + if _, ok := scoped[benchmarkDecoyPath]; !ok { + t.Fatalf("scoped token lacks the decoy grant the control needs: %v", scoped) + } +} + +func fenceGrants(t *testing.T, d *deployer, token string) map[string]any { + t.Helper() + req, err := http.NewRequest(http.MethodGet, d.fence.URL()+"/user", nil) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Authorization", token) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Fatalf("fence /user returned %d", resp.StatusCode) + } + var body struct { + Authz map[string]any `json:"authz"` + } + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + t.Fatal(err) + } + if body.Authz == nil { + t.Fatal("fence /user omitted authz") + } + return body.Authz +} + +const dsnForTest = "postgresql://u:p@127.0.0.1:55432/loom_bench_clitest?sslmode=disable" From cd34fd44dd62c184fd15428691234d0eec9ab5ad Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 01:59:10 +0000 Subject: [PATCH 17/45] test(benchmark): commit defect repros for PG-PG-AUTH-PATH-DISCOVERY-ALLGENS The two scratch proofs the fix (513b0520, PR #156) cites were held in the session artifacts dir but absent from the pushed head; verifier 208 flagged the gap and the coordinator ruled them ticket evidence to keep in-repo. - scratch_authrepro_test.go: hermetic unit-level branch discrimination against a real ScopeResolver + FenceUserAccessClient + MockFence. Variant A (bearer principal, matching grant, empty lister) proves the harness token/issuer wiring is correct by PASSING AuthorizeWrite; nil-principal and header-less variants pin the 'outside caller scope' shape the fake-based driver tests rely on. Runs always. - scratch_liveupload_test.go: live proof. Opt-in by substrate (skips without the scratch DSN file; binary path overridable via LOOM_BENCH_SCRATCH_BINARY). Pins TWO-WORLD CONSISTENCY rather than defect-era absolutes, so it does not false-red after the fix: the HTTP upload outcome and the store-level AllGenerations listing must agree (defect = both refuse, fix = both succeed); disagreement is substrate drift and fails. This is what caught the defect live: fence hit exactly 1x, then 403, while the listing refused. Behavior-only assertions: no coupling to the normalizer's output shape. Offline green: both pass here (defect-era binary + library = consistent refusal world); CI skips the live probe without the scratch DSN file. --- .../benchmark/scratch_authrepro_test.go | 61 +++++++ .../benchmark/scratch_liveupload_test.go | 152 ++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 internal/dataframe/benchmark/scratch_authrepro_test.go create mode 100644 internal/dataframe/benchmark/scratch_liveupload_test.go diff --git a/internal/dataframe/benchmark/scratch_authrepro_test.go b/internal/dataframe/benchmark/scratch_authrepro_test.go new file mode 100644 index 000000000..b9ee83a4b --- /dev/null +++ b/internal/dataframe/benchmark/scratch_authrepro_test.go @@ -0,0 +1,61 @@ +package benchmark_test + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/calypr/loom/internal/authscope" + "github.com/calypr/loom/internal/catalog" + "github.com/calypr/loom/internal/dataframe/benchmark" +) + +func newScratchResolver(t *testing.T, fenceURL string) *authscope.ScopeResolver { + t.Helper() + client := &http.Client{Timeout: 10 * time.Second} + ra, err := authscope.NewFenceUserAccessClientWithTrustedIssuer(client, fenceURL, time.Second) + if err != nil { + t.Fatalf("fence client: %v", err) + } + return authscope.NewScopeResolver(authscope.ScopeResolverConfig{ + ResourceAccess: ra, + ListExistingAuthResourcePaths: func(context.Context, catalog.AuthResourcePathOptions) ([]string, error) { + return nil, nil + }, + CacheTTL: time.Second, + }) +} + +func TestScratchAuthorizeWriteWithBearerPrincipal(t *testing.T) { + fence, err := benchmark.NewMockFence() + if err != nil { + t.Fatalf("fence: %v", err) + } + defer fence.Close() + const path = "/programs/loom/projects/loom-bench/benchmark" + fence.Grant("bench-admin", path) + resolver := newScratchResolver(t, fence.URL()) + + // Variant A: header-carrying principal, deployer double-prefix shape. + token := "Bearer " + benchmark.MakeBearerToken(fence.URL(), "bench-admin") + principal := &authscope.Principal{AuthorizationHeader: token, Subject: "bench-admin"} + if err := resolver.AuthorizeWrite(context.Background(), principal, "loom-bench", path); err != nil { + t.Fatalf("variant A AuthorizeWrite: %v (fence requests=%d)", err, fence.Requests()) + } + t.Logf("variant A passed; fence requests=%d", fence.Requests()) + + // Variant B: nil principal — expect the rehearsal's shape. + errB := resolver.AuthorizeWrite(context.Background(), nil, "loom-bench", path) + if errB == nil { + t.Fatalf("variant B unexpectedly passed") + } + t.Logf("variant B refused as expected: %v", errB) + + // Variant C: principal with no AuthorizationHeader (locals/ctx drop). + errC := resolver.AuthorizeWrite(context.Background(), &authscope.Principal{Subject: "bench-admin"}, "loom-bench", path) + if errC == nil { + t.Fatalf("variant C unexpectedly passed") + } + t.Logf("variant C refused as expected: %v", errC) +} diff --git a/internal/dataframe/benchmark/scratch_liveupload_test.go b/internal/dataframe/benchmark/scratch_liveupload_test.go new file mode 100644 index 000000000..b09da05ac --- /dev/null +++ b/internal/dataframe/benchmark/scratch_liveupload_test.go @@ -0,0 +1,152 @@ +package benchmark_test + +import ( + "context" + "fmt" + "net/http" + "os" + "strings" + "testing" + "time" + + backendspostgres "github.com/calypr/loom/internal/backends/postgres/control" + "github.com/calypr/loom/internal/catalog" + "github.com/calypr/loom/internal/dataframe/benchmark" +) + +// TestScratchLiveUploadProbe is the live proof for defect +// PG-PG-AUTH-PATH-DISCOVERY-ALLGENS (see the session artifact +// pg-pg-auth-path-discovery-allgens-evidence.md). It spawns the real +// server binary against a scratch benchmark database, uploads through the +// same Arm.UploadGeneration path benchcli uses, and calls the store-level +// AllGenerations listing directly. +// +// It is opt-in by substrate: it skips unless the scratch DSN file exists +// (default /tmp/opencode/.bench-dsn, override +// LOOM_BENCH_SCRATCH_DSN_FILE) and spawns the binary at +// LOOM_BENCH_SCRATCH_BINARY, default /tmp/opencode/loom-server. +// +// Two-world consistency is the pin: the defect linked the two surfaces — +// the store-level AllGenerations refusal made AuthorizeWrite 403 every +// scoped upload whose principal held the matching grant, and the +// bootstrap branch was unreachable. So the two surfaces must agree: +// defect present = upload refused AND listing refused; fix present = +// upload accepted AND listing answers. Mixed outcomes mean the server +// binary and this library disagree about the fix (substrate drift). + +func TestScratchLiveUploadProbe(t *testing.T) { + dsnFile := os.Getenv("LOOM_BENCH_SCRATCH_DSN_FILE") + if dsnFile == "" { + dsnFile = "/tmp/opencode/.bench-dsn" + } + dsnRaw, err := os.ReadFile(dsnFile) + if err != nil { + t.Skipf("scratch substrate absent (%s): %v", dsnFile, err) + } + dsn := strings.TrimSpace(string(dsnRaw)) + binary := os.Getenv("LOOM_BENCH_SCRATCH_BINARY") + if binary == "" { + binary = "/tmp/opencode/loom-server" + } + const path = "/programs/loom/projects/loom-bench/benchmark" + + fence, err := benchmark.NewMockFence() + if err != nil { + t.Fatalf("fence: %v", err) + } + defer fence.Close() + fence.Grant("bench-admin", path, "/programs/loom/projects/loom-bench/decoy") + token := "Bearer " + benchmark.MakeBearerToken(fence.URL(), "bench-admin") + + host, port, database, user, password, sslmode, err := splitDSNForScratch(dsn) + if err != nil { + t.Fatalf("dsn: %v", err) + } + portNum := 0 + fmt.Sscanf(port, "%d", &portNum) + backendYAML := benchmark.PostgresBackendYAML(host, portNum, database, user, password, sslmode, "loom_bench") + cfgYAML := benchmark.BuildServerConfigYAML(benchmark.ListenPlaceholder, "postgres", backendYAML, fence.URL(), "040f587511a7e4de") + + freePort, err := benchmark.FreePort() + if err != nil { + t.Fatalf("free port: %v", err) + } + listen := fmt.Sprintf("127.0.0.1:%d", freePort) + cfgYAML = strings.Replace(cfgYAML, benchmark.ListenPlaceholder, listen, 1) + + ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + spec := benchmark.ServerSpec{ + BinaryPath: binary, + Backend: "postgres", + ConfigYAML: cfgYAML, + ReadyURL: "http://" + listen + "/readyz", + Timeout: 60 * time.Second, + StderrDir: t.TempDir(), + } + proc, err := benchmark.SpawnServer(ctx, spec, nil, listen) + if err != nil { + t.Fatalf("spawn: %v", err) + } + defer proc.Stop() + + arm := &benchmark.Arm{Backend: "postgres", BaseURL: "http://" + listen, Client: &http.Client{Timeout: 60 * time.Second}} + files := map[string]string{"samples.ndjson": "{\"sample_id\":\"s1\"}\n"} + uploadErr := arm.UploadGeneration(ctx, token, "loom-bench", "bench-probe-1", files, path) + t.Logf("upload outcome: %v (fence requests=%d)", uploadErr, fence.Requests()) + + // Store-level surface on the same database. + probeRuntime, err := backendspostgres.Open(ctx, backendspostgres.Config{ + DSN: dsn, MaxConns: 2, MinConns: 1, AcquireTimeout: 30 * time.Second, + Schemas: backendspostgres.SeriesSchemas{ + "source": "loom_bench_source", "explorer_control": "loom_bench_explorer_control", + "publication_control": "loom_bench_publication_control", "dataset_release": "loom_bench_dataset_release", + "recipe_chains": "loom_bench_recipe_chains", "recipe_exec_registries": "loom_bench_recipe_exec_registries", + }, + Setup: backendspostgres.SetupNone, + }) + if err != nil { + t.Fatalf("open runtime: %v", err) + } + defer func() { _ = probeRuntime.Close() }() + paths, listErr := probeRuntime.DiscoverExistingAuthResourcePaths(ctx, catalog.AuthResourcePathOptions{Project: "loom-bench", AllGenerations: true}) + t.Logf("AllGenerations listing outcome: %v (paths=%v)", listErr, paths) + + if (uploadErr == nil) != (listErr == nil) { + t.Errorf("surface drift: uploadErr=%v listErr=%v — the server binary and this library must agree on the fix state of the AllGenerations read (substrate pin: defect-era binary with defect-era library, or fix with fix)", uploadErr, listErr) + } + if uploadErr != nil && !strings.Contains(uploadErr.Error(), "403") { + t.Errorf("defect-world upload should be the scoped-lane 403, got: %v", uploadErr) + } +} + +func splitDSNForScratch(dsn string) (host, port, database, user, password, sslmode string, err error) { + // postgresql://user:pass@host:port/db?sslmode=disable + if !strings.HasPrefix(dsn, "postgresql://") { + return "", "", "", "", "", "", fmt.Errorf("unexpected dsn scheme") + } + rest := strings.TrimPrefix(dsn, "postgresql://") + at := strings.LastIndex(rest, "@") + if at < 0 { + return "", "", "", "", "", "", fmt.Errorf("no userinfo") + } + userinfo, rest := rest[:at], rest[at+1:] + if c := strings.Index(userinfo, ":"); c >= 0 { + user, password = userinfo[:c], userinfo[c+1:] + } else { + user = userinfo + } + var query, hostport string + hostport, query, _ = strings.Cut(rest, "?") + hostport, database, _ = strings.Cut(hostport, "/") + host, port, _ = strings.Cut(hostport, ":") + for _, kv := range strings.Split(query, "&") { + if k, v, ok := strings.Cut(kv, "="); ok && k == "sslmode" { + sslmode = v + } + } + if host == "" || port == "" || database == "" { + return "", "", "", "", "", "", fmt.Errorf("incomplete dsn") + } + return host, port, database, user, password, sslmode, nil +} From 7b47cb6ee2c53f7a64f0a0de8ee71c998a2a806d Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 02:02:33 +0000 Subject: [PATCH 18/45] fix(benchmark): emit FHIR-valid Patient names and Condition clinicalStatus The first consolidated live run refused activation: GENERATION_LOAD_INCOMPLETE with validation_errors=32 and Patient/Condition committing zero rows, while Observation/Specimen/ResearchStudy loaded. The count was exactly the tiny preset's 12 patients + 20 conditions, and the server-side row builder (internal/ingest loadRowGeneratedInternal) showed two fixture defects, both harness bugs rather than backend behaviour: - HumanName.family is a single string in FHIR R4; the generator emitted a JSON array, so every Patient line failed decode. Each family name now gets its own name entry, so name[].family still yields every family a patient carries and DISTINCT-vs-ALL stays selective. The given list rides on the first entry only, so name[].given[] cannot duplicate a given value across entries and the sort-limit oracle keeps exactly one row per patient. - Condition.clinicalStatus is required (1..1); it was never emitted, so every Condition line failed Validate(). It is now a constant 'active' coding on the standard condition-clinical system. No workload predicate reads it, so it stays out of every oracle. Add TestGeneratedFilesAreValidFHIR, which decodes and validates every line of the tiny and small presets through the same generated concrete FHIR structs the loader uses, plus the non-blank and non-empty-id checks the loader applies. The client-side preflight does not run this validator, which is why the defect reached the server: this test closes that gap offline instead of discovering shape errors during a live window. Facts, oracles, and canonical comparison are unaffected: they read PatientFacts and ConditionFacts, not the documents. Offline suite green (benchmark + benchcli + node 12/12); go build and vet clean. --- internal/dataframe/benchmark/fixtures.go | 31 ++++++-- .../dataframe/benchmark/fixtures_fhir_test.go | 77 +++++++++++++++++++ 2 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 internal/dataframe/benchmark/fixtures_fhir_test.go diff --git a/internal/dataframe/benchmark/fixtures.go b/internal/dataframe/benchmark/fixtures.go index f410f3166..19904d02d 100644 --- a/internal/dataframe/benchmark/fixtures.go +++ b/internal/dataframe/benchmark/fixtures.go @@ -82,6 +82,9 @@ const ( const ( ConditionStatusConfirmed = "confirmed" ConditionStatusProvisional = "provisional" + // ConditionClinicalActive is the FHIR R4 required clinicalStatus. No + // workload predicates on it, so it is constant and never an oracle input. + ConditionClinicalActive = "active" ) // PatientNamePool sizes control duplicate contributors and repeated-owner @@ -415,15 +418,24 @@ func (d DatasetParams) buildPatientDocument(f PatientFacts) map[string]any { if f.Deceased != nil { doc["deceasedBoolean"] = *f.Deceased } - fam := make([]any, 0, len(f.FamilyNames)) - for _, name := range f.FamilyNames { - fam = append(fam, name) - } - giv := make([]any, 0, len(f.GivenNames)) - for _, name := range f.GivenNames { - giv = append(giv, name) + // FHIR R4 HumanName.family is a single string, so each family name gets + // its own name entry; name[].family still yields every family the patient + // carries, which keeps DISTINCT-vs-ALL selective. The given list rides on + // the first entry only so name[].given[] never duplicates a given value + // across entries: the sort-limit oracle emits exactly one row per patient. + names := make([]any, 0, len(f.FamilyNames)) + for i, family := range f.FamilyNames { + entry := map[string]any{"family": family} + if i == 0 { + giv := make([]any, 0, len(f.GivenNames)) + for _, name := range f.GivenNames { + giv = append(giv, name) + } + entry["given"] = giv + } + names = append(names, entry) } - doc["name"] = []any{map[string]any{"family": fam, "given": giv}} + doc["name"] = names ident := make([]any, 0, len(f.IdentifierValues)) for _, v := range f.IdentifierValues { ident = append(ident, map[string]any{"use": "official", "system": "https://bench.example/mrn", "value": v}) @@ -515,6 +527,9 @@ func (d DatasetParams) buildConditionDocument(f ConditionFacts) map[string]any { "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", "code": f.VerificationStatus, }}} } + doc["clinicalStatus"] = map[string]any{"coding": []any{map[string]any{ + "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", "code": ConditionClinicalActive, + }}} if ref := d.studyRefFor(patientIndexFromID(f.PatientID)); ref != "" { doc["extension"] = []any{partOfStudyExtension(ref)} } diff --git a/internal/dataframe/benchmark/fixtures_fhir_test.go b/internal/dataframe/benchmark/fixtures_fhir_test.go new file mode 100644 index 000000000..51f1f4fa4 --- /dev/null +++ b/internal/dataframe/benchmark/fixtures_fhir_test.go @@ -0,0 +1,77 @@ +package benchmark_test + +import ( + "encoding/json" + "sort" + "strings" + "testing" + + fhir "github.com/calypr/loom/generated/fhir" + + "github.com/calypr/loom/internal/dataframe/benchmark" +) + +// TestGeneratedFilesAreValidFHIR pins the fixture contract the loader +// enforces: the postgres generation load decodes each NDJSON line into the +// generated concrete FHIR struct and calls Validate(), counting every +// failure as a validation error and refusing activation +// (GENERATION_LOAD_INCOMPLETE). An invalid line is therefore a harness +// defect that only shows up live, so check it here instead. +// +// This mirrors internal/ingest.loadRowGeneratedInternal's first two steps +// (decode, validate) for every line of every preset rather than trusting +// the client-side preflight, which does not run this validator. +func TestGeneratedFilesAreValidFHIR(t *testing.T) { + for _, preset := range []string{benchmark.PresetTiny, benchmark.PresetSmall} { + preset := preset + t.Run(preset, func(t *testing.T) { + t.Parallel() + files, err := benchmark.GenerateFiles(benchmark.DatasetParams{Preset: preset, Seed: 1}) + if err != nil { + t.Fatalf("GenerateFiles(%s): %v", preset, err) + } + names := make([]string, 0, len(files.Files)) + for name := range files.Files { + if strings.HasSuffix(name, ".ndjson") { + names = append(names, name) + } + } + sort.Strings(names) + if len(names) == 0 { + t.Fatal("no ndjson fixture files") + } + for _, name := range names { + resourceType := strings.TrimSuffix(name, ".ndjson") + resource, ok := fhir.NewConcreteResource(resourceType) + if !ok { + t.Fatalf("%s: no concrete FHIR resource for %q", name, resourceType) + } + if _, ok := resource.(interface{ Validate() error }); !ok { + t.Fatalf("%s: %q does not implement Validate", name, resourceType) + } + lines := strings.Split(strings.TrimSuffix(files.Files[name], "\n"), "\n") + for i, line := range lines { + if strings.TrimSpace(line) == "" { + t.Errorf("%s line %d: blank line in fixture", name, i+1) + continue + } + probe, ok := fhir.NewConcreteResource(resourceType) + if !ok { + t.Fatalf("%s: no concrete FHIR resource for %q", name, resourceType) + } + if err := json.Unmarshal([]byte(line), probe); err != nil { + t.Errorf("%s line %d: decode: %v", name, i+1, err) + continue + } + if err := probe.(interface{ Validate() error }).Validate(); err != nil { + t.Errorf("%s line %d: validate: %v", name, i+1, err) + continue + } + if pid := strings.TrimSpace(probe.(interface{ GetID() string }).GetID()); pid == "" { + t.Errorf("%s line %d: missing string id", name, i+1) + } + } + } + }) + } +} From 2af2cf42c7039f304e7599a9717e7b4e5b77b867 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 02:04:20 +0000 Subject: [PATCH 19/45] fix(benchmark): send auth_resource_path on durable write routes The consolidated live run cleared generation upload and stopped at publication: POST /api/v1/projects/{project}/generations/{generation}/explorer-config returned 403 {"error":"forbidden"}. Cause: publishRepositoryExplorerConfig calls AuthorizeWrite with the authResourcePath taken from the query parameter, and with scoped authorization enabled AuthorizeWrite rejects an empty path outright ("auth_resource_path is required when scoped authorization is enabled", internal/authscope/scope_resolver.go). The benchmark harness only sent the path as the upload form field, so every durable write - publish, create-explorer, reconcile - was unreachable under a scoped deployment. The run reached publish first because publication precedes explorer creation in the authoring sequence. Add Arm.AuthResourcePath, set from the driver options at both arm construction sites, and apply it as a query parameter on the three write routes through Arm.scoped. Read routes are untouched: preview authorizes via authoringReadDirect, and an empty path sends no parameter so unscoped deployments are unaffected (an unrestricted principal ignores it for writes, and a spurious scope would only narrow reads). TestWriteRoutesCarryAuthResourcePath asserts the parameter per route and asserts its absence for an unscoped arm; it is RED with the scoped() helper short-circuited, so a regression cannot pass silently. Offline suite green (benchmark + benchcli + node 12/12), go build and vet clean. --- internal/dataframe/benchmark/driver.go | 4 +- internal/dataframe/benchmark/harness.go | 25 ++++++- .../dataframe/benchmark/harness_scope_test.go | 72 +++++++++++++++++++ 3 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 internal/dataframe/benchmark/harness_scope_test.go diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index b1fcee4b5..e779c70fa 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -400,7 +400,7 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden func startArm(ctx context.Context, backend string, cfg ArmConfig, spawn SpawnFunc, opts DriverOptions) (*runningArm, error) { if cfg.BaseURL != "" { return &runningArm{ - arm: &Arm{Backend: backend, BaseURL: strings.TrimRight(cfg.BaseURL, "/"), Client: &http.Client{Timeout: 120 * time.Second}}, + arm: &Arm{Backend: backend, BaseURL: strings.TrimRight(cfg.BaseURL, "/"), Client: &http.Client{Timeout: 120 * time.Second}, AuthResourcePath: opts.Params.AuthResourcePath}, evidence: ArmEvidence{Backend: backend, BaseURL: cfg.BaseURL, Spawned: false}, }, nil } @@ -429,7 +429,7 @@ func startArm(ctx context.Context, backend string, cfg ArmConfig, spawn SpawnFun return nil, err } return &runningArm{ - arm: &Arm{Backend: backend, BaseURL: "http://" + listen, Client: &http.Client{Timeout: 120 * time.Second}, Proc: proc}, + arm: &Arm{Backend: backend, BaseURL: "http://" + listen, Client: &http.Client{Timeout: 120 * time.Second}, Proc: proc, AuthResourcePath: opts.Params.AuthResourcePath}, proc: proc, evidence: ArmEvidence{Backend: backend, BaseURL: "http://" + listen, LogPath: proc.LogPath(), Spawned: true}, }, nil diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go index 6704f75fb..b1a7c5418 100644 --- a/internal/dataframe/benchmark/harness.go +++ b/internal/dataframe/benchmark/harness.go @@ -23,6 +23,13 @@ type Arm struct { BaseURL string Client *http.Client Proc *ServerProcess // nil for external arms; required for process-cold samples + // AuthResourcePath is the Calypr resource path this benchmark loads and + // publishes under. Scoped authorization rejects a durable write without + // it (AuthorizeWrite: "auth_resource_path is required when scoped + // authorization is enabled"), and the routes read it from the query, not + // the body. Empty means the deployment runs unscoped, and no param is + // sent. + AuthResourcePath string } func (a *Arm) authed(req *http.Request, token string) { @@ -31,6 +38,18 @@ func (a *Arm) authed(req *http.Request, token string) { } } +// scoped adds the auth_resource_path query parameter that every durable +// write route requires under scoped authorization. +func (a *Arm) scoped(endpoint string) string { + if strings.TrimSpace(a.AuthResourcePath) == "" { + return endpoint + } + if strings.Contains(endpoint, "?") { + return endpoint + "&auth_resource_path=" + url.QueryEscape(a.AuthResourcePath) + } + return endpoint + "?auth_resource_path=" + url.QueryEscape(a.AuthResourcePath) +} + // UploadGeneration posts the NDJSON fixture files to the dataset route. func (a *Arm) UploadGeneration(ctx context.Context, token, project, generation string, files map[string]string, authResourcePath string) error { var body bytes.Buffer @@ -85,7 +104,7 @@ type Publication struct { // PublishWorkspace posts the authored workspace for one workload. func (a *Arm) PublishWorkspace(ctx context.Context, token, project, generation string, workspace []byte, sourceCommit string) (Publication, error) { - endpoint := a.BaseURL + "/api/v1/projects/" + url.PathEscape(project) + "/generations/" + url.PathEscape(generation) + "/explorer-config" + endpoint := a.scoped(a.BaseURL + "/api/v1/projects/" + url.PathEscape(project) + "/generations/" + url.PathEscape(generation) + "/explorer-config") req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(workspace)) if err != nil { return Publication{}, err @@ -374,7 +393,7 @@ func (a *Arm) authoringURL(project, explorer string) string { // CreateExplorer clones a new explorer from sourceExplorerID (usually the // seeded "default") so each workload owns an independent draft. func (a *Arm) CreateExplorer(ctx context.Context, token, project, name, title, sourceExplorerID string) (string, error) { - endpoint := a.BaseURL + "/api/v1/projects/" + url.PathEscape(project) + "/explorers" + endpoint := a.scoped(a.BaseURL + "/api/v1/projects/" + url.PathEscape(project) + "/explorers") body, _ := json.Marshal(map[string]any{"name": name, "title": title, "sourceExplorerId": sourceExplorerID}) req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) if err != nil { @@ -425,7 +444,7 @@ func (a *Arm) FetchBuilderState(ctx context.Context, token, project, explorer st // Reconcile compiles the current draft and returns its receipt id. The // receipt is what ties a preview to an exact authored revision. func (a *Arm) Reconcile(ctx context.Context, token, project, explorer, outputID string, state BuilderState) (string, error) { - endpoint := a.authoringURL(project, explorer) + "/reconcile" + endpoint := a.scoped(a.authoringURL(project, explorer) + "/reconcile") body := map[string]any{ "snapshotToken": state.SnapshotToken(), "draftVersion": state.DraftVersion, diff --git a/internal/dataframe/benchmark/harness_scope_test.go b/internal/dataframe/benchmark/harness_scope_test.go new file mode 100644 index 000000000..abb72ae44 --- /dev/null +++ b/internal/dataframe/benchmark/harness_scope_test.go @@ -0,0 +1,72 @@ +package benchmark_test + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/calypr/loom/internal/dataframe/benchmark" +) + +// TestWriteRoutesCarryAuthResourcePath pins the scoped-authorization +// contract that a first live run discovered: AuthorizeWrite refuses a +// durable write with "auth_resource_path is required when scoped +// authorization is enabled", and every write route reads the value from the +// query string, not the body. A harness that omits it cannot publish a +// single workspace against a scoped deployment, so the param is asserted per +// route here instead of during a benchmark window. +func TestWriteRoutesCarryAuthResourcePath(t *testing.T) { + const path = "/programs/loom/projects/loom-bench/benchmark" + seen := map[string]string{} + mux := http.NewServeMux() + mux.HandleFunc("/api/v1/projects/loom-bench/generations/gen-1/explorer-config", func(w http.ResponseWriter, r *http.Request) { + seen["publish"] = r.URL.Query().Get("auth_resource_path") + _ = json.NewEncoder(w).Encode(map[string]any{"output": "out", "recipe": "rec", "translationVersion": "tv", "executionId": "ex"}) + }) + mux.HandleFunc("/api/v1/projects/loom-bench/explorers", func(w http.ResponseWriter, r *http.Request) { + seen["create"] = r.URL.Query().Get("auth_resource_path") + _ = json.NewEncoder(w).Encode(map[string]any{"explorerId": "ex-1"}) + }) + mux.HandleFunc("/api/v1/projects/loom-bench/explorers/ex-1/authoring/v2/reconcile", func(w http.ResponseWriter, r *http.Request) { + seen["reconcile"] = r.URL.Query().Get("auth_resource_path") + _ = json.NewEncoder(w).Encode(map[string]any{"receiptId": "rcpt-1"}) + }) + server := httptest.NewServer(mux) + defer server.Close() + + ctx := context.Background() + state := benchmark.BuilderState{DraftVersion: 1} + state.Catalog.SnapshotToken = "snap" + run := func(arm *benchmark.Arm) { + if _, err := arm.PublishWorkspace(ctx, "tok", "loom-bench", "gen-1", []byte(`{}`), "0123456789abcdef0123456789abcdef01234567"); err != nil { + t.Fatalf("publish: %v", err) + } + if _, err := arm.CreateExplorer(ctx, "tok", "loom-bench", "w1", "W1", "default"); err != nil { + t.Fatalf("create explorer: %v", err) + } + if _, err := arm.Reconcile(ctx, "tok", "loom-bench", "ex-1", "out", state); err != nil { + t.Fatalf("reconcile: %v", err) + } + } + + run(&benchmark.Arm{Backend: benchmark.BackendPostgres, BaseURL: server.URL, Client: server.Client(), AuthResourcePath: path}) + for _, route := range []string{"publish", "create", "reconcile"} { + if seen[route] != path { + t.Errorf("%s: auth_resource_path = %q, want %q", route, seen[route], path) + } + } + + // An unscoped deployment must not receive the parameter at all: sending a + // scope the caller does not hold would narrow reads for no reason. + for k := range seen { + delete(seen, k) + } + run(&benchmark.Arm{Backend: benchmark.BackendPostgres, BaseURL: server.URL, Client: server.Client()}) + for _, route := range []string{"publish", "create", "reconcile"} { + if seen[route] != "" { + t.Errorf("%s: unscoped arm sent auth_resource_path=%q, want none", route, seen[route]) + } + } +} From 875243e7d0fddb1f6e58ad0bec6a6379495f3eb6 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 02:12:10 +0000 Subject: [PATCH 20/45] fix(benchmark): pin arm working directory and real fixtures in the live probe Two live-window defects in the probe itself, both found while running the granted Phase B session at server 513b0520: - The server resolves --schema relative to the CWD (default schemas/graph-fhir.json), and SpawnServer inherits the spawner's directory. The probe runs under go test (package dir), so generation load failed with stat schemas/graph-fhir.json: no such file or directory and the upload returned 500 INTERNAL_ERROR (fs.PathError, request_id 29da9ff4). benchcli run only worked because the runbook launches it from the repository root. Add ServerSpec.WorkDir, honored only when set, and have the probe pin the repository root (auto-discovered by walking up for schemas/graph-fhir.json, LOOM_BENCH_SCRATCH_WORKDIR override) so probe and benchcli spawn identically regardless of caller CWD. - The probe uploaded samples.ndjson, which was never a loadable resource name; the 403-era probe never reached load, so nobody noticed. After the auth fix the file reaches the loader. Upload the real GenerateFiles fixtures instead, which is the path the benchmark actually measures. Probe outcome against loom-server-513b0520 (fixed) from this pre-merge checkout (library pre-fix): upload accepted (fence requests=1) while the store-level AllGenerations listing still refuses -> surface drift, correctly reported by the consistency pin. It goes green when 207's fix lands at this branch's base. --- internal/dataframe/benchmark/provision.go | 11 ++++++ .../benchmark/scratch_liveupload_test.go | 35 +++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/internal/dataframe/benchmark/provision.go b/internal/dataframe/benchmark/provision.go index aa51b85a3..ad211fa3a 100644 --- a/internal/dataframe/benchmark/provision.go +++ b/internal/dataframe/benchmark/provision.go @@ -33,6 +33,11 @@ type ServerSpec struct { ReadyHeaders map[string]string Timeout time.Duration // spawn+ready budget; 0 uses 60s StderrDir string // where arm logs are written (must be a scratch dir) + // WorkDir, when set, is the arm process's working directory. The server + // resolves its graph schema (--schema, default schemas/graph-fhir.json) + // relative to the CWD, so a spawner outside the repository root must + // pin it explicitly instead of inheriting a stale directory. + WorkDir string } // ServerProcess is a running arm. @@ -84,6 +89,12 @@ func (p *ServerProcess) start(ctx context.Context) error { args := []string{"--config", cfgPath, "--listen", p.listenAddr} cmd := exec.CommandContext(ctx, p.spec.BinaryPath, args...) cmd.Env = append(os.Environ(), p.spec.Env...) + if p.spec.WorkDir != "" { + if _, err := os.Stat(p.spec.WorkDir); err != nil { + return fmt.Errorf("benchmark: arm work dir: %w", err) + } + cmd.Dir = p.spec.WorkDir + } cmd.Stdout = p.scrubWriter() cmd.Stderr = p.scrubWriter() if err := cmd.Start(); err != nil { diff --git a/internal/dataframe/benchmark/scratch_liveupload_test.go b/internal/dataframe/benchmark/scratch_liveupload_test.go index b09da05ac..4536229d0 100644 --- a/internal/dataframe/benchmark/scratch_liveupload_test.go +++ b/internal/dataframe/benchmark/scratch_liveupload_test.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "os" + "path/filepath" "strings" "testing" "time" @@ -76,13 +77,38 @@ func TestScratchLiveUploadProbe(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() + stderrDir := os.Getenv("LOOM_BENCH_SCRATCH_STDERR_DIR") + if stderrDir == "" { + stderrDir = t.TempDir() + } + // The server resolves its graph schema relative to the CWD, and go test + // runs in the package directory, so pin the repository root explicitly. + workDir := os.Getenv("LOOM_BENCH_SCRATCH_WORKDIR") + if workDir == "" { + dir, err := os.Getwd() + if err != nil { + t.Fatalf("workdir: %v", err) + } + for { + if _, err := os.Stat(dir + "/schemas/graph-fhir.json"); err == nil { + workDir = dir + break + } + parent := filepath.Dir(dir) + if parent == dir { + t.Fatal("no schemas/graph-fhir.json found walking up from cwd; set LOOM_BENCH_SCRATCH_WORKDIR") + } + dir = parent + } + } spec := benchmark.ServerSpec{ BinaryPath: binary, Backend: "postgres", ConfigYAML: cfgYAML, ReadyURL: "http://" + listen + "/readyz", Timeout: 60 * time.Second, - StderrDir: t.TempDir(), + StderrDir: stderrDir, + WorkDir: workDir, } proc, err := benchmark.SpawnServer(ctx, spec, nil, listen) if err != nil { @@ -91,8 +117,11 @@ func TestScratchLiveUploadProbe(t *testing.T) { defer proc.Stop() arm := &benchmark.Arm{Backend: "postgres", BaseURL: "http://" + listen, Client: &http.Client{Timeout: 60 * time.Second}} - files := map[string]string{"samples.ndjson": "{\"sample_id\":\"s1\"}\n"} - uploadErr := arm.UploadGeneration(ctx, token, "loom-bench", "bench-probe-1", files, path) + fixtures, fixtureErr := benchmark.GenerateFiles(benchmark.DatasetParams{Preset: benchmark.PresetTiny, Seed: 1}) + if fixtureErr != nil { + t.Fatalf("fixtures: %v", fixtureErr) + } + uploadErr := arm.UploadGeneration(ctx, token, "loom-bench", "bench-probe-1", fixtures.Files, path) t.Logf("upload outcome: %v (fence requests=%d)", uploadErr, fence.Requests()) // Store-level surface on the same database. From 7a32a5b7e25e4505b36a55866f3e2fde751e8ef1 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 03:30:25 +0000 Subject: [PATCH 21/45] feat(benchmark): disclose the ClickHouse-backed publication chain as an excluded plan The publication chain is ClickHouse-backed end to end: publication.Target has exactly one production implementation (ClickHouseBundleStore) and POST .../explorer-config materializes through it before the revision that seeds the authoring draft persists. Publish, authoring, reconcile, receipt previews, workload gates, timing, and controls therefore share one gate behind the service (coordinator ruling 2026-11-13: no ClickHouse chasing in the container; non-publication stages must go green). RunPlan gains a checked Publication declaration. --publication=excluded (default) runs spawn, upload, and version probes for real and emits a report where VerifyReport refuses any samples, summaries, claims, or control values, requires every gate arm blocked, and one disclosure row per battery workload per ran leg under the capability CH--unavailable-in-container derived from ClickHouseCIVersion (tracked against the clickhouse/clickhouse-server pin in .github/workflows/tests.yaml). --publication=run requires --clickhouse-enabled; a ClickHouse-disabled identity claiming the chain ran no longer verifies. Driver protocol fixtures now state their ClickHouse posture explicitly instead of relying on a hardcoded flag. Validation: go test ./internal/dataframe/benchmark/... green including the new publication_test.go (honest round-trip, driver excluded protocol, 8 forgery RED cases, CI-pin drift guard); node orchestrator 12/12; go build and vet clean. Runner freeze respected: no scripts/** changes. --- internal/dataframe/benchmark/README.md | 37 ++- internal/dataframe/benchmark/armplan.go | 118 +++++++++ internal/dataframe/benchmark/armplan_test.go | 9 +- .../dataframe/benchmark/cmd/benchcli/main.go | 28 +- internal/dataframe/benchmark/driver.go | 55 +++- internal/dataframe/benchmark/driver_test.go | 6 +- .../dataframe/benchmark/publication_test.go | 246 ++++++++++++++++++ internal/dataframe/benchmark/report.go | 83 +++++- internal/dataframe/benchmark/report_test.go | 9 +- 9 files changed, 572 insertions(+), 19 deletions(-) create mode 100644 internal/dataframe/benchmark/publication_test.go diff --git a/internal/dataframe/benchmark/README.md b/internal/dataframe/benchmark/README.md index 1a4186430..1e972d80e 100644 --- a/internal/dataframe/benchmark/README.md +++ b/internal/dataframe/benchmark/README.md @@ -58,6 +58,28 @@ planned capabilities). window note, source commit, namespace guard, existing output) or the planned disclosure artifact was written. +## Publication chain (ClickHouse-backed) + +The publication chain is ClickHouse-backed end to end: `publication.Target` +has exactly one production implementation (`ClickHouseBundleStore`), and +`POST /generations/{g}/explorer-config` materializes through it *before* +the revision that seeds the authoring draft persists. Publish → authoring → +reconcile → preview receipts → workload gates → timing → controls is one +chain behind that service. (The preview *execution* path is ClickHouse-free; +it is the draft-seeding publish that needs it.) + +- `--publication=excluded` (default): spawn, upload, and version probes run + for real on every ran leg; the whole publication chain ships as + planned/not_run under the capability `CH--unavailable-in-container` + (`PublicationCapabilityCH`, derived from `ClickHouseCIVersion`, which + must equal the `clickhouse/clickhouse-server` tag in + `.github/workflows/tests.yaml` — currently 25.3). `VerifyReport` then + refuses any samples, summaries, claims, or control values, requires every + gate arm blocked, and one disclosure row per battery workload per ran leg. +- `--publication=run`: requires `--clickhouse-enabled`, i.e. the CI lane + with the pinned service. A report whose identity says ClickHouse is + disabled while claiming the chain ran fails verification. + ## Live-run guards (all fail closed, exit 3) `LOOM_BENCH_LIVE=1` (coordinator-granted exclusive window), `--window-note`, @@ -68,11 +90,16 @@ from the MockFence. ## Current blockers (cited in planned artifacts) -- `PG-PG-AUTH-PATH-DISCOVERY-ALLGENS` — scoped calypr ingest is impossible - on the postgres lane: `AuthorizeWrite`'s F-B27 project-binding branch - needs the all-generations auth-path listing that the postgres catalog - read lane refuses (`ErrAllGenerationsUnsupported`), and the load handler - wraps it as 403. Owner: arch-backend-09. Evidence: +- ClickHouse service: the publication chain (and everything timed behind + it) needs `clickhouse/clickhouse-server:25.3` as provisioned by the CI + lane; no container engine exists in the worker session, so live worker + runs use `--publication=excluded`. Owner: CI lane. +- `PG-PG-AUTH-PATH-DISCOVERY-ALLGENS` — FIXED in PR #156 (verified live: + upload accepted, fence hit once, no 403); the branch base predates the + library-side fix, so live legs go green when it merges. Evidence: `pg-pg-auth-path-discovery-allgens-evidence.md` (session artifacts). +- `PG-PG-CAPABILITY-EVIDENCE-INVENTORY-ONLY-STORE` — FIXED in PR #158 + (`NewFullStore` composition); verified live: no panic, reaches + materialization. Evidence: `pg-pg-capability-evidence-inventory-only-store-evidence.md`. - Arango leg: no ArangoDB service in the worker container; go-arango CI lane targets it. diff --git a/internal/dataframe/benchmark/armplan.go b/internal/dataframe/benchmark/armplan.go index 10aa80387..85d72d4fb 100644 --- a/internal/dataframe/benchmark/armplan.go +++ b/internal/dataframe/benchmark/armplan.go @@ -45,6 +45,119 @@ type PlannedNotRun struct { Reason string `json:"reason"` } +// ClickHouseCIVersion is the single source of truth for the ClickHouse +// line the benchmark's publication stages target. It must equal the image +// tag pinned by the repository CI services (currently +// clickhouse/clickhouse-server:25.3 in .github/workflows/tests.yaml); when +// CI bumps the pin, this constant, ClickHouseCIImage, and +// PublicationCapabilityCH follow automatically. One constant keeps the +// harness, the named capability, and the CI lane describing one substrate. +const ClickHouseCIVersion = "25.3" + +// ClickHouseCIImage is the container image the CI lane runs the +// publication-dependent stages against. No container engine exists in the +// worker container, so these stages are planned/not_run there (coordinator +// ruling 2026-11-13: no ClickHouse chasing); the CI lane provisions the +// service and runs them for real. +const ClickHouseCIImage = "clickhouse/clickhouse-server:" + ClickHouseCIVersion + +// PublicationCapabilityCH names the missing capability when the +// publication chain cannot execute because ClickHouse is not deployed. +// Publication is structurally ClickHouse-backed: publication.Target has +// exactly one production implementation (ClickHouseBundleStore) and +// POST .../explorer-config materializes through it BEFORE persisting the +// revision that seeds the authoring draft, so publish, reconcile, preview, +// workload gates, timing, and controls form one chain behind ClickHouse. +// The preview EXECUTION path itself stays ClickHouse-free (matched reader +// path ruling); it is the draft-seeding publish that needs the service. +const PublicationCapabilityCH = "CH-" + ClickHouseCIVersion + "-unavailable-in-container" + +// PublicationPlan declares whether the publication chain ran. It is a +// plan-level property so VerifyReport, not the driver's self-description, +// decides whether a report may carry timing evidence. +type PublicationPlan struct { + // Run is true when publication executed (CI lane with ClickHouse). + Run bool `json:"run"` + // Capability names why the chain did not run when Run is false; + // PublicationCapabilityCH is the canonical value for a ClickHouse-less + // deployment. + Capability string `json:"capability,omitempty"` +} + +// PublicationExcluded reports whether the plan declares the publication +// chain not run. A nil plan means the field predates the exclusion model +// and is treated as run for leg/scope checks; VerifyReport still rejects a +// ClickHouse-disabled report claiming it. +func (p RunPlan) PublicationExcluded() bool { + return p.Publication != nil && !p.Publication.Run +} + +// PublicationChainReason prefixes disclosure rows created by excluding +// the publication chain; VerifyReport recognizes the exclusion by it. +const PublicationChainReason = "publication chain not run: " + +// PublicationDisclosures expands the publication-chain exclusion across +// every battery workload of every running leg, so "publish excluded" +// cannot quietly leave workload rows claiming measurement on a chain that +// never executed. Legs that did not run are covered by their own leg +// capability rows instead. +func PublicationDisclosures(legs []LegPlan, capability string) []PlannedNotRun { + var rows []PlannedNotRun + for _, leg := range legs { + if leg.Mode != "run" { + continue + } + for _, spec := range Battery { + rows = append(rows, PlannedNotRun{ + WorkloadID: spec.ID, Backend: leg.Backend, + Reason: PublicationChainReason + capability, + }) + } + } + return rows +} + +// PublicationGateReports builds the honest per-workload gate reports for +// a run whose publication chain was excluded: every arm of every workload +// blocked — not-run legs by their leg capability, run legs by the +// publication capability — nothing claimable. +func PublicationGateReports(legs []LegPlan, capability string) []WorkloadGateReport { + legCapability := map[string]string{} + for _, leg := range legs { + if leg.Mode != "run" { + legCapability[leg.Backend] = leg.Capability + } + } + detail := func(backend string) string { + if cap, blocked := legCapability[backend]; blocked { + return "leg not run: " + cap + } + return PublicationChainReason + capability + } + var reports []WorkloadGateReport + for _, spec := range Battery { + reports = append(reports, WorkloadGateReport{ + WorkloadID: spec.ID, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelBlocked, Detail: detail(BackendArango)}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelBlocked, Detail: detail(BackendPostgres)}, + ExclusionReason: "publication chain excluded: " + detail(BackendArango) + " | " + detail(BackendPostgres), + }) + } + return reports +} + +// ValidatePublicationPlan checks the publication declaration is honest in +// shape: an excluded chain must name its missing capability. +func ValidatePublicationPlan(plan RunPlan) error { + if plan.Publication == nil || plan.Publication.Run { + return nil + } + if strings.TrimSpace(plan.Publication.Capability) == "" { + return fmt.Errorf("benchmark: publication chain excluded without a named capability") + } + return nil +} + // RunPlan is the declared participation of each arm for a run, stored in // the report and re-checked by VerifyReport: "we could only run the // PostgreSQL arm" must be a verified property, never a silent absence. @@ -52,6 +165,11 @@ type RunPlan struct { Scope string `json:"scope"` Legs []LegPlan `json:"legs"` PlannedNotRun []PlannedNotRun `json:"plannedNotRun,omitempty"` + // Publication declares whether the ClickHouse-backed publication chain + // executed. nil predates the exclusion model: treated as run for + // leg/scope checks, but VerifyReport still refuses a publication-run + // claim from a ClickHouse-disabled identity. + Publication *PublicationPlan `json:"publication,omitempty"` } // BothArmsPlan is the CI-target plan: both services execute every leg. diff --git a/internal/dataframe/benchmark/armplan_test.go b/internal/dataframe/benchmark/armplan_test.go index 4908a8d69..0316077ce 100644 --- a/internal/dataframe/benchmark/armplan_test.go +++ b/internal/dataframe/benchmark/armplan_test.go @@ -28,9 +28,11 @@ func validPostgresOnlyReport(t *testing.T) *Report { identity := RunIdentity{ RunID: "bench-2026-11-13-pg-a", StartedAt: time.Unix(1700000000, 0).UTC(), Preset: PresetTiny, SourceCommit: strings.Repeat("a", 40), - DatasetDigest: strings.Repeat("b", 64), - PostgresVersion: "PostgreSQL 18.x", - ClickHouseDisabled: true, + DatasetDigest: strings.Repeat("b", 64), + PostgresVersion: "PostgreSQL 18.x", + // Timing needs the publication chain, which is ClickHouse- + // backed: this fixture describes the CI lane with ClickHouse. + ClickHouseDisabled: false, Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, } controls := ControlRuns{ @@ -41,6 +43,7 @@ func validPostgresOnlyReport(t *testing.T) *Report { EmptyFloorMillis: 0.4, RatioPolicy: "ratio-only-both-passed", } plan := PostgresOnlyPlan(testArangoCapability) + plan.Publication = &PublicationPlan{Run: true} plan.PlannedNotRun = PlannedNotRunForBackends([]string{BackendArango}) for i := range plan.PlannedNotRun { plan.PlannedNotRun[i].Reason = "leg not run: " + testArangoCapability diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go index 777611f89..5956fa3ad 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/main.go +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -87,6 +87,8 @@ type runFlags struct { emptyFloorIters int arangoCapability string postgresCapability string + publication string + clickHouseEnabled bool } // Default capabilities cited by planned not_run legs. The postgres one @@ -126,6 +128,8 @@ func runLive() { fs.IntVar(&f.emptyFloorIters, "empty-floor-iterations", 5, "iterations for the empty-result floor control") fs.StringVar(&f.arangoCapability, "arango-capability", defaultArangoCapability, "capability cited by the arango not_run legs (planned scope)") fs.StringVar(&f.postgresCapability, "postgres-capability", defaultPostgresCapability, "capability cited by the postgres not_run legs (planned scope)") + fs.StringVar(&f.publication, "publication", "excluded", "publication chain: excluded | run. The chain is ClickHouse-backed end to end; run requires --clickhouse-enabled and a CI lane with "+benchmark.ClickHouseCIImage) + fs.BoolVar(&f.clickHouseEnabled, "clickhouse-enabled", false, "assert the arms were deployed with ClickHouse ("+benchmark.ClickHouseCIImage+"); required for --publication=run") _ = fs.Parse(os.Args[2:]) // Fail-closed preflight (exit 3: refused to run; exit 1: ran and failed). @@ -200,6 +204,20 @@ func runLive() { for i := range plan.PlannedNotRun { plan.PlannedNotRun[i].Reason = "leg not run: " + plan.Legs[0].Capability } + switch f.publication { + case "excluded": + // Coordinator ruling 2026-11-13: no ClickHouse in the worker + // container; the whole chain ships disclosed planned/not_run. + plan.Publication = &benchmark.PublicationPlan{Run: false, Capability: benchmark.PublicationCapabilityCH} + plan.PlannedNotRun = append(plan.PlannedNotRun, benchmark.PublicationDisclosures(plan.Legs, benchmark.PublicationCapabilityCH)...) + case "run": + if !f.clickHouseEnabled { + preflight("--publication=run requires --clickhouse-enabled: the chain is ClickHouse-backed end to end (%s)", benchmark.ClickHouseCIImage) + } + plan.Publication = &benchmark.PublicationPlan{Run: true} + default: + preflight("unknown --publication %q (want excluded | run)", f.publication) + } if !planNotRunMatchesBattery(plan) { fmt.Fprintln(os.Stderr, "benchcli run: internal plan inconsistency") os.Exit(1) @@ -229,6 +247,7 @@ func runLive() { Arango: d.arango, Postgres: d.postgres, StderrDir: stderrDir, + ClickHouseEnabled: f.clickHouseEnabled, Clock: benchmark.NewMonotonicClock(), VersionProbe: d.probe, EmptyFloorIters: f.emptyFloorIters, @@ -353,13 +372,20 @@ func runPlanned(f runFlags, preflight func(string, ...any)) { } func planNotRunMatchesBattery(plan benchmark.RunPlan) bool { + mode := map[string]string{} + for _, leg := range plan.Legs { + mode[leg.Backend] = leg.Mode + } counts := map[string]int{} for _, row := range plan.PlannedNotRun { + if mode[row.Backend] == "run" && !strings.HasPrefix(row.Reason, benchmark.PublicationChainReason) { + return false // only publication-chain disclosures may sit on a ran leg + } counts[row.Backend]++ } for _, leg := range plan.Legs { want := 0 - if leg.Mode == "not_run" { + if leg.Mode == "not_run" || plan.PublicationExcluded() { want = len(benchmark.Battery) } if counts[leg.Backend] != want { diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index e779c70fa..669fb44cb 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -68,6 +68,13 @@ type DriverOptions struct { StderrDir string Clock Clock + // ClickHouseEnabled asserts the arms were deployed with ClickHouse + // available, which the publication chain structurally requires (its + // only publication.Target is ClickHouseBundleStore). Report + // verification refuses any publication-run claim without it, so this + // is a checked assertion, not a self-description. + ClickHouseEnabled bool + Spawn SpawnFunc VersionProbe VersionProbeFunc @@ -102,6 +109,13 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden // is built through BuildReport+PlannedGateReports, never here. return nil, nil, fmt.Errorf("benchmark: the live driver refuses planned scope; build the planned report directly") } + if opts.Plan.PublicationExcluded() { + if strings.TrimSpace(opts.Plan.Publication.Capability) == "" { + return nil, nil, fmt.Errorf("benchmark: publication chain excluded without a named capability") + } + } else if opts.Plan.Publication != nil && opts.Plan.Publication.Run && !opts.ClickHouseEnabled { + return nil, nil, fmt.Errorf("benchmark: the publication chain is ClickHouse-backed end to end; refusing to claim it ran without ClickHouseEnabled") + } if err := opts.Config.Validate(); err != nil { return nil, nil, err } @@ -173,6 +187,45 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden } } + // Excluded publication chain (coordinator ruling 2026-11-13): the + // chain is ClickHouse-backed end to end and ClickHouse is not + // deployed here, so spawn and upload have run for real and every + // publication-dependent stage ships as planned/not_run with the + // capability named. Nothing below this point executes, so the report + // carries zero samples, zero claims, and zero control values. + if opts.Plan.PublicationExcluded() { + identity := RunIdentity{ + RunID: opts.RunID, StartedAt: opts.StartedAt.UTC(), Preset: opts.Preset, + SourceCommit: opts.SourceCommit, DatasetDigest: files.Digest, + ClickHouseDisabled: !opts.ClickHouseEnabled, Config: opts.Config, + } + for _, p := range participants { + version, err := opts.VersionProbe(ctx, p.cfg, p.backend) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: %s version probe: %w", p.backend, err) + } + if strings.TrimSpace(version) == "" { + return nil, nil, fmt.Errorf("benchmark: %s version probe returned an empty version", p.backend) + } + arms[p.backend].evidence.Version = version + if p.backend == BackendArango { + identity.ArangoVersion = version + } else { + identity.PostgresVersion = version + } + } + report, err := BuildReport(identity, opts.Plan, ControlRuns{}, + PublicationGateReports(opts.Plan.Legs, opts.Plan.Publication.Capability), nil) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: excluded-publication report would not verify (refusing to emit): %w", err) + } + var evidence []ArmEvidence + for _, p := range participants { + evidence = append(evidence, arms[p.backend].evidence) + } + return report, evidence, nil + } + // Author every workload on every participating arm. Explorer ids are // derived from names (StableExplorerID), so the arms must agree on the // id or the matched comparison is invalid. @@ -367,7 +420,7 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden identity := RunIdentity{ RunID: opts.RunID, StartedAt: opts.StartedAt.UTC(), Preset: opts.Preset, SourceCommit: opts.SourceCommit, DatasetDigest: files.Digest, - ClickHouseDisabled: true, Config: opts.Config, + ClickHouseDisabled: !opts.ClickHouseEnabled, Config: opts.Config, } for _, p := range participants { version, err := opts.VersionProbe(ctx, p.cfg, p.backend) diff --git a/internal/dataframe/benchmark/driver_test.go b/internal/dataframe/benchmark/driver_test.go index 55042f288..2f7d4d2f6 100644 --- a/internal/dataframe/benchmark/driver_test.go +++ b/internal/dataframe/benchmark/driver_test.go @@ -17,6 +17,9 @@ func driverOptions(fArango, fPostgres *fakeArm, scope string) DriverOptions { if scope == RunScopePostgresOnly { plan = PostgresOnlyPlan("no ArangoDB service in worker container") } + // The fake arms serve the whole battery, i.e. a CI-lane deployment + // with ClickHouse; the excluded-chain shape is pinned separately. + plan.Publication = &PublicationPlan{Run: true} plan.PlannedNotRun = PlannedNotRunForBackends(plan.NotRunBackends()) for i := range plan.PlannedNotRun { plan.PlannedNotRun[i].Reason = "leg not run: worker container lacks the service" @@ -41,7 +44,8 @@ func driverOptions(fArango, fPostgres *fakeArm, scope string) DriverOptions { Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, PreviewLimit: 1000, SourceCommit: strings.Repeat("a", 40), UnrestrictedToken: "Bearer unauth", ScopedToken: "Bearer scoped", - Clock: &scriptedClock{base: time.Unix(1700000000, 0)}, + ClickHouseEnabled: true, + Clock: &scriptedClock{base: time.Unix(1700000000, 0)}, VersionProbe: func(ctx context.Context, cfg ArmConfig, backend string) (string, error) { if backend == BackendArango { return "3.12.x-fake", nil diff --git a/internal/dataframe/benchmark/publication_test.go b/internal/dataframe/benchmark/publication_test.go new file mode 100644 index 000000000..44aac814f --- /dev/null +++ b/internal/dataframe/benchmark/publication_test.go @@ -0,0 +1,246 @@ +package benchmark + +import ( + "context" + "strings" + "testing" + "time" +) + +// The publication chain is ClickHouse-backed end to end (its only +// publication.Target is ClickHouseBundleStore; the workspace that seeds +// every receipt persists only after materialization). In a ClickHouse-less +// deployment the honest artifact is an excluded-chain report: spawn and +// upload ran for real, everything downstream is planned/not_run with the +// capability named, and zero timing evidence exists. These tests pin the +// honest shape and every deception path around it. + +func excludedPlan(scope string) RunPlan { + var plan RunPlan + if scope == RunScopePostgresOnly { + plan = PostgresOnlyPlan("no ArangoDB service in this execution environment; go-arango CI lane targets this leg") + } else { + plan = BothArmsPlan() + } + plan.Publication = &PublicationPlan{Run: false, Capability: PublicationCapabilityCH} + plan.PlannedNotRun = PlannedNotRunForBackends(plan.NotRunBackends()) + for i := range plan.PlannedNotRun { + plan.PlannedNotRun[i].Reason = "leg not run: " + plan.Legs[0].Capability + } + plan.PlannedNotRun = append(plan.PlannedNotRun, PublicationDisclosures(plan.Legs, PublicationCapabilityCH)...) + return plan +} + +func excludedIdentity(runID string) RunIdentity { + return RunIdentity{ + RunID: runID, StartedAt: time.Unix(1700000000, 0).UTC(), + Preset: PresetTiny, SourceCommit: strings.Repeat("a", 40), + DatasetDigest: strings.Repeat("b", 64), + // The excluded chain exists precisely because ClickHouse is + // NOT deployed; the identity must say so. + ClickHouseDisabled: true, + Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, + } +} + +func TestExcludedChainReportIsHonestAndVerifies(t *testing.T) { + plan := excludedPlan(RunScopeBothArms) + identity := excludedIdentity("bench-excl-a") + identity.ArangoVersion = "3.12.x-fake" + identity.PostgresVersion = "PostgreSQL 18.x-fake" + report, err := BuildReport(identity, plan, ControlRuns{}, + PublicationGateReports(plan.Legs, PublicationCapabilityCH), nil) + if err != nil { + t.Fatalf("honest excluded-chain report must build: %v", err) + } + if len(report.Samples) != 0 || len(report.Summaries) != 0 || len(report.Claims) != 0 { + t.Fatalf("excluded chain must carry zero timing evidence: %d samples, %d summaries, %d claims", + len(report.Samples), len(report.Summaries), len(report.Claims)) + } + for _, gate := range report.GateReports { + for _, arm := range []BackendResult{gate.Arango, gate.Postgres} { + if arm.Outcome != OutcomeLabelBlocked { + t.Fatalf("%s/%s must be blocked, got %s", gate.WorkloadID, arm.Backend, arm.Outcome) + } + if !strings.Contains(arm.Detail, PublicationCapabilityCH) { + t.Fatalf("%s/%s detail must name the capability: %q", gate.WorkloadID, arm.Backend, arm.Detail) + } + } + } + if want := 2 * len(Battery); len(report.Plan.PlannedNotRun) != want { + t.Fatalf("both running legs must each disclose the whole battery: %d rows want %d", len(report.Plan.PlannedNotRun), want) + } + // The artifact must survive marshal + LoadReport verification. + if _, err := LoadReport(mustMarshal(t, report)); err != nil { + t.Fatalf("excluded-chain artifact must verify: %v", err) + } +} + +func TestExcludedChainForgeryPaths(t *testing.T) { + build := func(t *testing.T) *Report { + t.Helper() + plan := excludedPlan(RunScopePostgresOnly) + identity := excludedIdentity("bench-excl-forge") + identity.PostgresVersion = "PostgreSQL 18.x-fake" + report, err := BuildReport(identity, plan, ControlRuns{}, + PublicationGateReports(plan.Legs, PublicationCapabilityCH), nil) + if err != nil { + t.Fatalf("baseline excluded report: %v", err) + } + return report + } + cases := []struct { + name string + tamper func(*testing.T, *Report) + wantErr string + }{ + {"stability limit borrowed into an excluded run", func(t *testing.T, r *Report) { + r.Controls.StabilityCVLimit = StabilityCVLimit + }, "control values claimed although the publication chain was excluded"}, + {"empty floor borrowed into an excluded run", func(t *testing.T, r *Report) { + r.Controls.EmptyFloorMillis = 0.4 + }, "control values claimed although the publication chain was excluded"}, + {"ratio policy cover for an excluded run", func(t *testing.T, r *Report) { + r.Controls.RatioPolicy = "ratio-only-both-passed" + }, "control values claimed although the publication chain was excluded"}, + {"self-ratio forged onto an excluded run leg", func(t *testing.T, r *Report) { + r.Controls.SelfRatioPostgres = 1.0 + }, "control values claimed although the publication chain was excluded"}, + {"samples fabricated into an excluded run", func(t *testing.T, r *Report) { + r.Samples = append(r.Samples, sample(BackendPostgres, WorkloadFilterActiveGender, OutcomePassed, 10, false)) + }, "timing evidence claimed although the publication chain was excluded"}, + {"one disclosure row quietly dropped", func(t *testing.T, r *Report) { + // drop a postgres publication-chain row (the last leg's block) + r.Plan.PlannedNotRun = r.Plan.PlannedNotRun[:len(r.Plan.PlannedNotRun)-1] + }, "without a disclosure row"}, + {"one gate arm flipped to passed", func(t *testing.T, r *Report) { + r.GateReports[0].Postgres.Outcome = OutcomeLabelPassed + }, "although the publication chain was excluded"}, + {"chain excluded without naming the capability", func(t *testing.T, r *Report) { + r.Plan.Publication.Capability = " " + }, "publication chain excluded without a named capability"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + report := build(t) + tc.tamper(t, report) + if err := VerifyReport(report); err == nil || !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("want error containing %q, got %v", tc.wantErr, err) + } + }) + } +} + +// The ClickHouse-disabled rule closes the 2026-11-13 forgery shape: a +// full timing report stamped from a deployment that cannot host the +// publication chain. +func TestCHDisabledCannotClaimPublicationRan(t *testing.T) { + report := validReport(t) // CI-lane fixture: ClickHouse deployed + if err := VerifyReport(report); err != nil { + t.Fatalf("CI-lane baseline must verify: %v", err) + } + report.Identity.ClickHouseDisabled = true + if err := VerifyReport(report); err == nil || + !strings.Contains(err.Error(), "publication chain claimed to run although ClickHouse is disabled") { + t.Fatalf("want CH-disabled refusal, got %v", err) + } + // An explicitly excluded chain is the honest alternative; this + // timing-bearing fixture must still be refused under it. + report.Plan.Publication = &PublicationPlan{Run: false, Capability: PublicationCapabilityCH} + if err := VerifyReport(report); err == nil || + !strings.Contains(err.Error(), "although the publication chain was excluded") { + t.Fatalf("excluded plan must refuse the timing evidence, got %v", err) + } +} + +// A publication-prefixed disclosure row is legal only under an excluding +// plan; elsewhere it is a "silently measured nothing and called it +// disclosed" path. +func TestPublicationRowsOnlyOnExcludingPlans(t *testing.T) { + report := validReport(t) + report.Plan.PlannedNotRun = append(report.Plan.PlannedNotRun, PlannedNotRun{ + WorkloadID: WorkloadFilterActiveGender, Backend: BackendArango, + Reason: PublicationChainReason + PublicationCapabilityCH, + }) + if err := VerifyReport(report); err == nil || !strings.Contains(err.Error(), "but its leg ran") { + t.Fatalf("want refusal of publication row on a non-excluding plan, got %v", err) + } +} + +// The driver must run the pre-publication stages for real in the excluded +// shape (arms up, dataset uploaded, versions probed) and refuse to publish +// or measure anything downstream. +func TestDriverExcludedPublicationProtocol(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + opts := driverOptions(fa, fp, RunScopeBothArms) + opts.ClickHouseEnabled = false + opts.Plan.Publication = &PublicationPlan{Run: false, Capability: PublicationCapabilityCH} + opts.Plan.PlannedNotRun = append(opts.Plan.PlannedNotRun, + PublicationDisclosures(opts.Plan.Legs, PublicationCapabilityCH)...) + report, evidence, err := RunBenchmark(context.Background(), opts) + if err != nil { + t.Fatal(err) + } + for name, f := range map[string]*fakeArm{"arango": fa, "postgres": fp} { + if f.uploads != 1 { + t.Fatalf("%s: upload is a non-publication stage and must run: uploads=%d want 1", name, f.uploads) + } + if f.publish != 0 { + t.Fatalf("%s: excluded chain must not publish: publish=%d want 0", name, f.publish) + } + } + if len(report.Samples) != 0 || len(report.Claims) != 0 { + t.Fatalf("excluded driver report must be timing-free: %d samples, %d claims", len(report.Samples), len(report.Claims)) + } + c := report.Controls + if c.StabilityCVLimit != 0 || c.EmptyFloorMillis != 0 || c.SelfRatioArango != 0 || + c.SelfRatioPostgres != 0 || c.RatioPolicy != "" || len(c.UnstableCells) != 0 { + t.Fatalf("excluded driver report must carry zero controls: %+v", c) + } + if len(evidence) != 2 { + t.Fatalf("both arms must be evidenced: %+v", evidence) + } + for _, ev := range evidence { + if ev.Version == "" { + t.Fatalf("live version probe is a non-publication stage: %+v", ev) + } + } + if _, err := LoadReport(mustMarshal(t, report)); err != nil { + t.Fatalf("driver excluded-chain artifact must verify: %v", err) + } +} + +func TestDriverRefusesIncoherentPublicationClaims(t *testing.T) { + fp := newFakeArm(BackendPostgres) + defer fp.close() + t.Run("run claimed without ClickHouse", func(t *testing.T) { + opts := driverOptions(nil, fp, RunScopePostgresOnly) + opts.ClickHouseEnabled = false + opts.Plan.Publication = &PublicationPlan{Run: true} + if _, _, err := RunBenchmark(context.Background(), opts); err == nil || + !strings.Contains(err.Error(), "refusing to claim it ran without ClickHouseEnabled") { + t.Fatalf("want refusal, got %v", err) + } + }) + t.Run("excluded without capability", func(t *testing.T) { + opts := driverOptions(nil, fp, RunScopePostgresOnly) + opts.Plan.Publication = &PublicationPlan{Run: false} + if _, _, err := RunBenchmark(context.Background(), opts); err == nil || + !strings.Contains(err.Error(), "without a named capability") { + t.Fatalf("want refusal, got %v", err) + } + }) +} + +// The named capability must track the CI ClickHouse pin so the harness, +// the disclosure text, and the CI lane cannot drift apart silently. +func TestPublicationCapabilityTracksCIPin(t *testing.T) { + if ClickHouseCIImage != "clickhouse/clickhouse-server:25.3" { + t.Fatalf("CI image drifted from the repository pin; update the constant deliberately: %s", ClickHouseCIImage) + } + if PublicationCapabilityCH != "CH-25.3-unavailable-in-container" { + t.Fatalf("capability must name the CI ClickHouse version: %s", PublicationCapabilityCH) + } +} diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go index 37b227427..5c2e7423f 100644 --- a/internal/dataframe/benchmark/report.go +++ b/internal/dataframe/benchmark/report.go @@ -205,7 +205,12 @@ func VerifyReport(report *Report) error { if err := ValidatePlannedNotRun(report.Plan.PlannedNotRun); err != nil { return fmt.Errorf("benchmark report: %w", err) } + if err := ValidatePublicationPlan(report.Plan); err != nil { + return fmt.Errorf("benchmark report: %w", err) + } + publicationExcluded := report.Plan.PublicationExcluded() notRun := map[string]string{} + pubDisclosed := map[string]bool{} runLegs := 0 for _, leg := range report.Plan.Legs { if leg.Mode == "not_run" { @@ -214,13 +219,50 @@ func VerifyReport(report *Report) error { runLegs++ } } + if !publicationExcluded && report.Identity.ClickHouseDisabled && runLegs > 0 { + // The publication chain is ClickHouse-backed end to end: its only + // publication.Target is ClickHouseBundleStore, and the workspace + // that seeds every receipt persists only after materialization. + // A report from a ClickHouse-disabled deployment that claims the + // chain ran is the 2026-11-13 forgery shape this rule closes. + return fmt.Errorf("benchmark report: publication chain claimed to run although ClickHouse is disabled") + } if runLegs > 0 { - // A run that measured anything must show the controls ran. - if report.Controls.StabilityCVLimit <= 0 || report.Controls.RatioPolicy == "" { - return fmt.Errorf("benchmark report: measurement controls were not run (missing stability limit or ratio policy)") - } - if report.Controls.EmptyFloorMillis <= 0 { - return fmt.Errorf("benchmark report: empty-result floor control was not run") + if publicationExcluded { + // The publication chain is ClickHouse-backed end to end: its + // only publication.Target is ClickHouseBundleStore, and the + // workspace seeding every receipt persists only after + // materialization. With the chain excluded nothing downstream + // measured, so any control value is forged (stolen limits, + // borrowed floors, ratio cover). + c := report.Controls + if c.StabilityCVLimit != 0 || c.EmptyFloorMillis != 0 || c.SelfRatioArango != 0 || + c.SelfRatioPostgres != 0 || c.RatioPolicy != "" || len(c.UnstableCells) != 0 { + return fmt.Errorf("benchmark report: control values claimed although the publication chain was excluded") + } + // Nothing downstream of publication executed, so no timing + // evidence may exist, every gate arm must read blocked, and + // each ran leg must carry one disclosure row per battery + // workload naming the publication capability. + if len(report.Samples) != 0 || len(report.Summaries) != 0 || len(report.Claims) != 0 { + return fmt.Errorf("benchmark report: timing evidence claimed although the publication chain was excluded") + } + for _, gate := range report.GateReports { + for _, arm := range []BackendResult{gate.Arango, gate.Postgres} { + if arm.Outcome != OutcomeLabelBlocked { + return fmt.Errorf("benchmark report: %s/%s arm shows %s although the publication chain was excluded", + gate.WorkloadID, arm.Backend, arm.Outcome) + } + } + } + } else { + // A run that measured anything must show the controls ran. + if report.Controls.StabilityCVLimit <= 0 || report.Controls.RatioPolicy == "" { + return fmt.Errorf("benchmark report: measurement controls were not run (missing stability limit or ratio policy)") + } + if report.Controls.EmptyFloorMillis <= 0 { + return fmt.Errorf("benchmark report: empty-result floor control was not run") + } } } else { // Planned report: nothing ran, so a control that claims to have run @@ -258,6 +300,12 @@ func VerifyReport(report *Report) error { } continue } + if publicationExcluded { + // The self-ratio control times published reads; with the + // chain excluded its absence is already enforced (zero) by + // the control-values rule above. + continue + } if sr.value <= 0 { return fmt.Errorf("benchmark report: %s self-ratio control was not run", sr.backend) } @@ -271,10 +319,33 @@ func VerifyReport(report *Report) error { disclosed := map[string]bool{} for _, row := range report.Plan.PlannedNotRun { if _, absent := notRun[row.Backend]; !absent { + // A disclosure row on a ran leg is only honest when it + // declares the excluded publication chain under an excluding + // plan; any other row on a ran leg is a forgery path. + if publicationExcluded && strings.HasPrefix(row.Reason, PublicationChainReason) { + pubDisclosed[row.WorkloadID+"|"+row.Backend] = true + continue + } return fmt.Errorf("benchmark report: planned/not_run row for %s/%s but its leg ran", row.WorkloadID, row.Backend) } disclosed[row.WorkloadID+"|"+row.Backend] = true } + // Every ran leg of an excluded run must carry one publication-chain + // disclosure row per battery workload, exactly as a not-run leg must + // carry its leg rows. Checked after the loop above has collected them. + if publicationExcluded { + for _, leg := range report.Plan.Legs { + if leg.Mode != "run" { + continue + } + for _, spec := range Battery { + if !pubDisclosed[spec.ID+"|"+leg.Backend] { + return fmt.Errorf("benchmark report: %s/%s publication chain excluded without a disclosure row", + spec.ID, leg.Backend) + } + } + } + } for _, gate := range report.GateReports { for _, arm := range []BackendResult{gate.Arango, gate.Postgres} { capability, absent := notRun[arm.Backend] diff --git a/internal/dataframe/benchmark/report_test.go b/internal/dataframe/benchmark/report_test.go index 59eefc102..e05c95f47 100644 --- a/internal/dataframe/benchmark/report_test.go +++ b/internal/dataframe/benchmark/report_test.go @@ -18,7 +18,10 @@ func validReport(t *testing.T) *Report { Preset: PresetTiny, SourceCommit: strings.Repeat("a", 40), DatasetDigest: strings.Repeat("b", 64), ArangoVersion: "3.12.x", PostgresVersion: "PostgreSQL 18.x", - ClickHouseDisabled: true, + // A timing report describes the CI lane with ClickHouse + // deployed; the worker container can only build excluded-chain + // reports (see the publication-exclusion forgery cases). + ClickHouseDisabled: false, Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, } controls := ControlRuns{ @@ -43,7 +46,9 @@ func validReport(t *testing.T) *Report { samples = append(samples, sample(backend, WorkloadFilterActiveGender, OutcomePassed, base+float64(i), false)) } } - report, err := BuildReport(identity, BothArmsPlan(), controls, []WorkloadGateReport{gate}, samples) + timingPlan := BothArmsPlan() + timingPlan.Publication = &PublicationPlan{Run: true} + report, err := BuildReport(identity, timingPlan, controls, []WorkloadGateReport{gate}, samples) if err != nil { t.Fatalf("valid report build failed: %v", err) } From 99835fd6d30e546afee578dfc7f623f911adf0ce Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 03:41:07 +0000 Subject: [PATCH 22/45] fix(benchmark): register audit packages and confine benchcli to the public postgres port Two CI failures on the benchmark branch: 1. scripts/package_audit.py refused the new packages (Python tests lane). Curation added for internal/dataframe/benchmark and its benchcli entry point; docs/PACKAGE_AUDIT.{csv,md} regenerated. package_audit_verify passed for both locations; the measured-importer column updates are the new packages appearing as importers, as expected. Not runner territory: no CI selector or orchestrator files touched, runner freeze respected. 2. TestPostgresFactoryOwnershipContract fired: production files import the PostgreSQL driver outside the register (internal/backends/ is the driver's only legitimate production resident). benchcli's version probe and setup-pg/drop-pg imported pgx/v5 directly. The contract test is arch-backend-09's; the fix stays in the benchmark: - probePostgresVersion now opens the public factory (no schema assignment, SetupNone) and runs SELECT version() on a connection acquired from Runtime.ConnSource(), the factory's public single-connection port. Zero jackc imports remain in the package. - setup-pg/drop-pg run their DDL on the same port; identifiers pass a closed regex before SQL-standard double-quote rendering. - DSN parsing moved from pgx.ParseConfig to net/url; the lane already mandates URL-form DSNs (guards suite unchanged). Validation: TestPostgresFactoryOwnershipContract green; go test -count=1 ./internal/dataframe/benchmark/... green; python suite 48+14 OK; node 12/12; go build/vet clean. Live smoke against the test PG lane: setup-pg created database + six-series ladder and drop-pg removed it (exit 0 both), and the probe returned the full PostgreSQL 18.6 version string. The internal/ci vet-gate failure reproduces on clean base faffab39 in this sandbox (TMPDIR relpath quirk), so it is environmental, not this branch. --- docs/PACKAGE_AUDIT.csv | 76 +++++++-------- docs/PACKAGE_AUDIT.md | 76 +++++++-------- .../benchmark/cmd/benchcli/probes.go | 94 ++++++++++++++----- .../dataframe/benchmark/cmd/benchcli/setup.go | 85 ++++++++++++----- scripts/package_audit.py | 2 + 5 files changed, 216 insertions(+), 117 deletions(-) diff --git a/docs/PACKAGE_AUDIT.csv b/docs/PACKAGE_AUDIT.csv index ccf78d7fd..2b271052f 100644 --- a/docs/PACKAGE_AUDIT.csv +++ b/docs/PACKAGE_AUDIT.csv @@ -3,7 +3,7 @@ location,import_path,package_name,kind,responsibility,production_files,test_file cmd/arango-fhir-proto,github.com/calypr/loom/cmd/arango-fhir-proto,main,executable,"Provide operator commands for FHIR loading, catalog diagnostics, generation repair, and activation.",main.go,main_test.go; resource_inventory_test.go,—,—,842,470,0,—,internal/backends/arango; internal/catalog; internal/dataset; internal/fhir/schema; internal/ingest,internal/catalog,—,—,—,—,legacy=4; migration=1,executable boundary; compare orchestration with sibling commands before combining,go test ./cmd/arango-fhir-proto,inventory complete; semantic disposition pending cmd/arango-fhir-server,github.com/calypr/loom/cmd/arango-fhir-server,main,executable,Start the explicit Loom HTTP server binary.,main.go,—,—,—,7,0,0,—,internal/server,—,—,—,—,—,—,executable boundary; compare orchestration with sibling commands before combining,go test ./cmd/arango-fhir-server,inventory complete; semantic disposition pending cmd/dataframe-profile,github.com/calypr/loom/cmd/dataframe-profile,main,executable,Run and compare developer-facing dataframe profiling workloads.,main.go,—,—,—,331,0,0,—,conformance/compiler; generated/graphql/graph/model; internal/api/graphql/graph/query; internal/backends/arango; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/recipe; internal/dataframe/semantic,—,—,—,—,—,legacy=1,executable boundary; compare orchestration with sibling commands before combining,go test ./cmd/dataframe-profile,inventory complete; semantic disposition pending -cmd/dataframe-query,github.com/calypr/loom/cmd/dataframe-query,main,executable,Execute a checked-in GraphQL dataframe request and report response timing and diagnostics.,main.go,main_test.go,—,—,459,432,3,Error; String; Unwrap,—,—,—,—,—,—,compatibility=1,executable boundary; compare orchestration with sibling commands before combining,go test ./cmd/dataframe-query,inventory complete; semantic disposition pending +cmd/dataframe-query,github.com/calypr/loom/cmd/dataframe-query,main,executable,Execute a checked-in GraphQL dataframe request and report response timing and diagnostics.,main.go,backend_neutral_diagnostics_test.go; main_test.go,—,—,475,508,3,Error; String; Unwrap,—,—,—,—,—,—,legacy=2; compatibility=1,executable boundary; compare orchestration with sibling commands before combining,go test ./cmd/dataframe-query,inventory complete; semantic disposition pending cmd/explorer-config-v2-convert,github.com/calypr/loom/cmd/explorer-config-v2-convert,main,executable,Convert legacy Explorer configuration input into the V2 authoring representation offline.,main.go,main_test.go,—,—,377,62,0,—,internal/dataframe/recipe; internal/explorer; internal/explorer/authoringv2,internal/explorer/authoringv2,—,—,—,—,legacy=3,executable boundary; compare orchestration with sibling commands before combining,go test ./cmd/explorer-config-v2-convert,inventory complete; semantic disposition pending cmd/generate,github.com/calypr/loom/cmd/generate,main,executable,"Generate FHIR models, schema metadata, validation, extraction, and GraphQL artifacts from source schemas.",extraction.go; graphql.go; helpers.go; main.go; metadata.go; model.go; names.go; resources.go; schema.go; validation.go,extraction_test.go; graphql_test.go; metadata_test.go; model_test.go; resources_test.go; root_test.go; schema_fixture_test.go,fhir_helpers.go.tmpl,—,1750,522,10,BindingStrength; Definition; EdgeDocument; Link; Property; Result; Schema; TargetHints; TargetSchema; TemplatePointers,—,—,—,—,—,go:embed,compatibility=1,executable boundary; compare orchestration with sibling commands before combining; runtime/build signals present; static importer counts are insufficient for deletion,go test ./cmd/generate,inventory complete; semantic disposition pending cmd/gqlgenfix,github.com/calypr/loom/cmd/gqlgenfix,main,executable,Post-process gqlgen output to enforce Loom-specific generated-code invariants.,main.go,main_test.go,—,—,173,67,1,MarshalJSON,—,—,—,—,—,—,—,executable boundary; compare orchestration with sibling commands before combining,go test ./cmd/gqlgenfix,inventory complete; semantic disposition pending @@ -12,10 +12,10 @@ cmd/loom-data,github.com/calypr/loom/cmd/loom-data,main,executable,"Load one imm cmd/loom-repository,github.com/calypr/loom/cmd/loom-repository,main,executable,Preflight a repository checkout and launch Loom against its META and CONFIG data with an owned Compose project.,main.go,main_test.go,—,—,426,231,0,—,internal/projectid; internal/repositoryseed,internal/projectid; internal/repositoryseed,—,—,—,—,legacy=2,executable boundary; compare orchestration with sibling commands before combining,go test ./cmd/loom-repository,inventory complete; semantic disposition pending cmd/preview-covering-index-artifact,github.com/calypr/loom/cmd/preview-covering-index-artifact,main,executable,Recreate a captured Builder proposal offline and emit the exact compiler artifact for index-coverage analysis.,main.go,main_test.go,—,—,1449,34,0,—,internal/authscope; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation,—,—,—,—,—,migration=1,executable boundary; compare orchestration with sibling commands before combining,go test ./cmd/preview-covering-index-artifact,inventory complete; semantic disposition pending conformance/compiler,github.com/calypr/loom/conformance/compiler,compilerfixture,conformance,Load and exercise the canonical dataframe compiler oracle corpus for conformance and performance checks.,types.go,benchmark_test.go; command_helpers_test.go; corpus_coverage_test.go; gdc_fixture_test.go; generated_semantics_coverage_test.go; physical_plan_cost_test.go; policy_ablation_test.go; run_test.go; schema_roots_test.go; types_test.go,—,—,119,1279,4,Expected; Fixture; LoadDir; Validate,internal/dataframe/recipe,internal/backends/arango; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/fhir/schema,cmd/dataframe-profile,—,—,—,—,non-runtime conformance boundary,go test ./conformance/compiler,inventory complete; semantic disposition pending -generated/fhir,github.com/calypr/loom/generated/fhir,fhir,generated,"Expose generated FHIR resource models and their extraction, validation, and GraphQL helpers.",extract.go; graphql.go; helpers.go; model.go; resources.go; validate.go,—,—,extract.go; graphql.go; helpers.go; model.go; resources.go; validate.go,72020,0,155,Address; Age; Annotation; Attachment; Availability; AvailabilityAvailableTime; AvailabilityNotAvailableTime; BodyStructure; BodyStructureIncludedStructure; BodyStructureIncludedStructureBodyLandmarkOrientation; BodyStructureIncludedStructureBodyLandmarkOrientationDistanceFromLandmark; CodeableConcept; CodeableReference; Coding; ConcreteResource; Condition; ConditionParticipant; ConditionStage; ContactDetail; ContactPoint; Count; DataRequirement; DataRequirementCodeFilter; DataRequirementDateFilter; DataRequirementSort; DataRequirementValueFilter; DiagnosticReport; DiagnosticReportMedia; DiagnosticReportSupportingInfo; Directory; Distance; DocumentReference; DocumentReferenceAttester; DocumentReferenceContent; DocumentReferenceContentProfile; DocumentReferenceRelatesTo; Dosage; DosageDoseAndRate; Duration; EdgeDocument; Expression; ExtendedContactDetail; Extension; ExtractEdges; FHIRComments; FHIRPrimitiveExtension; FamilyMemberHistory; FamilyMemberHistoryCondition; FamilyMemberHistoryParticipant; FamilyMemberHistoryProcedure; GetID; GetResourceType; Group; GroupCharacteristic; GroupMember; HumanName; Identifier; ImagingStudy; ImagingStudySeries; ImagingStudySeriesInstance; ImagingStudySeriesPerformer; IsFHIRResource; MarshalJSON; Medication; MedicationAdministration; MedicationAdministrationDosage; MedicationAdministrationPerformer; MedicationBatch; MedicationIngredient; MedicationRequest; MedicationRequestDispenseRequest; MedicationRequestDispenseRequestInitialFill; MedicationRequestSubstitution; MedicationStatement; MedicationStatementAdherence; Meta; Money; Narrative; NewConcreteResource; Observation; ObservationComponent; ObservationReferenceRange; ObservationTriggeredBy; Organization; OrganizationQualification; ParameterDefinition; Patient; PatientCommunication; PatientContact; PatientLink; Period; Practitioner; PractitionerCommunication; PractitionerQualification; PractitionerRole; Procedure; ProcedureFocalDevice; ProcedurePerformer; Quantity; Range; Ratio; RatioRange; Reference; RelatedArtifact; ResearchStudy; ResearchStudyAssociatedParty; ResearchStudyComparisonGroup; ResearchStudyLabel; ResearchStudyObjective; ResearchStudyOutcomeMeasure; ResearchStudyProgressStatus; ResearchStudyRecruitment; ResearchSubject; ResearchSubjectProgress; Resource; Result; SampledData; Signature; Specimen; SpecimenCollection; SpecimenContainer; SpecimenFeature; SpecimenProcessing; Substance; SubstanceDefinition; SubstanceDefinitionCharacterization; SubstanceDefinitionCode; SubstanceDefinitionMoiety; SubstanceDefinitionMolecularWeight; SubstanceDefinitionName; SubstanceDefinitionNameOfficial; SubstanceDefinitionProperty; SubstanceDefinitionRelationship; SubstanceDefinitionSourceMaterial; SubstanceDefinitionStructure; SubstanceDefinitionStructureRepresentation; SubstanceIngredient; Task; TaskInput; TaskOutput; TaskPerformer; TaskRestriction; Timing; TimingRepeat; TriggerDefinition; UnmarshalJSON; UsageContext; Validate; ValidateAndExtract; ValidateFhirBinary; ValidateFhirDate; ValidateFhirDateTime; ValidateFhirTime; ValidateFhirURI; ValidateFhirUUID,internal/fhir/schema,—,generated/graphql/graph/executor; internal/api/graphql/graph/resolver; internal/dataframe/recipe; internal/dataframe/spec; internal/ingest,—,github.com/bytedance/sonic; github.com/google/uuid,1 exported interface(s),—,"generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion",go test ./generated/fhir,inventory complete; semantic disposition pending +generated/fhir,github.com/calypr/loom/generated/fhir,fhir,generated,"Expose generated FHIR resource models and their extraction, validation, and GraphQL helpers.",extract.go; graphql.go; helpers.go; model.go; resources.go; validate.go,—,—,extract.go; graphql.go; helpers.go; model.go; resources.go; validate.go,72020,0,155,Address; Age; Annotation; Attachment; Availability; AvailabilityAvailableTime; AvailabilityNotAvailableTime; BodyStructure; BodyStructureIncludedStructure; BodyStructureIncludedStructureBodyLandmarkOrientation; BodyStructureIncludedStructureBodyLandmarkOrientationDistanceFromLandmark; CodeableConcept; CodeableReference; Coding; ConcreteResource; Condition; ConditionParticipant; ConditionStage; ContactDetail; ContactPoint; Count; DataRequirement; DataRequirementCodeFilter; DataRequirementDateFilter; DataRequirementSort; DataRequirementValueFilter; DiagnosticReport; DiagnosticReportMedia; DiagnosticReportSupportingInfo; Directory; Distance; DocumentReference; DocumentReferenceAttester; DocumentReferenceContent; DocumentReferenceContentProfile; DocumentReferenceRelatesTo; Dosage; DosageDoseAndRate; Duration; EdgeDocument; Expression; ExtendedContactDetail; Extension; ExtractEdges; FHIRComments; FHIRPrimitiveExtension; FamilyMemberHistory; FamilyMemberHistoryCondition; FamilyMemberHistoryParticipant; FamilyMemberHistoryProcedure; GetID; GetResourceType; Group; GroupCharacteristic; GroupMember; HumanName; Identifier; ImagingStudy; ImagingStudySeries; ImagingStudySeriesInstance; ImagingStudySeriesPerformer; IsFHIRResource; MarshalJSON; Medication; MedicationAdministration; MedicationAdministrationDosage; MedicationAdministrationPerformer; MedicationBatch; MedicationIngredient; MedicationRequest; MedicationRequestDispenseRequest; MedicationRequestDispenseRequestInitialFill; MedicationRequestSubstitution; MedicationStatement; MedicationStatementAdherence; Meta; Money; Narrative; NewConcreteResource; Observation; ObservationComponent; ObservationReferenceRange; ObservationTriggeredBy; Organization; OrganizationQualification; ParameterDefinition; Patient; PatientCommunication; PatientContact; PatientLink; Period; Practitioner; PractitionerCommunication; PractitionerQualification; PractitionerRole; Procedure; ProcedureFocalDevice; ProcedurePerformer; Quantity; Range; Ratio; RatioRange; Reference; RelatedArtifact; ResearchStudy; ResearchStudyAssociatedParty; ResearchStudyComparisonGroup; ResearchStudyLabel; ResearchStudyObjective; ResearchStudyOutcomeMeasure; ResearchStudyProgressStatus; ResearchStudyRecruitment; ResearchSubject; ResearchSubjectProgress; Resource; Result; SampledData; Signature; Specimen; SpecimenCollection; SpecimenContainer; SpecimenFeature; SpecimenProcessing; Substance; SubstanceDefinition; SubstanceDefinitionCharacterization; SubstanceDefinitionCode; SubstanceDefinitionMoiety; SubstanceDefinitionMolecularWeight; SubstanceDefinitionName; SubstanceDefinitionNameOfficial; SubstanceDefinitionProperty; SubstanceDefinitionRelationship; SubstanceDefinitionSourceMaterial; SubstanceDefinitionStructure; SubstanceDefinitionStructureRepresentation; SubstanceIngredient; Task; TaskInput; TaskOutput; TaskPerformer; TaskRestriction; Timing; TimingRepeat; TriggerDefinition; UnmarshalJSON; UsageContext; Validate; ValidateAndExtract; ValidateFhirBinary; ValidateFhirDate; ValidateFhirDateTime; ValidateFhirTime; ValidateFhirURI; ValidateFhirUUID,internal/fhir/schema,—,generated/graphql/graph/executor; internal/api/graphql/graph/resolver; internal/dataframe/recipe; internal/dataframe/spec; internal/ingest,internal/dataframe/benchmark,github.com/bytedance/sonic; github.com/google/uuid,1 exported interface(s),—,"generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion",go test ./generated/fhir,inventory complete; semantic disposition pending generated/fhirschema,github.com/calypr/loom/generated/fhirschema,fhirschema,generated,Expose generated raw FHIR schema metadata tables.,generated.go,—,—,generated.go,55277,0,3,Definition; Property; Traversal,—,—,internal/fhir/schema,internal/catalog,—,—,deprecated=1; compatibility=1,"generated boundary; change its source/generator, not emitted code",go test ./generated/fhirschema,inventory complete; semantic disposition pending -generated/graphql/graph/executor,github.com/calypr/loom/generated/graphql/graph/executor,executor,generated,Provide gqlgen-generated GraphQL executable schema and execution plumbing.,fhir_schema.generated.go; prelude.generated.go; root_.generated.go; schema.generated.go,—,—,fhir_schema.generated.go; prelude.generated.go; root_.generated.go; schema.generated.go,155289,0,328,Address; AddressResolver; Age; AgeResolver; Annotation; AnnotationResolver; Attachment; AttachmentResolver; Availability; AvailabilityAvailableTime; AvailabilityAvailableTimeResolver; AvailabilityNotAvailableTime; AvailabilityNotAvailableTimeResolver; AvailabilityResolver; BodyStructure; BodyStructureIncludedStructure; BodyStructureIncludedStructureBodyLandmarkOrientation; BodyStructureIncludedStructureBodyLandmarkOrientationDistanceFromLandmark; BodyStructureIncludedStructureBodyLandmarkOrientationDistanceFromLandmarkResolver; BodyStructureIncludedStructureBodyLandmarkOrientationResolver; BodyStructureIncludedStructureResolver; BodyStructureResolver; CodeableConcept; CodeableConceptResolver; CodeableReference; CodeableReferenceResolver; Coding; CodingResolver; Complexity; ComplexityRoot; Condition; ConditionParticipant; ConditionParticipantResolver; ConditionResolver; ConditionStage; ConditionStageResolver; Config; ContactDetail; ContactDetailResolver; ContactPoint; ContactPointResolver; Count; CountResolver; DataRequirement; DataRequirementCodeFilter; DataRequirementCodeFilterResolver; DataRequirementDateFilter; DataRequirementDateFilterResolver; DataRequirementResolver; DataRequirementSort; DataRequirementSortResolver; DataRequirementValueFilter; DataRequirementValueFilterResolver; DataframeAggregateResult; DataframeAggregationsResult; DataframeColumn; DataframeCompilerPlanDiagnostics; DataframeMaterialization; DataframeOptimizationDecision; DataframeOptimizationPolicy; DataframePageInfo; DataframeQueryDiagnostics; DataframeRecipeArangoAssessment; DataframeRecipeColumn; DataframeRecipeExecution; DataframeRecipeExecutionOutput; DataframeRecipeExpansionExplanation; DataframeRecipeExplainCollectionScan; DataframeRecipeExplainIndexLocation; DataframeRecipeExplainIndexSummary; DataframeRecipeExplainPlanEstimate; DataframeRecipeExplainWarning; DataframeRecipeExplanation; DataframeRecipeExpressionExplanation; DataframeRecipeOptimizationDecision; DataframeRecipeOptimizationExplanation; DataframeRecipeOptimizationRuleState; DataframeRecipeOutputExplanation; DataframeRecipeOutputValidation; DataframeRecipePhysicalExplanation; DataframeRecipePhysicalOutputExplanation; DataframeRecipePreflight; DataframeRecipePreview; DataframeRecipePreviewOutput; DataframeRecipeResult; DataframeRecipeResultOutput; DataframeRecipeRevision; DataframeRecipeValidation; DataframeRichSourceReuse; DataframeRowConnection; DataframeSelector; DiagnosticReport; DiagnosticReportMedia; DiagnosticReportMediaResolver; DiagnosticReportResolver; DiagnosticReportSupportingInfo; DiagnosticReportSupportingInfoResolver; DirectiveRoot; Directory; Distance; DistanceResolver; DocumentReference; DocumentReferenceAttester; DocumentReferenceAttesterResolver; DocumentReferenceContent; DocumentReferenceContentProfile; DocumentReferenceContentProfileResolver; DocumentReferenceContentResolver; DocumentReferenceRelatesTo; DocumentReferenceRelatesToResolver; DocumentReferenceResolver; Dosage; DosageDoseAndRate; DosageDoseAndRateResolver; DosageResolver; Duration; DurationResolver; Exec; Expression; ExpressionResolver; ExtendedContactDetail; ExtendedContactDetailResolver; Extension; ExtensionResolver; FHIRPrimitiveExtension; FHIRPrimitiveExtensionResolver; FamilyMemberHistory; FamilyMemberHistoryCondition; FamilyMemberHistoryConditionResolver; FamilyMemberHistoryParticipant; FamilyMemberHistoryParticipantResolver; FamilyMemberHistoryProcedure; FamilyMemberHistoryProcedureResolver; FamilyMemberHistoryResolver; FhirDataframeResult; FhirGraphNode; FhirGraphPageInfo; FhirGraphPath; FhirGraphQueryExplanation; FhirGraphQueryResult; FhirGraphRelationship; Group; GroupCharacteristic; GroupCharacteristicResolver; GroupMember; GroupMemberResolver; GroupResolver; HumanName; HumanNameResolver; Identifier; IdentifierResolver; ImagingStudy; ImagingStudyResolver; ImagingStudySeries; ImagingStudySeriesInstance; ImagingStudySeriesInstanceResolver; ImagingStudySeriesPerformer; ImagingStudySeriesPerformerResolver; ImagingStudySeriesResolver; MarshalJSON; Medication; MedicationAdministration; MedicationAdministrationDosage; MedicationAdministrationDosageResolver; MedicationAdministrationPerformer; MedicationAdministrationPerformerResolver; MedicationAdministrationResolver; MedicationBatch; MedicationBatchResolver; MedicationIngredient; MedicationIngredientResolver; MedicationRequest; MedicationRequestDispenseRequest; MedicationRequestDispenseRequestInitialFill; MedicationRequestDispenseRequestInitialFillResolver; MedicationRequestDispenseRequestResolver; MedicationRequestResolver; MedicationRequestSubstitution; MedicationRequestSubstitutionResolver; MedicationResolver; MedicationStatement; MedicationStatementAdherence; MedicationStatementAdherenceResolver; MedicationStatementResolver; Meta; MetaResolver; Money; MoneyResolver; Mutation; MutationResolver; Narrative; NarrativeResolver; NewExecutableSchema; Observation; ObservationComponent; ObservationComponentResolver; ObservationReferenceRange; ObservationReferenceRangeResolver; ObservationResolver; ObservationTriggeredBy; ObservationTriggeredByResolver; Organization; OrganizationQualification; OrganizationQualificationResolver; OrganizationResolver; ParameterDefinition; ParameterDefinitionResolver; Patient; PatientCommunication; PatientCommunicationResolver; PatientContact; PatientContactResolver; PatientLink; PatientLinkResolver; PatientResolver; Period; PeriodResolver; Practitioner; PractitionerCommunication; PractitionerCommunicationResolver; PractitionerQualification; PractitionerQualificationResolver; PractitionerResolver; PractitionerRole; PractitionerRoleResolver; Procedure; ProcedureFocalDevice; ProcedureFocalDeviceResolver; ProcedurePerformer; ProcedurePerformerResolver; ProcedureResolver; Quantity; QuantityResolver; QueryResolver; Range; RangeResolver; Ratio; RatioRange; RatioRangeResolver; RatioResolver; Reference; ReferenceResolver; RelatedArtifact; RelatedArtifactResolver; ResearchStudy; ResearchStudyAssociatedParty; ResearchStudyAssociatedPartyResolver; ResearchStudyComparisonGroup; ResearchStudyComparisonGroupResolver; ResearchStudyLabel; ResearchStudyLabelResolver; ResearchStudyObjective; ResearchStudyObjectiveResolver; ResearchStudyOutcomeMeasure; ResearchStudyOutcomeMeasureResolver; ResearchStudyProgressStatus; ResearchStudyProgressStatusResolver; ResearchStudyRecruitment; ResearchStudyRecruitmentResolver; ResearchStudyResolver; ResearchSubject; ResearchSubjectProgress; ResearchSubjectProgressResolver; ResearchSubjectResolver; ResolverRoot; Resource; ResourceResolver; SampledData; SampledDataResolver; Schema; Signature; SignatureResolver; Specimen; SpecimenCollection; SpecimenCollectionResolver; SpecimenContainer; SpecimenContainerResolver; SpecimenFeature; SpecimenFeatureResolver; SpecimenProcessing; SpecimenProcessingResolver; SpecimenResolver; Substance; SubstanceDefinition; SubstanceDefinitionCharacterization; SubstanceDefinitionCharacterizationResolver; SubstanceDefinitionCode; SubstanceDefinitionCodeResolver; SubstanceDefinitionMoiety; SubstanceDefinitionMoietyResolver; SubstanceDefinitionMolecularWeight; SubstanceDefinitionMolecularWeightResolver; SubstanceDefinitionName; SubstanceDefinitionNameOfficial; SubstanceDefinitionNameOfficialResolver; SubstanceDefinitionNameResolver; SubstanceDefinitionProperty; SubstanceDefinitionPropertyResolver; SubstanceDefinitionRelationship; SubstanceDefinitionRelationshipResolver; SubstanceDefinitionResolver; SubstanceDefinitionSourceMaterial; SubstanceDefinitionSourceMaterialResolver; SubstanceDefinitionStructure; SubstanceDefinitionStructureRepresentation; SubstanceDefinitionStructureRepresentationResolver; SubstanceDefinitionStructureResolver; SubstanceIngredient; SubstanceIngredientResolver; SubstanceResolver; Task; TaskInput; TaskInputResolver; TaskOutput; TaskOutputResolver; TaskPerformer; TaskPerformerResolver; TaskResolver; TaskRestriction; TaskRestrictionResolver; Timing; TimingRepeat; TimingRepeatResolver; TimingResolver; TriggerDefinition; TriggerDefinitionResolver; UsageContext; UsageContextResolver,generated/fhir; generated/graphql/graph/model,—,internal/api/graphql/graph; internal/api/graphql/graph/resolver,—,github.com/99designs/gqlgen/graphql; github.com/99designs/gqlgen/graphql/introspection; github.com/vektah/gqlparser/v2; github.com/vektah/gqlparser/v2/ast,138 exported interface(s),—,"generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion",go test ./generated/graphql/graph/executor,inventory complete; semantic disposition pending -generated/graphql/graph/model,github.com/calypr/loom/generated/graphql/graph/model,model,generated,Provide gqlgen-generated GraphQL transport model types.,models.go,—,—,models.go,1333,0,95,DataframeAggregateInput; DataframeAggregateResult; DataframeAggregationSpecInput; DataframeAggregationsInput; DataframeAggregationsResult; DataframeColumn; DataframeCompilerPlanDiagnostics; DataframeDatasetInput; DataframeFilterInput; DataframeMaterialization; DataframeMaterializationState; DataframeOptimizationDecision; DataframeOptimizationPolicy; DataframePageInfo; DataframeQueryDiagnostics; DataframeRecipeArangoAssessment; DataframeRecipeBindingsInput; DataframeRecipeColumn; DataframeRecipeExecution; DataframeRecipeExecutionOutput; DataframeRecipeExecutionState; DataframeRecipeExpansionExplanation; DataframeRecipeExplainCollectionScan; DataframeRecipeExplainIndexLocation; DataframeRecipeExplainIndexSummary; DataframeRecipeExplainPlanEstimate; DataframeRecipeExplainWarning; DataframeRecipeExplanation; DataframeRecipeExpressionExplanation; DataframeRecipeOptimizationDecision; DataframeRecipeOptimizationExplanation; DataframeRecipeOptimizationRuleState; DataframeRecipeOutputExplanation; DataframeRecipeOutputValidation; DataframeRecipePhysicalExplanation; DataframeRecipePhysicalOutputExplanation; DataframeRecipePreflight; DataframeRecipePreview; DataframeRecipePreviewOutput; DataframeRecipeResult; DataframeRecipeResultOutput; DataframeRecipeRevision; DataframeRecipeValidation; DataframeRichSourceReuse; DataframeRowConnection; DataframeRowsInput; DataframeSelector; DataframeSelectorInput; DataframeSortInput; ExplainDataframeRecipeInput; FHIRResource; FHIRResourceType; FhirAggregateInput; FhirAggregateOperation; FhirCatalogProjectionInput; FhirCodeValueInput; FhirColumnNaming; FhirDataframeInput; FhirDataframeResult; FhirFieldPredicateInput; FhirFieldPredicateOperation; FhirFieldSelectInput; FhirFieldSelectorInput; FhirFilterInput; FhirFilterOperator; FhirFilterQuantifier; FhirFilterValueInput; FhirFilterValueKind; FhirGraphNode; FhirGraphPageInfo; FhirGraphPath; FhirGraphQueryExplanation; FhirGraphQueryInput; FhirGraphQueryResult; FhirGraphRelationship; FhirGraphTraversalInput; FhirPivotDiscoveryInput; FhirPivotInput; FhirRepresentativeSliceInput; FhirTraversalMatchMode; FhirTraversalStepInput; FhirValueMode; IsValid; MarshalGQL; MarshalJSON; Mutation; PreflightDataframeRecipeInput; PreviewDataframeRecipeInput; Query; RegisterDataframeRecipeRevisionInput; RunDataframeRecipeInput; String; UnmarshalGQL; UnmarshalJSON; ValidateDataframeRecipeInput,—,—,cmd/dataframe-profile; generated/graphql/graph/executor; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver,internal/server,—,1 exported interface(s),—,"generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion",go test ./generated/graphql/graph/model,inventory complete; semantic disposition pending +generated/graphql/graph/executor,github.com/calypr/loom/generated/graphql/graph/executor,executor,generated,Provide gqlgen-generated GraphQL executable schema and execution plumbing.,fhir_schema.generated.go; prelude.generated.go; root_.generated.go; schema.generated.go,—,—,fhir_schema.generated.go; prelude.generated.go; root_.generated.go; schema.generated.go,155331,0,328,Address; AddressResolver; Age; AgeResolver; Annotation; AnnotationResolver; Attachment; AttachmentResolver; Availability; AvailabilityAvailableTime; AvailabilityAvailableTimeResolver; AvailabilityNotAvailableTime; AvailabilityNotAvailableTimeResolver; AvailabilityResolver; BodyStructure; BodyStructureIncludedStructure; BodyStructureIncludedStructureBodyLandmarkOrientation; BodyStructureIncludedStructureBodyLandmarkOrientationDistanceFromLandmark; BodyStructureIncludedStructureBodyLandmarkOrientationDistanceFromLandmarkResolver; BodyStructureIncludedStructureBodyLandmarkOrientationResolver; BodyStructureIncludedStructureResolver; BodyStructureResolver; CodeableConcept; CodeableConceptResolver; CodeableReference; CodeableReferenceResolver; Coding; CodingResolver; Complexity; ComplexityRoot; Condition; ConditionParticipant; ConditionParticipantResolver; ConditionResolver; ConditionStage; ConditionStageResolver; Config; ContactDetail; ContactDetailResolver; ContactPoint; ContactPointResolver; Count; CountResolver; DataRequirement; DataRequirementCodeFilter; DataRequirementCodeFilterResolver; DataRequirementDateFilter; DataRequirementDateFilterResolver; DataRequirementResolver; DataRequirementSort; DataRequirementSortResolver; DataRequirementValueFilter; DataRequirementValueFilterResolver; DataframeAggregateResult; DataframeAggregationsResult; DataframeColumn; DataframeCompilerPlanDiagnostics; DataframeMaterialization; DataframeOptimizationDecision; DataframeOptimizationPolicy; DataframePageInfo; DataframeQueryDiagnostics; DataframeRecipeArangoAssessment; DataframeRecipeColumn; DataframeRecipeExecution; DataframeRecipeExecutionOutput; DataframeRecipeExpansionExplanation; DataframeRecipeExplainCollectionScan; DataframeRecipeExplainIndexLocation; DataframeRecipeExplainIndexSummary; DataframeRecipeExplainPlanEstimate; DataframeRecipeExplainWarning; DataframeRecipeExplanation; DataframeRecipeExpressionExplanation; DataframeRecipeOptimizationDecision; DataframeRecipeOptimizationExplanation; DataframeRecipeOptimizationRuleState; DataframeRecipeOutputExplanation; DataframeRecipeOutputValidation; DataframeRecipePhysicalExplanation; DataframeRecipePhysicalOutputExplanation; DataframeRecipePreflight; DataframeRecipePreview; DataframeRecipePreviewOutput; DataframeRecipeResult; DataframeRecipeResultOutput; DataframeRecipeRevision; DataframeRecipeValidation; DataframeRichSourceReuse; DataframeRowConnection; DataframeSelector; DiagnosticReport; DiagnosticReportMedia; DiagnosticReportMediaResolver; DiagnosticReportResolver; DiagnosticReportSupportingInfo; DiagnosticReportSupportingInfoResolver; DirectiveRoot; Directory; Distance; DistanceResolver; DocumentReference; DocumentReferenceAttester; DocumentReferenceAttesterResolver; DocumentReferenceContent; DocumentReferenceContentProfile; DocumentReferenceContentProfileResolver; DocumentReferenceContentResolver; DocumentReferenceRelatesTo; DocumentReferenceRelatesToResolver; DocumentReferenceResolver; Dosage; DosageDoseAndRate; DosageDoseAndRateResolver; DosageResolver; Duration; DurationResolver; Exec; Expression; ExpressionResolver; ExtendedContactDetail; ExtendedContactDetailResolver; Extension; ExtensionResolver; FHIRPrimitiveExtension; FHIRPrimitiveExtensionResolver; FamilyMemberHistory; FamilyMemberHistoryCondition; FamilyMemberHistoryConditionResolver; FamilyMemberHistoryParticipant; FamilyMemberHistoryParticipantResolver; FamilyMemberHistoryProcedure; FamilyMemberHistoryProcedureResolver; FamilyMemberHistoryResolver; FhirDataframeResult; FhirGraphNode; FhirGraphPageInfo; FhirGraphPath; FhirGraphQueryExplanation; FhirGraphQueryResult; FhirGraphRelationship; Group; GroupCharacteristic; GroupCharacteristicResolver; GroupMember; GroupMemberResolver; GroupResolver; HumanName; HumanNameResolver; Identifier; IdentifierResolver; ImagingStudy; ImagingStudyResolver; ImagingStudySeries; ImagingStudySeriesInstance; ImagingStudySeriesInstanceResolver; ImagingStudySeriesPerformer; ImagingStudySeriesPerformerResolver; ImagingStudySeriesResolver; MarshalJSON; Medication; MedicationAdministration; MedicationAdministrationDosage; MedicationAdministrationDosageResolver; MedicationAdministrationPerformer; MedicationAdministrationPerformerResolver; MedicationAdministrationResolver; MedicationBatch; MedicationBatchResolver; MedicationIngredient; MedicationIngredientResolver; MedicationRequest; MedicationRequestDispenseRequest; MedicationRequestDispenseRequestInitialFill; MedicationRequestDispenseRequestInitialFillResolver; MedicationRequestDispenseRequestResolver; MedicationRequestResolver; MedicationRequestSubstitution; MedicationRequestSubstitutionResolver; MedicationResolver; MedicationStatement; MedicationStatementAdherence; MedicationStatementAdherenceResolver; MedicationStatementResolver; Meta; MetaResolver; Money; MoneyResolver; Mutation; MutationResolver; Narrative; NarrativeResolver; NewExecutableSchema; Observation; ObservationComponent; ObservationComponentResolver; ObservationReferenceRange; ObservationReferenceRangeResolver; ObservationResolver; ObservationTriggeredBy; ObservationTriggeredByResolver; Organization; OrganizationQualification; OrganizationQualificationResolver; OrganizationResolver; ParameterDefinition; ParameterDefinitionResolver; Patient; PatientCommunication; PatientCommunicationResolver; PatientContact; PatientContactResolver; PatientLink; PatientLinkResolver; PatientResolver; Period; PeriodResolver; Practitioner; PractitionerCommunication; PractitionerCommunicationResolver; PractitionerQualification; PractitionerQualificationResolver; PractitionerResolver; PractitionerRole; PractitionerRoleResolver; Procedure; ProcedureFocalDevice; ProcedureFocalDeviceResolver; ProcedurePerformer; ProcedurePerformerResolver; ProcedureResolver; Quantity; QuantityResolver; QueryResolver; Range; RangeResolver; Ratio; RatioRange; RatioRangeResolver; RatioResolver; Reference; ReferenceResolver; RelatedArtifact; RelatedArtifactResolver; ResearchStudy; ResearchStudyAssociatedParty; ResearchStudyAssociatedPartyResolver; ResearchStudyComparisonGroup; ResearchStudyComparisonGroupResolver; ResearchStudyLabel; ResearchStudyLabelResolver; ResearchStudyObjective; ResearchStudyObjectiveResolver; ResearchStudyOutcomeMeasure; ResearchStudyOutcomeMeasureResolver; ResearchStudyProgressStatus; ResearchStudyProgressStatusResolver; ResearchStudyRecruitment; ResearchStudyRecruitmentResolver; ResearchStudyResolver; ResearchSubject; ResearchSubjectProgress; ResearchSubjectProgressResolver; ResearchSubjectResolver; ResolverRoot; Resource; ResourceResolver; SampledData; SampledDataResolver; Schema; Signature; SignatureResolver; Specimen; SpecimenCollection; SpecimenCollectionResolver; SpecimenContainer; SpecimenContainerResolver; SpecimenFeature; SpecimenFeatureResolver; SpecimenProcessing; SpecimenProcessingResolver; SpecimenResolver; Substance; SubstanceDefinition; SubstanceDefinitionCharacterization; SubstanceDefinitionCharacterizationResolver; SubstanceDefinitionCode; SubstanceDefinitionCodeResolver; SubstanceDefinitionMoiety; SubstanceDefinitionMoietyResolver; SubstanceDefinitionMolecularWeight; SubstanceDefinitionMolecularWeightResolver; SubstanceDefinitionName; SubstanceDefinitionNameOfficial; SubstanceDefinitionNameOfficialResolver; SubstanceDefinitionNameResolver; SubstanceDefinitionProperty; SubstanceDefinitionPropertyResolver; SubstanceDefinitionRelationship; SubstanceDefinitionRelationshipResolver; SubstanceDefinitionResolver; SubstanceDefinitionSourceMaterial; SubstanceDefinitionSourceMaterialResolver; SubstanceDefinitionStructure; SubstanceDefinitionStructureRepresentation; SubstanceDefinitionStructureRepresentationResolver; SubstanceDefinitionStructureResolver; SubstanceIngredient; SubstanceIngredientResolver; SubstanceResolver; Task; TaskInput; TaskInputResolver; TaskOutput; TaskOutputResolver; TaskPerformer; TaskPerformerResolver; TaskResolver; TaskRestriction; TaskRestrictionResolver; Timing; TimingRepeat; TimingRepeatResolver; TimingResolver; TriggerDefinition; TriggerDefinitionResolver; UsageContext; UsageContextResolver,generated/fhir; generated/graphql/graph/model,—,internal/api/graphql/graph; internal/api/graphql/graph/resolver,—,github.com/99designs/gqlgen/graphql; github.com/99designs/gqlgen/graphql/introspection; github.com/vektah/gqlparser/v2; github.com/vektah/gqlparser/v2/ast,138 exported interface(s),legacy=1,"generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion",go test ./generated/graphql/graph/executor,inventory complete; semantic disposition pending +generated/graphql/graph/model,github.com/calypr/loom/generated/graphql/graph/model,model,generated,Provide gqlgen-generated GraphQL transport model types.,models.go,—,—,models.go,1334,0,95,DataframeAggregateInput; DataframeAggregateResult; DataframeAggregationSpecInput; DataframeAggregationsInput; DataframeAggregationsResult; DataframeColumn; DataframeCompilerPlanDiagnostics; DataframeDatasetInput; DataframeFilterInput; DataframeMaterialization; DataframeMaterializationState; DataframeOptimizationDecision; DataframeOptimizationPolicy; DataframePageInfo; DataframeQueryDiagnostics; DataframeRecipeArangoAssessment; DataframeRecipeBindingsInput; DataframeRecipeColumn; DataframeRecipeExecution; DataframeRecipeExecutionOutput; DataframeRecipeExecutionState; DataframeRecipeExpansionExplanation; DataframeRecipeExplainCollectionScan; DataframeRecipeExplainIndexLocation; DataframeRecipeExplainIndexSummary; DataframeRecipeExplainPlanEstimate; DataframeRecipeExplainWarning; DataframeRecipeExplanation; DataframeRecipeExpressionExplanation; DataframeRecipeOptimizationDecision; DataframeRecipeOptimizationExplanation; DataframeRecipeOptimizationRuleState; DataframeRecipeOutputExplanation; DataframeRecipeOutputValidation; DataframeRecipePhysicalExplanation; DataframeRecipePhysicalOutputExplanation; DataframeRecipePreflight; DataframeRecipePreview; DataframeRecipePreviewOutput; DataframeRecipeResult; DataframeRecipeResultOutput; DataframeRecipeRevision; DataframeRecipeValidation; DataframeRichSourceReuse; DataframeRowConnection; DataframeRowsInput; DataframeSelector; DataframeSelectorInput; DataframeSortInput; ExplainDataframeRecipeInput; FHIRResource; FHIRResourceType; FhirAggregateInput; FhirAggregateOperation; FhirCatalogProjectionInput; FhirCodeValueInput; FhirColumnNaming; FhirDataframeInput; FhirDataframeResult; FhirFieldPredicateInput; FhirFieldPredicateOperation; FhirFieldSelectInput; FhirFieldSelectorInput; FhirFilterInput; FhirFilterOperator; FhirFilterQuantifier; FhirFilterValueInput; FhirFilterValueKind; FhirGraphNode; FhirGraphPageInfo; FhirGraphPath; FhirGraphQueryExplanation; FhirGraphQueryInput; FhirGraphQueryResult; FhirGraphRelationship; FhirGraphTraversalInput; FhirPivotDiscoveryInput; FhirPivotInput; FhirRepresentativeSliceInput; FhirTraversalMatchMode; FhirTraversalStepInput; FhirValueMode; IsValid; MarshalGQL; MarshalJSON; Mutation; PreflightDataframeRecipeInput; PreviewDataframeRecipeInput; Query; RegisterDataframeRecipeRevisionInput; RunDataframeRecipeInput; String; UnmarshalGQL; UnmarshalJSON; ValidateDataframeRecipeInput,—,—,cmd/dataframe-profile; generated/graphql/graph/executor; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver,internal/server,—,1 exported interface(s),—,"generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion",go test ./generated/graphql/graph/model,inventory complete; semantic disposition pending generated/loomapi,github.com/calypr/loom/generated/loomapi,loomapi,generated,"Provide OpenAPI-generated HTTP request, response, and strict-server contracts.",api.gen.go; strict_json.go,construction_json_test.go; correlated_json_test.go; request_boundary_test.go; selection_json_test.go; strict_json_test.go,—,api.gen.go,23408,512,1620,APIVersion; AcceptedConstruction; AcceptedConstructionReceipt; Action; ActionColumn; ActivateDatasetGeneration; ActivateDatasetGeneration200JSONResponse; ActivateDatasetGeneration400JSONResponse; ActivateDatasetGeneration401JSONResponse; ActivateDatasetGeneration403JSONResponse; ActivateDatasetGeneration409JSONResponse; ActivateDatasetGeneration500JSONResponse; ActivateDatasetGeneration503JSONResponse; ActivateDatasetGenerationParams; ActivateDatasetGenerationRequestObject; ActivateDatasetGenerationResponseObject; AggregateOperationCapability; AggregateOperationCapabilityOperation; AggregateOperationCapabilityResultCardinality; AggregateOperationCapabilityRowContext; AggregateSource; AggregateSourceOperation; AggregateTransformationCapabilities; ApplyCommandsRequest; ApplyCommandsResponse; ApplyExplorerBuilderCommands; ApplyExplorerBuilderCommands200JSONResponse; ApplyExplorerBuilderCommands400JSONResponse; ApplyExplorerBuilderCommands401JSONResponse; ApplyExplorerBuilderCommands403JSONResponse; ApplyExplorerBuilderCommands404JSONResponse; ApplyExplorerBuilderCommands409JSONResponse; ApplyExplorerBuilderCommands422JSONResponse; ApplyExplorerBuilderCommands500JSONResponse; ApplyExplorerBuilderCommands503JSONResponse; ApplyExplorerBuilderCommandsJSONRequestBody; ApplyExplorerBuilderCommandsParams; ApplyExplorerBuilderCommandsRequestObject; ApplyExplorerBuilderCommandsResponseObject; Artifact; ArtifactFormat; ArtifactMediaType; ArtifactPrepareRequest; ArtifactState; AsConfiguredColumnReadyResolution; AsConfiguredColumnUnavailableResolution; AsConstructionGroupCodingIdentitySourceOption; AsConstructionGroupFieldScalarSourceOption; AsConstructionGroupFieldSourceChoice; AsConstructionGroupFieldSourceCodingIdentityBinding; AsConstructionGroupFieldSourceCurrentStageInput; AsConstructionGroupFieldSourceInputBinding; AsConstructionGroupFieldSourceRelatedOccurrenceInput; AsConstructionGroupFieldSourceScalarBinding; AsConstructionGroupKeyFieldSource; AsConstructionGroupKeyInputColumn; AsConstructionGroupKeySemanticCategory; AsConstructionGroupSemanticCategoryChoice; AsConstructionRowValue0; AsConstructionRowValue1; AsContributorCodeValue; AsContributorStringValue; AsExactCategoryRecodeTransformation; AsExactSemanticInventoryItemIdentity; AsFamilySemanticInventoryItemIdentity; AsFieldChoiceSource; AsFieldConstructionChoiceSearchSource; AsFieldPresentationGroupUnderSemantic; AsFieldPresentationKeepRaw; AsFieldPresentationReady; AsFieldPresentationUnavailable; AsLegacyErrorBody; AsLegacyErrorResponseError0; AsRemoveColumnTransformation; AsRowDefinitionComparisonAvailable; AsRowDefinitionComparisonUnavailable; AsRowDefinitionExpandedSelection; AsRowDefinitionExplicitGroupSelection; AsRowDefinitionFieldGroupSelection; AsRowDefinitionRecordsSelection; AsSemanticBindingChoiceSource; AsSemanticConstructionChoiceSearchSource; AsSetColumnTransformation; AsTableShapeComparisonAvailable; AsTableShapeComparisonUnavailable; AssessExplorerRowChange; AssessExplorerRowChange200JSONResponse; AssessExplorerRowChange400JSONResponse; AssessExplorerRowChange401JSONResponse; AssessExplorerRowChange403JSONResponse; AssessExplorerRowChange404JSONResponse; AssessExplorerRowChange409JSONResponse; AssessExplorerRowChange422JSONResponse; AssessExplorerRowChange500JSONResponse; AssessExplorerRowChange503JSONResponse; AssessExplorerRowChangeJSONRequestBody; AssessExplorerRowChangeRequestObject; AssessExplorerRowChangeResponseObject; AssessRowChangeRequest; AssessRowChangeRequestMode; AuthResourcePath; AuthoringBadRequest; AuthoringBadRequestJSONResponse; AuthoringCapability; AuthoringCapabilityKind; AuthoringCapabilityOperations; AuthoringClientClosedRequest; AuthoringClientClosedRequestJSONResponse; AuthoringCommand; AuthoringCommandForm; AuthoringCommandInitialPresentation; AuthoringCommandMatchMode; AuthoringCommandRowValuePolicy; AuthoringCommandType; AuthoringConflict; AuthoringConflictJSONResponse; AuthoringFeatures; AuthoringForbidden; AuthoringForbiddenJSONResponse; AuthoringGatewayTimeout; AuthoringGatewayTimeoutJSONResponse; AuthoringInternalError; AuthoringInternalErrorJSONResponse; AuthoringNotFound; AuthoringNotFoundJSONResponse; AuthoringPayloadTooLarge; AuthoringPayloadTooLargeJSONResponse; AuthoringTooManyRequests; AuthoringTooManyRequestsJSONResponse; AuthoringUnavailable; AuthoringUnavailableJSONResponse; AuthoringUnprocessable; AuthoringUnprocessableJSONResponse; BrowseExplorerFrameSourceOptions; BrowseExplorerFrameSourceOptions200JSONResponse; BrowseExplorerFrameSourceOptions400JSONResponse; BrowseExplorerFrameSourceOptions401JSONResponse; BrowseExplorerFrameSourceOptions403JSONResponse; BrowseExplorerFrameSourceOptions404JSONResponse; BrowseExplorerFrameSourceOptions409JSONResponse; BrowseExplorerFrameSourceOptions422JSONResponse; BrowseExplorerFrameSourceOptions500JSONResponse; BrowseExplorerFrameSourceOptions503JSONResponse; BrowseExplorerFrameSourceOptionsJSONRequestBody; BrowseExplorerFrameSourceOptionsRequestObject; BrowseExplorerFrameSourceOptionsResponseObject; BrowseExplorerSemanticInventory; BrowseExplorerSemanticInventory200JSONResponse; BrowseExplorerSemanticInventory400JSONResponse; BrowseExplorerSemanticInventory401JSONResponse; BrowseExplorerSemanticInventory403JSONResponse; BrowseExplorerSemanticInventory404JSONResponse; BrowseExplorerSemanticInventory409JSONResponse; BrowseExplorerSemanticInventory422JSONResponse; BrowseExplorerSemanticInventory500JSONResponse; BrowseExplorerSemanticInventory503JSONResponse; BrowseExplorerSemanticInventoryJSONRequestBody; BrowseExplorerSemanticInventoryRequestObject; BrowseExplorerSemanticInventoryResponseObject; BuilderState; BuilderStateKind; BuilderStateLifecycleState; CandidateSearchRequest; CandidateSearchResponse; CandidateSearchResponseKind; Catalog; CatalogAvailability; CatalogAvailabilityReadiness; CatalogAvailabilityReadinessStatus; CatalogAvailabilitySourceReadiness; CatalogAvailabilitySourceReadinessStatus; CatalogCandidate; CatalogEdge; CatalogEdgeStorageDirection; CatalogNode; CatalogObservedField; CatalogObservedRelationship; CatalogObservedRelationshipBuilderDirection; CatalogObservedRelationshipStorageDirection; CatalogObservedResource; CellTraceBinding; CellTraceContribution; CellTraceFeature; CellTraceRequest; CellTraceResponse; CellTraceTrace; CellTraceTraceStatus; ChartPresentation; CheckExplorerPopulationMapping; CheckExplorerPopulationMapping200JSONResponse; CheckExplorerPopulationMapping400JSONResponse; CheckExplorerPopulationMapping401JSONResponse; CheckExplorerPopulationMapping403JSONResponse; CheckExplorerPopulationMapping404JSONResponse; CheckExplorerPopulationMapping409JSONResponse; CheckExplorerPopulationMapping422JSONResponse; CheckExplorerPopulationMapping500JSONResponse; CheckExplorerPopulationMapping503JSONResponse; CheckExplorerPopulationMapping504JSONResponse; CheckExplorerPopulationMappingJSONRequestBody; CheckExplorerPopulationMappingRequestObject; CheckExplorerPopulationMappingResponseObject; CodedValueKey; Column; ColumnSource; ColumnSourceKind; ColumnSourceRequest; ColumnSourceResponse; ColumnSourceRouteStep; ColumnSourceRouteStepMatchMode; ColumnSourceRouteStepStorageDirection; ColumnTransformationChange; ColumnValueTransformation; ColumnValueTransformationCapabilities; CommandResult; CommandResultType; CompileResponse; CompileResponseKind; ConceptCandidate; ConfiguredColumnContext; ConfiguredColumnContextRequest; ConfiguredColumnContextResponse; ConfiguredColumnReadyResolution; ConfiguredColumnReadyResolutionState; ConfiguredColumnResolution; ConfiguredColumnUnavailableResolution; ConfiguredColumnUnavailableResolutionState; Construction; ConstructionActiveRelatedRecordDescriptor; ConstructionCapabilitiesRequest; ConstructionCapabilitiesResponse; ConstructionCategoryDiscoveryRequest; ConstructionCategoryDiscoveryResponse; ConstructionCategoryDiscoveryResponseOutcome; ConstructionChoice; ConstructionChoiceFact; ConstructionChoiceOption; ConstructionChoiceOptionContributorPredicateOperators; ConstructionChoiceOptionDecision; ConstructionChoiceOptionForm; ConstructionChoiceOptionPreservation; ConstructionChoiceOptionRowEffect; ConstructionChoiceOptionShape; ConstructionChoiceOptionSupport; ConstructionChoicePresentation; ConstructionChoiceProposalRequest; ConstructionChoiceProposalResponse; ConstructionChoiceProposalResponsePreviewStatus; ConstructionChoiceProposalSelection; ConstructionChoiceProposalSelectionForm; ConstructionChoiceProposalSelectionRowValuePolicy; ConstructionChoiceSearchRequest; ConstructionChoiceSearchRequest_Source; ConstructionChoiceSearchResponse; ConstructionChoiceSelection; ConstructionChoiceSelectionForm; ConstructionChoiceSelectionRowValuePolicy; ConstructionChoice_Source; ConstructionCodeValue; ConstructionCodedGroup; ConstructionCodedGroupChoice; ConstructionCodedGroupMissingKeyPolicy; ConstructionCodedGroupSourceCardinality; ConstructionCodedGroupSourceFhirType; ConstructionCodedGroupSourceShape; ConstructionCodedPivot; ConstructionCodedPivotCategory; ConstructionCodedPivotDuplicatePolicy; ConstructionCodedPivotMissingCellPolicy; ConstructionCodedPivotSource; ConstructionCombine; ConstructionCombineJoinType; ConstructionCombineKey; ConstructionCombineKind; ConstructionCombineMembershipMode; ConstructionCombineProjection; ConstructionCombineRightMatchPolicy; ConstructionDependencyImpact; ConstructionDependencyIssue; ConstructionDerive; ConstructionDeriveDivisionByZeroPolicy; ConstructionDeriveMissingInputPolicy; ConstructionDeriveOperation; ConstructionDeriveSourceChoice; ConstructionDeriveSourceInput; ConstructionDeriveSourceSelection; ConstructionDiscoveredCategory; ConstructionExpand; ConstructionExpandEmptyPolicy; ConstructionExpandSourceChoice; ConstructionExpandSourceInput; ConstructionExpandSourceSelection; ConstructionExpandedFieldSource; ConstructionExpandedFieldSourceCardinality; ConstructionFilter; ConstructionFilterOperator; ConstructionFilterSourceChoice; ConstructionFilterSourceInput; ConstructionFilterSourceSelection; ConstructionFilterValue; ConstructionFilterValueKind; ConstructionGroup; ConstructionGroupAggregate; ConstructionGroupAggregateOperation; ConstructionGroupCodingIdentitySourceOption; ConstructionGroupCodingIdentitySourceOptionKind; ConstructionGroupCodingIdentitySourceOptionSourceRole; ConstructionGroupCodingIdentitySourceOptionValueKind; ConstructionGroupFieldChoice; ConstructionGroupFieldChoiceRelatedOccurrence; ConstructionGroupFieldCurrentStage; ConstructionGroupFieldCurrentStageSourceRole; ConstructionGroupFieldOwner; ConstructionGroupFieldOwnerRole; ConstructionGroupFieldRelatedOccurrence; ConstructionGroupFieldRelatedOccurrenceCodingEmptyPolicy; ConstructionGroupFieldRelatedOccurrenceEmptyPolicy; ConstructionGroupFieldRelatedOccurrenceMultiplicity; ConstructionGroupFieldRelatedOccurrenceOrdinalSemantics; ConstructionGroupFieldRelatedRouteStep; ConstructionGroupFieldRelatedRouteStepMatchMode; ConstructionGroupFieldRelatedRouteStepStorageDirection; ConstructionGroupFieldScalarSourceOption; ConstructionGroupFieldScalarSourceOptionKind; ConstructionGroupFieldScalarSourceOptionSourceRole; ConstructionGroupFieldScalarSourceOptionValueKind; ConstructionGroupFieldSource; ConstructionGroupFieldSourceAncestor; ConstructionGroupFieldSourceChoice; ConstructionGroupFieldSourceChoiceKind; ConstructionGroupFieldSourceCodingIdentityBinding; ConstructionGroupFieldSourceCodingIdentityBindingKind; ConstructionGroupFieldSourceCodingIdentityBindingSourceRole; ConstructionGroupFieldSourceCodingIdentityBindingValueKind; ConstructionGroupFieldSourceContext; ConstructionGroupFieldSourceCurrentStageInput; ConstructionGroupFieldSourceCurrentStageInputKind; ConstructionGroupFieldSourceExpansion; ConstructionGroupFieldSourceInput; ConstructionGroupFieldSourceInputBinding; ConstructionGroupFieldSourceInputBindingKind; ConstructionGroupFieldSourceInputBindingValueKind; ConstructionGroupFieldSourceRelatedOccurrenceInput; ConstructionGroupFieldSourceRelatedOccurrenceInputKind; ConstructionGroupFieldSourceRouteStep; ConstructionGroupFieldSourceRouteStepDirection; ConstructionGroupFieldSourceScalarBinding; ConstructionGroupFieldSourceScalarBindingKind; ConstructionGroupFieldSourceScalarBindingSourceRole; ConstructionGroupFieldSourceScalarBindingValueKind; ConstructionGroupKey; ConstructionGroupKeyFieldSource; ConstructionGroupKeyInputColumn; ConstructionGroupKeySemanticCategory; ConstructionGroupMissingKeyPolicy; ConstructionGroupSemanticCategory; ConstructionGroupSemanticCategoryChoice; ConstructionGroupSemanticCategoryChoiceKind; ConstructionGroupSemanticCategoryChoiceSemanticCategoryKind; ConstructionGroupSemanticCategoryKind; ConstructionGroupSemanticCategorySourceKind; ConstructionGroupSource; ConstructionInputColumn; ConstructionInputRef; ConstructionInputRefKind; ConstructionInputRevision; ConstructionInputRevisionKind; ConstructionInputsRequest; ConstructionInputsResponse; ConstructionLiteral; ConstructionLiteralKind; ConstructionOperand; ConstructionOperandKind; ConstructionOperation; ConstructionOperationCapability; ConstructionOperationCapabilityKind; ConstructionOperationKind; ConstructionOutputPresentation; ConstructionPivot; ConstructionPivotCategory; ConstructionPivotDuplicatePolicy; ConstructionPivotMissingCellPolicy; ConstructionPivotSourceSelection; ConstructionPivotUnlistedCategoryPolicy; ConstructionProposalRequest; ConstructionProposalResponse; ConstructionProposalResponsePreviewStatus; ConstructionRelatedEligibility; ConstructionRelatedEligibilityMatch; ConstructionRelatedEligibilityMatchKind; ConstructionRelatedExpand; ConstructionRelatedExpandAnchor; ConstructionRelatedExpandAnchorKind; ConstructionRelatedExpandEmptyPolicy; ConstructionRelatedExpandStageDescriptor; ConstructionRelatedExpandStageDescriptorAnchorKind; ConstructionRelatedField; ConstructionRelatedSource; ConstructionRelatedSourceForm; ConstructionRelatedSourceRowValuePolicy; ConstructionRouteStep; ConstructionRouteStepMatchMode; ConstructionRouteStepStorageDirection; ConstructionRowValue; ConstructionRowValue0; ConstructionRowValue1; ConstructionRowValueFieldSource; ConstructionRowValuePolicy; ConstructionSemanticGroupChoice; ConstructionSourceInput; ConstructionSourceInputChoice; ConstructionSourceProjection; ConstructionSourceProjectionAvailability; ConstructionStageColumn; ConstructionStageColumnDescriptor; ConstructionStageColumnDescriptorCardinality; ConstructionStageDescriptor; ConstructionStep; ConstructionTableScalar; ConstructionTableScalarKind; ConstructionUnpivot; ConstructionUnpivotInput; ConstructionUnpivotNullRowPolicy; ConstructionUnpivotSourceChoice; ConstructionUnpivotSourceInput; ConstructionUnpivotSourceSelection; ConstructionWorkspaceInput; ConstructionWorkspaceInputColumn; ConstructionWorkspaceInputColumnCardinality; ContractColumn; ContractColumnStructuralSuitability; ContributorCodeValue; ContributorCodeValueKind; ContributorPredicate; ContributorPredicateOperator; ContributorPredicateQuantifier; ContributorStringValue; ContributorStringValueKind; ContributorValue; ContributorWindowSource; ContributorWindowSourcePrecision; CorrelatedBinding; CorrelatedBindingSelectionScope; CorrelatedBindingValueScope; CreateDatasetGeneration; CreateDatasetGeneration200JSONResponse; CreateDatasetGeneration400JSONResponse; CreateDatasetGeneration401JSONResponse; CreateDatasetGeneration403JSONResponse; CreateDatasetGeneration409JSONResponse; CreateDatasetGeneration415JSONResponse; CreateDatasetGeneration422JSONResponse; CreateDatasetGeneration500JSONResponse; CreateDatasetGeneration503JSONResponse; CreateDatasetGenerationMultipartBody; CreateDatasetGenerationMultipartRequestBody; CreateDatasetGenerationRequestObject; CreateDatasetGenerationResponseObject; CreateExplorer; CreateExplorer201JSONResponse; CreateExplorer400JSONResponse; CreateExplorer401JSONResponse; CreateExplorer403JSONResponse; CreateExplorer409JSONResponse; CreateExplorer422JSONResponse; CreateExplorer500JSONResponse; CreateExplorerExplicitGroupRevision; CreateExplorerExplicitGroupRevision201JSONResponse; CreateExplorerExplicitGroupRevision400JSONResponse; CreateExplorerExplicitGroupRevision401JSONResponse; CreateExplorerExplicitGroupRevision403JSONResponse; CreateExplorerExplicitGroupRevision404JSONResponse; CreateExplorerExplicitGroupRevision409JSONResponse; CreateExplorerExplicitGroupRevision422JSONResponse; CreateExplorerExplicitGroupRevision500JSONResponse; CreateExplorerExplicitGroupRevision503JSONResponse; CreateExplorerExplicitGroupRevisionJSONRequestBody; CreateExplorerExplicitGroupRevisionParams; CreateExplorerExplicitGroupRevisionRequestObject; CreateExplorerExplicitGroupRevisionResponseObject; CreateExplorerJSONRequestBody; CreateExplorerParams; CreateExplorerRequestObject; CreateExplorerResponseObject; CreateExplorerSelection; CreateExplorerSelection201JSONResponse; CreateExplorerSelection400JSONResponse; CreateExplorerSelection401JSONResponse; CreateExplorerSelection403JSONResponse; CreateExplorerSelection404JSONResponse; CreateExplorerSelection409JSONResponse; CreateExplorerSelection422JSONResponse; CreateExplorerSelection500JSONResponse; CreateExplorerSelection503JSONResponse; CreateExplorerSelectionJSONRequestBody; CreateExplorerSelectionParams; CreateExplorerSelectionRequestObject; CreateExplorerSelectionResponseObject; CreateInterpretationRevision; CreateInterpretationRevision201JSONResponse; CreateInterpretationRevision400JSONResponse; CreateInterpretationRevision401JSONResponse; CreateInterpretationRevision403JSONResponse; CreateInterpretationRevision404JSONResponse; CreateInterpretationRevision409JSONResponse; CreateInterpretationRevision422JSONResponse; CreateInterpretationRevision500JSONResponse; CreateInterpretationRevision503JSONResponse; CreateInterpretationRevisionFromColumn; CreateInterpretationRevisionFromColumn201JSONResponse; CreateInterpretationRevisionFromColumn400JSONResponse; CreateInterpretationRevisionFromColumn401JSONResponse; CreateInterpretationRevisionFromColumn403JSONResponse; CreateInterpretationRevisionFromColumn404JSONResponse; CreateInterpretationRevisionFromColumn409JSONResponse; CreateInterpretationRevisionFromColumn422JSONResponse; CreateInterpretationRevisionFromColumn500JSONResponse; CreateInterpretationRevisionFromColumn503JSONResponse; CreateInterpretationRevisionFromColumnJSONRequestBody; CreateInterpretationRevisionFromColumnParams; CreateInterpretationRevisionFromColumnRequestObject; CreateInterpretationRevisionFromColumnResponseObject; CreateInterpretationRevisionJSONRequestBody; CreateInterpretationRevisionParams; CreateInterpretationRevisionRequestObject; CreateInterpretationRevisionResponseObject; Diagnostic; DiscoverExplorerConstructionCategories; DiscoverExplorerConstructionCategories200JSONResponse; DiscoverExplorerConstructionCategories400JSONResponse; DiscoverExplorerConstructionCategories401JSONResponse; DiscoverExplorerConstructionCategories403JSONResponse; DiscoverExplorerConstructionCategories404JSONResponse; DiscoverExplorerConstructionCategories409JSONResponse; DiscoverExplorerConstructionCategories422JSONResponse; DiscoverExplorerConstructionCategories500JSONResponse; DiscoverExplorerConstructionCategories503JSONResponse; DiscoverExplorerConstructionCategoriesJSONRequestBody; DiscoverExplorerConstructionCategoriesRequestObject; DiscoverExplorerConstructionCategoriesResponseObject; DiscoverExplorerTableShapeCategories; DiscoverExplorerTableShapeCategories200JSONResponse; DiscoverExplorerTableShapeCategories400JSONResponse; DiscoverExplorerTableShapeCategories401JSONResponse; DiscoverExplorerTableShapeCategories403JSONResponse; DiscoverExplorerTableShapeCategories404JSONResponse; DiscoverExplorerTableShapeCategories409JSONResponse; DiscoverExplorerTableShapeCategories422JSONResponse; DiscoverExplorerTableShapeCategories500JSONResponse; DiscoverExplorerTableShapeCategories503JSONResponse; DiscoverExplorerTableShapeCategoriesJSONRequestBody; DiscoverExplorerTableShapeCategoriesRequestObject; DiscoverExplorerTableShapeCategoriesResponseObject; Discriminator; Document; DocumentKind; DownloadExplorerArtifact; DownloadExplorerArtifact200ApplicationzipResponse; DownloadExplorerArtifact200ResponseHeaders; DownloadExplorerArtifact401JSONResponse; DownloadExplorerArtifact403JSONResponse; DownloadExplorerArtifact404JSONResponse; DownloadExplorerArtifact409JSONResponse; DownloadExplorerArtifact410JSONResponse; DownloadExplorerArtifact500JSONResponse; DownloadExplorerArtifact503JSONResponse; DownloadExplorerArtifactRequestObject; DownloadExplorerArtifactResponseObject; ErrorBody; ErrorResponse; ExactCategoryMapping; ExactCategoryRecodeTransformation; ExactCategoryRecodeTransformationExactCategoryRecodeUnknownPolicy; ExactCategoryRecodeTransformationKind; ExactSemanticInventoryItemIdentity; ExactSemanticInventoryItemIdentityKind; ExecuteDataframeGraphQL; ExecuteDataframeGraphQL200JSONResponse; ExecuteDataframeGraphQL400JSONResponse; ExecuteDataframeGraphQL401JSONResponse; ExecuteDataframeGraphQL403JSONResponse; ExecuteDataframeGraphQL404JSONResponse; ExecuteDataframeGraphQL409JSONResponse; ExecuteDataframeGraphQL413JSONResponse; ExecuteDataframeGraphQL422JSONResponse; ExecuteDataframeGraphQL429JSONResponse; ExecuteDataframeGraphQL499JSONResponse; ExecuteDataframeGraphQL500JSONResponse; ExecuteDataframeGraphQL503JSONResponse; ExecuteDataframeGraphQL504JSONResponse; ExecuteDataframeGraphQLJSONRequestBody; ExecuteDataframeGraphQLRequestObject; ExecuteDataframeGraphQLResponseObject; ExecuteGraphQL; ExecuteGraphQL200JSONResponse; ExecuteGraphQL400JSONResponse; ExecuteGraphQL401JSONResponse; ExecuteGraphQL403JSONResponse; ExecuteGraphQL404JSONResponse; ExecuteGraphQL409JSONResponse; ExecuteGraphQL413JSONResponse; ExecuteGraphQL422JSONResponse; ExecuteGraphQL429JSONResponse; ExecuteGraphQL499JSONResponse; ExecuteGraphQL500JSONResponse; ExecuteGraphQL503JSONResponse; ExecuteGraphQL504JSONResponse; ExecuteGraphQLJSONRequestBody; ExecuteGraphQLRequestObject; ExecuteGraphQLResponseObject; ExplicitGroupCreateRequest; ExplicitGroupInput; ExplicitGroupRevisionChoice; ExplicitGroupRevisionChoiceUnassignedMemberPolicies; ExplicitGroupRevisionSummary; ExplicitGroupSummary; ExplorerId; ExplorerMetadata; ExtensionBinding; FamilySemanticInventoryItemIdentity; FamilySemanticInventoryItemIdentityKind; FiberServerOptions; FieldChoiceSource; FieldChoiceSourceKind; FieldConstructionChoiceSearchSource; FieldConstructionChoiceSearchSourceKind; FieldPresentation; FieldPresentationContext; FieldPresentationDecision; FieldPresentationGroupUnderSemantic; FieldPresentationGroupUnderSemanticAccess; FieldPresentationGroupUnderSemanticKind; FieldPresentationKeepRaw; FieldPresentationKeepRawKind; FieldPresentationReady; FieldPresentationReadyKind; FieldPresentationReason; FieldPresentationRequest; FieldPresentationUnavailable; FieldPresentationUnavailableKind; FieldSource; FieldSourceProjectionMode; FileActions; FilterPresentation; FixedFilter; FrameDefinition; FrameDefinitionForm; FrameDefinitionManyPolicy; FrameDefinitionZeroPolicy; FrameSourceForm; FrameSourceFormDecision; FrameSourceFormForm; FrameSourceFormManyPolicy; FrameSourceFormZeroPolicy; FrameSourceOption; FrameSourceOptionDefaultForm; FrameSourceOptionsRequest; FrameSourceOptionsResponse; FromConfiguredColumnReadyResolution; FromConfiguredColumnUnavailableResolution; FromConstructionGroupCodingIdentitySourceOption; FromConstructionGroupFieldScalarSourceOption; FromConstructionGroupFieldSourceChoice; FromConstructionGroupFieldSourceCodingIdentityBinding; FromConstructionGroupFieldSourceCurrentStageInput; FromConstructionGroupFieldSourceInputBinding; FromConstructionGroupFieldSourceRelatedOccurrenceInput; FromConstructionGroupFieldSourceScalarBinding; FromConstructionGroupKeyFieldSource; FromConstructionGroupKeyInputColumn; FromConstructionGroupKeySemanticCategory; FromConstructionGroupSemanticCategoryChoice; FromConstructionRowValue0; FromConstructionRowValue1; FromContributorCodeValue; FromContributorStringValue; FromExactCategoryRecodeTransformation; FromExactSemanticInventoryItemIdentity; FromFamilySemanticInventoryItemIdentity; FromFieldChoiceSource; FromFieldConstructionChoiceSearchSource; FromFieldPresentationGroupUnderSemantic; FromFieldPresentationKeepRaw; FromFieldPresentationReady; FromFieldPresentationUnavailable; FromLegacyErrorBody; FromLegacyErrorResponseError0; FromRemoveColumnTransformation; FromRowDefinitionComparisonAvailable; FromRowDefinitionComparisonUnavailable; FromRowDefinitionExpandedSelection; FromRowDefinitionExplicitGroupSelection; FromRowDefinitionFieldGroupSelection; FromRowDefinitionRecordsSelection; FromSemanticBindingChoiceSource; FromSemanticConstructionChoiceSearchSource; FromSetColumnTransformation; FromTableShapeComparisonAvailable; FromTableShapeComparisonUnavailable; Generation; GenerationActivationResponse; GenerationStatusResponse; Get; GetApolloSandbox; GetApolloSandbox200TexthtmlResponse; GetApolloSandbox401JSONResponse; GetApolloSandbox404JSONResponse; GetApolloSandboxRequestObject; GetApolloSandboxResponseObject; GetDatasetGenerationStatus; GetDatasetGenerationStatus200JSONResponse; GetDatasetGenerationStatus400JSONResponse; GetDatasetGenerationStatus401JSONResponse; GetDatasetGenerationStatus403JSONResponse; GetDatasetGenerationStatus404JSONResponse; GetDatasetGenerationStatus500JSONResponse; GetDatasetGenerationStatus503JSONResponse; GetDatasetGenerationStatusParams; GetDatasetGenerationStatusRequestObject; GetDatasetGenerationStatusResponseObject; GetExplorer; GetExplorer200JSONResponse; GetExplorer401JSONResponse; GetExplorer403JSONResponse; GetExplorer404JSONResponse; GetExplorer500JSONResponse; GetExplorerAuthoringCapability; GetExplorerAuthoringCapability200JSONResponse; GetExplorerAuthoringCapability401JSONResponse; GetExplorerAuthoringCapability403JSONResponse; GetExplorerAuthoringCapability500JSONResponse; GetExplorerAuthoringCapabilityRequestObject; GetExplorerAuthoringCapabilityResponseObject; GetExplorerBuilder; GetExplorerBuilder200JSONResponse; GetExplorerBuilder401JSONResponse; GetExplorerBuilder403JSONResponse; GetExplorerBuilder404JSONResponse; GetExplorerBuilder409JSONResponse; GetExplorerBuilder500JSONResponse; GetExplorerBuilder503JSONResponse; GetExplorerBuilderRequestObject; GetExplorerBuilderResponseObject; GetExplorerColumnSource; GetExplorerColumnSource200JSONResponse; GetExplorerColumnSource400JSONResponse; GetExplorerColumnSource401JSONResponse; GetExplorerColumnSource403JSONResponse; GetExplorerColumnSource404JSONResponse; GetExplorerColumnSource409JSONResponse; GetExplorerColumnSource500JSONResponse; GetExplorerColumnSource503JSONResponse; GetExplorerColumnSourceJSONRequestBody; GetExplorerColumnSourceRequestObject; GetExplorerColumnSourceResponseObject; GetExplorerConstructionCapabilities; GetExplorerConstructionCapabilities200JSONResponse; GetExplorerConstructionCapabilities400JSONResponse; GetExplorerConstructionCapabilities401JSONResponse; GetExplorerConstructionCapabilities403JSONResponse; GetExplorerConstructionCapabilities404JSONResponse; GetExplorerConstructionCapabilities409JSONResponse; GetExplorerConstructionCapabilities422JSONResponse; GetExplorerConstructionCapabilities500JSONResponse; GetExplorerConstructionCapabilities503JSONResponse; GetExplorerConstructionCapabilitiesJSONRequestBody; GetExplorerConstructionCapabilitiesRequestObject; GetExplorerConstructionCapabilitiesResponseObject; GetExplorerConstructionInputs; GetExplorerConstructionInputs200JSONResponse; GetExplorerConstructionInputs400JSONResponse; GetExplorerConstructionInputs401JSONResponse; GetExplorerConstructionInputs403JSONResponse; GetExplorerConstructionInputs404JSONResponse; GetExplorerConstructionInputs409JSONResponse; GetExplorerConstructionInputs422JSONResponse; GetExplorerConstructionInputs500JSONResponse; GetExplorerConstructionInputs503JSONResponse; GetExplorerConstructionInputsJSONRequestBody; GetExplorerConstructionInputsRequestObject; GetExplorerConstructionInputsResponseObject; GetExplorerRequestObject; GetExplorerResponseObject; GetExplorerSelection; GetExplorerSelection200JSONResponse; GetExplorerSelection400JSONResponse; GetExplorerSelection401JSONResponse; GetExplorerSelection403JSONResponse; GetExplorerSelection404JSONResponse; GetExplorerSelection409JSONResponse; GetExplorerSelection422JSONResponse; GetExplorerSelection500JSONResponse; GetExplorerSelection503JSONResponse; GetExplorerSelectionParams; GetExplorerSelectionRequestObject; GetExplorerSelectionResponseObject; GetExplorerTableShapeCapabilities; GetExplorerTableShapeCapabilities200JSONResponse; GetExplorerTableShapeCapabilities400JSONResponse; GetExplorerTableShapeCapabilities401JSONResponse; GetExplorerTableShapeCapabilities403JSONResponse; GetExplorerTableShapeCapabilities404JSONResponse; GetExplorerTableShapeCapabilities409JSONResponse; GetExplorerTableShapeCapabilities422JSONResponse; GetExplorerTableShapeCapabilities500JSONResponse; GetExplorerTableShapeCapabilities503JSONResponse; GetExplorerTableShapeCapabilitiesJSONRequestBody; GetExplorerTableShapeCapabilitiesRequestObject; GetExplorerTableShapeCapabilitiesResponseObject; GetGraphQLPlayground; GetGraphQLPlayground200TexthtmlResponse; GetGraphQLPlayground401JSONResponse; GetGraphQLPlayground404JSONResponse; GetGraphQLPlaygroundRequestObject; GetGraphQLPlaygroundResponseObject; GetHealth; GetHealth200JSONResponse; GetHealth503JSONResponse; GetHealthRequestObject; GetHealthResponseObject; GetInterpretationRevision; GetInterpretationRevision200JSONResponse; GetInterpretationRevision401JSONResponse; GetInterpretationRevision403JSONResponse; GetInterpretationRevision404JSONResponse; GetInterpretationRevision500JSONResponse; GetInterpretationRevision503JSONResponse; GetInterpretationRevisionRequestObject; GetInterpretationRevisionResponseObject; GetLiveness; GetLiveness200JSONResponse; GetLivenessRequestObject; GetLivenessResponseObject; GetReadiness; GetReadiness200JSONResponse; GetReadiness503JSONResponse; GetReadinessRequestObject; GetReadinessResponseObject; GetRecipeExecution; GetRecipeExecution200JSONResponse; GetRecipeExecution401JSONResponse; GetRecipeExecution403JSONResponse; GetRecipeExecution404JSONResponse; GetRecipeExecution503JSONResponse; GetRecipeExecutionRequestObject; GetRecipeExecutionResponseObject; GetSpec; GetSpecJSON; GetSwagger; HTMLTexthtmlResponse; HandlerMiddlewareFunc; IdentifierBinding; InterpretationApplicability; InterpretationCandidatePayload; InterpretationFeatureDefinition; InterpretationLibrary; InterpretationLibraryListResponse; InterpretationLibrarySummary; InterpretationLibraryView; InterpretationPreviewCounts; InterpretationPreviewRequest; InterpretationPreviewResponse; InterpretationPreviewResponseCompleteness; InterpretationPreviewSample; InterpretationPreviewSampleState; InterpretationRevision; InterpretationRevisionCreateRequest; InterpretationRevisionFromColumnRequest; InterpretationRevisionSummary; InterpretationRule; InterpretationStructuralMatch; LegacyBadRequest; LegacyBadRequestJSONResponse; LegacyConflict; LegacyConflictJSONResponse; LegacyErrorBody; LegacyErrorResponse; LegacyErrorResponseError0; LegacyErrorResponse_Error; LegacyForbidden; LegacyForbiddenJSONResponse; LegacyInternalError; LegacyInternalErrorJSONResponse; LegacyNotFound; LegacyNotFoundJSONResponse; LegacyUnavailable; LegacyUnavailableJSONResponse; LegacyUnprocessable; LegacyUnprocessableJSONResponse; ListExplorerRowDefinitionChoices; ListExplorerRowDefinitionChoices200JSONResponse; ListExplorerRowDefinitionChoices400JSONResponse; ListExplorerRowDefinitionChoices401JSONResponse; ListExplorerRowDefinitionChoices403JSONResponse; ListExplorerRowDefinitionChoices404JSONResponse; ListExplorerRowDefinitionChoices409JSONResponse; ListExplorerRowDefinitionChoices422JSONResponse; ListExplorerRowDefinitionChoices500JSONResponse; ListExplorerRowDefinitionChoices503JSONResponse; ListExplorerRowDefinitionChoicesParams; ListExplorerRowDefinitionChoicesRequestObject; ListExplorerRowDefinitionChoicesResponseObject; ListExplorers; ListExplorers200JSONResponse; ListExplorers401JSONResponse; ListExplorers403JSONResponse; ListExplorers500JSONResponse; ListExplorersRequestObject; ListExplorersResponseObject; ListInterpretationLibraries; ListInterpretationLibraries200JSONResponse; ListInterpretationLibraries401JSONResponse; ListInterpretationLibraries403JSONResponse; ListInterpretationLibraries500JSONResponse; ListInterpretationLibraries503JSONResponse; ListInterpretationLibrariesRequestObject; ListInterpretationLibrariesResponseObject; LookupSource; LookupSourceProjectionMode; MarshalJSON; MergeConfiguredColumnReadyResolution; MergeConfiguredColumnUnavailableResolution; MergeConstructionGroupCodingIdentitySourceOption; MergeConstructionGroupFieldScalarSourceOption; MergeConstructionGroupFieldSourceChoice; MergeConstructionGroupFieldSourceCodingIdentityBinding; MergeConstructionGroupFieldSourceCurrentStageInput; MergeConstructionGroupFieldSourceInputBinding; MergeConstructionGroupFieldSourceRelatedOccurrenceInput; MergeConstructionGroupFieldSourceScalarBinding; MergeConstructionGroupKeyFieldSource; MergeConstructionGroupKeyInputColumn; MergeConstructionGroupKeySemanticCategory; MergeConstructionGroupSemanticCategoryChoice; MergeConstructionRowValue0; MergeConstructionRowValue1; MergeContributorCodeValue; MergeContributorStringValue; MergeExactCategoryRecodeTransformation; MergeExactSemanticInventoryItemIdentity; MergeFamilySemanticInventoryItemIdentity; MergeFieldChoiceSource; MergeFieldConstructionChoiceSearchSource; MergeFieldPresentationGroupUnderSemantic; MergeFieldPresentationKeepRaw; MergeFieldPresentationReady; MergeFieldPresentationUnavailable; MergeLegacyErrorBody; MergeLegacyErrorResponseError0; MergeRemoveColumnTransformation; MergeRowDefinitionComparisonAvailable; MergeRowDefinitionComparisonUnavailable; MergeRowDefinitionExpandedSelection; MergeRowDefinitionExplicitGroupSelection; MergeRowDefinitionFieldGroupSelection; MergeRowDefinitionRecordsSelection; MergeSemanticBindingChoiceSource; MergeSemanticConstructionChoiceSearchSource; MergeSetColumnTransformation; MergeTableShapeComparisonAvailable; MergeTableShapeComparisonUnavailable; MiddlewareFunc; NewStrictHandler; Output; OwnerRecordsSource; PathToRawSpec; Population; PopulationMappingBinding; PopulationMappingCounts; PopulationMappingRequest; PopulationMappingResponse; PopulationMappingResponseStatus; PopulationMemberRemovalProposalRequest; PopulationMemberRemovalProposalResponse; PopulationMemberRemovalProposalResponsePreviewStatus; PopulationRouteChoice; PopulationRouteStep; PopulationRouteStepStorageDirection; PopulationRoutesRequest; PopulationRoutesResponse; PrepareExplorerArtifact; PrepareExplorerArtifact200JSONResponse; PrepareExplorerArtifact400JSONResponse; PrepareExplorerArtifact401JSONResponse; PrepareExplorerArtifact403JSONResponse; PrepareExplorerArtifact404JSONResponse; PrepareExplorerArtifact409JSONResponse; PrepareExplorerArtifact413JSONResponse; PrepareExplorerArtifact422JSONResponse; PrepareExplorerArtifact500JSONResponse; PrepareExplorerArtifact503JSONResponse; PrepareExplorerArtifact504JSONResponse; PrepareExplorerArtifactJSONRequestBody; PrepareExplorerArtifactRequestObject; PrepareExplorerArtifactResponseObject; PreviewExplorer; PreviewExplorer200JSONResponse; PreviewExplorer400JSONResponse; PreviewExplorer401JSONResponse; PreviewExplorer403JSONResponse; PreviewExplorer404JSONResponse; PreviewExplorer409JSONResponse; PreviewExplorer413JSONResponse; PreviewExplorer422JSONResponse; PreviewExplorer429JSONResponse; PreviewExplorer499JSONResponse; PreviewExplorer500JSONResponse; PreviewExplorer503JSONResponse; PreviewExplorer504JSONResponse; PreviewExplorerJSONRequestBody; PreviewExplorerRequestObject; PreviewExplorerResponseObject; PreviewInterpretationCandidate; PreviewInterpretationCandidate200JSONResponse; PreviewInterpretationCandidate400JSONResponse; PreviewInterpretationCandidate401JSONResponse; PreviewInterpretationCandidate403JSONResponse; PreviewInterpretationCandidate404JSONResponse; PreviewInterpretationCandidate409JSONResponse; PreviewInterpretationCandidate413JSONResponse; PreviewInterpretationCandidate422JSONResponse; PreviewInterpretationCandidate429JSONResponse; PreviewInterpretationCandidate499JSONResponse; PreviewInterpretationCandidate500JSONResponse; PreviewInterpretationCandidate503JSONResponse; PreviewInterpretationCandidate504JSONResponse; PreviewInterpretationCandidateJSONRequestBody; PreviewInterpretationCandidateRequestObject; PreviewInterpretationCandidateResponseObject; PreviewRequest; PreviewResponse; PreviewResponseKind; PreviewRowSource; PreviewRowSourceKind; Project; ProjectionMode; ProposeExplorerConstruction; ProposeExplorerConstruction200JSONResponse; ProposeExplorerConstruction400JSONResponse; ProposeExplorerConstruction401JSONResponse; ProposeExplorerConstruction403JSONResponse; ProposeExplorerConstruction404JSONResponse; ProposeExplorerConstruction409JSONResponse; ProposeExplorerConstruction422JSONResponse; ProposeExplorerConstruction500JSONResponse; ProposeExplorerConstruction503JSONResponse; ProposeExplorerConstruction504JSONResponse; ProposeExplorerConstructionChoice; ProposeExplorerConstructionChoice200JSONResponse; ProposeExplorerConstructionChoice400JSONResponse; ProposeExplorerConstructionChoice401JSONResponse; ProposeExplorerConstructionChoice403JSONResponse; ProposeExplorerConstructionChoice404JSONResponse; ProposeExplorerConstructionChoice409JSONResponse; ProposeExplorerConstructionChoice422JSONResponse; ProposeExplorerConstructionChoice500JSONResponse; ProposeExplorerConstructionChoice503JSONResponse; ProposeExplorerConstructionChoice504JSONResponse; ProposeExplorerConstructionChoiceJSONRequestBody; ProposeExplorerConstructionChoiceRequestObject; ProposeExplorerConstructionChoiceResponseObject; ProposeExplorerConstructionJSONRequestBody; ProposeExplorerConstructionRequestObject; ProposeExplorerConstructionResponseObject; ProposeExplorerPopulationMemberRemoval; ProposeExplorerPopulationMemberRemoval200JSONResponse; ProposeExplorerPopulationMemberRemoval400JSONResponse; ProposeExplorerPopulationMemberRemoval401JSONResponse; ProposeExplorerPopulationMemberRemoval403JSONResponse; ProposeExplorerPopulationMemberRemoval404JSONResponse; ProposeExplorerPopulationMemberRemoval409JSONResponse; ProposeExplorerPopulationMemberRemoval422JSONResponse; ProposeExplorerPopulationMemberRemoval500JSONResponse; ProposeExplorerPopulationMemberRemoval503JSONResponse; ProposeExplorerPopulationMemberRemoval504JSONResponse; ProposeExplorerPopulationMemberRemovalJSONRequestBody; ProposeExplorerPopulationMemberRemovalRequestObject; ProposeExplorerPopulationMemberRemovalResponseObject; ProposeExplorerRowDefinition; ProposeExplorerRowDefinition200JSONResponse; ProposeExplorerRowDefinition400JSONResponse; ProposeExplorerRowDefinition401JSONResponse; ProposeExplorerRowDefinition403JSONResponse; ProposeExplorerRowDefinition404JSONResponse; ProposeExplorerRowDefinition409JSONResponse; ProposeExplorerRowDefinition422JSONResponse; ProposeExplorerRowDefinition500JSONResponse; ProposeExplorerRowDefinition503JSONResponse; ProposeExplorerRowDefinitionJSONRequestBody; ProposeExplorerRowDefinitionRequestObject; ProposeExplorerRowDefinitionResponseObject; ProposeExplorerTableShape; ProposeExplorerTableShape200JSONResponse; ProposeExplorerTableShape400JSONResponse; ProposeExplorerTableShape401JSONResponse; ProposeExplorerTableShape403JSONResponse; ProposeExplorerTableShape404JSONResponse; ProposeExplorerTableShape409JSONResponse; ProposeExplorerTableShape422JSONResponse; ProposeExplorerTableShape500JSONResponse; ProposeExplorerTableShape503JSONResponse; ProposeExplorerTableShapeJSONRequestBody; ProposeExplorerTableShapeRequestObject; ProposeExplorerTableShapeResponseObject; PublicationOutput; PublishExplorer; PublishExplorer200JSONResponse; PublishExplorer400JSONResponse; PublishExplorer401JSONResponse; PublishExplorer403JSONResponse; PublishExplorer404JSONResponse; PublishExplorer409JSONResponse; PublishExplorer422JSONResponse; PublishExplorer500JSONResponse; PublishExplorer503JSONResponse; PublishExplorerJSONRequestBody; PublishExplorerParams; PublishExplorerRequestObject; PublishExplorerResponseObject; PublishRepositoryExplorerConfig; PublishRepositoryExplorerConfig200JSONResponse; PublishRepositoryExplorerConfig400JSONResponse; PublishRepositoryExplorerConfig401JSONResponse; PublishRepositoryExplorerConfig403JSONResponse; PublishRepositoryExplorerConfig409JSONResponse; PublishRepositoryExplorerConfig422JSONResponse; PublishRepositoryExplorerConfig500JSONResponse; PublishRepositoryExplorerConfig503JSONResponse; PublishRepositoryExplorerConfigJSONRequestBody; PublishRepositoryExplorerConfigParams; PublishRepositoryExplorerConfigRequestObject; PublishRepositoryExplorerConfigResponseObject; PublishRequest; PublishResponse; PublishResponseKind; RawJSON; ReceiptOutput; ReceiptOutputRowMultiplication; ReceiptOutputStructuralSuitability; RecipeExecutionColumn; RecipeExecutionOutput; RecipeExecutionResponse; ReconcileExplorerBuilder; ReconcileExplorerBuilder200JSONResponse; ReconcileExplorerBuilder400JSONResponse; ReconcileExplorerBuilder401JSONResponse; ReconcileExplorerBuilder403JSONResponse; ReconcileExplorerBuilder404JSONResponse; ReconcileExplorerBuilder409JSONResponse; ReconcileExplorerBuilder422JSONResponse; ReconcileExplorerBuilder500JSONResponse; ReconcileExplorerBuilder503JSONResponse; ReconcileExplorerBuilderJSONRequestBody; ReconcileExplorerBuilderParams; ReconcileExplorerBuilderRequestObject; ReconcileExplorerBuilderResponseObject; ReconcileRequest; RegisterHandlers; RegisterHandlersWithOptions; RelatedExpandChoiceSearchRequest; RelatedExpandChoiceSearchResponse; RelatedExpandContributorChoice; RelatedExpandContributorChoiceOperators; RelatedExpandContributorChoiceSearchRequest; RelatedExpandContributorChoiceSearchResponse; RelatedExpandContributorChoiceSuggestionsSource; RelatedExpandContributorSource; RelatedExpandContributorSourceKind; RelatedExpandRouteChoice; RelatedExpandRouteChoiceKind; RelatedFieldChoice; RelatedFieldChoiceSearchRequest; RelatedFieldChoiceSearchResponse; RelatedSelection; RelatedSelectionKind; RelatedSourceContributorRule; RelatedSourceContributorRulePolicy; RelatedSourceField; RelatedSourceFieldCardinality; RelatedSourceFieldKind; RemoveColumnTransformation; RemoveColumnTransformationKind; RepeatedBoundary; RepeatedCoordinate; ResolveConfiguredColumnContext; ResolveConfiguredColumnContext200JSONResponse; ResolveConfiguredColumnContext400JSONResponse; ResolveConfiguredColumnContext401JSONResponse; ResolveConfiguredColumnContext403JSONResponse; ResolveConfiguredColumnContext404JSONResponse; ResolveConfiguredColumnContext409JSONResponse; ResolveConfiguredColumnContext422JSONResponse; ResolveConfiguredColumnContext500JSONResponse; ResolveConfiguredColumnContext503JSONResponse; ResolveConfiguredColumnContextJSONRequestBody; ResolveConfiguredColumnContextRequestObject; ResolveConfiguredColumnContextResponseObject; ResolveExplorerTableShape; ResolveExplorerTableShape200JSONResponse; ResolveExplorerTableShape400JSONResponse; ResolveExplorerTableShape401JSONResponse; ResolveExplorerTableShape403JSONResponse; ResolveExplorerTableShape404JSONResponse; ResolveExplorerTableShape409JSONResponse; ResolveExplorerTableShape422JSONResponse; ResolveExplorerTableShape500JSONResponse; ResolveExplorerTableShape503JSONResponse; ResolveExplorerTableShapeJSONRequestBody; ResolveExplorerTableShapeRequestObject; ResolveExplorerTableShapeResponseObject; RouteNode; RouteNodeMatchMode; RoutePolicy; RouteRebaseChoice; RowChangeAssessmentResponse; RowChangeAssessmentResponseMode; RowChangeAssessmentResponseStatus; RowChangeProposal; RowChangeProposalMode; RowChangeUnresolvedReference; RowChangeUnresolvedReferenceKind; RowDefinition; RowDefinitionChoice; RowDefinitionChoiceKind; RowDefinitionChoicePolicy; RowDefinitionChoicePolicyName; RowDefinitionChoiceValueType; RowDefinitionChoicesResponse; RowDefinitionComparison; RowDefinitionComparisonAvailable; RowDefinitionComparisonAvailableStatus; RowDefinitionComparisonExample; RowDefinitionComparisonUnavailable; RowDefinitionComparisonUnavailableStatus; RowDefinitionExpandedSelection; RowDefinitionExpandedSelectionExpandedEmptyCollectionPolicy; RowDefinitionExpandedSelectionKind; RowDefinitionExplicitGroupSelection; RowDefinitionExplicitGroupSelectionExplicitGroupUnassignedMemberPolicy; RowDefinitionExplicitGroupSelectionKind; RowDefinitionFieldGroupSelection; RowDefinitionFieldGroupSelectionFieldGroupMissingKeyPolicy; RowDefinitionFieldGroupSelectionKind; RowDefinitionPreviewSummary; RowDefinitionProposal; RowDefinitionProposalMode; RowDefinitionProposalRequest; RowDefinitionProposalRequest_Selection; RowDefinitionRecordsSelection; RowDefinitionRecordsSelectionKind; RowLineageCapability; RowLineageCapabilityStatus; RowLineageContributor; RowLineageRequest; RowLineageResponse; RowLineageResponseStatus; SchemaFieldOption; SchemaFieldOptionCardinality; SchemaFieldSearchRequest; SchemaFieldSearchResponse; SchemaFieldSearchResponseKind; SearchExplorerCandidates; SearchExplorerCandidates200JSONResponse; SearchExplorerCandidates400JSONResponse; SearchExplorerCandidates401JSONResponse; SearchExplorerCandidates403JSONResponse; SearchExplorerCandidates409JSONResponse; SearchExplorerCandidates500JSONResponse; SearchExplorerCandidates503JSONResponse; SearchExplorerCandidatesJSONRequestBody; SearchExplorerCandidatesRequestObject; SearchExplorerCandidatesResponseObject; SearchExplorerConstructionChoices; SearchExplorerConstructionChoices200JSONResponse; SearchExplorerConstructionChoices400JSONResponse; SearchExplorerConstructionChoices401JSONResponse; SearchExplorerConstructionChoices403JSONResponse; SearchExplorerConstructionChoices404JSONResponse; SearchExplorerConstructionChoices409JSONResponse; SearchExplorerConstructionChoices422JSONResponse; SearchExplorerConstructionChoices500JSONResponse; SearchExplorerConstructionChoices503JSONResponse; SearchExplorerConstructionChoicesJSONRequestBody; SearchExplorerConstructionChoicesRequestObject; SearchExplorerConstructionChoicesResponseObject; SearchExplorerPopulationRoutes; SearchExplorerPopulationRoutes200JSONResponse; SearchExplorerPopulationRoutes400JSONResponse; SearchExplorerPopulationRoutes401JSONResponse; SearchExplorerPopulationRoutes403JSONResponse; SearchExplorerPopulationRoutes404JSONResponse; SearchExplorerPopulationRoutes409JSONResponse; SearchExplorerPopulationRoutes422JSONResponse; SearchExplorerPopulationRoutes500JSONResponse; SearchExplorerPopulationRoutes503JSONResponse; SearchExplorerPopulationRoutesJSONRequestBody; SearchExplorerPopulationRoutesRequestObject; SearchExplorerPopulationRoutesResponseObject; SearchExplorerRelatedExpandChoices; SearchExplorerRelatedExpandChoices200JSONResponse; SearchExplorerRelatedExpandChoices400JSONResponse; SearchExplorerRelatedExpandChoices401JSONResponse; SearchExplorerRelatedExpandChoices403JSONResponse; SearchExplorerRelatedExpandChoices404JSONResponse; SearchExplorerRelatedExpandChoices409JSONResponse; SearchExplorerRelatedExpandChoices422JSONResponse; SearchExplorerRelatedExpandChoices500JSONResponse; SearchExplorerRelatedExpandChoices503JSONResponse; SearchExplorerRelatedExpandChoicesJSONRequestBody; SearchExplorerRelatedExpandChoicesRequestObject; SearchExplorerRelatedExpandChoicesResponseObject; SearchExplorerRelatedExpandContributors; SearchExplorerRelatedExpandContributors200JSONResponse; SearchExplorerRelatedExpandContributors400JSONResponse; SearchExplorerRelatedExpandContributors401JSONResponse; SearchExplorerRelatedExpandContributors403JSONResponse; SearchExplorerRelatedExpandContributors404JSONResponse; SearchExplorerRelatedExpandContributors409JSONResponse; SearchExplorerRelatedExpandContributors422JSONResponse; SearchExplorerRelatedExpandContributors500JSONResponse; SearchExplorerRelatedExpandContributors503JSONResponse; SearchExplorerRelatedExpandContributorsJSONRequestBody; SearchExplorerRelatedExpandContributorsRequestObject; SearchExplorerRelatedExpandContributorsResponseObject; SearchExplorerRelatedFieldChoices; SearchExplorerRelatedFieldChoices200JSONResponse; SearchExplorerRelatedFieldChoices400JSONResponse; SearchExplorerRelatedFieldChoices401JSONResponse; SearchExplorerRelatedFieldChoices403JSONResponse; SearchExplorerRelatedFieldChoices404JSONResponse; SearchExplorerRelatedFieldChoices409JSONResponse; SearchExplorerRelatedFieldChoices422JSONResponse; SearchExplorerRelatedFieldChoices500JSONResponse; SearchExplorerRelatedFieldChoices503JSONResponse; SearchExplorerRelatedFieldChoicesJSONRequestBody; SearchExplorerRelatedFieldChoicesRequestObject; SearchExplorerRelatedFieldChoicesResponseObject; SearchExplorerSchemaFields; SearchExplorerSchemaFields200JSONResponse; SearchExplorerSchemaFields400JSONResponse; SearchExplorerSchemaFields401JSONResponse; SearchExplorerSchemaFields403JSONResponse; SearchExplorerSchemaFields409JSONResponse; SearchExplorerSchemaFields422JSONResponse; SearchExplorerSchemaFields500JSONResponse; SearchExplorerSchemaFields503JSONResponse; SearchExplorerSchemaFieldsJSONRequestBody; SearchExplorerSchemaFieldsRequestObject; SearchExplorerSchemaFieldsResponseObject; SelectionBaseRevision; SelectionCreateRequest; SelectionExplicitResources; SelectionFilter; SelectionMember; SelectionPage; SelectionPublishedOutput; SelectionRequestSource; SelectionRequestSourceKind; SelectionResourceRef; SelectionRevision; SelectionRule; SelectionRuleKind; SelectionSource; SelectionSourceKind; SemanticBindingChoiceSource; SemanticBindingChoiceSourceKind; SemanticBindingChoiceSourceSourceKind; SemanticConstructionChoiceSearchSource; SemanticConstructionChoiceSearchSourceKind; SemanticConstructionChoiceSearchSourceSelectionMode; SemanticFrameFamily; SemanticInventoryBrowseRequest; SemanticInventoryBrowseRequestSelectionMode; SemanticInventoryBrowseResponse; SemanticInventoryBrowseResponseSourceAvailability; SemanticInventoryBrowseResponseState; SemanticInventoryItem; SemanticInventoryItemCompleteness; SemanticInventoryItemIdentity; SemanticInventoryItemSelectionMode; SemanticSelectionIntent; SemanticSelectionIntentProjectionMode; SemanticSelectionReadiness; SemanticSelectionReadinessStatus; SemanticSelectionResult; SemanticSelectionResultStatus; ServerInterface; ServerInterfaceWrapper; ServiceBadRequest; ServiceBadRequestJSONResponse; ServiceConflict; ServiceConflictJSONResponse; ServiceErrorBody; ServiceErrorResponse; ServiceForbidden; ServiceForbiddenJSONResponse; ServiceInternalError; ServiceInternalErrorJSONResponse; ServiceNotFound; ServiceNotFoundJSONResponse; ServiceUnauthorized; ServiceUnauthorizedJSONResponse; ServiceUnavailable; ServiceUnavailableJSONResponse; ServiceUnprocessable; ServiceUnprocessableJSONResponse; ServiceUnsupportedMediaType; ServiceUnsupportedMediaTypeJSONResponse; Set; SetColumnTransformation; SetColumnTransformationKind; SharedFilterBinding; SharedFilterBindingRef; StrictHandlerFunc; StrictMiddlewareFunc; StrictServerInterface; Tab; TablePresentation; TablePresentationCellRenderer; TableShapeBinaryOperatorChoice; TableShapeBinaryOperatorChoiceChoiceKind; TableShapeBinaryOperatorReference; TableShapeBinaryOperatorReferenceKind; TableShapeCapabilitiesRequest; TableShapeCapabilitiesResponse; TableShapeCategoryDiscoveryRequest; TableShapeCategoryDiscoveryResponse; TableShapeCategoryDiscoveryResponseKind; TableShapeCategoryValue; TableShapeCellContributor; TableShapeCellTrace; TableShapeCellTraceState; TableShapeCellValue; TableShapeChangedCell; TableShapeChangedRow; TableShapeChoiceAvailability; TableShapeChoiceAvailabilityKind; TableShapeColumnReference; TableShapeColumnReferenceKind; TableShapeComparison; TableShapeComparisonAvailable; TableShapeComparisonAvailableStatus; TableShapeComparisonUnavailable; TableShapeComparisonUnavailableStatus; TableShapeContributor; TableShapeDeclaredInformationLoss; TableShapeDeclaredInformationLossStatus; TableShapeDerivedColumnProposal; TableShapeDerivedOperandIntent; TableShapeDerivedOperandIntentKind; TableShapeDerivedOperandIntentRepresentation; TableShapeDerivedOperandReference; TableShapeDerivedOperandReferenceKind; TableShapeDerivedSelection; TableShapeDivisionByZeroPolicyReference; TableShapeDivisionByZeroPolicyReferenceKind; TableShapeDuplicatePolicyReference; TableShapeDuplicatePolicyReferenceKind; TableShapeEditorChoice; TableShapeEditorChoiceChoiceKind; TableShapeEvidenceLimitation; TableShapeExcludedRecord; TableShapeExclusionEvidence; TableShapeExclusionEvidenceStatus; TableShapeInformationLoss; TableShapeLogicalType; TableShapeMissingCellPolicyReference; TableShapeMissingCellPolicyReferenceKind; TableShapeMissingInputPolicyReference; TableShapeMissingInputPolicyReferenceKind; TableShapeOperandSelection; TableShapeOperandSelectionKind; TableShapeOutputDescriptorSupport; TableShapeOutputDescriptorSupportKind; TableShapeOutputName; TableShapeOutputSuggestion; TableShapeOutputSuggestionChoiceKind; TableShapePivotCategoryChoice; TableShapePivotCategoryChoiceChoiceKind; TableShapePivotCategoryDiscovery; TableShapePivotCategoryDiscoveryKind; TableShapePivotCategoryPair; TableShapePivotCategoryReference; TableShapePivotCategoryReferenceKind; TableShapePivotCategorySelection; TableShapePivotProposal; TableShapePivotSelection; TableShapePreviewSummary; TableShapeProposal; TableShapeProposalIntent; TableShapeProposalIntentKind; TableShapeProposalMode; TableShapeProposalRequest; TableShapeProposalRequestMode; TableShapeReshapeModeChoice; TableShapeReshapeModeChoiceChoiceKind; TableShapeReshapeModeChoiceMode; TableShapeReshapeModeReference; TableShapeReshapeModeReferenceKind; TableShapeResolutionRequest; TableShapeResolutionRequestKind; TableShapeResolutionResponse; TableShapeResolutionResponseKind; TableShapeResolvedCategory; TableShapeResolvedOutputDescriptor; TableShapeResolvedOutputDescriptorKind; TableShapeSavedPivotCategorySelection; TableShapeScalar; TableShapeScalarKind; TableShapeSourceIdentity; TableShapeTypeFact; TableShapeUnlistedCategoryPolicyReference; TableShapeUnlistedCategoryPolicyReferenceKind; TableShapeUnpivotNullRowPolicyReference; TableShapeUnpivotNullRowPolicyReferenceKind; TableShapeUnpivotProposal; TableShapeUnpivotSelection; TemporalFieldChoice; TemporalOrderingSource; TemporalOrderingSourceDirection; TemporalOrderingSourceTiePolicy; TemporalReductionCapabilities; TerminologyKey; TraceExplorerCell; TraceExplorerCell200JSONResponse; TraceExplorerCell400JSONResponse; TraceExplorerCell401JSONResponse; TraceExplorerCell403JSONResponse; TraceExplorerCell404JSONResponse; TraceExplorerCell409JSONResponse; TraceExplorerCell422JSONResponse; TraceExplorerCell500JSONResponse; TraceExplorerCell503JSONResponse; TraceExplorerCellJSONRequestBody; TraceExplorerCellRequestObject; TraceExplorerCellResponseObject; TraceExplorerRowLineage; TraceExplorerRowLineage200JSONResponse; TraceExplorerRowLineage400JSONResponse; TraceExplorerRowLineage401JSONResponse; TraceExplorerRowLineage403JSONResponse; TraceExplorerRowLineage404JSONResponse; TraceExplorerRowLineage409JSONResponse; TraceExplorerRowLineage422JSONResponse; TraceExplorerRowLineage500JSONResponse; TraceExplorerRowLineage503JSONResponse; TraceExplorerRowLineage504JSONResponse; TraceExplorerRowLineageJSONRequestBody; TraceExplorerRowLineageRequestObject; TraceExplorerRowLineageResponseObject; UnitIdentity; UnitNormalizationCapabilities; UnitNormalizationContract; UnitNormalizationPolicy; UnitNormalizationPresetCapability; UnitRuleReference; UnmarshalJSON; Valid; ValueByDiscriminator; ValueTransformationCapability; VisitActivateDatasetGenerationResponse; VisitApplyExplorerBuilderCommandsResponse; VisitAssessExplorerRowChangeResponse; VisitBrowseExplorerFrameSourceOptionsResponse; VisitBrowseExplorerSemanticInventoryResponse; VisitCheckExplorerPopulationMappingResponse; VisitCreateDatasetGenerationResponse; VisitCreateExplorerExplicitGroupRevisionResponse; VisitCreateExplorerResponse; VisitCreateExplorerSelectionResponse; VisitCreateInterpretationRevisionFromColumnResponse; VisitCreateInterpretationRevisionResponse; VisitDiscoverExplorerConstructionCategoriesResponse; VisitDiscoverExplorerTableShapeCategoriesResponse; VisitDownloadExplorerArtifactResponse; VisitExecuteDataframeGraphQLResponse; VisitExecuteGraphQLResponse; VisitGetApolloSandboxResponse; VisitGetDatasetGenerationStatusResponse; VisitGetExplorerAuthoringCapabilityResponse; VisitGetExplorerBuilderResponse; VisitGetExplorerColumnSourceResponse; VisitGetExplorerConstructionCapabilitiesResponse; VisitGetExplorerConstructionInputsResponse; VisitGetExplorerResponse; VisitGetExplorerSelectionResponse; VisitGetExplorerTableShapeCapabilitiesResponse; VisitGetGraphQLPlaygroundResponse; VisitGetHealthResponse; VisitGetInterpretationRevisionResponse; VisitGetLivenessResponse; VisitGetReadinessResponse; VisitGetRecipeExecutionResponse; VisitListExplorerRowDefinitionChoicesResponse; VisitListExplorersResponse; VisitListInterpretationLibrariesResponse; VisitPrepareExplorerArtifactResponse; VisitPreviewExplorerResponse; VisitPreviewInterpretationCandidateResponse; VisitProposeExplorerConstructionChoiceResponse; VisitProposeExplorerConstructionResponse; VisitProposeExplorerPopulationMemberRemovalResponse; VisitProposeExplorerRowDefinitionResponse; VisitProposeExplorerTableShapeResponse; VisitPublishExplorerResponse; VisitPublishRepositoryExplorerConfigResponse; VisitReconcileExplorerBuilderResponse; VisitResolveConfiguredColumnContextResponse; VisitResolveExplorerTableShapeResponse; VisitSearchExplorerCandidatesResponse; VisitSearchExplorerConstructionChoicesResponse; VisitSearchExplorerPopulationRoutesResponse; VisitSearchExplorerRelatedExpandChoicesResponse; VisitSearchExplorerRelatedExpandContributorsResponse; VisitSearchExplorerRelatedFieldChoicesResponse; VisitSearchExplorerSchemaFieldsResponse; VisitTraceExplorerCellResponse; VisitTraceExplorerRowLineageResponse; Workspace; WorkspaceOutputConsumer,internal/explorer/authoringv2,—,internal/server,internal/acceptance; internal/explorer/capability,github.com/getkin/kin-openapi/openapi3; github.com/gofiber/fiber/v3; github.com/oapi-codegen/runtime; github.com/oapi-codegen/runtime/types,60 exported interface(s),legacy=17; deprecated=1; compatibility=1,"generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion",go test ./generated/loomapi,inventory complete; semantic disposition pending internal/acceptance,github.com/calypr/loom/internal/acceptance,acceptance,domain/library,"Fetch and cache the locked FHIR fixture, drive the real-data acceptance scenario, and verify its outputs and performance evidence.",fixture.go; performance.go; runner.go; scenario.go,fixture_test.go; performance_test.go; runner_locking_test.go; runner_status_test.go; runner_zero_value_test.go; scenario_test.go,—,—,2283,1570,41,Acquire; Close; ComparePerformance; ComparePerformanceReportFiles; ComparePerformanceRuns; ComputedDigest; Config; Connections; DefaultLimits; Fetcher; FixtureDigest; FixtureLock; Lease; Limits; LoadLock; Namespace; New; Oracle; PerformanceComparisonReport; PerformanceFailure; PerformanceObservation; PerformanceStageSeconds; PerformanceSuspicion; RefreshLock; Report; ResourceLock; Run; RunID; RunScenario; RunSpec; Runner; ScenarioConfig; ScenarioResult; SelectionRule; StageReport; StaticTarget; Target; Validate; ValidatedFixture; WriteLock; WritePerformanceComparison,internal/backends/arango; internal/dataframe/publication; internal/explorer/authoringv2; internal/store/clickhouse,generated/loomapi; internal/dataframe/publication; internal/explorer/authoringv2,cmd/loom-acceptance,—,—,2 exported interface(s),legacy=1; migration=18; compatibility=1,one production importer (cmd/loom-acceptance); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/acceptance,inventory complete; semantic disposition pending internal/api/authpolicy,github.com/calypr/loom/internal/api/authpolicy,authpolicy,transport adapter,Centralize authorization-sentinel classification for transport operations while preserving operation-specific concealment.,policy.go,policy_test.go,—,—,56,44,2,Classify; Operation,internal/authscope; internal/dataframe/errors,internal/authscope; internal/dataframe/errors,internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/server,—,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/api/authpolicy,inventory complete; semantic disposition pending @@ -23,45 +23,47 @@ internal/api/bulk/load,github.com/calypr/loom/internal/api/bulk/load,load,transp internal/api/columncapabilities,github.com/calypr/loom/internal/api/columncapabilities,columncapabilities,transport adapter,"Derive transport-neutral filter, sort, and aggregate capabilities from published ClickHouse column types.",capabilities.go,capabilities_test.go,—,—,56,23,2,FromClickHouse; Tuple,—,—,internal/api/graphql/graph/dataframe; internal/server,—,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/api/columncapabilities,inventory complete; semantic disposition pending internal/api/graphql/graph,github.com/calypr/loom/internal/api/graphql/graph,graphqlapi,transport adapter,"Own GraphQL HTTP handler construction, error presentation, and generated executor binding.",doc.go; errors.go; handler.go,aggregate_http_integration_test.go; errors_test.go; handler_test.go; http_integration_test.go,—,—,511,944,8,Flush; Header; NewApolloSandboxHandler; NewHandler; NewPlaygroundHandler; RouteConfig; Write; WriteHeader,generated/graphql/graph/executor; internal/api/graphql/graph/resolver; internal/api/http; internal/dataframe/errors,internal/api/graphql/graph; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/publication; internal/dataframe/published; internal/dataset,internal/server,internal/api/graphql/graph,github.com/99designs/gqlgen/graphql; github.com/99designs/gqlgen/graphql/handler; github.com/99designs/gqlgen/graphql/playground; github.com/gofiber/fiber/v3/middleware/adaptor; github.com/vektah/gqlparser/v2/gqlerror,—,—,one production importer (internal/server); inspect ownership/inline potential,go test ./internal/api/graphql/graph,completed/keep internal/api/graphql/graph/dataframe,github.com/calypr/loom/internal/api/graphql/graph/dataframe,dataframe,transport adapter,"Adapt published ClickHouse dataframe discovery, row, aggregate, and export operations to GraphQL.",aggregates.go; export.go; model.go; rows.go; service.go,aggregates_test.go; export_test.go; fb22_principal_auth_path_control_test.go; filter_test.go; model_test.go; service_test.go,—,—,1072,506,17,AggregateInput; AggregateRowsResult; AggregationsInput; AggregationsJSON; ColumnFromPhysical; ColumnModel; Config; Dataset; ExportDataframe; Model; NewService; PersistedFailure; ProjectDatasets; Rows; Service; WithOperationContext; Write,generated/graphql/graph/model; internal/api/authpolicy; internal/api/columncapabilities; internal/api/http; internal/authscope; internal/dataframe/errors; internal/dataframe/publication; internal/dataframe/published; internal/projectid,generated/graphql/graph/model; internal/authscope; internal/dataframe/errors; internal/dataframe/published,internal/api/graphql/graph/resolver; internal/server,—,—,—,legacy=9,no automatic consolidation signal; perform semantic review,go test ./internal/api/graphql/graph/dataframe,inventory complete; semantic disposition pending -internal/api/graphql/graph/query,github.com/calypr/loom/internal/api/graphql/graph/query,queryapi,transport adapter,Resolve and authorize Arango-backed FHIR dataframe and graph query requests for GraphQL.,active_generation.go; doc.go; errors.go; fhir_service.go; fieldrefs.go; graph_service.go; helpers.go; input_resolution.go; introspection.go; recipe_mapping.go; recipe_resolve.go; service.go; types.go,active_generation_test.go; auth_scope_test.go; errors_test.go; fhir_service_test.go; fieldrefs_test.go; graph_service_test.go; introspection_test.go; project_identity_test.go; read_scope_ingress_control_chars_e2e_test.go; recipe_mapping_test.go; service_test.go,—,—,1888,1447,28,Config; ExplainFHIRGraph; FHIRGraphExplanation; FHIRGraphNode; FHIRGraphPageInfo; FHIRGraphPath; FHIRGraphQuery; FHIRGraphRelationship; FHIRGraphResult; FHIRGraphTraversal; FHIRListRequest; FHIRListResult; FHIRReadContext; FieldHint; FieldPredicate; FieldSelector; Introspect; IntrospectionRequest; IntrospectionResponse; ListFHIR; ListFHIRInContext; NewService; RecipeBundleFromInput; RelatedResourceHints; ResourceHints; Run; RunFHIRGraph; Service,generated/graphql/graph/model; internal/api/authpolicy; internal/api/recipe; internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataset; internal/fhir/schema; internal/projectid,generated/graphql/graph/model; internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataset,cmd/dataframe-profile; internal/api/graphql/graph/resolver; internal/server,internal/api/graphql/graph,—,—,—,broad surface (13 production files); inspect cohesion,go test ./internal/api/graphql/graph/query,inventory complete; semantic disposition pending -internal/api/graphql/graph/resolver,github.com/calypr/loom/internal/api/graphql/graph/resolver,resolver,transport adapter,Implement gqlgen field resolvers and map GraphQL values to query and lifecycle services.,dataframe_diagnostics.go; fhir_reference_loader.go; fhir_resolver_helpers.go; fhir_schema.resolvers.go; graph_resolvers.go; graphql_output_helpers.go; operation_context.go; recipe_control_graphql.go; recipe_execution_reader.go; recipe_revision_model.go; resolver.go; schema.resolvers.go,fhir_reference_loader_test.go; output_mapping_test.go; recipe_control_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_oracle_probe_test.go; recipe_execution_reader_authz_e2e_test.go; recipe_execution_twin_wire_parity_test.go; recipe_revision_register_test.go,—,fhir_schema.resolvers.go; schema.resolvers.go,5459,2334,362,AbatementDateTime; AbatementString; Active; Actual; ActualComparisonGroup; ActualNumber; Address; Age; AgeString; Alias; AllDay; AllowedBoolean; AmountString; Annotation; AsNeeded; AssignedComparisonGroup; Attachment; AuthorString; AuthoredOn; Availability; AvailabilityAvailableTime; AvailabilityNotAvailableTime; AvailableEndTime; AvailableStartTime; BirthDate; BodyStructure; BodyStructureIncludedStructure; BodyStructureIncludedStructureBodyLandmarkOrientation; BodyStructureIncludedStructureBodyLandmarkOrientationDistanceFromLandmark; BornDate; BornString; Citation; City; Code; CodeMap; CodeableConcept; CodeableReference; Coding; CollectedDateTime; Combined; Comment; Comparator; Conclusion; Condition; ConditionParticipant; ConditionStage; ContactDetail; ContactPoint; ContentType; ContributedToDeath; Count; CountMax; Country; Creation; Currency; Data; DataRequirement; DataRequirementCodeFilter; DataRequirementDateFilter; DataRequirementSort; DataRequirementValueFilter; DataframeAggregate; DataframeAggregations; DataframeDataset; DataframeRecipeExecution; DataframeRecipeRevision; DataframeRecipeRevisions; DataframeRows; Date; DateAsserted; DayOfWeek; DaysOfWeek; DeceasedBoolean; DeceasedDate; DeceasedDateTime; DeceasedString; Description; DescriptionSummary; DiagnosticReport; DiagnosticReportMedia; DiagnosticReportSupportingInfo; Dimensions; Direction; Display; Distance; District; Div; DoNotPerform; DocStatus; DocumentReference; DocumentReferenceAttester; DocumentReferenceContent; DocumentReferenceContentProfile; DocumentReferenceRelatesTo; Documentation; Dosage; DosageDoseAndRate; Duration; DurationMax; DurationUnit; EffectiveDateTime; EffectiveInstant; End; EndDate; EstimatedAge; Event; Exclude; ExpirationDate; Expiry; ExplainDataframeRecipe; ExplainFhirGraph; Expression; ExtendedContactDetail; Extension; FHIRPrimitiveExtension; Factor; Family; FamilyMemberHistory; FamilyMemberHistoryCondition; FamilyMemberHistoryParticipant; FamilyMemberHistoryProcedure; FhirComments; FhirGraph; Frames; Frequency; FrequencyMax; Gender; Given; Group; GroupCharacteristic; GroupMember; Hash; Height; HumanName; Identifier; ImagingStudy; ImagingStudySeries; ImagingStudySeriesInstance; ImagingStudySeriesPerformer; ImplicitRules; Inactive; Instance; InstantiatesCanonical; InstantiatesURI; Intent; Interval; IntervalUnit; IsActive; IsDefining; IsSubPotent; Issued; Label; Language; LastModified; LastUpdated; Limit; Line; LinkID; LotNumber; LowerLimit; Max; Medication; MedicationAdministration; MedicationAdministrationDosage; MedicationAdministrationPerformer; MedicationBatch; MedicationIngredient; MedicationRequest; MedicationRequestDispenseRequest; MedicationRequestDispenseRequestInitialFill; MedicationRequestSubstitution; MedicationStatement; MedicationStatementAdherence; Membership; Meta; Min; MolecularFormula; MolecularFormulaByMoiety; Money; MultipleBirthBoolean; MultipleBirthInteger; MustSupport; Mutation; Name; Narrative; NewAuthorizedRecipeExecutionReader; NewResolver; Number; NumberOfInstances; NumberOfRepeatsAllowed; NumberOfSeries; Observation; ObservationComponent; ObservationReferenceRange; ObservationTriggeredBy; OccurenceDateTime; OccurrenceDateTime; OccurrenceString; Offset; Offsets; OnsetDateTime; OnsetString; Organization; OrganizationQualification; Pages; ParameterDefinition; Path; Patient; PatientCommunication; PatientContact; PatientInstruction; PatientLink; PerformedDateTime; PerformedString; Period; PeriodMax; PeriodUnit; PostalCode; Practitioner; PractitionerCommunication; PractitionerQualification; PractitionerRole; Preferred; Prefix; PreflightDataframeRecipe; PreviewDataframeRecipe; Priority; Procedure; ProcedureFocalDevice; ProcedurePerformer; Profile; ProjectDataframeDatasets; PublicationDate; PublicationStatus; Quantity; Query; Range; Rank; Ratio; RatioRange; Reason; ReceivedTime; RecipeAuthorizer; RecipeControl; RecipeExecution; RecipeExecutionOutput; RecipeExecutionReader; RecipeExecutionStore; Recorded; RecordedDate; Reference; RegisterDataframeRecipeRevision; RelatedArtifact; RenderedDosageInstruction; Repetitions; Reported; ReportedBoolean; Representation; ResearchStudy; ResearchStudyAssociatedParty; ResearchStudyComparisonGroup; ResearchStudyLabel; ResearchStudyObjective; ResearchStudyOutcomeMeasure; ResearchStudyProgressStatus; ResearchStudyRecruitment; ResearchSubject; ResearchSubjectProgress; Resolver; ResolverConfig; Resource; RunDataframeRecipe; RunFhirDataframe; SampledData; SearchParam; Sequence; SigFormat; Signature; Size; Source; Specimen; SpecimenCollection; SpecimenContainer; SpecimenFeature; SpecimenProcessing; Start; StartDate; Started; State; Status; StatusChanged; StatusDate; SubscriptionTopic; Substance; SubstanceDefinition; SubstanceDefinitionCharacterization; SubstanceDefinitionCode; SubstanceDefinitionMoiety; SubstanceDefinitionMolecularWeight; SubstanceDefinitionName; SubstanceDefinitionNameOfficial; SubstanceDefinitionProperty; SubstanceDefinitionRelationship; SubstanceDefinitionSourceMaterial; SubstanceDefinitionStructure; SubstanceDefinitionStructureRepresentation; SubstanceIngredient; Suffix; System; TargetFormat; TargetNumber; Task; TaskInput; TaskOutput; TaskPerformer; TaskRestriction; Text; Time; TimeDateTime; TimeOfDay; Timing; TimingDate; TimingDateTime; TimingRepeat; Title; TriggerDefinition; Type; UID; URL; Unit; UpperLimit; UsageContext; Use; UserSelected; ValidateDataframeRecipe; Value; ValueBase64Binary; ValueBoolean; ValueCanonical; ValueCode; ValueDate; ValueDateTime; ValueDecimal; ValueID; ValueInstant; ValueInteger; ValueInteger64; ValueMarkdown; ValueOid; ValuePositiveInt; ValueSet; ValueString; ValueTime; ValueURI; ValueURL; ValueUUID; ValueUnsignedInt; Version; VersionID; When; Width; WithOperationContext,generated/fhir; generated/graphql/graph/executor; generated/graphql/graph/model; internal/api/authpolicy; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataset; internal/fhir/schema,generated/fhir; generated/graphql/graph/model; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/publication; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec,internal/api/graphql/graph; internal/server,—,github.com/99designs/gqlgen/graphql; github.com/vektah/gqlparser/v2/ast,3 exported interface(s),—,broad surface (12 production files); inspect cohesion; mixed handwritten/generated package (2 generated file(s)); preserve regeneration boundary; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/api/graphql/graph/resolver,inventory complete; semantic disposition pending -internal/api/http,github.com/calypr/loom/internal/api/http,httpapi,transport adapter,"Own shared Fiber server setup, middleware, request IDs, errors, health, and readiness behavior.",doc.go; errors.go; middleware.go; request_id.go; routes.go; server.go,backend_error_boundary_contract_test.go; errors_test.go; http_test.go; request_id_test.go,—,—,533,887,15,App; ContextWithRequestID; ErrorResponse; HTTPConfig; HTTPErrorBody; HTTPServer; Health; Liveness; Logger; MapDataframeError; MappedError; NewHTTPServer; Readiness; RequestIDFromContext; StatusForErrorCode,internal/authscope; internal/dataframe/errors,internal/authscope; internal/backends/arango; internal/backends/postgres; internal/dataframe/errors,internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/server,internal/api/bulk/load; internal/backends/postgres/internal/store,github.com/gofiber/fiber/v3; github.com/google/uuid,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/api/http,inventory complete; semantic disposition pending +internal/api/graphql/graph/query,github.com/calypr/loom/internal/api/graphql/graph/query,queryapi,transport adapter,Resolve and authorize Arango-backed FHIR dataframe and graph query requests for GraphQL.,active_generation.go; doc.go; errors.go; fhir_service.go; fieldrefs.go; graph_service.go; helpers.go; input_resolution.go; introspection.go; recipe_mapping.go; recipe_resolve.go; service.go; types.go,active_generation_test.go; auth_scope_test.go; errors_test.go; fhir_service_test.go; fieldrefs_test.go; graph_service_test.go; introspection_test.go; postgres_query_test.go; project_identity_test.go; read_scope_ingress_control_chars_e2e_test.go; recipe_mapping_test.go; service_test.go,—,—,1888,2363,28,Config; ExplainFHIRGraph; FHIRGraphExplanation; FHIRGraphNode; FHIRGraphPageInfo; FHIRGraphPath; FHIRGraphQuery; FHIRGraphRelationship; FHIRGraphResult; FHIRGraphTraversal; FHIRListRequest; FHIRListResult; FHIRReadContext; FieldHint; FieldPredicate; FieldSelector; Introspect; IntrospectionRequest; IntrospectionResponse; ListFHIR; ListFHIRInContext; NewService; RecipeBundleFromInput; RelatedResourceHints; ResourceHints; Run; RunFHIRGraph; Service,generated/graphql/graph/model; internal/api/authpolicy; internal/api/recipe; internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataset; internal/fhir/schema; internal/projectid,generated/graphql/graph/model; internal/authscope; internal/backends/postgres; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataset; internal/ingest,cmd/dataframe-profile; internal/api/graphql/graph/resolver; internal/server,internal/api/graphql/graph,—,—,—,broad surface (13 production files); inspect cohesion,go test ./internal/api/graphql/graph/query,inventory complete; semantic disposition pending +internal/api/graphql/graph/resolver,github.com/calypr/loom/internal/api/graphql/graph/resolver,resolver,transport adapter,Implement gqlgen field resolvers and map GraphQL values to query and lifecycle services.,dataframe_diagnostics.go; fhir_reference_loader.go; fhir_resolver_helpers.go; fhir_schema.resolvers.go; graph_resolvers.go; graphql_output_helpers.go; operation_context.go; recipe_control_graphql.go; recipe_execution_reader.go; recipe_revision_model.go; resolver.go; schema.resolvers.go,dataframe_diagnostics_test.go; fhir_reference_loader_test.go; output_mapping_test.go; recipe_control_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_oracle_probe_test.go; recipe_execution_reader_authz_e2e_test.go; recipe_execution_twin_wire_parity_test.go; recipe_revision_register_test.go,—,fhir_schema.resolvers.go; schema.resolvers.go,5469,2376,362,AbatementDateTime; AbatementString; Active; Actual; ActualComparisonGroup; ActualNumber; Address; Age; AgeString; Alias; AllDay; AllowedBoolean; AmountString; Annotation; AsNeeded; AssignedComparisonGroup; Attachment; AuthorString; AuthoredOn; Availability; AvailabilityAvailableTime; AvailabilityNotAvailableTime; AvailableEndTime; AvailableStartTime; BirthDate; BodyStructure; BodyStructureIncludedStructure; BodyStructureIncludedStructureBodyLandmarkOrientation; BodyStructureIncludedStructureBodyLandmarkOrientationDistanceFromLandmark; BornDate; BornString; Citation; City; Code; CodeMap; CodeableConcept; CodeableReference; Coding; CollectedDateTime; Combined; Comment; Comparator; Conclusion; Condition; ConditionParticipant; ConditionStage; ContactDetail; ContactPoint; ContentType; ContributedToDeath; Count; CountMax; Country; Creation; Currency; Data; DataRequirement; DataRequirementCodeFilter; DataRequirementDateFilter; DataRequirementSort; DataRequirementValueFilter; DataframeAggregate; DataframeAggregations; DataframeDataset; DataframeRecipeExecution; DataframeRecipeRevision; DataframeRecipeRevisions; DataframeRows; Date; DateAsserted; DayOfWeek; DaysOfWeek; DeceasedBoolean; DeceasedDate; DeceasedDateTime; DeceasedString; Description; DescriptionSummary; DiagnosticReport; DiagnosticReportMedia; DiagnosticReportSupportingInfo; Dimensions; Direction; Display; Distance; District; Div; DoNotPerform; DocStatus; DocumentReference; DocumentReferenceAttester; DocumentReferenceContent; DocumentReferenceContentProfile; DocumentReferenceRelatesTo; Documentation; Dosage; DosageDoseAndRate; Duration; DurationMax; DurationUnit; EffectiveDateTime; EffectiveInstant; End; EndDate; EstimatedAge; Event; Exclude; ExpirationDate; Expiry; ExplainDataframeRecipe; ExplainFhirGraph; Expression; ExtendedContactDetail; Extension; FHIRPrimitiveExtension; Factor; Family; FamilyMemberHistory; FamilyMemberHistoryCondition; FamilyMemberHistoryParticipant; FamilyMemberHistoryProcedure; FhirComments; FhirGraph; Frames; Frequency; FrequencyMax; Gender; Given; Group; GroupCharacteristic; GroupMember; Hash; Height; HumanName; Identifier; ImagingStudy; ImagingStudySeries; ImagingStudySeriesInstance; ImagingStudySeriesPerformer; ImplicitRules; Inactive; Instance; InstantiatesCanonical; InstantiatesURI; Intent; Interval; IntervalUnit; IsActive; IsDefining; IsSubPotent; Issued; Label; Language; LastModified; LastUpdated; Limit; Line; LinkID; LotNumber; LowerLimit; Max; Medication; MedicationAdministration; MedicationAdministrationDosage; MedicationAdministrationPerformer; MedicationBatch; MedicationIngredient; MedicationRequest; MedicationRequestDispenseRequest; MedicationRequestDispenseRequestInitialFill; MedicationRequestSubstitution; MedicationStatement; MedicationStatementAdherence; Membership; Meta; Min; MolecularFormula; MolecularFormulaByMoiety; Money; MultipleBirthBoolean; MultipleBirthInteger; MustSupport; Mutation; Name; Narrative; NewAuthorizedRecipeExecutionReader; NewResolver; Number; NumberOfInstances; NumberOfRepeatsAllowed; NumberOfSeries; Observation; ObservationComponent; ObservationReferenceRange; ObservationTriggeredBy; OccurenceDateTime; OccurrenceDateTime; OccurrenceString; Offset; Offsets; OnsetDateTime; OnsetString; Organization; OrganizationQualification; Pages; ParameterDefinition; Path; Patient; PatientCommunication; PatientContact; PatientInstruction; PatientLink; PerformedDateTime; PerformedString; Period; PeriodMax; PeriodUnit; PostalCode; Practitioner; PractitionerCommunication; PractitionerQualification; PractitionerRole; Preferred; Prefix; PreflightDataframeRecipe; PreviewDataframeRecipe; Priority; Procedure; ProcedureFocalDevice; ProcedurePerformer; Profile; ProjectDataframeDatasets; PublicationDate; PublicationStatus; Quantity; Query; Range; Rank; Ratio; RatioRange; Reason; ReceivedTime; RecipeAuthorizer; RecipeControl; RecipeExecution; RecipeExecutionOutput; RecipeExecutionReader; RecipeExecutionStore; Recorded; RecordedDate; Reference; RegisterDataframeRecipeRevision; RelatedArtifact; RenderedDosageInstruction; Repetitions; Reported; ReportedBoolean; Representation; ResearchStudy; ResearchStudyAssociatedParty; ResearchStudyComparisonGroup; ResearchStudyLabel; ResearchStudyObjective; ResearchStudyOutcomeMeasure; ResearchStudyProgressStatus; ResearchStudyRecruitment; ResearchSubject; ResearchSubjectProgress; Resolver; ResolverConfig; Resource; RunDataframeRecipe; RunFhirDataframe; SampledData; SearchParam; Sequence; SigFormat; Signature; Size; Source; Specimen; SpecimenCollection; SpecimenContainer; SpecimenFeature; SpecimenProcessing; Start; StartDate; Started; State; Status; StatusChanged; StatusDate; SubscriptionTopic; Substance; SubstanceDefinition; SubstanceDefinitionCharacterization; SubstanceDefinitionCode; SubstanceDefinitionMoiety; SubstanceDefinitionMolecularWeight; SubstanceDefinitionName; SubstanceDefinitionNameOfficial; SubstanceDefinitionProperty; SubstanceDefinitionRelationship; SubstanceDefinitionSourceMaterial; SubstanceDefinitionStructure; SubstanceDefinitionStructureRepresentation; SubstanceIngredient; Suffix; System; TargetFormat; TargetNumber; Task; TaskInput; TaskOutput; TaskPerformer; TaskRestriction; Text; Time; TimeDateTime; TimeOfDay; Timing; TimingDate; TimingDateTime; TimingRepeat; Title; TriggerDefinition; Type; UID; URL; Unit; UpperLimit; UsageContext; Use; UserSelected; ValidateDataframeRecipe; Value; ValueBase64Binary; ValueBoolean; ValueCanonical; ValueCode; ValueDate; ValueDateTime; ValueDecimal; ValueID; ValueInstant; ValueInteger; ValueInteger64; ValueMarkdown; ValueOid; ValuePositiveInt; ValueSet; ValueString; ValueTime; ValueURI; ValueURL; ValueUUID; ValueUnsignedInt; Version; VersionID; When; Width; WithOperationContext,generated/fhir; generated/graphql/graph/executor; generated/graphql/graph/model; internal/api/authpolicy; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataset; internal/fhir/schema,generated/fhir; generated/graphql/graph/model; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/publication; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec,internal/api/graphql/graph; internal/server,—,github.com/99designs/gqlgen/graphql; github.com/vektah/gqlparser/v2/ast,3 exported interface(s),compatibility=1,broad surface (12 production files); inspect cohesion; mixed handwritten/generated package (2 generated file(s)); preserve regeneration boundary; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/api/graphql/graph/resolver,inventory complete; semantic disposition pending +internal/api/http,github.com/calypr/loom/internal/api/http,httpapi,transport adapter,"Own shared Fiber server setup, middleware, request IDs, errors, health, and readiness behavior.",doc.go; errors.go; middleware.go; request_id.go; routes.go; server.go,backend_error_boundary_contract_test.go; errors_test.go; health_readiness_test.go; http_test.go; request_id_test.go,—,—,642,1145,15,App; ContextWithRequestID; ErrorResponse; HTTPConfig; HTTPErrorBody; HTTPServer; Health; Liveness; Logger; MapDataframeError; MappedError; NewHTTPServer; Readiness; RequestIDFromContext; StatusForErrorCode,internal/authscope; internal/dataframe/errors,internal/authscope; internal/backends/arango; internal/backends/postgres; internal/dataframe/errors,internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/server,internal/api/bulk/load; internal/backends/postgres/internal/store,github.com/gofiber/fiber/v3; github.com/google/uuid,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/api/http,inventory complete; semantic disposition pending internal/api/recipe,github.com/calypr/loom/internal/api/recipe,recipeapi,transport adapter,Adapt scoped catalog field discovery to recipe schema resolution for one-shot and stored recipes.,discovery.go,discovery_auth_mode_test.go; discovery_test.go,—,—,110,126,3,FieldDiscovery; Fields; NewFieldDiscovery,internal/authscope; internal/catalog; internal/dataframe/errors; internal/dataframe/recipe/schema,internal/authscope; internal/catalog; internal/dataframe/errors; internal/dataframe/recipe/schema,internal/api/graphql/graph/query; internal/server,—,—,—,legacy=1; compatibility=1,no automatic consolidation signal; perform semantic review,go test ./internal/api/recipe,inventory complete; semantic disposition pending -internal/authscope,github.com/calypr/loom/internal/authscope,authscope,domain/library,"Represent request principals and resolve authorized project, generation, and resource scopes.",authentication.go; doc.go; fence_resource_access.go; principal.go; scope_resolver.go,all_generations_cache_key_test.go; auth_path_control_ingress_test.go; auth_resource_path_options_conflict_test.go; authentication_test.go; covers_execution_paths_control_test.go; persisted_scope_fails_closed_test.go; probe_double_normalize_test.go; scope_cache_key_namespace_test.go; scope_test.go,—,—,1175,2104,36,AllowAllAuthorizer; Authenticate; Authenticator; AuthorizeProject; AuthorizeReadProject; AuthorizeWrite; Authorizer; BasicAuthenticator; BearerTokenAuthenticator; CalyprAuthenticator; Clone; ContextWithPrincipal; CoversExecutionPaths; FenceUserAccessClient; GetAllowedResources; InvalidateGeneration; InvalidateProject; NewFenceUserAccessClientWithTrustedIssuer; NewScopeResolver; NormalizeAuthResourcePath; NormalizeAuthResourcePaths; Permission; Principal; PrincipalFromContext; ReadScope; ReadScopeMode; ResolveReadScope; ResolveReadScopeForGeneration; ResolveWriteScopeForGeneration; ResourceAccessClient; ScopeAuthorizer; ScopeResolver; ScopeResolverConfig; StaticAuthenticator; Unrestricted; ValidateTrustedFenceIssuer,internal/catalog; internal/projectid,internal/catalog,cmd/preview-covering-index-artifact; internal/api/authpolicy; internal/api/bulk/load; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/backends/arango/internal/explorer; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer/lifecycle; internal/server,internal/api/graphql/graph; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/store; internal/dataframe/compiler; internal/dataframe/compiler/render/aql,—,3 exported interface(s),legacy=5,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/authscope,inventory complete; semantic disposition pending -internal/backends/arango,github.com/calypr/loom/internal/backends/arango,arango,storage adapter,"Expose the only public entry to the ArangoDB 3.12 backend: a single Open factory validates explicit config, owns exactly one lifecycle client with the required collection bootstrap policy, and returns the typed runtime carrying the catalog, dataset lifecycle, Explorer, recipe registry/revisions, and published-dataframe ports assembled from the private implementation tree, with alias re-exports for witness-side reassembly.",aliases.go; doc.go; open.go,ownership_contract_test.go,—,—,329,574,4,Close; Config; Open; Runtime,internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec; internal/backends/arango/internal/store,internal/backends/arango,cmd/arango-fhir-proto; cmd/dataframe-profile; cmd/loom-data; internal/acceptance; internal/ingest; internal/server,conformance/compiler; internal/api/http; internal/backends/arango; internal/dataframe/compiler; internal/dataframe/execution; internal/dataframe/publication/clickhouse; internal/dataset; internal/explorer/compilation,—,—,—,storage adapter; preserve interface and persisted-data contracts,go test ./internal/backends/arango,inventory complete; semantic disposition pending +internal/authscope,github.com/calypr/loom/internal/authscope,authscope,domain/library,"Represent request principals and resolve authorized project, generation, and resource scopes.",authentication.go; doc.go; fence_resource_access.go; principal.go; scope_resolver.go,all_generations_cache_key_test.go; auth_path_control_ingress_test.go; auth_resource_path_options_conflict_test.go; authentication_test.go; covers_execution_paths_control_test.go; persisted_scope_fails_closed_test.go; probe_double_normalize_test.go; scope_cache_key_namespace_test.go; scope_test.go,—,—,1175,2104,36,AllowAllAuthorizer; Authenticate; Authenticator; AuthorizeProject; AuthorizeReadProject; AuthorizeWrite; Authorizer; BasicAuthenticator; BearerTokenAuthenticator; CalyprAuthenticator; Clone; ContextWithPrincipal; CoversExecutionPaths; FenceUserAccessClient; GetAllowedResources; InvalidateGeneration; InvalidateProject; NewFenceUserAccessClientWithTrustedIssuer; NewScopeResolver; NormalizeAuthResourcePath; NormalizeAuthResourcePaths; Permission; Principal; PrincipalFromContext; ReadScope; ReadScopeMode; ResolveReadScope; ResolveReadScopeForGeneration; ResolveWriteScopeForGeneration; ResourceAccessClient; ScopeAuthorizer; ScopeResolver; ScopeResolverConfig; StaticAuthenticator; Unrestricted; ValidateTrustedFenceIssuer,internal/catalog; internal/projectid,internal/catalog,cmd/preview-covering-index-artifact; internal/api/authpolicy; internal/api/bulk/load; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/backends/arango/internal/explorer; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer/lifecycle; internal/server,internal/api/graphql/graph; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/store; internal/dataframe/benchmark; internal/dataframe/compiler; internal/dataframe/compiler/render/aql,—,3 exported interface(s),legacy=5,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/authscope,inventory complete; semantic disposition pending +internal/backends/arango,github.com/calypr/loom/internal/backends/arango,arango,storage adapter,"Expose the only public entry to the ArangoDB 3.12 backend: a single Open factory validates explicit config, owns exactly one lifecycle client with the required collection bootstrap policy, and returns the typed runtime carrying the catalog, dataset lifecycle, Explorer, recipe registry/revisions, and published-dataframe ports assembled from the private implementation tree, with alias re-exports for witness-side reassembly.",aliases.go; doc.go; open.go,ownership_contract_test.go,—,—,336,800,4,Close; Config; Open; Runtime,internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec; internal/backends/arango/internal/store,internal/backends/arango,cmd/arango-fhir-proto; cmd/dataframe-profile; cmd/loom-data; internal/acceptance; internal/ingest; internal/server,conformance/compiler; internal/api/http; internal/backends/arango; internal/dataframe/compiler; internal/dataframe/execution; internal/dataframe/publication/clickhouse; internal/dataset; internal/explorer/compilation,—,—,—,storage adapter; preserve interface and persisted-data contracts,go test ./internal/backends/arango,inventory complete; semantic disposition pending internal/backends/arango/internal/catalog,github.com/calypr/loom/internal/backends/arango/internal/catalog,arango,domain/library,Persist and retrieve catalog facts in ArangoDB.,codec.go; evidence.go; fields.go; relationships.go; resource_inventory.go; resources.go; semantic_coverage.go; semantic_coverage_envelope.go; semantic_inventory.go; store.go,all_generations_auth_path_conflict_test.go; all_generations_project_binding_integration_test.go; resource_inventory_integration_test.go; resource_inventory_unit_test.go; semantic_coded_category_integration_test.go; semantic_coverage_envelope_test.go; semantic_coverage_resource_integration_test.go; semantic_coverage_test.go; semantic_inventory_category_test.go; semantic_inventory_integration_test.go; semantic_inventory_selection_test.go; semantic_inventory_source_availability_test.go; store_test.go,—,—,5267,5466,54,AccumulateRelationshipCatalog; AddSource; AdvanceSemanticInventoryBackfill; AdvanceSemanticInventoryBuild; AuditRelationshipEdges; BeginSemanticCoverageBuild; BeginSemanticCoverageResource; BeginSemanticInventoryBuild; ClaimSemanticInventoryBackfill; CompleteRetainedSemanticCoverageResource; CompleteSemanticCoverageResource; CompleteSemanticInventoryBackfill; CompleteSemanticInventoryBuild; DiscoverExistingAuthResourcePaths; DiscoverFieldEnrichment; DiscoverFields; DiscoverReferences; DiscoverRelationshipObservations; DiscoverResourceInventory; EncodeSemanticCoverageSourceEnvelope; EnsureSemanticInventoryEntries; FailSemanticCoverageBuild; FailSemanticCoverageResource; FailSemanticInventoryBackfill; FailSemanticInventoryBuild; FinalizeSemanticCoverageBuild; LookupSemanticCoverage; MarkSemanticCoverageResourceMissing; New; NewSemanticCoverageSourceSetAccumulator; PageSemanticInventory; PrepareSemanticInventoryBackfill; ReadFieldEnrichment; ReadRelationshipObservations; ReadResourceInventory; ReadRetainedSemanticInventoryPage; RebuildRelationshipCatalog; RebuildResourceInventory; RefreshSemanticInventorySourceAvailability; ResolveSemanticInventorySelections; SemanticCoverageBuildIdentity; SemanticCoverageCollectionSpec; SemanticCoveragePage; SemanticCoverageResourceExpectation; SemanticCoverageSourcePartitionExpectation; SemanticCoverageSourceSetAccumulator; SemanticCoverageWriteStats; Snapshot; Store; ValidateAuthResourcePathOptions; WriteFieldCatalog; WriteRelationshipCatalog; WriteSemanticCoverageContributions; WriteSemanticInventoryContributions,internal/backends/arango/internal/store; internal/catalog; internal/fhir/schema,internal/authscope; internal/backends/arango/internal/store; internal/catalog,internal/backends/arango,—,github.com/bytedance/sonic,—,—,one production importer (internal/backends/arango); inspect ownership/inline potential,go test ./internal/backends/arango/internal/catalog,inventory complete; semantic disposition pending internal/backends/arango/internal/chartifact,github.com/calypr/loom/internal/backends/arango/internal/chartifact,arango,domain/library,Persist private ClickHouse artifact manifests and leases in a dedicated Arango collection.,catalog.go,catalog_arango_test.go; catalog_test.go,—,—,379,723,10,BootstrapSpec; Catalog; ClaimCleanup; Create; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update,internal/backends/arango/internal/store; internal/dataframe/execution/chartifact,internal/authscope; internal/backends/arango/internal/store; internal/dataframe/execution/chartifact; internal/store/clickhouse,internal/backends/arango,—,—,—,—,one production importer (internal/backends/arango); inspect ownership/inline potential,go test ./internal/backends/arango/internal/chartifact,inventory complete; semantic disposition pending internal/backends/arango/internal/dataset,github.com/calypr/loom/internal/backends/arango/internal/dataset,arango,domain/library,Persist immutable dataset generation lifecycle state in ArangoDB.,collections.go; doc.go; document_codec.go; manifest_lifecycle.go; release_store.go,generation_activation_cas_e2e_test.go; release_policy_change_e2e_test.go; revision_inflation_e2e_test.go; store_test.go,—,—,764,1652,15,Activate; ActiveDocumentKey; BootstrapSpec; CollectionSpecs; CompareAndSwapActivateRelease; CompareAndSwapPublishRelease; CreateManifest; ManifestDocumentKey; New; ReadActiveRelease; ReadManifest; ResolveActiveManifest; SaveRelease; Store; TransitionManifest,internal/backends/arango/internal/store; internal/dataset,internal/backends/arango/internal/store; internal/dataset,internal/backends/arango; internal/backends/arango/internal/explorer,internal/backends/arango/internal/publication,github.com/arangodb/go-driver/v2/arangodb/shared,—,compatibility=2,no automatic consolidation signal; perform semantic review,go test ./internal/backends/arango/internal/dataset,inventory complete; semantic disposition pending internal/backends/arango/internal/explorer,github.com/calypr/loom/internal/backends/arango/internal/explorer,arango,domain/library,"Persist Explorer drafts, revisions, receipts, publications, capabilities, and repository state in ArangoDB.",activation.go; capability_snapshot_store.go; codec.go; collections.go; drafts.go; explicit_groups.go; explorers.go; interpretations.go; publication.go; receipts.go; repository.go; revisions.go; selection_references.go; selections.go; store.go; table_shape_capabilities.go,capability_snapshot_store_test.go; collections_test.go; cross_generation_publish_e2e_test.go; draft_conflict_test.go; draft_transaction_formal_replay_test.go; drafts_integration_test.go; drafts_test.go; explicit_group_storage_integration_test.go; explicit_groups_test.go; interpretations_test.go; receipt_store_test.go; repository_test.go; selection_references_test.go; selection_scale_integration_test.go; selection_storage_integration_test.go; selections_cleanup_e2e_test.go; store_test.go; table_shape_capabilities_test.go,—,—,3366,4552,56,AbortExplicitGroupRevision; AbortSelection; ActivateRepositoryGeneration; AppendExplicitGroupMemberships; AppendSelectionMembers; BeginExplicitGroupRevision; BeginSelection; BootstrapSpec; CapabilitySnapshotCollectionSpec; CapabilitySnapshotStore; CleanupExplicitGroupStaging; CleanupSelectionStaging; CollectionSpecs; CompleteExplicitGroupRevision; CompleteSelection; Create; CreateInterpretationRevision; DigestExplicitGroupRevision; DigestSelectionMembers; FailRevision; Get; GetByIdentity; GetByToken; GetCatalog; GetCatalogForLookup; GetCategoryScan; GetCompilationReceiptForExplorer; GetDraftRevision; GetExplicitGroupRevision; GetInterpretationRevision; GetResolution; GetRevision; GetSelection; InsertCompilationReceipt; InsertRevision; List; ListExplicitGroupDefinitions; ListExplicitGroupRevisions; ListInterpretationLibraries; MigrateLegacyRepositoryConfigs; New; NewCapabilitySnapshotStore; NewTableShapeCapabilityRepository; PublishAuthoring; Put; PutCatalog; PutCategoryScan; PutExplicitGroupDefinitions; PutResolution; SaveDraft; Store; TableShapeCapabilitiesCollectionSpec; TableShapeCapabilityRepository; ValidateSelectionReferences; VisitExplicitGroupMemberships; VisitSelectionMembers,internal/authscope; internal/backends/arango/internal/dataset; internal/backends/arango/internal/publication; internal/backends/arango/internal/store; internal/dataframe/publication; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/tableshapecap; internal/projectid,internal/authscope; internal/backends/arango/internal/dataset; internal/backends/arango/internal/publication; internal/backends/arango/internal/store; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/projectid,internal/backends/arango,—,github.com/arangodb/go-driver/v2/arangodb/shared,—,legacy=12; migration=4; compatibility=1,one production importer (internal/backends/arango); inspect ownership/inline potential; broad surface (16 production files); inspect cohesion,go test ./internal/backends/arango/internal/explorer,inventory complete; semantic disposition pending internal/backends/arango/internal/publication,github.com/calypr/loom/internal/backends/arango/internal/publication,arango,domain/library,Persist dataframe publication registry and bundle metadata in ArangoDB.,bundle_registry.go; registry.go,bundle_registry_test.go; dangling_binding_e2e_test.go; execution_read_pin_integration_test.go; name_latest_binding_e2e_test.go; publish_atomicity_e2e_test.go; release_dangle_reconcile_e2e_test.go,—,—,533,2990,22,AcquireBundleLease; AcquireExecutionReadPin; BootstrapSpec; ClaimExecutionCleanup; Client; CompareAndSwapPointer; FindExecutionByKey; FindExecutionBySelector; GetExecution; GetPointer; ListExecutions; New; PublishExecution; Registry; ReleaseBundleLease; ReleaseExecutionCleanup; ReleaseExecutionReadPin; RenewBundleLease; RenewExecutionCleanup; RenewExecutionReadPin; SaveExecution; VisitExecutionPages,internal/backends/arango/internal/store; internal/dataframe/publication,internal/backends/arango/internal/dataset; internal/backends/arango/internal/publication; internal/backends/arango/internal/store; internal/dataframe/errors; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataset; internal/store/clickhouse,internal/backends/arango; internal/backends/arango/internal/explorer,internal/backends/arango/internal/publication,github.com/arangodb/go-driver/v2/arangodb/shared,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/backends/arango/internal/publication,inventory complete; semantic disposition pending internal/backends/arango/internal/recipeexec,github.com/calypr/loom/internal/backends/arango/internal/recipeexec,arango,domain/library,Persist recipe versions and execution-use records in ArangoDB.,registry.go; revisions.go,registry_test.go; revisions_test.go; revisions_user_faults_test.go,—,—,294,555,15,BootstrapSpec; Client; Get; List; LoadRecipe; LoadRecipeVersion; New; NewRevisionRegistry; RecipeVersionUsed; Register; Registry; ReplaceRecipeVersion; RevisionBootstrapSpec; RevisionRegistry; SaveRecipe,internal/backends/arango/internal/store; internal/dataframe/errors; internal/dataframe/recipe; internal/dataframe/recipe/exec,internal/backends/arango/internal/store; internal/dataframe/errors; internal/dataframe/recipe; internal/dataframe/recipe/exec,internal/backends/arango,—,—,—,legacy=1,one production importer (internal/backends/arango); inspect ownership/inline potential,go test ./internal/backends/arango/internal/recipeexec,inventory complete; semantic disposition pending -internal/backends/arango/internal/store,github.com/calypr/loom/internal/backends/arango/internal/store,arango,domain/library,"Provide the low-level typed ArangoDB client, query streaming, profiling, and Explain assessment boundary.",client.go; errors.go; explain.go; explain_assessment.go; explain_client.go; options.go; preview_index.go; profile.go; profile_client.go; types.go,client_query_memory_limit_test.go; client_queryrows_test.go; client_revision_test.go; errors_test.go; explain_assessment_test.go; explain_client_test.go; explain_test.go; population_profile_integration_test.go; preview_index_test.go; profile_client_test.go; profile_integration_test.go; profile_test.go,—,—,1861,2210,70,AQLExecutor; AssessExplainResult; BatchInserter; Bootstrap; BootstrapSpec; Client; Close; CollectionCount; CollectionExists; CollectionRevision; CollectionSpec; ConnectionOptions; DatabaseExists; DiagnosticCause; EnsurePreviewCoveringIndex; EnsurePreviewCoveringIndexReplacing; EnsurePreviewCoveringIndexWithStoredValues; EnsurePreviewCoveringIndexWithStoredValuesReplacing; ExecuteAQL; Explain; ExplainAssessment; ExplainCollection; ExplainCollectionScan; ExplainIndex; ExplainIndexLocation; ExplainIndexSummary; ExplainIndexUse; ExplainIndexes; ExplainNode; ExplainOptions; ExplainPlan; ExplainPlanEstimate; ExplainRequest; ExplainResult; ExplainStats; ExplainWarning; InsertBatchRaw; IsArangoErrorResponse; IsArangoPermissionResponse; ListPreviewCoveringIndexes; Open; OptimizerOptions; ParseExplainResult; ParseProfileResult; PreviewCoveringIndex; Profile; ProfileExtra; ProfileNode; ProfileNodeGroup; ProfileNodeSummary; ProfileOptions; ProfilePhases; ProfileRequest; ProfileResult; ProfileStats; ProfileSummary; QueryMemoryLimitFromContext; QueryRows; QueryRowsWithMaxRuntime; RowBatchClient; RowQueryer; RowVisitor; SummarizeProfile; TransactionCollections; TransactionFunc; TransactionalRowClient; TranslateQueryError; UnmarshalJSON; WithQueryMemoryLimit; WithTransaction,internal/dataframe/errors,internal/dataframe/errors,internal/backends/arango; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec,—,github.com/arangodb/go-driver/v2/arangodb; github.com/arangodb/go-driver/v2/arangodb/shared; github.com/arangodb/go-driver/v2/connection; github.com/arangodb/go-driver/v2/utils,5 exported interface(s),legacy=1,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/arango/internal/store,inventory complete; semantic disposition pending -internal/backends/postgres,github.com/calypr/loom/internal/backends/postgres,postgres,storage adapter,"Expose the only public entry to the PostgreSQL 18 backend: a single Open factory validates explicit config, owns exactly one readiness-gated pool with own-prefix connection tagging, and returns the typed runtime carrying the SourceRows and PlanJSON data-plane ports over tagged compiled commands plus every control-plane port assembled from the private implementation tree.",aliases.go; doc.go; open.go; plan_executor.go,engine_postgres_command_boundary_pg18_test.go; engine_postgres_pg18_test.go; explain_postgres_test.go; open_live_test.go; ownership_contract_test.go; plan_executor_test.go,—,—,317,4349,4,NewPlanJSONExecutor; NewSourceRows; Open; Runtime,internal/backends/postgres/control; internal/backends/postgres/internal/rows; internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/execution,internal/backends/postgres; internal/backends/postgres/control; internal/backends/postgres/internal/rows; internal/backends/postgres/internal/store; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/server; internal/source,cmd/loom-data; internal/ingest,internal/api/http; internal/backends/postgres; internal/dataframe/execution; internal/server,—,—,migration=1,storage adapter; preserve interface and persisted-data contracts,go test ./internal/backends/postgres,inventory complete; semantic disposition pending -internal/backends/postgres/control,github.com/calypr/loom/internal/backends/postgres/control,control,domain/library,"Host the cycle-free public control plane of the PostgreSQL backend: configuration and series-assignment validation, the bare-pool and batch-vocabulary re-exports, the dataset manifest lifecycle and catalog evidence port assembly, and the structural query surface that backend-adjacent witnesses drive without importing the execution bridge.",config.go; errors.go; runtime.go; vocabulary.go; vocabulary_control.go,—,—,—,893,0,42,AllSeries; CatalogStore; Close; Config; ConnSource; Error; FieldProfileScope; GenerationAdvisoryLockKey; Health; MigrateDatasetReleaseSchema; MigrateResult; MigrateSchema; NewCatalogFieldProfileStore; NewCatalogRelationshipEvidenceRepository; NewCatalogRelationshipEvidenceStore; NewCatalogStore; NewEdgeRepository; NewEdgeWriter; NewExplicitGroupStore; NewExplorerStore; NewFieldProfileRepository; NewInventoryRepository; NewResourceRepository; NewResourceWriter; NewSemanticContributionRepository; NewSemanticReadRepository; Open; OpenPool; QuerierAsAny; Query; QueryRow; Row; Rows; Runtime; Scan; SemanticContributionRepository; Series; SeriesSchemas; SetupPolicy; Unwrap; Validate; WithAdvisoryLock,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/store; internal/catalog; internal/dataset,—,internal/backends/postgres,internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/server,—,2 exported interface(s),migration=14,one production importer (internal/backends/postgres); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/control,inventory complete; semantic disposition pending -internal/backends/postgres/internal/catalog,github.com/calypr/loom/internal/backends/postgres/internal/catalog,postgres,domain/library,"Persist and retrieve catalog facts in PostgreSQL 18: publish-last generation-scoped resource inventory builds, bounded field-profile records with completeness and truncation metadata, leased compare-and-swap-guarded semantic inventory build state, deterministic semantic-inventory contribution rows with content-addressed replay and conflict classification, byte-order-stable keyset pages over retained resources, and authorization-scoped semantic-inventory pages and selection resolution served directly from contribution aggregates.",field_profile.go; relationships.go; resource_inventory.go; semantic_build.go; semantic_contribution.go; semantic_page.go; store.go,field_profile_test.go; relationships_pg_test.go; relationships_test.go; resource_inventory_test.go; semantic_build_test.go; semantic_contribution_test.go; semantic_page_test.go,—,—,4133,10664,52,AdvanceSemanticInventoryBackfill; ClaimSemanticInventoryBackfill; CompleteSemanticInventoryBackfill; DiscoverFieldEnrichment; DiscoverRelationshipObservations; DiscoverResourceInventory; EnsureSemanticInventoryEntries; Error; FailSemanticInventoryBackfill; FieldProfileStore; FieldProfileUnavailableError; FieldProfileWriteError; InventoryStore; New; NewFieldProfileStore; NewRelationshipEvidenceRepository; NewRelationshipEvidenceStore; NewSemanticBuildRepository; NewSemanticContributionRepository; NewSemanticInventoryBackfillStore; NewSemanticReadRepository; PageSemanticInventory; PrepareSemanticInventoryBackfill; ReadFieldProfile; ReadRelationshipObservations; ReadResourceInventory; ReadRetainedSemanticInventoryPage; ReadSemanticInventoryBuild; RebuildResourceInventory; RelationshipEvidenceRepository; RelationshipOccurrenceGroup; RelationshipScope; RelationshipStore; ResolveSemanticInventorySelections; ResourceInventoryUnavailableError; ResourceInventoryWriteError; RetainedSemanticSourceMissingError; ScanRelationshipOccurrences; SemanticBuildRepository; SemanticContributionRepository; SemanticInventoryBackfillStore; SemanticInventoryClaimLostError; SemanticInventoryClaimedError; SemanticInventoryContributionConflictError; SemanticInventoryEntriesUnsupportedError; SemanticInventoryStorageMissingError; SemanticInventoryUnsupportedError; SemanticReadRepository; Store; Unwrap; WriteFieldProfile; WriteSemanticInventoryContributions,internal/backends/postgres/internal/store; internal/catalog; internal/fhir/schema,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/catalog; internal/dataset; internal/ingest,internal/backends/postgres/control,internal/backends/postgres/internal/catalog,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,3 exported interface(s),legacy=9; migration=16,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/catalog,inventory complete; semantic disposition pending -internal/backends/postgres/internal/chartifact,github.com/calypr/loom/internal/backends/postgres/internal/chartifact,postgres,domain/library,"Persist private ClickHouse artifact manifests and cleanup leases in the PostgreSQL 18 control plane with CAS progress writes, bounded expired scans, and one-winner cleanup claims while physical tables stay in ClickHouse.",catalog.go,catalog_manager_test.go; catalog_test.go,—,—,535,1605,10,Catalog; ClaimCleanup; Create; DB; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update,internal/dataframe/execution/chartifact,internal/authscope; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/store; internal/dataframe/execution/chartifact; internal/store/clickhouse,—,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,1 exported interface(s),migration=6,no in-repo production importer; verify runtime/tooling reachability; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/chartifact,inventory complete; semantic disposition pending -internal/backends/postgres/internal/dataset,github.com/calypr/loom/internal/backends/postgres/internal/dataset,postgres,domain/library,"Persist dataset generation manifests, the active-generation compare-and-swap pointer, and immutable project release candidates with the active-release compare-and-swap pointer in PostgreSQL 18.",commands.go; manifest_lifecycle.go; migrate.go; release_store.go; store.go,commands_test.go; manifest_lifecycle_pg_test.go; release_store_pg_test.go,—,—,1699,2488,27,ActivateGeneration; Backend; Command; CommandBackend; CompareAndSwapActivateRelease; ConnSource; CreateManifest; EnsureGenerationPrerequisites; GenerationOwnerAdapter; MigrateSchema; New; NewPostgreSQLCommand; NewReleaseStore; OnReleaseActivated; OwnerMigrations; Parameter; ParameterType; PostgreSQL; ReadActiveRelease; ReadManifest; ReleaseActivation; ReleaseStore; ResolveActiveManifest; SaveRelease; Scan; Store; TransitionManifest,internal/backends/postgres/internal/store; internal/dataset,internal/backends/postgres/internal/store; internal/dataset,internal/backends/postgres/control; internal/backends/postgres/internal/explorer,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,go:embed; 2 exported interface(s),legacy=1; migration=23; compatibility=4,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/dataset,inventory complete; semantic disposition pending -internal/backends/postgres/internal/explorer,github.com/calypr/loom/internal/backends/postgres/internal/explorer,postgres,domain/library,"Persist Explorer owner records, draft saves with atomic version/digest CAS and exact preimage history, insert-once compilation receipts, immutable Explorer revisions, create-once immutable capability snapshots and table-shape capability receipts, project-scoped interpretation libraries advancing heads over immutable content-addressed revisions by transactional parent CAS, the staged selection lifecycle with project-scoped idempotency, an expiring writer owner, and insert-once membership staged in bounded idempotent batches with the digest/count finalized under the current writer fence and bounded cancellation-aware keyset membership paging, atomic publish authoring that commits a verified compilation receipt, the immutable Explorer revision, the active Explorer owner pointer, and the project release activation CAS in one PostgreSQL transaction, and staging explicit-group revision headers with ordered definition rows and insert-once group membership (one row per revision/group/reference so an overlap is several rows, bounded keyset paging, and a fenced digest/count-verified STAGING to COMPLETE transition) under guarded writer-lease, abort, and expired-cleanup lifecycle in PostgreSQL 18 with unique project/Explorer identity, typed columns, lossless JSONB payloads, DB-enforced row freezing and content-digest collision checks, guard-triggered immutability, and CAS-guarded revision lifecycle on the shared pg-store pool and migration runner.",capabilities.go; drafts.go; explicit_group_memberships.go; explicit_groups.go; explorers.go; interpretations.go; publication.go; receipts.go; revisions.go; selection_members.go; selections.go,capabilities_test.go; drafts_test.go; explicit_groups_readpath_test.go; explicit_groups_test.go; explorers_test.go; interpretations_test.go; pgstate02_fixture_test.go; pubauth_fixture_test.go; publication_golden_test.go; publication_guarddrop_test.go; publication_race_test.go; publication_retry_test.go; publication_test.go; publication_zz_test.go; receipts_test.go; revisions_test.go; selection_members_test.go; selections_test.go,—,—,7405,21659,59,AbortExplicitGroupRevision; AbortSelection; AppendExplicitGroupMemberships; AppendSelectionMembers; BeginExplicitGroupRevision; BeginSelection; CapabilitySnapshotStore; CleanupExplicitGroupStaging; CleanupSelectionStaging; CompleteExplicitGroupRevision; CompleteSelection; Create; CreateInterpretationRevision; DigestExplicitGroupRevision; DigestSelectionMembers; ExplicitGroupStore; FailRevision; FailRevisionFrom; Get; GetByIdentity; GetByToken; GetCatalog; GetCatalogForLookup; GetCategoryScan; GetCompilationReceiptForExplorer; GetDraftRevision; GetExplicitGroupRevision; GetInterpretationRevision; GetResolution; GetRevision; GetRevisionForExplorer; GetSelection; InsertCompilationReceipt; InsertRevision; InterpretationStore; List; ListExplicitGroupDefinitions; ListExplicitGroupRevisions; ListInterpretationLibraries; MigrateSchema; New; NewCapabilitySnapshotStore; NewExplicitGroupStore; NewInterpretationStore; NewPublishStore; NewTableShapeCapabilityStore; OwnerMigrations; PublishAuthoring; PublishStore; Put; PutCatalog; PutCategoryScan; PutExplicitGroupDefinitions; PutResolution; SaveDraft; Store; TableShapeCapabilityStore; VisitExplicitGroupMemberships; VisitSelectionMembers,internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/store; internal/dataframe/publication; internal/dataframe/recipe; internal/dataset; internal/explorer; internal/explorer/capability; internal/explorer/tableshapecap,internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/store; internal/dataframe/publication; internal/dataframe/recipe; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/tableshapecap,internal/backends/postgres/control,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,go:embed,legacy=1; migration=41,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/explorer,inventory complete; semantic disposition pending -internal/backends/postgres/internal/publication,github.com/calypr/loom/internal/backends/postgres/internal/publication,postgres,domain/library,"Persist dataframe publication execution metadata, exact logical pointers, fenced bundle leases, owner-conditional execution checkpoints, read retention pins arbitrated against cleanup claims on a shared retention anchor, and page-scoped stale execution reconciliation with owner-conditional cleanup claims, plus the single-transaction publish commit of PUBLISHED state with its reader pointer, in the PostgreSQL 18 control plane while ClickHouse remains the row store.",leases.go; migrate.go; publish.go; read_pins.go; reconcile.go; registry.go,leases_test.go; publish_atomicity_test.go; read_pins_test.go; reconcile_test.go; registry_test.go,—,—,2482,7214,43,AcquireBundleLease; AcquireExecutionReadPin; AcquireLease; BundleCatalog; Checkpoint; CheckpointRecord; ClaimExecutionCleanup; CompareAndSwapPointer; DB; ExpireLease; FencedLeases; FindExecutionByKey; FindExecutionBySelector; GetExecution; GetPointer; LatestCheckpoint; ListExecutions; MigrateSchema; New; NewBundleCatalog; NewLeaseKeeper; NewReadPins; NewReconciler; OwnerMigrations; PhysicalDropTable; Pins; PublishExecution; ReadPins; ReconcileDecision; ReconcileOutcome; Reconciler; Registry; ReleaseBundleLease; ReleaseExecutionCleanup; ReleaseExecutionReadPin; ReleaseLease; RenewBundleLease; RenewExecutionCleanup; RenewExecutionReadPin; RenewLease; SaveExecution; SweepStale; VisitExecutionPages,internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/publication; internal/dataset,internal/backends/postgres/internal/store; internal/dataframe/publication; internal/dataset,internal/backends/postgres/control,internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/store,github.com/google/uuid; github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,go:embed; 1 exported interface(s),legacy=4; migration=21; compatibility=2,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/publication,inventory complete; semantic disposition pending -internal/backends/postgres/internal/recipeexec,github.com/calypr/loom/internal/backends/postgres/internal/recipeexec,postgres,domain/library,Persist immutable recipe registrations and fenced execution-occurrence usage markers in the PostgreSQL 18 control plane.,registry.go,registry_test.go,—,—,408,1087,11,DB; LoadRecipe; LoadRecipeVersion; MigrateSchema; New; RecipeVersionUsed; RecordRecipeExecutionOccurrence; Registry; RegistryMigrations; ReplaceRecipeVersion; SaveRecipe,internal/backends/postgres/internal/store; internal/dataframe/recipe/exec,internal/backends/postgres/internal/store; internal/dataframe/recipe; internal/dataframe/recipe/exec,internal/backends/postgres/control,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,go:embed; 1 exported interface(s),legacy=1; migration=18,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/recipeexec,inventory complete; semantic disposition pending -internal/backends/postgres/internal/recipes,github.com/calypr/loom/internal/backends/postgres/internal/recipes,postgres,domain/library,Persist project recipe revision chains with CAS head tracking in the PostgreSQL 18 control plane.,revisions.go,revisions_test.go,—,—,389,1072,8,ChainMigrations; DB; Get; List; MigrateSchema; New; Register; RevisionRegistry,internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/recipe,internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/recipe,internal/backends/postgres/control,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,go:embed; 1 exported interface(s),migration=19,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/recipes,inventory complete; semantic disposition pending +internal/backends/arango/internal/store,github.com/calypr/loom/internal/backends/arango/internal/store,arango,domain/library,"Provide the low-level typed ArangoDB client, query streaming, profiling, and Explain assessment boundary.",client.go; errors.go; explain.go; explain_assessment.go; explain_client.go; options.go; preview_index.go; profile.go; profile_client.go; types.go,client_query_memory_limit_test.go; client_queryrows_test.go; client_revision_test.go; errors_test.go; explain_assessment_test.go; explain_client_test.go; explain_test.go; population_profile_integration_test.go; preview_index_test.go; profile_client_test.go; profile_integration_test.go; profile_test.go,—,—,1950,2210,70,AQLExecutor; AssessExplainResult; BatchInserter; Bootstrap; BootstrapSpec; Client; Close; CollectionCount; CollectionExists; CollectionRevision; CollectionSpec; ConnectionOptions; DatabaseExists; DiagnosticCause; EnsurePreviewCoveringIndex; EnsurePreviewCoveringIndexReplacing; EnsurePreviewCoveringIndexWithStoredValues; EnsurePreviewCoveringIndexWithStoredValuesReplacing; ExecuteAQL; Explain; ExplainAssessment; ExplainCollection; ExplainCollectionScan; ExplainIndex; ExplainIndexLocation; ExplainIndexSummary; ExplainIndexUse; ExplainIndexes; ExplainNode; ExplainOptions; ExplainPlan; ExplainPlanEstimate; ExplainRequest; ExplainResult; ExplainStats; ExplainWarning; InsertBatchRaw; IsArangoErrorResponse; IsArangoPermissionResponse; ListPreviewCoveringIndexes; Open; OptimizerOptions; ParseExplainResult; ParseProfileResult; PreviewCoveringIndex; Profile; ProfileExtra; ProfileNode; ProfileNodeGroup; ProfileNodeSummary; ProfileOptions; ProfilePhases; ProfileRequest; ProfileResult; ProfileStats; ProfileSummary; QueryMemoryLimitFromContext; QueryRows; QueryRowsWithMaxRuntime; RowBatchClient; RowQueryer; RowVisitor; SummarizeProfile; TransactionCollections; TransactionFunc; TransactionalRowClient; TranslateQueryError; UnmarshalJSON; WithQueryMemoryLimit; WithTransaction,internal/dataframe/errors,internal/dataframe/errors,internal/backends/arango; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec,—,github.com/arangodb/go-driver/v2/arangodb; github.com/arangodb/go-driver/v2/arangodb/shared; github.com/arangodb/go-driver/v2/connection; github.com/arangodb/go-driver/v2/utils,5 exported interface(s),legacy=1,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/arango/internal/store,inventory complete; semantic disposition pending +internal/backends/postgres,github.com/calypr/loom/internal/backends/postgres,postgres,storage adapter,"Expose the only public entry to the PostgreSQL 18 backend: a single Open factory validates explicit config, owns exactly one readiness-gated pool with own-prefix connection tagging, and returns the typed runtime carrying the SourceRows and PlanJSON data-plane ports over tagged compiled commands plus every control-plane port assembled from the private implementation tree.",aliases.go; doc.go; open.go; plan_executor.go,engine_postgres_command_boundary_pg18_test.go; engine_postgres_pg18_test.go; explain_postgres_test.go; open_live_test.go; ownership_contract_test.go; plan_executor_test.go; sentinel_identity_test.go,—,—,354,4406,4,NewPlanJSONExecutor; NewSourceRows; Open; Runtime,internal/backends/postgres/control; internal/backends/postgres/internal/rows; internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/execution,internal/backends/postgres; internal/backends/postgres/control; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/rows; internal/backends/postgres/internal/store; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/server; internal/source,cmd/loom-data; internal/ingest; internal/server,internal/api/graphql/graph/query; internal/api/http; internal/backends/postgres; internal/dataframe/execution,—,—,migration=1,storage adapter; preserve interface and persisted-data contracts,go test ./internal/backends/postgres,inventory complete; semantic disposition pending +internal/backends/postgres/control,github.com/calypr/loom/internal/backends/postgres/control,control,domain/library,"Host the cycle-free public control plane of the PostgreSQL backend: configuration and series-assignment validation, the bare-pool and batch-vocabulary re-exports, the dataset manifest lifecycle and catalog evidence port assembly, and the structural query surface that backend-adjacent witnesses drive without importing the execution bridge.",catalog_read.go; config.go; errors.go; runtime.go; vocabulary.go; vocabulary_control.go,catalog_read_test.go,—,—,1385,284,65,AllSeries; Begin; CatalogStore; Close; Commit; Config; ConnSource; DiscoverExistingAuthResourcePaths; DiscoverFields; Error; Exec; FieldProfileScope; GenerationAdvisoryLockKey; Health; MigrateDatasetReleaseSchema; MigrateResult; MigrateSchema; NewBundleCatalog; NewCapabilitySnapshotStore; NewCatalogFieldProfileStore; NewCatalogRelationshipEvidenceRepository; NewCatalogRelationshipEvidenceStore; NewCatalogStore; NewChartifactCatalog; NewDatasetReleaseStore; NewEdgeRepository; NewEdgeWriter; NewExplicitGroupStore; NewExplorerStore; NewFieldProfileRepository; NewInterpretationStore; NewInventoryRepository; NewPublicationReconciler; NewPublishStore; NewRecipeExecRegistry; NewRecipeRevisionRegistry; NewResourceRepository; NewResourceWriter; NewSemanticContributionRepository; NewSemanticReadRepository; NewTableShapeCapabilityStore; Open; OpenPool; QuerierAsAny; Query; QueryRow; Readiness; Rollback; Row; Rows; Runtime; Scan; SemanticContributionRepository; Series; SeriesSchemas; SetupPolicy; Unwrap; Validate; VerifyDatasetReleaseSchema; VerifyExplorerControlSchema; VerifyPublicationControlSchema; VerifyRecipeChainsSchema; VerifyRecipeExecRegistriesSchema; VerifySchema; WithAdvisoryLock,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/store; internal/catalog; internal/dataset,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/catalog,internal/backends/postgres; internal/dataframe/benchmark/cmd/benchcli,internal/dataframe/benchmark; internal/dataframe/compiler; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/server,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,2 exported interface(s),migration=21,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/control,inventory complete; semantic disposition pending +internal/backends/postgres/internal/catalog,github.com/calypr/loom/internal/backends/postgres/internal/catalog,postgres,domain/library,"Persist and retrieve catalog facts in PostgreSQL 18: publish-last generation-scoped resource inventory builds, bounded field-profile records with completeness and truncation metadata, leased compare-and-swap-guarded semantic inventory build state, deterministic semantic-inventory contribution rows with content-addressed replay and conflict classification, byte-order-stable keyset pages over retained resources, and authorization-scoped semantic-inventory pages and selection resolution served directly from contribution aggregates.",field_discovery.go; field_profile.go; relationships.go; resource_inventory.go; semantic_build.go; semantic_contribution.go; semantic_page.go; store.go,field_discovery_test.go; field_profile_test.go; relationships_pg_test.go; relationships_test.go; resource_inventory_test.go; semantic_build_test.go; semantic_contribution_test.go; semantic_page_test.go,—,—,4340,11088,54,AdvanceSemanticInventoryBackfill; ClaimSemanticInventoryBackfill; CompleteSemanticInventoryBackfill; DiscoverExistingAuthResourcePaths; DiscoverFieldEnrichment; DiscoverFields; DiscoverRelationshipObservations; DiscoverResourceInventory; EnsureSemanticInventoryEntries; Error; FailSemanticInventoryBackfill; FieldProfileStore; FieldProfileUnavailableError; FieldProfileWriteError; InventoryStore; New; NewFieldProfileStore; NewRelationshipEvidenceRepository; NewRelationshipEvidenceStore; NewSemanticBuildRepository; NewSemanticContributionRepository; NewSemanticInventoryBackfillStore; NewSemanticReadRepository; PageSemanticInventory; PrepareSemanticInventoryBackfill; ReadFieldProfile; ReadRelationshipObservations; ReadResourceInventory; ReadRetainedSemanticInventoryPage; ReadSemanticInventoryBuild; RebuildResourceInventory; RelationshipEvidenceRepository; RelationshipOccurrenceGroup; RelationshipScope; RelationshipStore; ResolveSemanticInventorySelections; ResourceInventoryUnavailableError; ResourceInventoryWriteError; RetainedSemanticSourceMissingError; ScanRelationshipOccurrences; SemanticBuildRepository; SemanticContributionRepository; SemanticInventoryBackfillStore; SemanticInventoryClaimLostError; SemanticInventoryClaimedError; SemanticInventoryContributionConflictError; SemanticInventoryEntriesUnsupportedError; SemanticInventoryStorageMissingError; SemanticInventoryUnsupportedError; SemanticReadRepository; Store; Unwrap; WriteFieldProfile; WriteSemanticInventoryContributions,internal/backends/postgres/internal/store; internal/catalog; internal/fhir/schema,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/catalog; internal/dataset; internal/ingest,internal/backends/postgres/control,internal/backends/postgres/internal/catalog,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,3 exported interface(s),legacy=9; migration=16,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/catalog,inventory complete; semantic disposition pending +internal/backends/postgres/internal/chartifact,github.com/calypr/loom/internal/backends/postgres/internal/chartifact,postgres,domain/library,"Persist private ClickHouse artifact manifests and cleanup leases in the PostgreSQL 18 control plane with CAS progress writes, bounded expired scans, and one-winner cleanup claims while physical tables stay in ClickHouse.",catalog.go,catalog_manager_test.go; catalog_test.go,—,—,535,1605,10,Catalog; ClaimCleanup; Create; DB; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update,internal/dataframe/execution/chartifact,internal/authscope; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/store; internal/dataframe/execution/chartifact; internal/store/clickhouse,internal/backends/postgres/control,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,1 exported interface(s),migration=6,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/chartifact,inventory complete; semantic disposition pending +internal/backends/postgres/internal/dataset,github.com/calypr/loom/internal/backends/postgres/internal/dataset,postgres,domain/library,"Persist dataset generation manifests, the active-generation compare-and-swap pointer, and immutable project release candidates with the active-release compare-and-swap pointer in PostgreSQL 18.",commands.go; manifest_lifecycle.go; migrate.go; release_store.go; store.go,commands_test.go; manifest_lifecycle_pg_test.go; release_store_pg_test.go,—,—,1725,2488,28,ActivateGeneration; Backend; Command; CommandBackend; CompareAndSwapActivateRelease; ConnSource; CreateManifest; EnsureGenerationPrerequisites; GenerationOwnerAdapter; MigrateSchema; New; NewPostgreSQLCommand; NewReleaseStore; OnReleaseActivated; OwnerMigrations; Parameter; ParameterType; PostgreSQL; ReadActiveRelease; ReadManifest; ReleaseActivation; ReleaseStore; ResolveActiveManifest; SaveRelease; Scan; Store; TransitionManifest; VerifySchema,internal/backends/postgres/internal/store; internal/dataset,internal/backends/postgres/internal/store; internal/dataset,internal/backends/postgres/control; internal/backends/postgres/internal/explorer,internal/backends/postgres; internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,go:embed; 2 exported interface(s),legacy=1; migration=28; compatibility=4,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/dataset,inventory complete; semantic disposition pending +internal/backends/postgres/internal/explorer,github.com/calypr/loom/internal/backends/postgres/internal/explorer,postgres,domain/library,"Persist Explorer owner records, draft saves with atomic version/digest CAS and exact preimage history, insert-once compilation receipts, immutable Explorer revisions, create-once immutable capability snapshots and table-shape capability receipts, project-scoped interpretation libraries advancing heads over immutable content-addressed revisions by transactional parent CAS, the staged selection lifecycle with project-scoped idempotency, an expiring writer owner, and insert-once membership staged in bounded idempotent batches with the digest/count finalized under the current writer fence and bounded cancellation-aware keyset membership paging, atomic publish authoring that commits a verified compilation receipt, the immutable Explorer revision, the active Explorer owner pointer, and the project release activation CAS in one PostgreSQL transaction, and staging explicit-group revision headers with ordered definition rows and insert-once group membership (one row per revision/group/reference so an overlap is several rows, bounded keyset paging, and a fenced digest/count-verified STAGING to COMPLETE transition) under guarded writer-lease, abort, and expired-cleanup lifecycle in PostgreSQL 18 with unique project/Explorer identity, typed columns, lossless JSONB payloads, DB-enforced row freezing and content-digest collision checks, guard-triggered immutability, and CAS-guarded revision lifecycle on the shared pg-store pool and migration runner.",capabilities.go; drafts.go; explicit_group_memberships.go; explicit_groups.go; explorers.go; interpretations.go; publication.go; receipts.go; revisions.go; selection_members.go; selections.go,capabilities_test.go; drafts_test.go; explicit_groups_readpath_test.go; explicit_groups_test.go; explorers_test.go; interpretations_test.go; pgstate02_fixture_test.go; pubauth_fixture_test.go; publication_golden_test.go; publication_guarddrop_test.go; publication_race_test.go; publication_retry_test.go; publication_test.go; publication_zz_test.go; receipts_test.go; revisions_test.go; selection_members_test.go; selections_test.go,—,—,7425,21659,60,AbortExplicitGroupRevision; AbortSelection; AppendExplicitGroupMemberships; AppendSelectionMembers; BeginExplicitGroupRevision; BeginSelection; CapabilitySnapshotStore; CleanupExplicitGroupStaging; CleanupSelectionStaging; CompleteExplicitGroupRevision; CompleteSelection; Create; CreateInterpretationRevision; DigestExplicitGroupRevision; DigestSelectionMembers; ExplicitGroupStore; FailRevision; FailRevisionFrom; Get; GetByIdentity; GetByToken; GetCatalog; GetCatalogForLookup; GetCategoryScan; GetCompilationReceiptForExplorer; GetDraftRevision; GetExplicitGroupRevision; GetInterpretationRevision; GetResolution; GetRevision; GetRevisionForExplorer; GetSelection; InsertCompilationReceipt; InsertRevision; InterpretationStore; List; ListExplicitGroupDefinitions; ListExplicitGroupRevisions; ListInterpretationLibraries; MigrateSchema; New; NewCapabilitySnapshotStore; NewExplicitGroupStore; NewInterpretationStore; NewPublishStore; NewTableShapeCapabilityStore; OwnerMigrations; PublishAuthoring; PublishStore; Put; PutCatalog; PutCategoryScan; PutExplicitGroupDefinitions; PutResolution; SaveDraft; Store; TableShapeCapabilityStore; VerifySchema; VisitExplicitGroupMemberships; VisitSelectionMembers,internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/store; internal/dataframe/publication; internal/dataframe/recipe; internal/dataset; internal/explorer; internal/explorer/capability; internal/explorer/tableshapecap,internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/store; internal/dataframe/publication; internal/dataframe/recipe; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/tableshapecap,internal/backends/postgres/control,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,go:embed,legacy=1; migration=44,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/explorer,inventory complete; semantic disposition pending +internal/backends/postgres/internal/publication,github.com/calypr/loom/internal/backends/postgres/internal/publication,postgres,domain/library,"Persist dataframe publication execution metadata, exact logical pointers, fenced bundle leases, owner-conditional execution checkpoints, read retention pins arbitrated against cleanup claims on a shared retention anchor, and page-scoped stale execution reconciliation with owner-conditional cleanup claims, plus the single-transaction publish commit of PUBLISHED state with its reader pointer, in the PostgreSQL 18 control plane while ClickHouse remains the row store.",leases.go; migrate.go; publish.go; read_pins.go; reconcile.go; registry.go,leases_test.go; publish_atomicity_test.go; read_pins_test.go; reconcile_test.go; registry_test.go,—,—,2502,7214,44,AcquireBundleLease; AcquireExecutionReadPin; AcquireLease; BundleCatalog; Checkpoint; CheckpointRecord; ClaimExecutionCleanup; CompareAndSwapPointer; DB; ExpireLease; FencedLeases; FindExecutionByKey; FindExecutionBySelector; GetExecution; GetPointer; LatestCheckpoint; ListExecutions; MigrateSchema; New; NewBundleCatalog; NewLeaseKeeper; NewReadPins; NewReconciler; OwnerMigrations; PhysicalDropTable; Pins; PublishExecution; ReadPins; ReconcileDecision; ReconcileOutcome; Reconciler; Registry; ReleaseBundleLease; ReleaseExecutionCleanup; ReleaseExecutionReadPin; ReleaseLease; RenewBundleLease; RenewExecutionCleanup; RenewExecutionReadPin; RenewLease; SaveExecution; SweepStale; VerifySchema; VisitExecutionPages,internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/publication; internal/dataset,internal/backends/postgres/internal/store; internal/dataframe/publication; internal/dataset,internal/backends/postgres/control,internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/store,github.com/google/uuid; github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,go:embed; 1 exported interface(s),legacy=4; migration=25; compatibility=2,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/publication,inventory complete; semantic disposition pending +internal/backends/postgres/internal/recipeexec,github.com/calypr/loom/internal/backends/postgres/internal/recipeexec,postgres,domain/library,Persist immutable recipe registrations and fenced execution-occurrence usage markers in the PostgreSQL 18 control plane.,registry.go,registry_test.go,—,—,428,1087,12,DB; LoadRecipe; LoadRecipeVersion; MigrateSchema; New; RecipeVersionUsed; RecordRecipeExecutionOccurrence; Registry; RegistryMigrations; ReplaceRecipeVersion; SaveRecipe; VerifySchema,internal/backends/postgres/internal/store; internal/dataframe/recipe/exec,internal/backends/postgres/internal/store; internal/dataframe/recipe; internal/dataframe/recipe/exec,internal/backends/postgres/control,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,go:embed; 1 exported interface(s),legacy=1; migration=22,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/recipeexec,inventory complete; semantic disposition pending +internal/backends/postgres/internal/recipes,github.com/calypr/loom/internal/backends/postgres/internal/recipes,postgres,domain/library,Persist project recipe revision chains with CAS head tracking in the PostgreSQL 18 control plane.,revisions.go,revisions_test.go,—,—,409,1072,9,ChainMigrations; DB; Get; List; MigrateSchema; New; Register; RevisionRegistry; VerifySchema,internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/recipe,internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/recipe,internal/backends/postgres/control,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,go:embed; 1 exported interface(s),migration=23,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/recipes,inventory complete; semantic disposition pending internal/backends/postgres/internal/redact,github.com/calypr/loom/internal/backends/postgres/internal/redact,redact,domain/library,Scrub DSN passwords and secret parameter values from pool-open diagnostics so credential material never reaches rendered error text while the cause chain stays intact.,redact.go,redact_internal_test.go,—,—,79,84,3,ExtractDSNPassword; RedactDSN; ScrubSecretText,—,—,internal/backends/postgres/control,—,—,—,—,one production importer (internal/backends/postgres/control); inspect ownership/inline potential,go test ./internal/backends/postgres/internal/redact,inventory complete; semantic disposition pending internal/backends/postgres/internal/rows,github.com/calypr/loom/internal/backends/postgres/internal/rows,postgres,domain/library,"Stream typed rows from tagged compiled PostgreSQL commands over pgx connections, preserving bound-value integrity and three-state typed decoding, enforcing output column contracts, and classifying cancellation, visitor stops, contract violations, and driver failures with identifiable wrapped causes.",rows.go,rows_boundary_pg18_ab03_test.go; rows_postgres_test.go; rows_postquery_postgres_test.go; rows_test.go,—,—,483,3537,8,Error; Executor; NewExecutor; Querier; QueryError; RowVisitor; SourceRows; Unwrap,internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/execution,internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/errors; internal/dataframe/execution,internal/backends/postgres,—,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgtype,1 exported interface(s),migration=1,one production importer (internal/backends/postgres); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/rows,inventory complete; semantic disposition pending -internal/backends/postgres/internal/store,github.com/calypr/loom/internal/backends/postgres/internal/store,postgres,domain/library,"Provide the bounded pgx/v5-backed PostgreSQL 18 source store: readiness-pinged pool, advisory-lock serialized migration runner, scoped FHIR resource repository that accepts the logical internal/source contract on writes and answers reads with its store-owned physical row under the server-assigned keyset sequence, scoped typed edge-occurrence repository, publish-last inventory repository, scoped bounded field-profile record persistence, the ledgered idempotent COPY batch writer for resource and edge rows, and resource-only and edge-only batch facades over that writer.",advisory.go; batch_writer.go; client.go; commit_outcome.go; edge.go; edge_writer.go; errors.go; field_profile.go; inventory.go; migrate.go; migration_ownership.go; resource.go; resource_writer.go,batch_writer_test.go; client_test.go; edge_test.go; edge_writer_test.go; errors_test.go; field_profile_ddl_test.go; inventory_test.go; migrate_test.go; ownership_contract_test.go; resource_test.go; resource_writer_test.go,—,—,5761,13350,110,Acquire; AsUniqueConflict; Assignment; AssignmentConflict; Batch; BatchAccountingError; BatchEdgeConflictError; BatchIdentityConflictError; BatchResourceConflictError; BatchResult; BatchWriteError; BatchWriter; Cause; CheckServerVersion; Client; Close; CommitOutcomeUnknown; Config; ConnSource; ConnectError; DriverCondition; EdgeBatch; EdgeBatchResult; EdgeConflictError; EdgeOccurrenceRow; EdgeRepository; EdgeScope; EdgeSourceMissingError; EdgeWriter; EmbeddedMigrations; Error; FieldProfileCoverage; FieldProfileFilter; FieldProfileRepository; FieldProfileScope; FutureSchemaVersionError; GenerationAdvisoryLockKey; GetResource; HistoryEntry; HistoryManifest; InsertCounts; InsertEdge; InsertResource; InventoryCountConflictError; InventoryCountScope; InventoryPublishGuardError; InventoryRepository; InventorySourceGroup; IsAbortedTransaction; IsRefusedConnection; IsUniqueViolation; LedgerRecord; LoadMigrations; LookupManifest; Manifest; ManifestJSON; Migrate; MigrateResult; MigrateSchema; Migration; MigrationApplyError; MigrationChecksumMismatchError; MigrationRunner; MissingEmbeddedMigrationError; NewBatchWriter; NewEdgeRepository; NewEdgeWriter; NewFieldProfileRepository; NewInventoryRepository; NewMigrationRunner; NewResourceRepository; NewResourceWriter; Open; OutOfOrderMigrationError; Ping; PublishBuild; Query; QueryRow; ReadBuild; ReadCountSums; ReadEdgesFromSource; ReadEdgesToTarget; ReadFieldProfileRows; ReadFieldProfileRowsFiltered; ReadProfileCoverage; ReadResources; ResourceBatch; ResourceBatchResult; ResourceConflictError; ResourceNotFoundError; ResourceRepository; ResourceRow; ResourceWriter; RetryableTransactionConflict; Scan; ScanSourceCounts; SeriesID; TranslateCommitError; TranslateError; TranslatedError; UniqueConflictError; UnsupportedVersionError; Unwrap; UpsertFieldProfileBatch; Validate; ValidateAssignment; WithAdvisoryLock; WriteBatch; WriteEdgeBatch; WriteResourceBatch,internal/catalog; internal/dataframe/errors; internal/source,internal/api/http; internal/authscope; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/store; internal/catalog; internal/dataframe/errors; internal/dataframe/execution/chartifact; internal/dataframe/publication; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataset; internal/explorer; internal/source,internal/backends/postgres; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/rows,internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,go:embed; 1 exported interface(s),legacy=16; migration=128,broad surface (13 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/store,inventory complete; semantic disposition pending -internal/catalog,github.com/calypr/loom/internal/catalog,catalog,domain/library,"Own persistence-neutral observed FHIR field, pivot, relationship, and capability facts.",auth_scope.go; backend_contract.go; cache.go; capability_evidence.go; generation.go; helpers.go; limits.go; rebuild.go; relationship_evidence.go; relationships.go; resource_inventory.go; retained_semantic_page.go; semantic.go; semantic_coverage.go; semantic_coverage_contract.go; semantic_inventory.go; semantic_schema.go; types.go; write_profiler.go,auth_scope_test.go; cache_test.go; capability_evidence_test.go; generation_test.go; relationship_evidence_census_test.go; relationship_evidence_test.go; relationships_test.go; semantic_coding_display_projection_test.go; semantic_coverage_diagnostics_test.go; semantic_coverage_test.go; semantic_inventory_buildid_cache_test.go; semantic_inventory_category_test.go; semantic_inventory_emitter_anchor_test.go; semantic_inventory_test.go; semantic_schema_identifier_type_test.go; semantic_schema_test.go; semantic_test.go; write_profiler_coverage_test.go; write_profiler_test.go,—,—,6471,3946,146,AuthResourcePathOptions; Cache; CacheOptions; CacheStats; CapabilityEvidence; CapabilityEvidenceOptions; CapabilityEvidenceReader; CensusSemanticCoverage; ConceptCandidate; DecodeSemanticInventoryCursor; DefaultProfileLimits; DiscoverFields; DiscoverReferences; DiscoverSemanticCodeableConceptTemplates; Documents; EffectiveAuthResourcePathsUnrestricted; EncodeSemanticInventoryCursor; EvidenceDiagnostic; EvidenceStatus; EvidenceStatusFor; ExplicitAuthResourcePathsUnrestricted; ExtensionValueObservation; FieldCatalogDocument; FieldEnrichmentDigest; FieldEnrichmentObservation; FieldEnrichmentOptions; FieldEnrichmentResult; FieldProfileScope; FieldProfileWriter; HasDatasetGeneration; InvalidRelationship; InvalidateAll; InvalidateProject; InventoryRebuilder; LegacySemanticBindingIDV1; Merge; MergeRelationshipCounts; NewCache; NewCacheWithOptions; NewProfilerForGenerationWithLimits; NewResourceInventoryBuild; NewSemanticInventoryBuild; NewSemanticInventoryEmitter; NewShapePlanCacheWithLimit; NewShapePlanCacheWithLimits; NormalizeDatasetGeneration; NormalizeProfileLimits; ObservePayload; ObservePayloadWithCoverage; ObservePayloadWithInventory; PopulatedField; PopulatedFieldOptions; PopulatedReference; PopulatedReferenceOptions; ProfileLimits; Profiler; ProjectRelationshipObservation; ReadCapabilityEvidence; ReconcileSemanticCoverage; RelationshipAuditOptions; RelationshipAuditSummary; RelationshipCatalogDocument; RelationshipCatalogDocuments; RelationshipCountsFromRawEdges; RelationshipDiscoverer; RelationshipEvidenceKey; RelationshipEvidenceKeyFor; RelationshipEvidenceLess; RelationshipKey; RelationshipObservation; RelationshipObservationDigest; RelationshipObservationFromKey; RelationshipObservationOptions; RelationshipObservationResult; RelationshipRebuildOptions; RelationshipRebuildSummary; RepeatedShapeDigest; ResourceInventoryBuild; ResourceInventoryBuildKey; ResourceInventoryCount; ResourceInventoryCoverageDigest; ResourceInventoryDigest; ResourceInventoryObservation; ResourceInventoryOptions; ResourceInventoryRebuildOptions; ResourceInventoryRebuildSummary; ResourceInventoryResult; RetainedSemanticInventoryPage; RetainedSemanticInventoryRow; RetentionBytes; SemanticCodeableConceptTemplate; SemanticContributor; SemanticCoverageBuildState; SemanticCoverageCandidatePath; SemanticCoverageCandidateResult; SemanticCoverageCandidateStatus; SemanticCoverageCensus; SemanticCoverageCensusGap; SemanticCoverageCensusStatus; SemanticCoverageCounts; SemanticCoverageDiagnostic; SemanticCoverageDispositionKind; SemanticCoverageEmissionEvidence; SemanticCoverageEmissionSink; SemanticCoverageFacetAggregate; SemanticCoverageLookupOptions; SemanticCoverageLookupResult; SemanticCoverageOccurrenceKind; SemanticCoverageOwnerLocator; SemanticCoveragePathAggregate; SemanticCoveragePresence; SemanticCoverageReason; SemanticCoverageReference; SemanticCoverageReferenceKind; SemanticCoverageRole; SemanticCoverageSourceContribution; SemanticCoverageSourceIdentity; SemanticCoverageSourceSink; SemanticInventoryBuild; SemanticInventoryBuildID; SemanticInventoryBuildKey; SemanticInventoryCheckpoint; SemanticInventoryContribution; SemanticInventoryEmitter; SemanticInventoryEntry; SemanticInventoryFamily; SemanticInventoryFamilyID; SemanticInventoryFamilyReference; SemanticInventoryObservationSink; SemanticInventoryPage; SemanticInventoryPageDigest; SemanticInventoryPageOptions; SemanticInventoryReference; SemanticInventoryResolveOptions; SemanticInventoryResolveResult; SemanticInventorySelectionMode; SemanticInventoryState; SemanticObservation; SemanticObservationCompleteness; SemanticObservationKey; SemanticObservationSource; SemanticObservationValue; ShapePlanCache; Stats; Truncated; ValidateSemanticInventoryBuild,internal/fhir/schema; internal/fhir/semantic,generated/fhirschema; internal/fhir/schema; internal/fhir/semantic,cmd/arango-fhir-proto; cmd/loom-data; internal/api/graphql/graph/query; internal/api/recipe; internal/authscope; internal/backends/arango/internal/catalog; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/explorer/authoringv2; internal/explorer/lifecycle; internal/ingest; internal/server,internal/api/bulk/load; internal/api/graphql/graph; internal/api/graphql/graph/resolver; internal/dataframe/compiler; internal/explorer/capability; internal/explorer/compilation,github.com/bytedance/sonic,5 exported interface(s),legacy=13; migration=1; compatibility=1,broad surface (19 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/catalog,inventory complete; semantic disposition pending +internal/backends/postgres/internal/store,github.com/calypr/loom/internal/backends/postgres/internal/store,postgres,domain/library,"Provide the bounded pgx/v5-backed PostgreSQL 18 source store: readiness-pinged pool, advisory-lock serialized migration runner, scoped FHIR resource repository that accepts the logical internal/source contract on writes and answers reads with its store-owned physical row under the server-assigned keyset sequence, scoped typed edge-occurrence repository, publish-last inventory repository, scoped bounded field-profile record persistence, the ledgered idempotent COPY batch writer for resource and edge rows, and resource-only and edge-only batch facades over that writer.",advisory.go; batch_writer.go; client.go; commit_outcome.go; edge.go; edge_writer.go; errors.go; field_profile.go; inventory.go; migrate.go; migration_ownership.go; resource.go; resource_writer.go,batch_writer_test.go; client_test.go; edge_test.go; edge_writer_test.go; errors_test.go; field_profile_ddl_test.go; field_profile_filter_golden_test.go; inventory_test.go; migrate_test.go; ownership_contract_test.go; resource_test.go; resource_writer_test.go; verify_classify_test.go; verify_readonly_test.go,—,—,5898,13789,112,Acquire; AsUniqueConflict; Assignment; AssignmentConflict; Batch; BatchAccountingError; BatchEdgeConflictError; BatchIdentityConflictError; BatchResourceConflictError; BatchResult; BatchWriteError; BatchWriter; Cause; CheckServerVersion; Client; Close; CommitOutcomeUnknown; Config; ConnSource; ConnectError; DriverCondition; EdgeBatch; EdgeBatchResult; EdgeConflictError; EdgeOccurrenceRow; EdgeRepository; EdgeScope; EdgeSourceMissingError; EdgeWriter; EmbeddedMigrations; Error; FieldProfileCoverage; FieldProfileFilter; FieldProfileRepository; FieldProfileScope; FutureSchemaVersionError; GenerationAdvisoryLockKey; GetResource; HistoryEntry; HistoryManifest; InsertCounts; InsertEdge; InsertResource; InventoryCountConflictError; InventoryCountScope; InventoryPublishGuardError; InventoryRepository; InventorySourceGroup; IsAbortedTransaction; IsRefusedConnection; IsUniqueViolation; LedgerRecord; LoadMigrations; LookupManifest; Manifest; ManifestJSON; Migrate; MigrateResult; MigrateSchema; Migration; MigrationApplyError; MigrationChecksumMismatchError; MigrationRunner; MissingEmbeddedMigrationError; NewBatchWriter; NewEdgeRepository; NewEdgeWriter; NewFieldProfileRepository; NewInventoryRepository; NewMigrationRunner; NewResourceRepository; NewResourceWriter; Open; OutOfOrderMigrationError; Ping; PublishBuild; Query; QueryRow; ReadBuild; ReadCountSums; ReadEdgesFromSource; ReadEdgesToTarget; ReadFieldProfileRows; ReadFieldProfileRowsFiltered; ReadProfileCoverage; ReadResources; ResourceBatch; ResourceBatchResult; ResourceConflictError; ResourceNotFoundError; ResourceRepository; ResourceRow; ResourceWriter; RetryableTransactionConflict; Scan; ScanSourceCounts; SeriesID; TranslateCommitError; TranslateError; TranslatedError; UniqueConflictError; UnsupportedVersionError; Unwrap; UpsertFieldProfileBatch; Validate; ValidateAssignment; Verify; VerifySchema; WithAdvisoryLock; WriteBatch; WriteEdgeBatch; WriteResourceBatch,internal/catalog; internal/dataframe/errors; internal/source,internal/api/http; internal/authscope; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/store; internal/catalog; internal/dataframe/errors; internal/dataframe/execution/chartifact; internal/dataframe/publication; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataset; internal/explorer; internal/source,internal/backends/postgres; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/rows,internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,go:embed; 1 exported interface(s),legacy=16; migration=143,broad surface (13 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/store,inventory complete; semantic disposition pending +internal/catalog,github.com/calypr/loom/internal/catalog,catalog,domain/library,"Own persistence-neutral observed FHIR field, pivot, relationship, and capability facts.",auth_scope.go; backend_contract.go; cache.go; capability_evidence.go; generation.go; helpers.go; limits.go; rebuild.go; relationship_evidence.go; relationships.go; resource_inventory.go; retained_semantic_page.go; semantic.go; semantic_coverage.go; semantic_coverage_contract.go; semantic_inventory.go; semantic_schema.go; types.go; write_profiler.go,auth_scope_test.go; cache_test.go; capability_evidence_test.go; generation_test.go; relationship_evidence_census_test.go; relationship_evidence_test.go; relationships_test.go; semantic_coding_display_projection_test.go; semantic_coverage_diagnostics_test.go; semantic_coverage_test.go; semantic_inventory_buildid_cache_test.go; semantic_inventory_category_test.go; semantic_inventory_emitter_anchor_test.go; semantic_inventory_test.go; semantic_schema_identifier_type_test.go; semantic_schema_test.go; semantic_test.go; write_profiler_coverage_test.go; write_profiler_test.go,—,—,6471,3946,146,AuthResourcePathOptions; Cache; CacheOptions; CacheStats; CapabilityEvidence; CapabilityEvidenceOptions; CapabilityEvidenceReader; CensusSemanticCoverage; ConceptCandidate; DecodeSemanticInventoryCursor; DefaultProfileLimits; DiscoverFields; DiscoverReferences; DiscoverSemanticCodeableConceptTemplates; Documents; EffectiveAuthResourcePathsUnrestricted; EncodeSemanticInventoryCursor; EvidenceDiagnostic; EvidenceStatus; EvidenceStatusFor; ExplicitAuthResourcePathsUnrestricted; ExtensionValueObservation; FieldCatalogDocument; FieldEnrichmentDigest; FieldEnrichmentObservation; FieldEnrichmentOptions; FieldEnrichmentResult; FieldProfileScope; FieldProfileWriter; HasDatasetGeneration; InvalidRelationship; InvalidateAll; InvalidateProject; InventoryRebuilder; LegacySemanticBindingIDV1; Merge; MergeRelationshipCounts; NewCache; NewCacheWithOptions; NewProfilerForGenerationWithLimits; NewResourceInventoryBuild; NewSemanticInventoryBuild; NewSemanticInventoryEmitter; NewShapePlanCacheWithLimit; NewShapePlanCacheWithLimits; NormalizeDatasetGeneration; NormalizeProfileLimits; ObservePayload; ObservePayloadWithCoverage; ObservePayloadWithInventory; PopulatedField; PopulatedFieldOptions; PopulatedReference; PopulatedReferenceOptions; ProfileLimits; Profiler; ProjectRelationshipObservation; ReadCapabilityEvidence; ReconcileSemanticCoverage; RelationshipAuditOptions; RelationshipAuditSummary; RelationshipCatalogDocument; RelationshipCatalogDocuments; RelationshipCountsFromRawEdges; RelationshipDiscoverer; RelationshipEvidenceKey; RelationshipEvidenceKeyFor; RelationshipEvidenceLess; RelationshipKey; RelationshipObservation; RelationshipObservationDigest; RelationshipObservationFromKey; RelationshipObservationOptions; RelationshipObservationResult; RelationshipRebuildOptions; RelationshipRebuildSummary; RepeatedShapeDigest; ResourceInventoryBuild; ResourceInventoryBuildKey; ResourceInventoryCount; ResourceInventoryCoverageDigest; ResourceInventoryDigest; ResourceInventoryObservation; ResourceInventoryOptions; ResourceInventoryRebuildOptions; ResourceInventoryRebuildSummary; ResourceInventoryResult; RetainedSemanticInventoryPage; RetainedSemanticInventoryRow; RetentionBytes; SemanticCodeableConceptTemplate; SemanticContributor; SemanticCoverageBuildState; SemanticCoverageCandidatePath; SemanticCoverageCandidateResult; SemanticCoverageCandidateStatus; SemanticCoverageCensus; SemanticCoverageCensusGap; SemanticCoverageCensusStatus; SemanticCoverageCounts; SemanticCoverageDiagnostic; SemanticCoverageDispositionKind; SemanticCoverageEmissionEvidence; SemanticCoverageEmissionSink; SemanticCoverageFacetAggregate; SemanticCoverageLookupOptions; SemanticCoverageLookupResult; SemanticCoverageOccurrenceKind; SemanticCoverageOwnerLocator; SemanticCoveragePathAggregate; SemanticCoveragePresence; SemanticCoverageReason; SemanticCoverageReference; SemanticCoverageReferenceKind; SemanticCoverageRole; SemanticCoverageSourceContribution; SemanticCoverageSourceIdentity; SemanticCoverageSourceSink; SemanticInventoryBuild; SemanticInventoryBuildID; SemanticInventoryBuildKey; SemanticInventoryCheckpoint; SemanticInventoryContribution; SemanticInventoryEmitter; SemanticInventoryEntry; SemanticInventoryFamily; SemanticInventoryFamilyID; SemanticInventoryFamilyReference; SemanticInventoryObservationSink; SemanticInventoryPage; SemanticInventoryPageDigest; SemanticInventoryPageOptions; SemanticInventoryReference; SemanticInventoryResolveOptions; SemanticInventoryResolveResult; SemanticInventorySelectionMode; SemanticInventoryState; SemanticObservation; SemanticObservationCompleteness; SemanticObservationKey; SemanticObservationSource; SemanticObservationValue; ShapePlanCache; Stats; Truncated; ValidateSemanticInventoryBuild,internal/fhir/schema; internal/fhir/semantic,generated/fhirschema; internal/fhir/schema; internal/fhir/semantic,cmd/arango-fhir-proto; cmd/loom-data; internal/api/graphql/graph/query; internal/api/recipe; internal/authscope; internal/backends/arango/internal/catalog; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/explorer/authoringv2; internal/explorer/lifecycle; internal/ingest; internal/server,internal/api/bulk/load; internal/api/graphql/graph; internal/api/graphql/graph/resolver; internal/dataframe/benchmark; internal/dataframe/compiler; internal/explorer/capability; internal/explorer/compilation,github.com/bytedance/sonic,5 exported interface(s),legacy=13; migration=1; compatibility=1,broad surface (19 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/catalog,inventory complete; semantic disposition pending internal/ci,github.com/calypr/loom/internal/ci,ci,domain/library,"Exercise repository-wide Go formatting and vet gates, including injected fault detection.",—,gofmt_gate_test.go; vet_gate_test.go,—,—,0,283,0,—,—,—,—,—,—,—,—,no in-repo production importer; verify runtime/tooling reachability,go test ./internal/ci,inventory complete; semantic disposition pending +internal/dataframe/benchmark,github.com/calypr/loom/internal/dataframe/benchmark,benchmark,domain/library,"Run the Arango-versus-PostgreSQL matched dataframe benchmark: deterministic fixtures, canonical bags and correctness gates evaluated before any timing is claimed, control runs, and report artifacts that a re-computing verifier must accept, with ClickHouse-backed publication legs disclosed as planned/not_run when the service is absent.",armplan.go; authoring.go; canonical.go; controls.go; driver.go; fixtures.go; gates.go; harness.go; matrix.go; measure.go; oracle.go; provision.go; report.go; report_md.go,armplan_test.go; authoring_test.go; canonical_test.go; controls_test.go; driver_test.go; fixtures_fhir_test.go; fixtures_test.go; gates_test.go; harness_scope_test.go; harness_test.go; matrix_test.go; measure_test.go; oracle_test.go; publication_test.go; report_test.go; scratch_authrepro_test.go; scratch_liveupload_test.go,README.md,—,5072,2964,146,Apply; ArangoBackendYAML; Arm; ArmConfig; ArmEvidence; AuthorWorkload; AuthoringStatus; AuthoringVerification; BackendResult; Bag; BothArmsPlan; BuildAllWorkspaces; BuildEmptyFloorWorkspace; BuildReport; BuildServerConfigYAML; BuildWorkspace; BuilderState; CBool; CList; CMissing; CNull; CNumber; CObject; CString; CacheLabel; CanonicalBag; CanonicalPair; CanonicalRow; CanonicalValue; CanonicalizeRows; CanonicalizeValue; Claim; ClaimableTimings; Clock; Close; ConditionFacts; ControlEmptyFloorMillis; ControlRuns; ControlSelfRatio; ControlUnstableCells; CreateExplorer; DatasetParams; DatasetSize; Digest; DriverOptions; ElapsedMillis; EvaluateGates; EvaluateGatesSingleArm; EvaluateNegativeAuth; EvaluateNegativeAuthSingleArm; ExecuteOutput; ExpectedOutcomeForScope; Facts; FetchBuilderState; FirstDifference; FixtureFiles; FormatExclusionList; FreePort; GateFunc; GateName; GateResult; GenerateFacts; GenerateFiles; Grant; LegPlan; LoadReport; LogPath; MakeBearerToken; MarshalReport; MeasureArms; MeasureEmptyFloor; MockFence; NegativeAuthControl; NegativeAuthControlSingleArm; NewMockFence; NewMonotonicClock; NewSpeedRatio; NewStorageIDMap; NotRunBackends; Now; ObservationFacts; OracleBackedCount; OracleFor; Outcome; OutputIDFor; PatientFacts; PlannedGateReports; PlannedNotRun; PlannedNotRunForBackends; PlannedPlan; PostgresBackendYAML; PostgresOnlyPlan; PreviewResult; Publication; PublicationDisclosures; PublicationExcluded; PublicationGateReports; PublicationPlan; PublishWorkspace; Reconcile; RenderMarkdown; Report; Requests; Restart; RunBenchmark; RunConfig; RunIdentity; RunOptions; RunPlan; RunStatus; RunStatusScoped; Sample; ServerProcess; ServerSpec; Since; SingleArmGateFunc; SnapshotToken; SortedBattery; SpawnFunc; SpawnServer; SpecimenFacts; SpecsByPreset; SpeedRatio; Stop; StorageIDMap; Summarize; Summary; SummaryRow; ThroughputSummary; URL; UnsupportedCases; UploadGeneration; ValidBackend; ValidScope; Validate; ValidateBattery; ValidateLegPlan; ValidatePlannedNotRun; ValidatePublicationPlan; VerifyReport; VersionProbeFunc; WorkloadCategory; WorkloadGateReport; WorkloadOutcome; WorkloadSpec; Write,internal/explorer/authoringv2,generated/fhir; internal/authscope; internal/backends/postgres/control; internal/catalog; internal/dataframe/benchmark; internal/explorer/authoringv2,internal/dataframe/benchmark/cmd/benchcli,internal/dataframe/benchmark,—,2 exported interface(s),migration=1,one production importer (internal/dataframe/benchmark/cmd/benchcli); inspect ownership/inline potential; broad surface (14 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/benchmark,inventory complete; semantic disposition pending +internal/dataframe/benchmark/cmd/benchcli,github.com/calypr/loom/internal/dataframe/benchmark/cmd/benchcli,main,domain/library,"Own the benchmark command-line entry point: fail-closed live-run preflight, planned disclosure artifacts, report verification authority, and the loom_bench PostgreSQL database lifecycle.",deployer.go; main.go; probes.go; setup.go,guards_test.go,—,—,949,322,0,—,internal/backends/postgres/control; internal/dataframe/benchmark,internal/dataframe/benchmark,—,—,—,—,migration=1,no in-repo production importer; verify runtime/tooling reachability,go test ./internal/dataframe/benchmark/cmd/benchcli,inventory complete; semantic disposition pending internal/dataframe/columntransform,github.com/calypr/loom/internal/dataframe/columntransform,columntransform,domain/library,"Define closed, backend-neutral transformations over one selected output column value.",types.go,types_test.go,—,—,79,61,7,CategoryMapping; Clone; ExactCategoryRecode; Kind; UnknownPolicy; Validate; ValueTransformation,—,—,internal/dataframe/compiler/lower; internal/dataframe/recipe; internal/explorer/authoringv2,internal/dataframe/compiler; internal/explorer/compilation,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/columntransform,inventory complete; semantic disposition pending -internal/dataframe/compiler,github.com/calypr/loom/internal/dataframe/compiler,compiler,domain/library,"Orchestrate semantic planning, physical lowering, optimization, rendering, and compiled-query assembly.",api.go; category_scan.go; category_scan_filter_pushdown.go; category_scan_related.go; cell_trace.go; clone_helpers.go; compiled_query.go; construction_preview_root_id_filter.go; graph_execution.go; ir_helpers.go; optimizer.go; output.go; population_mapping.go; postgres_target.go; preview_covering_index.go; recipe_execution.go; recipe_paging.go; row_lineage.go; semantic_group_projection_preview.go; table_shape_exclusions.go; target.go,backend_leaf_guard_test.go; category_scan_arango_test.go; category_scan_parent_presence_arango_test.go; category_scan_related_collection_identity_test.go; category_scan_related_presence_arango_test.go; category_scan_related_test.go; category_scan_test.go; cell_trace_test.go; cohort_drop_boundary_arango_test.go; command_helpers_external_test.go; command_helpers_test.go; compiled_command_test.go; construction_arango_test.go; construction_coded_group_arango_test.go; construction_coded_group_execution_test.go; construction_coded_pivot_test.go; construction_execution_test.go; construction_group_expand_arango_test.go; construction_group_preview_liveness_arango_test.go; construction_group_related_summary_arango_test.go; construction_group_source_streaming_test.go; construction_pivot_lineage_arango_test.go; construction_preview_root_id_filter_test.go; construction_related_count_arango_test.go; construction_related_expand_arango_test.go; construction_related_group_preview_arango_test.go; correlated_arango_test.go; correlated_legacy_compat_test.go; correlated_physical_test.go; correlated_selection_test.go; correlated_system_version_arango_test.go; explicit_cohort_values_arango_test.go; filter_semantics_test.go; filter_test.go; generic_physical_plan_test.go; grain_test.go; group_connected_field_source_arango_test.go; group_field_source_arango_test.go; nested_expansion_arango_test.go; operator_oracles_arango_test.go; physical_diagnostics_test.go; physical_lowering_test.go; physical_optimize_test.go; physical_plan_test.go; physical_projection_reserved_name_arango_test.go; physical_render_test.go; physical_required_match_test.go; physical_scope_test.go; pivot_presence_arango_audit_test.go; pivot_presence_audit_test.go; plan_test_helpers_test.go; population_frontier_arango_test.go; population_mapping_filter_exists_test.go; population_mapping_identity_guard_test.go; population_mapping_routed_group_arango_test.go; population_mapping_test.go; postgres_coded_group_compile_test.go; postgres_parity_fixture_test.go; postgres_target_fingerprint_golden_test.go; public_columns_freeze_test.go; recipe_paging_related_filter_sequence_test.go; recipe_paging_routed_exists_arango_test.go; recipe_paging_test.go; root_page_related_seed_arango_test.go; row_expansion_test.go; row_item_projection_arango_test.go; row_lineage_cohort_filter_arango_test.go; row_lineage_composed_arango_test.go; row_lineage_group_pivot_arango_test.go; row_lineage_group_rows_arango_test.go; row_lineage_test.go; selected_source_contract_test.go; selection_semantics_test.go; selector_first_limit_test.go; semantic_group_arango_test.go; semantic_group_preview_arango_test.go; semantic_group_projection_factorization_arango_test.go; semantic_validation_test.go; source_group_preview_arango_test.go; streaming_missing_category_pivot_test.go; table_reshape_arango_test.go; table_reshape_execution_window_test.go; table_shape_exclusions_test.go; workspace_output_capture_test.go,—,—,7647,30756,42,ArangoTarget; Backend; CategoryOverflowWitness; CategoryScanProof; CategoryScanRefusal; CategoryScanRefusalCode; CategoryScanRefusalCodeOf; CompileCategoryScanOutput; CompileCategoryScanStage; CompileCellTraceOutput; CompilePopulationMappingOutput; CompileRecipeOutput; CompileRecipeOutputPage; CompileResolvedGraphQuery; CompileResolvedRecipePlan; CompileRowLineageOutput; CompileTableShapeExclusions; CompiledCategoryScanQuery; CompiledCellTraceQuery; CompiledOutputPage; CompiledPopulationMappingQuery; CompiledQuery; CompiledRowLineageQuery; CompiledTableShapeExclusionQuery; Error; PostgreSQLTarget; PreviewCoveringIndexReplacement; PreviewCoveringIndexSpec; PreviewGroupProjectionIndexSpec; PreviewGroupProjectionSpec; PreviewGroupScanSpec; PublicOutputColumns; RenderPreviewGroupProjectionAssessment; RenderPreviewGroupProjectionFactorization; RenderPreviewPhysicalPlanWithRootIndexHint; RowLineageCapability; RowLineageCapabilityForOutput; RowLineageUnsupportedError; TableShapeExclusionRefusal; TableShapeExclusionRefusalCode; Target; Valid,internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec,internal/authscope; internal/backends/arango; internal/catalog; internal/dataframe/columntransform; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/explorer/capability; internal/fhir/schema; internal/projectid,cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/dataframe/execution; internal/explorer/lifecycle; internal/server,conformance/compiler; internal/api/graphql/graph; internal/api/graphql/graph/resolver; internal/backends/postgres; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/render/aql; internal/explorer/compilation,—,—,legacy=2; compatibility=1,broad surface (21 production files); inspect cohesion,go test ./internal/dataframe/compiler,inventory complete; semantic disposition pending +internal/dataframe/compiler,github.com/calypr/loom/internal/dataframe/compiler,compiler,domain/library,"Orchestrate semantic planning, physical lowering, optimization, rendering, and compiled-query assembly.",api.go; category_scan.go; category_scan_filter_pushdown.go; category_scan_related.go; cell_trace.go; clone_helpers.go; compiled_query.go; construction_preview_root_id_filter.go; graph_execution.go; ir_helpers.go; optimizer.go; output.go; population_mapping.go; postgres_target.go; preview_covering_index.go; recipe_execution.go; recipe_paging.go; row_lineage.go; semantic_group_projection_preview.go; table_shape_exclusions.go; target.go,backend_leaf_guard_test.go; category_scan_arango_test.go; category_scan_parent_presence_arango_test.go; category_scan_related_collection_identity_test.go; category_scan_related_presence_arango_test.go; category_scan_related_test.go; category_scan_test.go; cell_trace_test.go; cohort_drop_boundary_arango_test.go; command_helpers_external_test.go; command_helpers_test.go; compiled_command_test.go; construction_arango_test.go; construction_coded_group_arango_test.go; construction_coded_group_execution_test.go; construction_coded_pivot_test.go; construction_execution_test.go; construction_group_expand_arango_test.go; construction_group_preview_liveness_arango_test.go; construction_group_related_summary_arango_test.go; construction_group_source_streaming_test.go; construction_pivot_lineage_arango_test.go; construction_preview_root_id_filter_test.go; construction_related_count_arango_test.go; construction_related_expand_arango_test.go; construction_related_group_preview_arango_test.go; correlated_arango_test.go; correlated_legacy_compat_test.go; correlated_physical_test.go; correlated_selection_test.go; correlated_system_version_arango_test.go; explicit_cohort_values_arango_test.go; filter_semantics_test.go; filter_test.go; generic_physical_plan_test.go; grain_test.go; group_connected_field_source_arango_test.go; group_field_source_arango_test.go; nested_expansion_arango_test.go; operator_oracles_arango_test.go; physical_diagnostics_test.go; physical_lowering_test.go; physical_optimize_test.go; physical_plan_test.go; physical_projection_reserved_name_arango_test.go; physical_render_test.go; physical_required_match_test.go; physical_scope_test.go; pivot_presence_arango_audit_test.go; pivot_presence_audit_test.go; plan_test_helpers_test.go; population_frontier_arango_test.go; population_mapping_filter_exists_test.go; population_mapping_identity_guard_test.go; population_mapping_routed_group_arango_test.go; population_mapping_test.go; postgres_coded_group_compile_test.go; postgres_parity_fixture_test.go; postgres_target_fingerprint_golden_test.go; public_columns_freeze_test.go; recipe_execution_pg18_test.go; recipe_execution_test.go; recipe_paging_related_filter_sequence_test.go; recipe_paging_routed_exists_arango_test.go; recipe_paging_test.go; root_page_related_seed_arango_test.go; row_expansion_test.go; row_item_projection_arango_test.go; row_lineage_cohort_filter_arango_test.go; row_lineage_composed_arango_test.go; row_lineage_group_pivot_arango_test.go; row_lineage_group_rows_arango_test.go; row_lineage_test.go; selected_source_contract_test.go; selection_semantics_test.go; selector_first_limit_test.go; semantic_group_arango_test.go; semantic_group_preview_arango_test.go; semantic_group_projection_factorization_arango_test.go; semantic_validation_test.go; source_group_preview_arango_test.go; streaming_missing_category_pivot_test.go; table_reshape_arango_test.go; table_reshape_execution_window_test.go; table_shape_exclusions_test.go; workspace_output_capture_test.go,—,—,8120,32667,44,ArangoTarget; Backend; CategoryOverflowWitness; CategoryScanProof; CategoryScanRefusal; CategoryScanRefusalCode; CategoryScanRefusalCodeOf; CompileCategoryScanOutput; CompileCategoryScanStage; CompileCellTraceOutput; CompileClickHousePrefix; CompilePopulationMappingOutput; CompileRecipeOutput; CompileRecipeOutputPage; CompileResolvedGraphQuery; CompileResolvedRecipePlan; CompileRowLineageOutput; CompileTableShapeExclusions; CompiledCategoryScanQuery; CompiledCellTraceQuery; CompiledClickHousePrefix; CompiledOutputPage; CompiledPopulationMappingQuery; CompiledQuery; CompiledRowLineageQuery; CompiledTableShapeExclusionQuery; Error; PostgreSQLTarget; PreviewCoveringIndexReplacement; PreviewCoveringIndexSpec; PreviewGroupProjectionIndexSpec; PreviewGroupProjectionSpec; PreviewGroupScanSpec; PublicOutputColumns; RenderPreviewGroupProjectionAssessment; RenderPreviewGroupProjectionFactorization; RenderPreviewPhysicalPlanWithRootIndexHint; RowLineageCapability; RowLineageCapabilityForOutput; RowLineageUnsupportedError; TableShapeExclusionRefusal; TableShapeExclusionRefusalCode; Target; Valid,internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec,internal/authscope; internal/backends/arango; internal/backends/postgres/control; internal/catalog; internal/dataframe/columntransform; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/explorer/capability; internal/fhir/schema; internal/projectid; internal/source,cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/dataframe/execution; internal/explorer/lifecycle; internal/server,conformance/compiler; internal/api/graphql/graph; internal/api/graphql/graph/resolver; internal/backends/postgres; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/render/aql; internal/explorer/compilation,—,—,legacy=2; compatibility=1,broad surface (21 production files); inspect cohesion,go test ./internal/dataframe/compiler,inventory complete; semantic disposition pending internal/dataframe/compiler/capability,github.com/calypr/loom/internal/dataframe/compiler/capability,capability,domain/library,Probe which dataframe language capabilities the compiler can prove and support.,probe.go; schema_fields.go,probe_test.go; schema_fields_test.go,—,—,803,313,22,CandidateCapability; CandidateRequest; Chart; Filter; Options; OwnerRecordsCapability; OwnerRecordsRequest; ProbeCandidate; ProbeOwnerRecords; ProbeProjection; ProbeRoot; ProbeTraversal; Rendered; Result; RootCapability; RootRequest; SchemaFieldDescriptor; SchemaFieldDescriptors; Scope; Traversal; TraversalCapability; TraversalRequest,internal/authscope; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/fhir/schema,internal/authscope; internal/dataframe/recipe; internal/dataframe/spec; internal/fhir/schema,internal/explorer/lifecycle; internal/server,—,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/compiler/capability,inventory complete; semantic disposition pending -internal/dataframe/compiler/command,github.com/calypr/loom/internal/dataframe/compiler/command,command,domain/library,"Own the closed dispatch-tagged executable command types, rendered PostgreSQL plan validation, and constructor guarantees shared by compilers and row adapters.",command.go; rendered_postgres.go,compiled_command_test.go; imports_guard_test.go; rendered_postgres_test.go,—,—,433,526,15,AQLCommand; AQLParameters; Backend; CompiledBackend; CompiledCommand; Dispatch; NewAQLCommand; NewPostgreSQLCommand; NewRenderedPostgresPhysicalPlan; PostgreSQLCommand; PostgreSQLParameter; PostgreSQLParameterType; PostgreSQLParameters; RenderedPostgresPhysicalPlan; WithAQLBinds,—,internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/render/aql; internal/dataframe/recipe; internal/dataframe/semantic,internal/backends/postgres; internal/backends/postgres/internal/rows; internal/dataframe/compiler; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/server,conformance/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/render/aql; internal/explorer/compilation,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/compiler/command,inventory complete; semantic disposition pending -internal/dataframe/compiler/ir,github.com/calypr/loom/internal/dataframe/compiler/ir,ir,domain/library,"Define typed physical operations, values, scope proofs, policies, and validation.",clone.go; dataset_generation.go; diagnostics.go; doc.go; navigation.go; physical_combine.go; physical_combine_scalar.go; physical_operations.go; physical_stage_sequence.go; physical_stage_validation.go; physical_table_shape.go; physical_types.go; physical_validation.go; physical_validation_expr.go; physical_validation_refs.go; physical_validation_shapes.go; physical_values.go; policy.go; postgres_storage.go; prefix.go; scope.go; selector_helpers.go; whole_relation_scope_evidence.go; workspace_combine_scope_operand.go,group_related_field_source_validation_test.go; physical_combine_scalar_test.go; physical_combine_test.go; physical_group_pivot_lineage_validation_test.go; physical_root_page_candidate_seed_test.go; physical_scope_cycle_test.go; physical_stage_validation_test.go; population_mapping_test.go; population_root_test.go; related_coding_group_lineage_validation_test.go; row_lineage_trace_validation_test.go; subplan_test.go; unit_validation_test.go; unnest_test.go; workspace_output_validation_test.go,—,—,12210,1691,214,AddDecision; CanonicalExecutionPhysicalPlan; ClickHouseArtifactIdentity; ClickHouseCombineScalarBaseType; ClonePhysicalExpression; ClonePhysicalOperation; ClonePhysicalOperations; ClonePhysicalPlan; ClonePhysicalPredicateExpression; ClonePhysicalSubplan; Column; CompilerPlanDiagnostics; DatasetGenerationBindValue; DecomposePhysicalTraversalPrefixAt; DefaultPhysicalOptimizationPolicy; Digest; Error; EstimatePreparedSelectorWork; EstimateTraversalSharingWork; Identifier; IsRegistered; Kind; Matches; MustColumn; Name; NewPhysicalOptimizationReport; NewPostgresStorageBinding; NewPostgresTableIdentifier; NewPrivateAQLPrefixScopeOperand; NewPublishedWorkspaceScopeOperand; NewWholeRelationScopeEvidence; NewWorkspaceCombineScopeEvidence; NewWorkspaceOutputScopeOperand; NormalizeDatasetGeneration; PhysicalAggregate; PhysicalAggregateOperation; PhysicalCall; PhysicalCardinality; PhysicalCellTraceConstruction; PhysicalCellTraceConstructionInput; PhysicalCellTraceContribution; PhysicalCellTraceRelatedSource; PhysicalCellTraceReshape; PhysicalCellTraceReshapeSource; PhysicalCellTraceReshapeSourceKind; PhysicalCellTraceReturn; PhysicalClickHouseCombine; PhysicalClickHousePrefix; PhysicalCodedGroupPathSegment; PhysicalCohortRootSource; PhysicalCollectionScan; PhysicalCombineInputRef; PhysicalCombineKey; PhysicalCombineKind; PhysicalCombineOutputColumn; PhysicalCombineProjection; PhysicalConstructionStage; PhysicalContributorWindow; PhysicalCorrelation; PhysicalDerivedLet; PhysicalDocumentLookup; PhysicalEngine; PhysicalExpression; PhysicalExpressionKind; PhysicalExpressionLet; PhysicalExpressionProjection; PhysicalExtract; PhysicalFilter; PhysicalGraphReturn; PhysicalGroupMemberValue; PhysicalGroupRows; PhysicalGroupRowsCellTrace; PhysicalGroupedCodedPivotCategory; PhysicalGroupedPivot; PhysicalGroupedPivotCategory; PhysicalGroupedPivotKey; PhysicalKeySet; PhysicalKeySetLookup; PhysicalKeyedMap; PhysicalLimit; PhysicalLiteral; PhysicalMapReduction; PhysicalNullBehavior; PhysicalObject; PhysicalObjectKeys; PhysicalObjectLookup; PhysicalOperation; PhysicalOperationKind; PhysicalOptimizationDecision; PhysicalOptimizationPolicy; PhysicalOptimizationReport; PhysicalOptimizationRule; PhysicalOptimizationRuleState; PhysicalOwnerRecords; PhysicalPathExtend; PhysicalPathNode; PhysicalPathRelationship; PhysicalPathSeed; PhysicalPivotCategoryMatchKind; PhysicalPivotMap; PhysicalPlan; PhysicalPlanDiagnostics; PhysicalPopulationMappingIdentityPart; PhysicalPopulationMappingReturn; PhysicalPopulationRootSource; PhysicalPredicate; PhysicalPredicateExpression; PhysicalPredicateKind; PhysicalPreparedField; PhysicalPreparedReference; PhysicalPreparedSet; PhysicalProjection; PhysicalProjectionPresence; PhysicalRelatedField; PhysicalReturn; PhysicalRootPageCandidateSeed; PhysicalRootScan; PhysicalRowLineageReturn; PhysicalRowLineageStageMatch; PhysicalRowLineageTrace; PhysicalScopeWindowEnd; PhysicalSelectorExecutionMode; PhysicalSelectorFallback; PhysicalSelectorValueDemand; PhysicalSet; PhysicalSetKind; PhysicalSetOutput; PhysicalSetOutputField; PhysicalSetProjection; PhysicalSetProjectionField; PhysicalSetReduction; PhysicalSetReductionField; PhysicalSetReductionMode; PhysicalSlice; PhysicalSort; PhysicalSource; PhysicalStageCodedGroup; PhysicalStageCohortGroup; PhysicalStageColumn; PhysicalStageExpand; PhysicalStageGroup; PhysicalStageGroupAggregate; PhysicalStageGroupCodingKey; PhysicalStageGroupCurrentStageFieldSource; PhysicalStageGroupFieldSource; PhysicalStageGroupFieldSourceKind; PhysicalStageGroupKey; PhysicalStageGroupMissingKeyPolicy; PhysicalStageGroupRelatedOccurrenceAdapter; PhysicalStageGroupRelatedOccurrenceFieldSource; PhysicalStageGroupRelatedProjection; PhysicalStageGroupRelatedRouteStep; PhysicalStageGroupSemanticKey; PhysicalStageOperationKind; PhysicalStagePopulationMappingReturn; PhysicalStageRelatedExpand; PhysicalStageRelatedField; PhysicalStageRelatedRecordAnchor; PhysicalStageRelatedRouteStep; PhysicalStageRelatedSource; PhysicalStageRowValue; PhysicalStageSequence; PhysicalSubplan; PhysicalTableShapeExclusionReturn; PhysicalTemporalOrdering; PhysicalTraversal; PhysicalTraversalDecision; PhysicalTraversalDirection; PhysicalTraversalPrefix; PhysicalTraversalPrefixDecomposition; PhysicalTraversalPrefixError; PhysicalTraversalPrefixRejectionReason; PhysicalTraversalStrategy; PhysicalTraversalSubset; PhysicalUnitNormalization; PhysicalUnnest; PhysicalUnnestAncestor; PhysicalUnnestEmptyPolicy; PhysicalUnnestExactGroupShapeEqual; PhysicalUnnestOwner; PhysicalUnnestRouteStep; PhysicalUnnestSortKeys; PhysicalUnpivot; PhysicalUnpivotIdentityPart; PhysicalUnpivotInput; PhysicalUnpivotOutput; PhysicalValue; PostgresColumnIdentifier; PostgresLogicalTable; PostgresLogicalTableKind; PostgresStorageBinding; PostgresTableIdentifier; RegisteredPostgresColumnNames; ResolvedClickHouseColumn; ResolvedClickHousePrivateArtifact; ResolvedClickHouseTable; RichConsumerGroup; RichSourceReuse; RuleEnabled; SQLText; Schema; Table; TableKind; TotalConsumers; Validate; ValidateForWorkspaceCompilation; ValidateGenericPhysicalPlanScope; ValidateScope; ValidateWithPrivateStage; ValidateWithWorkspaceArtifacts; WholeRelationScopeEvidence; WholeRelationScopeIdentity; WithRule; WorkspaceCombineScopeOperand,internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema; internal/projectid,internal/dataframe/spec; internal/dataframe/unit,cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/clickhouse; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/explorer/lifecycle; internal/server,conformance/compiler; internal/backends/postgres; internal/dataframe/compiler/command; internal/explorer/compilation; internal/store/clickhouse,—,—,legacy=9; migration=7; compatibility=4,broad surface (24 production files); inspect cohesion,go test ./internal/dataframe/compiler/ir,inventory complete; semantic disposition pending -internal/dataframe/compiler/lower,github.com/calypr/loom/internal/dataframe/compiler/lower,lower,domain/library,Lower logical FHIR dataframe plans into storage-aware physical IR.,auth_scope.go; child_sets.go; column_naming.go; correlated_binding.go; doc.go; filter_literal.go; graph_plan.go; group_field_source_context.go; group_field_source_related.go; group_related_occurrence_lower.go; group_rows.go; helpers.go; plan_orchestration.go; population.go; recipe_column_transformations.go; recipe_compile.go; recipe_construction.go; recipe_construction_coded_group.go; recipe_construction_coded_pivot.go; recipe_construction_combine.go; recipe_construction_combine_metadata.go; recipe_construction_composite.go; recipe_construction_group_sources.go; recipe_construction_related_eligibility.go; recipe_construction_related_expand.go; recipe_construction_related_field.go; recipe_construction_reshape.go; recipe_derived_columns.go; recipe_dynamic_columns.go; recipe_expression.go; recipe_expression_lowering.go; recipe_schema.go; recipe_table_reshape.go; required_match.go; root_projection.go; row_expansion.go; storage_route.go; traversal.go; typed_predicate.go; whole_relation_scope.go; workspace_output_dependencies.go,bound_scalar_coding_group_test.go; child_set_selector_demand_test.go; construction_group_streaming_test.go; correlated_binding_test.go; empty_bound_group_source_test.go; graph_plan_test.go; group_field_source_context_test.go; group_related_occurrence_lower_test.go; group_rows_test.go; group_specimen_inbound_coding_test.go; owner_records_test.go; physical_nested_scope_test.go; population_test.go; recipe_construction_coded_group_test.go; recipe_construction_coded_pivot_display_test.go; recipe_construction_coded_pivot_test.go; recipe_construction_combine_test.go; recipe_construction_composite_test.go; recipe_construction_related_count_test.go; recipe_construction_reshape_test.go; recipe_construction_row_values_test.go; recipe_construction_test.go; recipe_derived_columns_test.go; recipe_expression_test.go; recipe_output_test.go; recipe_schema_cardinality_test.go; recipe_semantic_path_test.go; recipe_table_reshape_test.go; recipe_unpivot_capability_test.go; root_id_filter_test.go; row_expansion_test.go; traversal_test.go; whole_relation_scope_evidence_test.go; workspace_combine_scope_evidence_test.go; workspace_output_compile_test.go,—,—,12185,10741,40,BindGroupRelatedOccurrenceSource; BuildGenericPhysicalPlanWithPolicy; BuildGraphPhysicalPlan; BuildPhysicalTraversal; CloneCompiledOutputSchema; CompileResolvedGraphPlan; CompileResolvedRecipePlan; CompiledActiveRelatedRecordStage; CompiledGroupFieldOwner; CompiledGroupFieldSourceAncestor; CompiledGroupFieldSourceContext; CompiledGroupFieldSourceExpansion; CompiledGroupFieldSourceRouteStep; CompiledOutputColumn; CompiledRecipe; CompiledRecipeOutput; CompiledRelatedExpandAnchor; CompiledRelatedExpandStage; CompiledRelatedRecordAnchor; CompiledStageDescriptor; ComposeClickHouseCombine; ConstructionCombineColumnType; DescribeConstructionSourceStage; DynamicColumnMetadata; Error; LowerCorrelatedBinding; LowerCorrelatedBindingWithVersion; LowerCorrelatedPredicate; LowerCorrelatedPredicateWithIdentity; LowerExtensionBinding; LowerRecipeExpression; PivotReducerTypeError; RelatedEligibilityAnchorError; RelatedSourceAnchorError; ResolveGroupRelatedOccurrenceSource; StageOperationCapability; TableReshapeScalarKind; TraversalLoweringRequest; TraversalLoweringResult; WorkspaceOutputSource,internal/authscope; internal/dataframe/columntransform; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema; internal/projectid,internal/authscope; internal/dataframe/columntransform; internal/dataframe/compiler/ir; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,internal/backends/postgres; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres,—,—,legacy=3; compatibility=3,broad surface (41 production files); inspect cohesion,go test ./internal/dataframe/compiler/lower,inventory complete; semantic disposition pending +internal/dataframe/compiler/command,github.com/calypr/loom/internal/dataframe/compiler/command,command,domain/library,"Own the closed dispatch-tagged executable command types, rendered PostgreSQL plan validation, and constructor guarantees shared by compilers and row adapters.",command.go; rendered_postgres.go,compiled_command_test.go; imports_guard_test.go; rendered_postgres_test.go,—,—,433,526,15,AQLCommand; AQLParameters; Backend; CompiledBackend; CompiledCommand; Dispatch; NewAQLCommand; NewPostgreSQLCommand; NewRenderedPostgresPhysicalPlan; PostgreSQLCommand; PostgreSQLParameter; PostgreSQLParameterType; PostgreSQLParameters; RenderedPostgresPhysicalPlan; WithAQLBinds,—,internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/render/aql; internal/dataframe/recipe; internal/dataframe/semantic,internal/api/graphql/graph/resolver; internal/backends/postgres; internal/backends/postgres/internal/rows; internal/dataframe/compiler; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/server,conformance/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/render/aql; internal/explorer/compilation,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/compiler/command,inventory complete; semantic disposition pending +internal/dataframe/compiler/ir,github.com/calypr/loom/internal/dataframe/compiler/ir,ir,domain/library,"Define typed physical operations, values, scope proofs, policies, and validation.",clone.go; dataset_generation.go; diagnostics.go; doc.go; navigation.go; physical_combine.go; physical_combine_scalar.go; physical_operations.go; physical_stage_sequence.go; physical_stage_validation.go; physical_table_shape.go; physical_types.go; physical_validation.go; physical_validation_expr.go; physical_validation_refs.go; physical_validation_shapes.go; physical_values.go; policy.go; postgres_storage.go; prefix.go; scope.go; selector_helpers.go; whole_relation_scope_evidence.go; workspace_combine_scope_operand.go,group_related_field_source_validation_test.go; physical_combine_scalar_test.go; physical_combine_test.go; physical_group_pivot_lineage_validation_test.go; physical_root_page_candidate_seed_test.go; physical_scope_cycle_test.go; physical_stage_validation_test.go; population_mapping_test.go; population_root_test.go; related_coding_group_lineage_validation_test.go; row_lineage_trace_validation_test.go; subplan_test.go; unit_validation_test.go; unnest_test.go; workspace_output_validation_test.go,—,—,12283,1804,215,AddDecision; CanonicalExecutionPhysicalPlan; ClickHouseArtifactIdentity; ClickHouseCombineScalarBaseType; ClonePhysicalExpression; ClonePhysicalOperation; ClonePhysicalOperations; ClonePhysicalPlan; ClonePhysicalPredicateExpression; ClonePhysicalSubplan; Column; CompilerPlanDiagnostics; DatasetGenerationBindValue; DecomposePhysicalTraversalPrefixAt; DefaultPhysicalOptimizationPolicy; Digest; Error; EstimatePreparedSelectorWork; EstimateTraversalSharingWork; Identifier; IsRegistered; Kind; Matches; MustColumn; Name; NewPhysicalOptimizationReport; NewPostgresStorageBinding; NewPostgresTableIdentifier; NewPrivateAQLPrefixScopeOperand; NewPublishedWorkspaceScopeOperand; NewWholeRelationScopeEvidence; NewWorkspaceCombineScopeEvidence; NewWorkspaceOutputScopeOperand; NormalizeDatasetGeneration; PhysicalAggregate; PhysicalAggregateOperation; PhysicalCall; PhysicalCardinality; PhysicalCellTraceConstruction; PhysicalCellTraceConstructionInput; PhysicalCellTraceContribution; PhysicalCellTraceRelatedSource; PhysicalCellTraceReshape; PhysicalCellTraceReshapeSource; PhysicalCellTraceReshapeSourceKind; PhysicalCellTraceReturn; PhysicalClickHouseCombine; PhysicalClickHousePrefix; PhysicalCodedGroupPathSegment; PhysicalCohortRootSource; PhysicalCollectionScan; PhysicalCombineInputRef; PhysicalCombineKey; PhysicalCombineKind; PhysicalCombineOutputColumn; PhysicalCombineProjection; PhysicalConstructionStage; PhysicalContributorWindow; PhysicalCorrelation; PhysicalDerivedLet; PhysicalDocumentLookup; PhysicalEngine; PhysicalExpression; PhysicalExpressionKind; PhysicalExpressionLet; PhysicalExpressionProjection; PhysicalExtract; PhysicalFilter; PhysicalGraphReturn; PhysicalGroupMemberValue; PhysicalGroupRows; PhysicalGroupRowsCellTrace; PhysicalGroupedCodedPivotCategory; PhysicalGroupedPivot; PhysicalGroupedPivotCategory; PhysicalGroupedPivotKey; PhysicalKeySet; PhysicalKeySetLookup; PhysicalKeyedMap; PhysicalLimit; PhysicalLiteral; PhysicalMapReduction; PhysicalNullBehavior; PhysicalObject; PhysicalObjectKeys; PhysicalObjectLookup; PhysicalOperation; PhysicalOperationKind; PhysicalOptimizationDecision; PhysicalOptimizationPolicy; PhysicalOptimizationReport; PhysicalOptimizationRule; PhysicalOptimizationRuleState; PhysicalOwnerRecords; PhysicalPathExtend; PhysicalPathNode; PhysicalPathRelationship; PhysicalPathSeed; PhysicalPivotCategoryMatchKind; PhysicalPivotMap; PhysicalPlan; PhysicalPlanDiagnostics; PhysicalPopulationMappingIdentityPart; PhysicalPopulationMappingReturn; PhysicalPopulationRootSource; PhysicalPredicate; PhysicalPredicateExpression; PhysicalPredicateKind; PhysicalPrefixSourceBackend; PhysicalPreparedField; PhysicalPreparedReference; PhysicalPreparedSet; PhysicalProjection; PhysicalProjectionPresence; PhysicalRelatedField; PhysicalReturn; PhysicalRootPageCandidateSeed; PhysicalRootScan; PhysicalRowLineageReturn; PhysicalRowLineageStageMatch; PhysicalRowLineageTrace; PhysicalScopeWindowEnd; PhysicalSelectorExecutionMode; PhysicalSelectorFallback; PhysicalSelectorValueDemand; PhysicalSet; PhysicalSetKind; PhysicalSetOutput; PhysicalSetOutputField; PhysicalSetProjection; PhysicalSetProjectionField; PhysicalSetReduction; PhysicalSetReductionField; PhysicalSetReductionMode; PhysicalSlice; PhysicalSort; PhysicalSource; PhysicalStageCodedGroup; PhysicalStageCohortGroup; PhysicalStageColumn; PhysicalStageExpand; PhysicalStageGroup; PhysicalStageGroupAggregate; PhysicalStageGroupCodingKey; PhysicalStageGroupCurrentStageFieldSource; PhysicalStageGroupFieldSource; PhysicalStageGroupFieldSourceKind; PhysicalStageGroupKey; PhysicalStageGroupMissingKeyPolicy; PhysicalStageGroupRelatedOccurrenceAdapter; PhysicalStageGroupRelatedOccurrenceFieldSource; PhysicalStageGroupRelatedProjection; PhysicalStageGroupRelatedRouteStep; PhysicalStageGroupSemanticKey; PhysicalStageOperationKind; PhysicalStagePopulationMappingReturn; PhysicalStageRelatedExpand; PhysicalStageRelatedField; PhysicalStageRelatedRecordAnchor; PhysicalStageRelatedRouteStep; PhysicalStageRelatedSource; PhysicalStageRowValue; PhysicalStageSequence; PhysicalSubplan; PhysicalTableShapeExclusionReturn; PhysicalTemporalOrdering; PhysicalTraversal; PhysicalTraversalDecision; PhysicalTraversalDirection; PhysicalTraversalPrefix; PhysicalTraversalPrefixDecomposition; PhysicalTraversalPrefixError; PhysicalTraversalPrefixRejectionReason; PhysicalTraversalStrategy; PhysicalTraversalSubset; PhysicalUnitNormalization; PhysicalUnnest; PhysicalUnnestAncestor; PhysicalUnnestEmptyPolicy; PhysicalUnnestExactGroupShapeEqual; PhysicalUnnestOwner; PhysicalUnnestRouteStep; PhysicalUnnestSortKeys; PhysicalUnpivot; PhysicalUnpivotIdentityPart; PhysicalUnpivotInput; PhysicalUnpivotOutput; PhysicalValue; PostgresColumnIdentifier; PostgresLogicalTable; PostgresLogicalTableKind; PostgresStorageBinding; PostgresTableIdentifier; RegisteredPostgresColumnNames; ResolvedClickHouseColumn; ResolvedClickHousePrivateArtifact; ResolvedClickHouseTable; RichConsumerGroup; RichSourceReuse; RuleEnabled; SQLText; Schema; Table; TableKind; TotalConsumers; Validate; ValidateForWorkspaceCompilation; ValidateGenericPhysicalPlanScope; ValidateScope; ValidateWithPrivateStage; ValidateWithWorkspaceArtifacts; WholeRelationScopeEvidence; WholeRelationScopeIdentity; WithRule; WorkspaceCombineScopeOperand,internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema; internal/projectid,internal/dataframe/spec; internal/dataframe/unit,cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/clickhouse; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/explorer/lifecycle; internal/server,conformance/compiler; internal/backends/postgres; internal/dataframe/compiler/command; internal/explorer/compilation; internal/store/clickhouse,—,—,legacy=9; migration=7; compatibility=4,broad surface (24 production files); inspect cohesion,go test ./internal/dataframe/compiler/ir,inventory complete; semantic disposition pending +internal/dataframe/compiler/lower,github.com/calypr/loom/internal/dataframe/compiler/lower,lower,domain/library,Lower logical FHIR dataframe plans into storage-aware physical IR.,auth_scope.go; child_sets.go; column_naming.go; correlated_binding.go; doc.go; filter_literal.go; graph_plan.go; group_field_source_context.go; group_field_source_related.go; group_related_occurrence_lower.go; group_rows.go; helpers.go; plan_orchestration.go; population.go; recipe_column_transformations.go; recipe_compile.go; recipe_construction.go; recipe_construction_coded_group.go; recipe_construction_coded_pivot.go; recipe_construction_combine.go; recipe_construction_combine_metadata.go; recipe_construction_composite.go; recipe_construction_group_sources.go; recipe_construction_related_eligibility.go; recipe_construction_related_expand.go; recipe_construction_related_field.go; recipe_construction_reshape.go; recipe_derived_columns.go; recipe_dynamic_columns.go; recipe_expression.go; recipe_expression_lowering.go; recipe_schema.go; recipe_table_reshape.go; required_match.go; root_projection.go; row_expansion.go; storage_route.go; traversal.go; typed_predicate.go; whole_relation_scope.go; workspace_output_dependencies.go,bound_scalar_coding_group_test.go; child_set_selector_demand_test.go; construction_group_streaming_test.go; correlated_binding_test.go; empty_bound_group_source_test.go; graph_plan_test.go; group_field_source_context_test.go; group_related_occurrence_lower_test.go; group_rows_test.go; group_specimen_inbound_coding_test.go; owner_records_test.go; physical_nested_scope_test.go; population_test.go; recipe_construction_coded_group_test.go; recipe_construction_coded_pivot_display_test.go; recipe_construction_coded_pivot_test.go; recipe_construction_combine_test.go; recipe_construction_composite_test.go; recipe_construction_related_count_test.go; recipe_construction_reshape_test.go; recipe_construction_row_values_test.go; recipe_construction_test.go; recipe_derived_columns_test.go; recipe_expression_test.go; recipe_output_test.go; recipe_schema_cardinality_test.go; recipe_semantic_path_test.go; recipe_table_reshape_test.go; recipe_unpivot_capability_test.go; root_id_filter_test.go; row_expansion_test.go; traversal_test.go; whole_relation_scope_evidence_test.go; workspace_combine_scope_evidence_test.go; workspace_output_compile_test.go,—,—,12219,10802,40,BindGroupRelatedOccurrenceSource; BuildGenericPhysicalPlanWithPolicy; BuildGraphPhysicalPlan; BuildPhysicalTraversal; CloneCompiledOutputSchema; CompileResolvedGraphPlan; CompileResolvedRecipePlan; CompiledActiveRelatedRecordStage; CompiledGroupFieldOwner; CompiledGroupFieldSourceAncestor; CompiledGroupFieldSourceContext; CompiledGroupFieldSourceExpansion; CompiledGroupFieldSourceRouteStep; CompiledOutputColumn; CompiledRecipe; CompiledRecipeOutput; CompiledRelatedExpandAnchor; CompiledRelatedExpandStage; CompiledRelatedRecordAnchor; CompiledStageDescriptor; ComposeClickHouseCombine; ConstructionCombineColumnType; DescribeConstructionSourceStage; DynamicColumnMetadata; Error; LowerCorrelatedBinding; LowerCorrelatedBindingWithVersion; LowerCorrelatedPredicate; LowerCorrelatedPredicateWithIdentity; LowerExtensionBinding; LowerRecipeExpression; PivotReducerTypeError; RelatedEligibilityAnchorError; RelatedSourceAnchorError; ResolveGroupRelatedOccurrenceSource; StageOperationCapability; TableReshapeScalarKind; TraversalLoweringRequest; TraversalLoweringResult; WorkspaceOutputSource,internal/authscope; internal/dataframe/columntransform; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema; internal/projectid,internal/authscope; internal/dataframe/columntransform; internal/dataframe/compiler/ir; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,internal/backends/postgres; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres,—,—,legacy=3; compatibility=3,broad surface (41 production files); inspect cohesion,go test ./internal/dataframe/compiler/lower,inventory complete; semantic disposition pending internal/dataframe/compiler/optimize,github.com/calypr/loom/internal/dataframe/compiler/optimize,optimize,domain/library,Apply explainable semantics-preserving rewrites to validated physical IR.,doc.go; optimize.go,optimize_test.go,—,—,438,124,1,OptimizePhysicalPlanWithPolicy,internal/dataframe/compiler/ir,internal/dataframe/compiler/ir,internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/execution,internal/dataframe/compiler/lower,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/compiler/optimize,inventory complete; semantic disposition pending -internal/dataframe/compiler/render/aql,github.com/calypr/loom/internal/dataframe/compiler/render/aql,aql,domain/library,Render validated physical IR as deterministic parameterized AQL.,calls.go; cell_trace.go; collections.go; combine_prefix.go; construction_coded_group.go; construction_expand.go; construction_group.go; construction_related_expand.go; correlation_value_scope.go; doc.go; graph_render.go; group_row_value_preview.go; group_rows.go; navigation_layout.go; navigation_render.go; owner_records.go; population_mapping.go; predicates.go; related_count.go; related_group_preview.go; render.go; row_lineage.go; row_lineage_coded_group.go; row_lineage_composed.go; row_lineage_group_pivot.go; row_lineage_group_rows.go; row_lineage_pivot.go; row_lineage_related_coding.go; row_values.go; selector.go; selectors.go; source_group_preview.go; stage_sequence.go; table_reshape.go; table_reshape_dynamic.go; table_reshape_streaming.go; table_shape_exclusion.go; validation.go; values.go,call_test.go; command_helpers_ext_test.go; construction_group_related_occurrence_test.go; construction_group_test.go; construction_related_expand_test.go; correlated_selection_test.go; graph_test.go; group_row_value_preview_test.go; navigation_layout_test.go; related_count_category_test.go; row_lineage_composed_test.go; selectors_test.go; source_group_preview_test.go; stage_sequence_test.go; table_reshape_dynamic_test.go; table_reshape_streaming_scalar_test.go; two_scan_preview_test.go; unnest_test.go,—,—,14514,3483,22,RenderClickHousePrefix; RenderPhysicalCohortGroupRowsLineage; RenderPhysicalGroupRowsLineage; RenderPhysicalPlan; RenderPhysicalPlanWithCategoryScanPresenceMarker; RenderPhysicalPlanWithDynamicCategoryPivotPreview; RenderPhysicalPlanWithRelatedExpandPreviewLimit; RenderPhysicalPlanWithRootIndexDisabled; RenderPhysicalPlanWithRootIndexHint; RenderPhysicalPlanWithRootPageKeys; RenderPhysicalPlanWithSemanticGroupProjectionAssessment; RenderPhysicalPlanWithSemanticGroupProjectionFactorization; RenderPhysicalPlanWithStreamingMissingCategoryPivotPreview; RenderPhysicalPlanWithStreamingMissingCategoryPivotPreviewAndRootIndexHint; RenderPhysicalPlanWithTerminalProjection; RenderPhysicalPlanWithTwoScanPivotPreview; RenderPhysicalPlanWithUnorderedTerminalProjection; RenderPhysicalPlanWithUnorderedTerminalProjectionPresenceMarker; RenderPhysicalRootScopeCount; RenderRelatedEligibilityCategoryOverflowWitness; RenderedPhysicalPlan; ValidateSemanticGroupProjectionPreviewPlan,internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/authscope; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/errors; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/dataframe/compiler; internal/dataframe/compiler/capability,internal/dataframe/compiler/command; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/explorer/compilation,—,—,—,broad surface (39 production files); inspect cohesion,go test ./internal/dataframe/compiler/render/aql,inventory complete; semantic disposition pending -internal/dataframe/compiler/render/clickhouse,github.com/calypr/loom/internal/dataframe/compiler/render/clickhouse,clickhouse,storage adapter,Render typed Combine plans and resolved published or private table inputs as deterministic ClickHouse SQL.,combine.go; workspace_combine.go,combine_test.go; membership_nullable_integration_test.go; workspace_combine_test.go,—,—,930,1092,6,RenderCombine; RenderCombineWithLimit; RenderCompositeClickHouseCombine; RenderWorkspaceClickHouseCombine; RenderedCombine; WorkspaceArtifact,internal/dataframe/compiler/ir,internal/dataframe/compiler/ir; internal/store/clickhouse,internal/dataframe/execution,internal/store/clickhouse,—,—,—,one production importer (internal/dataframe/execution); inspect ownership/inline potential; storage adapter; preserve interface and persisted-data contracts,go test ./internal/dataframe/compiler/render/clickhouse,inventory complete; semantic disposition pending -internal/dataframe/compiler/render/postgres,github.com/calypr/loom/internal/dataframe/compiler/render/postgres,postgres,storage adapter,"Render the supported physical IR read spine and typed selector extractions with two-valued JSONB path-presence evidence onto allowlisted PostgreSQL storage bindings as deterministic parameterized SQL with ordered typed arguments, failing closed on every unsupported plan shape.",calls.go; coded_group.go; graph.go; group.go; null_semantics.go; objects.go; pivot.go; population.go; predicates.go; render.go; root_scan.go; scope.go; scope_ops.go; selectors.go; sets.go; traversal.go; unit_normalization.go; unnest.go; unpivot.go; window.go,calls_live_test.go; calls_test.go; coded_group_test.go; coded_pivot_test.go; graph_test.go; group_test.go; main_test.go; null_semantics_test.go; obj11_production_test.go; objects_test.go; pivot14_noncoded_test.go; pivot_test.go; population_live_test.go; population_test.go; predicates_pg18_test.go; predicates_test.go; render_pg18_test.go; render_test.go; root_scan_test.go; scope_ops_witness_test.go; scope_test.go; selectors_test.go; sets_live_test.go; sets_test.go; traversal_test.go; unit_normalization_production_test.go; unit_normalization_test.go; unit_normalization_tie_test.go; unnest_test.go; unpivot_test.go; window_test.go,—,—,16217,31618,13,NewPostgresPopulationRootBinding; PostgresPopulationRootBinding; RenderGraphPhysicalPlan; RenderGroupedCodedPivot; RenderGroupedPivot; RenderPhysicalPlan; RenderPopulationRootScan; RenderRootScan; RenderSelectorExtraction; RenderUnnest; RenderUnpivot; Validate; ValidatePopulationRootSource,internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/backends/postgres/control; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/explorer; internal/fhir/schema; internal/source,internal/dataframe/compiler,internal/dataframe/execution,—,—,legacy=13; migration=19; compatibility=2,one production importer (internal/dataframe/compiler); inspect ownership/inline potential; broad surface (20 production files); inspect cohesion; storage adapter; preserve interface and persisted-data contracts,go test ./internal/dataframe/compiler/render/postgres,inventory complete; semantic disposition pending +internal/dataframe/compiler/render/aql,github.com/calypr/loom/internal/dataframe/compiler/render/aql,aql,domain/library,Render validated physical IR as deterministic parameterized AQL.,calls.go; cell_trace.go; collections.go; combine_prefix.go; construction_coded_group.go; construction_expand.go; construction_group.go; construction_related_expand.go; correlation_value_scope.go; doc.go; graph_render.go; group_row_value_preview.go; group_rows.go; navigation_layout.go; navigation_render.go; owner_records.go; population_mapping.go; predicates.go; related_count.go; related_group_preview.go; render.go; row_lineage.go; row_lineage_coded_group.go; row_lineage_composed.go; row_lineage_group_pivot.go; row_lineage_group_rows.go; row_lineage_pivot.go; row_lineage_related_coding.go; row_values.go; selector.go; selectors.go; source_group_preview.go; stage_sequence.go; table_reshape.go; table_reshape_dynamic.go; table_reshape_streaming.go; table_shape_exclusion.go; validation.go; values.go,call_test.go; command_helpers_ext_test.go; construction_group_related_occurrence_test.go; construction_group_test.go; construction_related_expand_test.go; correlated_selection_test.go; graph_test.go; group_row_value_preview_test.go; navigation_layout_test.go; recipe_unpivot_identity_test.go; related_count_category_test.go; row_lineage_composed_test.go; selectors_test.go; source_group_preview_test.go; stage_sequence_test.go; table_reshape_dynamic_test.go; table_reshape_streaming_scalar_test.go; two_scan_preview_test.go; unnest_test.go,—,—,14519,3608,22,RenderClickHousePrefix; RenderPhysicalCohortGroupRowsLineage; RenderPhysicalGroupRowsLineage; RenderPhysicalPlan; RenderPhysicalPlanWithCategoryScanPresenceMarker; RenderPhysicalPlanWithDynamicCategoryPivotPreview; RenderPhysicalPlanWithRelatedExpandPreviewLimit; RenderPhysicalPlanWithRootIndexDisabled; RenderPhysicalPlanWithRootIndexHint; RenderPhysicalPlanWithRootPageKeys; RenderPhysicalPlanWithSemanticGroupProjectionAssessment; RenderPhysicalPlanWithSemanticGroupProjectionFactorization; RenderPhysicalPlanWithStreamingMissingCategoryPivotPreview; RenderPhysicalPlanWithStreamingMissingCategoryPivotPreviewAndRootIndexHint; RenderPhysicalPlanWithTerminalProjection; RenderPhysicalPlanWithTwoScanPivotPreview; RenderPhysicalPlanWithUnorderedTerminalProjection; RenderPhysicalPlanWithUnorderedTerminalProjectionPresenceMarker; RenderPhysicalRootScopeCount; RenderRelatedEligibilityCategoryOverflowWitness; RenderedPhysicalPlan; ValidateSemanticGroupProjectionPreviewPlan,internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/authscope; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/errors; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/dataframe/compiler; internal/dataframe/compiler/capability,internal/dataframe/compiler/command; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/explorer/compilation,—,—,—,broad surface (39 production files); inspect cohesion,go test ./internal/dataframe/compiler/render/aql,inventory complete; semantic disposition pending +internal/dataframe/compiler/render/clickhouse,github.com/calypr/loom/internal/dataframe/compiler/render/clickhouse,clickhouse,storage adapter,Render typed Combine plans and resolved published or private table inputs as deterministic ClickHouse SQL.,combine.go; workspace_combine.go,combine_test.go; membership_nullable_integration_test.go; workspace_combine_test.go,—,—,930,1093,6,RenderCombine; RenderCombineWithLimit; RenderCompositeClickHouseCombine; RenderWorkspaceClickHouseCombine; RenderedCombine; WorkspaceArtifact,internal/dataframe/compiler/ir,internal/dataframe/compiler/ir; internal/store/clickhouse,internal/dataframe/execution,internal/store/clickhouse,—,—,—,one production importer (internal/dataframe/execution); inspect ownership/inline potential; storage adapter; preserve interface and persisted-data contracts,go test ./internal/dataframe/compiler/render/clickhouse,inventory complete; semantic disposition pending +internal/dataframe/compiler/render/postgres,github.com/calypr/loom/internal/dataframe/compiler/render/postgres,postgres,storage adapter,"Render the supported physical IR read spine and typed selector extractions with two-valued JSONB path-presence evidence onto allowlisted PostgreSQL storage bindings as deterministic parameterized SQL with ordered typed arguments, failing closed on every unsupported plan shape.",calls.go; category_scan.go; cell_trace.go; coded_group.go; graph.go; group.go; null_semantics.go; objects.go; pivot.go; population.go; population_mapping.go; predicates.go; render.go; root_scan.go; row_lineage.go; scope.go; scope_ops.go; selectors.go; sets.go; stage_related_occurrence.go; stage_semantic.go; stage_sequence.go; table_shape_exclusions.go; traversal.go; unit_normalization.go; unnest.go; unpivot.go; window.go,calls_live_test.go; calls_test.go; category_scan_test.go; cell_trace_test.go; coded_group_test.go; coded_pivot_test.go; graph_test.go; group_test.go; main_test.go; null_semantics_test.go; obj11_production_test.go; objects_test.go; pivot14_noncoded_test.go; pivot_test.go; population_live_test.go; population_mapping_test.go; population_test.go; predicates_pg18_test.go; predicates_test.go; render_pg18_test.go; render_test.go; root_scan_test.go; row_lineage_test.go; scope_ops_witness_test.go; scope_test.go; selectors_test.go; sets_live_test.go; sets_test.go; stage_related_occurrence_p4_test.go; stage_related_occurrence_production_test.go; stage_related_occurrence_test.go; stage_semantic_production_test.go; stage_semantic_test.go; stage_sequence_production_test.go; stage_sequence_test.go; table_shape_exclusions_live_test.go; table_shape_exclusions_test.go; traversal_test.go; unit_normalization_production_test.go; unit_normalization_test.go; unit_normalization_tie_test.go; unnest_test.go; unpivot_test.go; window_test.go,—,—,23444,42018,18,NewPostgresPopulationRootBinding; PostgresPopulationRootBinding; RenderCategoryScan; RenderGraphPhysicalPlan; RenderGroupedCodedPivot; RenderGroupedPivot; RenderPhysicalPlan; RenderPopulationMappingPlan; RenderPopulationRootScan; RenderRootScan; RenderSelectorExtraction; RenderTableShapeExclusions; RenderUnnest; RenderUnpivot; RenderedCategoryScan; Validate; ValidatePopulationMappingSource; ValidatePopulationRootSource,internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/backends/postgres/control; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/explorer; internal/fhir/schema; internal/source,internal/dataframe/compiler; internal/dataframe/execution,—,—,—,legacy=13; migration=20; compatibility=2,broad surface (28 production files); inspect cohesion; storage adapter; preserve interface and persisted-data contracts,go test ./internal/dataframe/compiler/render/postgres,inventory complete; semantic disposition pending internal/dataframe/errors,github.com/calypr/loom/internal/dataframe/errors,dataframeerrors,domain/library,Define and normalize stable structured dataframe errors for transport presentation.,errors.go,errors_test.go,—,—,488,172,21,AsUserError; Code; Details; Error; ErrorCode; ErrorOption; FieldPath; IsFeatureResolutionCode; IsRetryableCode; NewError; Normalize; PublicMessage; Retryable; SanitizePersistedFailure; Unwrap; UserError; WithCause; WithDetails; WithFieldPath; WithRetryable; Wrap,—,—,internal/api/authpolicy; internal/api/bulk/load; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/backends/arango/internal/recipeexec; internal/backends/arango/internal/store; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/store; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/execution; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/semantic; internal/dataframe/spec; internal/explorer/lifecycle; internal/server,internal/backends/arango/internal/publication; internal/backends/postgres; internal/backends/postgres/internal/rows; internal/dataframe/compiler,—,1 exported interface(s),legacy=1,small shared semantic contract; compare direct callers before inlining; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/errors,inventory complete; semantic disposition pending -internal/dataframe/execution,github.com/calypr/loom/internal/dataframe/execution,execution,domain/library,"Prepare, authorize, execute, explain, and post-process compiled dataframe requests.",category_scan.go; cell_trace.go; clickhouse_artifact.go; control.go; control_explain_types.go; control_types.go; engine.go; execution_service.go; explain.go; explain_postgres.go; group_preview_cache.go; group_preview_scan.go; helpers.go; named_uuid.go; population_mapping.go; postquery.go; preview_capabilities.go; query_budget.go; related_preview_paging.go; row_lineage.go; service.go; table_shape_exclusions.go; types.go,auth_scope_test.go; boundary_helpers_test.go; category_scan_test.go; cell_trace_row_not_found_class_test.go; cell_trace_test.go; clickhouse_artifact_test.go; cohort_preview_paging_arango_test.go; command_boundary_contract_test.go; construction_cell_trace_arango_test.go; control_validate_digest_test.go; engine_blank_project_review_test.go; engine_clickhouse_test.go; engine_postgres_postquery_pg18_test.go; engine_postgres_preview_pg18_test.go; engine_postgres_test.go; engine_run_output_class_test.go; engine_test.go; engine_workspace_artifact_test.go; execution_test.go; explain_test.go; group_preview_cache_test.go; group_preview_scan_test.go; helpers_test.go; identity_test.go; named_uuid_test.go; population_group_preview_test.go; population_mapping_test.go; postgres_explain_parser_internal_test.go; postgres_target_fixture_test.go; postquery_test.go; preview_capability_boundary_test.go; recipe_paging_postgres_adapter_test.go; recipe_paging_postgres_test.go; recipe_preview_paging_postgres_internal_test.go; row_lineage_test.go; selected_source_contract_test.go; table_shape_exclusions_test.go; vendor_import_guard_test.go; verify_arch01_pins_test.go,—,—,6401,16663,108,BuildPostgreSQLExplainCommand; CategoryScanRequest; CategoryScanResult; CategoryValue; CellTrace; CellTraceCompiled; CellTraceContribution; CellTraceRequest; CellTraceResult; CellTraceStatus; ClickHouseQueryRows; CompileResolvedBundle; CompiledQueryFingerprint; CompilerPlanDiagnostics; Config; Control; DynamicColumnCheck; DynamicDriftError; Engine; Error; Explain; ExplainAssessment; ExplainCollectionScan; ExplainIndexLocation; ExplainIndexSummary; ExplainPhysical; ExplainPlanEstimate; ExplainResolved; ExplainWarning; Materialize; MaterializeBundle; MaterializeClickHouseArtifact; MaterializeResolvedBundle; MaterializeVersion; New; NewService; OutputRows; OutputStream; PhysicalExplanation; PhysicalOutputExplanation; PopulationMapping; PopulationMappingCompiled; PopulationMappingDiagnostic; PopulationMappingRequest; PopulationMappingResult; PopulationMappingStatus; PopulationMemberReader; PopulationMemberReaderFunc; PostgreSQLExplainPlan; PostgreSQLPlanAssessment; PostgreSQLPlanEstimate; PostgreSQLPlanJSONExecutor; PostgreSQLPlanNode; PostgreSQLRootPageSteps; PostgreSQLScanEvidence; Preview; PreviewBundle; PreviewCollectionIndex; PreviewCollectionScanEvidence; PreviewCollectionScans; PreviewIndexCoverage; PreviewIndexNodeEvidence; PreviewOutput; PreviewRequest; PreviewResolvedBundle; PreviewSummary; PrivateClickHouseArtifactInput; PrivateClickHouseArtifactSource; QueryDiagnostics; QueryMemoryBudgetFromContext; ResolutionError; Resolve; ResolveBundle; ResolveClickHouseInputs; ResolveVersion; Resolved; Result; RowIdentity; RowLineage; RowLineageContributor; RowLineageRequest; RowLineageResult; RowVisitor; RowsRequest; Run; RunCompiled; ScanCategories; ScanCategoriesCompiled; Service; ServiceConfig; SourceRows; Stream; StreamResult; Streams; TableShapeExclusion; TableShapeExclusionRequest; TableShapeExclusionResult; TableShapeExclusionStatus; TableShapeExclusions; TableShapeExclusionsCompiled; TableShapeSourceIdentity; Unwrap; Validate; Validation; VisitMembers; WithExecutionReadPins; WithPrivateClickHouseArtifacts; WithQueryMemoryBudget,internal/authscope; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/clickhouse; internal/dataframe/errors; internal/dataframe/execution/chartifact; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/projectid,internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/projectid; internal/source; internal/store/clickhouse,internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/backends/postgres; internal/backends/postgres/internal/rows; internal/explorer/lifecycle; internal/server,internal/api/graphql/graph; internal/dataframe/compiler; internal/dataframe/execution,github.com/google/uuid,1 exported interface(s),legacy=4; deprecated=1; compatibility=22,broad surface (23 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/execution,inventory complete; semantic disposition pending +internal/dataframe/execution,github.com/calypr/loom/internal/dataframe/execution,execution,domain/library,"Prepare, authorize, execute, explain, and post-process compiled dataframe requests.",category_scan.go; cell_trace.go; clickhouse_artifact.go; control.go; control_explain_types.go; control_types.go; engine.go; execution_service.go; explain.go; explain_postgres.go; group_preview_cache.go; group_preview_scan.go; helpers.go; named_uuid.go; population_mapping.go; postquery.go; preview_capabilities.go; query_budget.go; related_preview_paging.go; row_lineage.go; service.go; table_shape_exclusions.go; types.go,auth_scope_test.go; backend_neutral_diagnostics_test.go; boundary_helpers_test.go; category_scan_postgres_test.go; category_scan_test.go; cell_trace_postgres_test.go; cell_trace_row_not_found_class_test.go; cell_trace_test.go; clickhouse_artifact_test.go; cohort_preview_paging_arango_test.go; command_boundary_contract_test.go; construction_cell_trace_arango_test.go; control_validate_digest_test.go; engine_blank_project_review_test.go; engine_clickhouse_test.go; engine_postgres_capture_pg18_test.go; engine_postgres_capture_test.go; engine_postgres_category_pg18_test.go; engine_postgres_cell_trace_pg18_test.go; engine_postgres_exclusions_pg18_test.go; engine_postgres_postquery_pg18_test.go; engine_postgres_preview_pg18_test.go; engine_postgres_test.go; engine_run_output_class_test.go; engine_test.go; engine_workspace_artifact_test.go; execution_test.go; explain_test.go; group_preview_cache_test.go; group_preview_scan_test.go; helpers_test.go; identity_test.go; named_uuid_test.go; population_group_preview_test.go; population_mapping_postgres_pg18_test.go; population_mapping_test.go; postgres_explain_parser_internal_test.go; postgres_target_fixture_test.go; postquery_test.go; preview_capability_boundary_test.go; recipe_paging_postgres_adapter_test.go; recipe_paging_postgres_test.go; recipe_preview_paging_postgres_internal_test.go; row_lineage_postgres_pg18_test.go; row_lineage_test.go; selected_source_contract_test.go; table_shape_exclusions_test.go; vendor_import_guard_test.go; verify_arch01_pins_test.go,—,—,7241,27582,110,BuildPostgreSQLExplainCommand; CategoryScanRequest; CategoryScanResult; CategoryValue; CellTrace; CellTraceCompiled; CellTraceContribution; CellTraceRequest; CellTraceResult; CellTraceStatus; ClickHouseQueryRows; CombinePortsConfigured; CompileResolvedBundle; CompiledQueryFingerprint; CompilerPlanDiagnostics; Config; Control; DynamicColumnCheck; DynamicDriftError; Engine; Error; Explain; ExplainAssessment; ExplainCollectionScan; ExplainIndexLocation; ExplainIndexSummary; ExplainPhysical; ExplainPlanEstimate; ExplainResolved; ExplainWarning; Materialize; MaterializeBundle; MaterializeClickHouseArtifact; MaterializeResolvedBundle; MaterializeVersion; New; NewService; OutputRows; OutputStream; PhysicalExplanation; PhysicalOutputExplanation; PopulationMapping; PopulationMappingCompiled; PopulationMappingDiagnostic; PopulationMappingRequest; PopulationMappingResult; PopulationMappingStatus; PopulationMemberReader; PopulationMemberReaderFunc; PostgreSQLExplainPlan; PostgreSQLPlanAssessment; PostgreSQLPlanEstimate; PostgreSQLPlanJSONExecutor; PostgreSQLPlanNode; PostgreSQLRootPageSteps; PostgreSQLScanEvidence; Preview; PreviewBundle; PreviewCollectionIndex; PreviewCollectionScanEvidence; PreviewCollectionScans; PreviewIndexCoverage; PreviewIndexNodeEvidence; PreviewOutput; PreviewRequest; PreviewResolvedBundle; PreviewSummary; PrivateClickHouseArtifactInput; PrivateClickHouseArtifactSource; QueryDiagnostics; QueryMemoryBudgetFromContext; ResolutionError; Resolve; ResolveBundle; ResolveClickHouseInputs; ResolveVersion; Resolved; Result; RowIdentity; RowLineage; RowLineageContributor; RowLineageRequest; RowLineageResult; RowVisitor; RowsRequest; Run; RunCompiled; ScanCategories; ScanCategoriesCompiled; Service; ServiceConfig; SourcePlanFingerprint; SourceRows; Stream; StreamResult; Streams; TableShapeExclusion; TableShapeExclusionRequest; TableShapeExclusionResult; TableShapeExclusionStatus; TableShapeExclusions; TableShapeExclusionsCompiled; TableShapeSourceIdentity; Unwrap; Validate; Validation; VisitMembers; WithExecutionReadPins; WithPrivateClickHouseArtifacts; WithQueryMemoryBudget,internal/authscope; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/clickhouse; internal/dataframe/compiler/render/postgres; internal/dataframe/errors; internal/dataframe/execution/chartifact; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/projectid,internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/explorer; internal/explorer/capability; internal/explorer/lifecycle; internal/projectid; internal/source; internal/store/clickhouse,internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/backends/postgres; internal/backends/postgres/internal/rows; internal/explorer/lifecycle; internal/server,internal/api/graphql/graph; internal/dataframe/compiler; internal/dataframe/execution,github.com/google/uuid,1 exported interface(s),legacy=5; deprecated=1; compatibility=22,broad surface (23 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/execution,inventory complete; semantic disposition pending internal/dataframe/execution/chartifact,github.com/calypr/loom/internal/dataframe/execution/chartifact,chartifact,domain/library,Manage private ClickHouse staging tables and leases used to cross execution boundaries without publishing outputs.,artifact.go,artifact_test.go,—,—,1038,490,24,Abort; Artifact; Begin; Catalog; CheckLease; ClickHouse; Column; ColumnsFromCompiledOutput; Config; Finalize; Identity; LeaseContext; Manager; Manifest; New; NormalizeSchema; Progress; Reconcile; Release; SchemaDigest; ScopeDigest; State; Write; Writer,internal/authscope; internal/dataframe/compiler/lower; internal/dataframe/expression; internal/store/clickhouse,internal/authscope; internal/dataframe/compiler/lower; internal/dataframe/expression; internal/store/clickhouse,internal/backends/arango/internal/chartifact; internal/backends/postgres/internal/chartifact; internal/dataframe/execution; internal/server,internal/backends/postgres/internal/store,github.com/google/uuid,2 exported interface(s),legacy=1,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/execution/chartifact,inventory complete; semantic disposition pending internal/dataframe/expression,github.com/calypr/loom/internal/dataframe/expression,expression,domain/library,Define and validate the typed backend-neutral expression AST used by recipes.,expression_checking.go; expression_types.go; recipe_adapter.go,expression_test.go; recipe_adapter_test.go,—,—,856,235,26,AllowsMany; Call; Cardinality; Check; CheckedExpression; Constant; Document; DocumentRef; Expression; FromRecipe; FromRecipeInContexts; Function; Literal; NodeKind; NullBehavior; NullType; Optional; Select; SelectorRef; SelectorResolver; String; Type; TypeContext; Valid; Validate; ValueKind,internal/dataframe/recipe,internal/dataframe/recipe,internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution/chartifact; internal/dataframe/semantic; internal/explorer; internal/explorer/lifecycle; internal/server,internal/dataframe/execution,—,—,compatibility=3,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/expression,inventory complete; semantic disposition pending internal/dataframe/lineage,github.com/calypr/loom/internal/dataframe/lineage,lineage,domain/library,Define stable identities and provenance for columns expanded from one authored source slot.,source_child.go,source_child_test.go,—,—,163,77,7,CloneSourceChild; Coordinate; IsSourceChildID; ParseSourceChildID; SourceChild; SourceChildKind; StableSourceChildID,—,—,internal/dataframe/compiler/lower; internal/dataframe/recipe; internal/explorer/authoringv2; internal/explorer/compilation,internal/server,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/lineage,inventory complete; semantic disposition pending @@ -71,23 +73,23 @@ internal/dataframe/published,github.com/calypr/loom/internal/dataframe/published internal/dataframe/recipe,github.com/calypr/loom/internal/dataframe/recipe,recipe,domain/library,"Define, canonicalize, expand, encode, and validate persistence-neutral recipe documents.",canonical.go; construction.go; construction_combine.go; construction_reshape.go; derived.go; derived_validation.go; document_types.go; document_validation.go; dynamic_validation.go; fragments.go; json_encoding.go; revisions.go; strict_json.go; table_reshape.go; validation_rich.go; workspace_output_dependencies.go,construction_combine_test.go; construction_group_policy_test.go; construction_test.go; correlated_selection_test.go; derived_validation_test.go; documentation_test.go; fragments_test.go; publication_identity_nul_http_test.go; table_reshape_test.go; types_test.go; workspace_output_dependencies_test.go,—,—,5611,1930,147,Aggregate; AggregateOperation; ArrayQuantifier; Bundle; CanonicalBundle; CanonicalJSON; CanonicalJSONWithWorkspaceOutputs; CatalogProjection; Clone; CodeValue; ColumnNaming; ColumnTransformation; Construction; ConstructionCodedGroup; ConstructionCodedGroupSource; ConstructionCodedPivot; ConstructionCodedPivotCategory; ConstructionCodedPivotSource; ConstructionCombine; ConstructionCombineJoinType; ConstructionCombineKey; ConstructionCombineKind; ConstructionCombineMembershipMode; ConstructionCombineProjection; ConstructionCombineRightMatchPolicy; ConstructionDerive; ConstructionExpand; ConstructionFilter; ConstructionGroup; ConstructionGroupAggregate; ConstructionGroupAggregateOp; ConstructionGroupCodingEmptyPolicy; ConstructionGroupCurrentStageFieldSource; ConstructionGroupFieldSource; ConstructionGroupFieldSourceInput; ConstructionGroupKey; ConstructionGroupMissingKeyPolicy; ConstructionGroupRelatedOccurrenceFieldSource; ConstructionGroupRelatedRouteStep; ConstructionGroupSemanticCategory; ConstructionInputKind; ConstructionInputRef; ConstructionOperand; ConstructionOperation; ConstructionOperationKind; ConstructionPivot; ConstructionPivotCategory; ConstructionRelatedEligibility; ConstructionRelatedExpand; ConstructionRelatedField; ConstructionRelatedFieldSource; ConstructionRelatedPredicate; ConstructionRelatedRepeatedBoundary; ConstructionRelatedRouteStep; ConstructionRelatedSource; ConstructionRowValue; ConstructionRowValuePolicy; ConstructionStep; ConstructionUnpivot; ConstructionUnpivotInput; ContributorWindow; DerivedColumn; DerivedLiteral; DerivedOperand; DerivedOperandKind; DerivedOperation; Digest; DigestWithWorkspaceOutputs; DivisionByZeroPolicy; DocumentRef; DynamicColumn; Error; ExpandBundle; ExpandExpression; ExpandFragments; Expansion; ExpansionEmptyPolicy; ExpansionIdentity; Explain; Explanation; Expression; ExtensionColumn; ExtensionColumnMapping; Field; Filter; FilterOperator; FilterValue; FilterValueKind; Fragment; FragmentLibrary; GroupRowValuePolicy; GroupRows; GroupedPivot; GroupedPivotCategory; Identity; IsValidConstructionLogicalType; MarshalJSON; MissingInputPolicy; Normalized; NormalizedPivotProjectionMode; NumericKind; Output; OutputDependencyOrder; OutputExplanation; OwnerRecordProjection; Parse; Pivot; PivotDiscovery; PivotDuplicatePolicy; PivotMissingCellPolicy; PivotUnlistedCategoryPolicy; PopulationConstraint; PopulationRouteStep; RecipeRevision; RepresentativeSlice; RevisionStore; RootColumnNaming; RuntimeBindings; SortedNames; StageColumn; TableReshape; TableReshapeKind; TableScalar; TableScalarKind; TemporalDirection; TemporalOrdering; TemporalPrecision; TemporalTiePolicy; TerminalCombineStep; Traversal; TraversalColumnNaming; TraversalMatchMode; UnitNormalizationPolicy; UnmarshalJSON; Unpivot; UnpivotInput; UnpivotNullRowPolicy; Valid; ValidPivotProjectionMode; Validate; ValidateConcreteValue; ValidatePivotCategoryKey; ValidateStructure; ValidateWithGroupRows; ValidateWithWorkspaceOutputs; ValidationError; ValueMode,generated/fhir; internal/authscope; internal/dataframe/columntransform; internal/dataframe/lineage; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/dataframe/lineage; internal/dataframe/publication; internal/fhir/schema,cmd/dataframe-profile; cmd/explorer-config-v2-convert; cmd/preview-covering-index-artifact; conformance/compiler; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/backends/arango/internal/recipeexec; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/recipes; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/explorer; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,internal/backends/arango/internal/explorer; internal/backends/postgres; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres,—,1 exported interface(s),legacy=17; compatibility=6,broad surface (16 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/recipe,inventory complete; semantic disposition pending internal/dataframe/recipe/exec,github.com/calypr/loom/internal/dataframe/recipe/exec,exec,domain/library,Define durable recipe-version storage contracts and immutable registration behavior.,persistent.go,persistent_test.go,—,—,84,102,5,Entry; PersistentRegistry; Reader; RegisterVersion; Store,internal/dataframe/recipe,internal/dataframe/recipe,internal/api/graphql/graph/resolver; internal/backends/arango/internal/recipeexec; internal/backends/postgres/internal/recipeexec; internal/dataframe/execution; internal/server,internal/backends/postgres; internal/backends/postgres/internal/store; internal/dataframe/compiler; internal/explorer/lifecycle,—,2 exported interface(s),—,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/recipe/exec,inventory complete; semantic disposition pending internal/dataframe/recipe/schema,github.com/calypr/loom/internal/dataframe/recipe/schema,schema,domain/library,Resolve catalog-backed recipe declarations into concrete validated schemas.,discovery.go; extension_columns.go; resolve.go,resolve_test.go,—,—,882,293,8,Discovery; ExtensionValueObservation; FieldCandidate; Fields; Resolve; Resolved; Scope; SourceSnapshot,internal/dataframe/recipe; internal/fhir/schema,internal/dataframe/recipe; internal/dataframe/semantic,internal/api/graphql/graph/query; internal/api/recipe; internal/server,—,—,1 exported interface(s),legacy=2,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/recipe/schema,inventory complete; semantic disposition pending -internal/dataframe/semantic,github.com/calypr/loom/internal/dataframe/semantic,semantic,domain/library,Convert validated dataframe requests and recipes into backend-neutral logical FHIR plans.,doc.go; dynamic_schema.go; execution_context.go; recipe_filter.go; recipe_plan_build.go; recipe_plan_output.go; recipe_plan_types.go; recipe_projection.go; recipe_rich_shaping.go; recipe_traversal.go; resolved_plan.go; selection_semantics.go; selector_spec.go; semantic_nodes.go; semantic_validation.go,dynamic_schema_test.go; recipe_plan_test.go; recipe_plan_user_fault_class_test.go; recipe_rich_shaping_test.go; resolved_plan_test.go,—,—,3022,931,47,BuildRecipePlan; Candidate; Column; DynamicSpec; ExecutionContext; ExpansionEmptyPolicy; ExpansionExplanation; Explain; ExpressionExplanation; FallbackSelectors; Freeze; FrozenSchema; LowerRecipeFilters; LowerRecipeFiltersForAlias; NormalizeRecipeMatchMode; OutputPlan; OutputPlanExplanation; PrimarySelector; RecipePlan; RecipePlanExplanation; ResolveRecipePlan; ResolveSemanticField; ResolvedColumn; ResolvedRecipePlan; SelectionSemanticSpec; SemanticAggregate; SemanticContributorWindow; SemanticDynamicMap; SemanticExpression; SemanticField; SemanticGroupRows; SemanticGroupedPivot; SemanticGroupedPivotCategory; SemanticNode; SemanticOccurrence; SemanticOwnerRecords; SemanticPivot; SemanticPopulation; SemanticPopulationRouteStep; SemanticRowExpansion; SemanticSlice; SemanticTableReshape; SemanticTemporalOrdering; SemanticUnpivot; SemanticUnpivotInput; Validate; ValidateSemanticGraph,internal/authscope; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution,conformance/compiler; internal/backends/postgres; internal/dataframe/compiler/command; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/recipe/schema; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,—,—,legacy=3; compatibility=1,broad surface (15 production files); inspect cohesion,go test ./internal/dataframe/semantic,inventory complete; semantic disposition pending +internal/dataframe/semantic,github.com/calypr/loom/internal/dataframe/semantic,semantic,domain/library,Convert validated dataframe requests and recipes into backend-neutral logical FHIR plans.,doc.go; dynamic_schema.go; execution_context.go; recipe_filter.go; recipe_plan_build.go; recipe_plan_output.go; recipe_plan_types.go; recipe_projection.go; recipe_rich_shaping.go; recipe_traversal.go; resolved_plan.go; selection_semantics.go; selector_spec.go; semantic_nodes.go; semantic_validation.go,dynamic_schema_test.go; recipe_plan_test.go; recipe_plan_user_fault_class_test.go; recipe_rich_shaping_test.go; resolved_plan_test.go,—,—,3022,931,47,BuildRecipePlan; Candidate; Column; DynamicSpec; ExecutionContext; ExpansionEmptyPolicy; ExpansionExplanation; Explain; ExpressionExplanation; FallbackSelectors; Freeze; FrozenSchema; LowerRecipeFilters; LowerRecipeFiltersForAlias; NormalizeRecipeMatchMode; OutputPlan; OutputPlanExplanation; PrimarySelector; RecipePlan; RecipePlanExplanation; ResolveRecipePlan; ResolveSemanticField; ResolvedColumn; ResolvedRecipePlan; SelectionSemanticSpec; SemanticAggregate; SemanticContributorWindow; SemanticDynamicMap; SemanticExpression; SemanticField; SemanticGroupRows; SemanticGroupedPivot; SemanticGroupedPivotCategory; SemanticNode; SemanticOccurrence; SemanticOwnerRecords; SemanticPivot; SemanticPopulation; SemanticPopulationRouteStep; SemanticRowExpansion; SemanticSlice; SemanticTableReshape; SemanticTemporalOrdering; SemanticUnpivot; SemanticUnpivotInput; Validate; ValidateSemanticGraph,internal/authscope; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema,cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/server,conformance/compiler; internal/backends/postgres; internal/dataframe/compiler/command; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/recipe/schema; internal/explorer/compilation; internal/explorer/lifecycle,—,—,legacy=3; compatibility=1,broad surface (15 production files); inspect cohesion,go test ./internal/dataframe/semantic,inventory complete; semantic disposition pending internal/dataframe/spec,github.com/calypr/loom/internal/dataframe/spec,spec,domain/library,"Define the backend-independent dataframe request, filter, grain, relationship, and selector language.",doc.go; filter.go; filter_semantics.go; grain.go; relationship_match.go; selector_validation.go; selectors.go,correlated_selection_test.go; grain_classification_test.go,—,—,745,71,31,AllowsMany; ArrayQuantifier; Cardinality; Clone; CodeValue; ContainsFilter; DefaultRowIdentity; ExpandsRows; FilterOperator; FilterValue; FilterValueKind; InferRowGrain; IsRequired; OperatorSupportsKind; ParseDirectScalarSelector; ParseSelector; ProjectionMode; Required; RootResourceForGrain; RowGrain; RowIdentity; Selector; SelectorCardinality; SelectorStep; TraversalMatchMode; TypedFilter; Valid; Validate; ValidateProjection; ValidateRootGrain; ValidateTypedFilterForResource,generated/fhir; internal/dataframe/errors; internal/fhir/schema,internal/dataframe/errors; internal/fhir/schema,internal/api/graphql/graph/query; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,conformance/compiler; internal/api/graphql/graph/resolver; internal/backends/postgres,—,—,legacy=1; compatibility=2,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/spec,inventory complete; semantic disposition pending internal/dataframe/unit,github.com/calypr/loom/internal/dataframe/unit,unit,domain/library,"Define unit identity, dimensional compatibility, and approved conversion rules for quantity values.",arithmetic.go; unit.go,arithmetic_test.go; unit_test.go,—,—,442,203,27,ApprovedUnitPolicies; ApprovedUnitPolicy; ArithmeticOperand; ArithmeticOperation; ArithmeticUnitError; ArithmeticUnitErrorReason; ColumnArithmeticOperand; Equal; Error; LiteralArithmeticOperand; ResolveApprovedUnitPolicy; ResolveApprovedUnitRules; ResolveArithmeticUnit; String; SupportsSources; UnitConversionKind; UnitConversionRule; UnitDimension; UnitDimensionFor; UnitIdentity; UnitNormalization; UnitRuleReference; Valid; Validate; ValidateUnitPolicyReference; ValidateUnitRuleReference; Zero,—,—,internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer; internal/explorer/authoringv2; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,internal/dataframe/compiler,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/unit,inventory complete; semantic disposition pending internal/dataset,github.com/calypr/loom/internal/dataset,dataset,domain/library,"Define immutable dataset generation manifests, releases, active pointers, and lifecycle validation.",active_resolver.go; dataframe_selector.go; doc.go; manifest.go; manifest_port.go; ref.go; release.go; schema.go; validation.go,active_resolver_test.go; manifest_test.go; memory_store_test.go; ref_test.go; release_id_framing_test.go; release_idempotency_test.go; release_requirements_e2e_test.go; release_test.go; release_verifier_fault_classification_test.go; schema_test.go,README.md,—,802,2253,34,Activate; ActivationRequest; Active; ActiveRelease; ActiveReleaseResolver; ActiveResolver; ContractVerification; Create; DataframeSelector; Error; IsStaged; Key; Manifest; ManifestLifecycle; ManifestReader; NewManifest; NewRef; NewSchemaSnapshot; ProjectRelease; PublicationVerification; PublicationVerifier; Ref; ReleasePublication; ReleaseRepository; ReleaseRequirementsError; ReleaseService; ResolveActive; SchemaSnapshot; State; Transition; Unwrap; Valid; Validate; ValidateGeneration,—,internal/backends/arango; internal/dataset,cmd/arango-fhir-proto; cmd/loom-data; internal/api/bulk/load; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/postgres/control; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/explorer; internal/explorer/lifecycle; internal/ingest; internal/server,internal/api/graphql/graph; internal/backends/arango/internal/publication; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/dataset,—,6 exported interface(s),legacy=1; compatibility=2,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataset,inventory complete; semantic disposition pending -internal/explorer,github.com/calypr/loom/internal/explorer,explorer,domain/library,"Define Explorer aggregate identities, configs, revisions, receipts, output contracts, stores, and core service operations.",artifact.go; authoring_errors.go; canonical_json.go; compilation.go; compilation_receipt.go; config_v2.go; explicit_group_repository.go; explicit_groups.go; identity.go; interpretation.go; output_contract.go; selection.go; service.go; state_v1.go; store.go; types.go; viewer_projection.go,artifact_test.go; compilation_receipt_test.go; explicit_groups_test.go; interpretation_test.go; output_contract_test.go; repository_revision_id_test.go; selection_test.go; types_test.go; viewer_projection_order_test.go,—,—,4335,1587,165,AbortSelection; ActivateRepositoryGeneration; ActiveRevision; AppendSelectionMembers; ApplyWorkspaceCommands; ApplyWorkspaceCommandsChecked; ArtifactCompletion; ArtifactID; ArtifactRecord; ArtifactStage; ArtifactState; ArtifactStore; AuthoringDiagnostic; AuthoringError; BeginSelection; BuildViewerProjection; Canonical; CanonicalContent; CanonicalExplicitGroupDefinitions; CanonicalExplicitGroupMemberships; CanonicalInterpretationProject; CleanupSelectionStaging; CompilationArtifactDigest; CompilationKey; CompilationReceipt; CompilationReceiptForExplorer; CompilationWarning; CompleteSelection; ComputeContentDigest; ComputeRevisionID; ConfigAction; ConfigChart; ConfigColumn; ConfigExplorer; ConfigFilter; ConfigManagementForID; ConfigTable; ConfigV2; ConfigView; ConstructionProposalBinding; CreateInteractiveFrom; DatasetMetadata; DatasetOutput; DecodePublicOutputContracts; Diagnostic; DigestSelectionMembers; DraftRevision; DraftRevisionReader; EmittedColumn; Error; ExplicitGroupDefinition; ExplicitGroupDefinitionDigest; ExplicitGroupID; ExplicitGroupMembership; ExplicitGroupMembershipCursor; ExplicitGroupMembershipDigest; ExplicitGroupRepository; ExplicitGroupRevision; ExplicitGroupRevisionID; ExplicitGroupRevisionIDFor; ExplicitGroupRevisionState; Explorer; ExplorerRuntimeBindingV1; ExplorerRuntimeColumnV1; ExplorerRuntimeOutputV1; ExplorerRuntimeSchemaV1; ExplorerRuntimeTableColumnV1; ExplorerRuntimeTableV1; ExplorerRuntimeV1; ExplorerStateV1; ExplorerStateV1Active; ExplorerStateV1Draft; ExplorerStateV1Generated; ExplorerSummaryV1; FailRevision; FileActions; Get; GetDraftRevision; GetRevision; GetSelection; IdentityMapping; InterpretationApplicability; InterpretationContentDigest; InterpretationFeatureDefinition; InterpretationLibrary; InterpretationLibraryID; InterpretationPriority; InterpretationRepository; InterpretationRevision; InterpretationRevisionID; InterpretationRule; InterpretationRuleID; InterpretationStructuralCandidate; InterpretationStructuralMatch; Is; List; LoadExplorerState; ManagementMode; Matches; Materialization; MembershipDigest; MigrateLegacyConstructionSteps; NewInterpretationContentDigest; NewInterpretationLibraryID; NewInterpretationRevisionID; NewInterpretationRuleID; NewService; Output; PopulationMemberRemovalProposalBinding; PrepareInterpretationRevision; PublicOutputColumn; PublicOutputContract; PublicOutputContracts; PublicUnitNormalization; PublicUnitRuleIdentity; PublicationMetadata; PublishAuthoring; ReceiptCompiledOutputColumn; ReceiptConstructionActiveRelatedRecord; ReceiptConstructionCodedGroupChoice; ReceiptConstructionGroupFieldOwner; ReceiptConstructionGroupFieldSourceAncestor; ReceiptConstructionGroupFieldSourceContext; ReceiptConstructionGroupFieldSourceExpansion; ReceiptConstructionGroupFieldSourceRouteStep; ReceiptConstructionOperationChoice; ReceiptConstructionRelatedExpand; ReceiptConstructionRelatedExpandAnchor; ReceiptConstructionStage; ReceiptConstructionStageColumn; ReceiptContractSupportedForExecution; ReceiptID; RepositoryRevisionID; ResolvedInterpretation; ResourceRef; Revision; RevisionIdentityEnvelope; RevisionStatus; RowDefinitionProposalBinding; SelectRule; SelectionFilter; SelectionMember; SelectionRevision; SelectionRule; SelectionRuleDigest; SelectionRuleDigestWithMembers; SelectionSource; Service; SharedFilter; StableExplorerID; StartExplicitGroupMembershipDigest; Store; StoreCompilationReceipt; TableShapeProposalBinding; Unwrap; UpsertRepositoryV2; Validate; ValidateAgainst; ValidateID; ValidateSource; ValidateSourceSelection; ViewerProjectionIntegrityError; VisitSelectionMembers; WriteExplicitGroupMembershipFrame; WriteMembershipFrame,internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/unit; internal/dataset; internal/explorer/authoringv2; internal/explorer/capability; internal/projectid,internal/dataframe/publication; internal/dataframe/recipe; internal/dataframe/unit; internal/explorer/authoringv2,cmd/explorer-config-v2-convert; cmd/loom-acceptance; internal/backends/arango/internal/explorer; internal/backends/postgres/internal/explorer; internal/explorer/artifactfs; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,internal/backends/postgres/internal/store; internal/dataframe/compiler/render/postgres,—,6 exported interface(s),legacy=17; migration=2; compatibility=4,broad surface (17 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/explorer,inventory complete; semantic disposition pending +internal/explorer,github.com/calypr/loom/internal/explorer,explorer,domain/library,"Define Explorer aggregate identities, configs, revisions, receipts, output contracts, stores, and core service operations.",artifact.go; authoring_errors.go; canonical_json.go; compilation.go; compilation_receipt.go; config_v2.go; explicit_group_repository.go; explicit_groups.go; identity.go; interpretation.go; output_contract.go; selection.go; service.go; state_v1.go; store.go; types.go; viewer_projection.go,artifact_test.go; compilation_receipt_test.go; explicit_groups_test.go; interpretation_test.go; output_contract_test.go; repository_revision_id_test.go; selection_test.go; types_test.go; viewer_projection_order_test.go,—,—,4335,1587,165,AbortSelection; ActivateRepositoryGeneration; ActiveRevision; AppendSelectionMembers; ApplyWorkspaceCommands; ApplyWorkspaceCommandsChecked; ArtifactCompletion; ArtifactID; ArtifactRecord; ArtifactStage; ArtifactState; ArtifactStore; AuthoringDiagnostic; AuthoringError; BeginSelection; BuildViewerProjection; Canonical; CanonicalContent; CanonicalExplicitGroupDefinitions; CanonicalExplicitGroupMemberships; CanonicalInterpretationProject; CleanupSelectionStaging; CompilationArtifactDigest; CompilationKey; CompilationReceipt; CompilationReceiptForExplorer; CompilationWarning; CompleteSelection; ComputeContentDigest; ComputeRevisionID; ConfigAction; ConfigChart; ConfigColumn; ConfigExplorer; ConfigFilter; ConfigManagementForID; ConfigTable; ConfigV2; ConfigView; ConstructionProposalBinding; CreateInteractiveFrom; DatasetMetadata; DatasetOutput; DecodePublicOutputContracts; Diagnostic; DigestSelectionMembers; DraftRevision; DraftRevisionReader; EmittedColumn; Error; ExplicitGroupDefinition; ExplicitGroupDefinitionDigest; ExplicitGroupID; ExplicitGroupMembership; ExplicitGroupMembershipCursor; ExplicitGroupMembershipDigest; ExplicitGroupRepository; ExplicitGroupRevision; ExplicitGroupRevisionID; ExplicitGroupRevisionIDFor; ExplicitGroupRevisionState; Explorer; ExplorerRuntimeBindingV1; ExplorerRuntimeColumnV1; ExplorerRuntimeOutputV1; ExplorerRuntimeSchemaV1; ExplorerRuntimeTableColumnV1; ExplorerRuntimeTableV1; ExplorerRuntimeV1; ExplorerStateV1; ExplorerStateV1Active; ExplorerStateV1Draft; ExplorerStateV1Generated; ExplorerSummaryV1; FailRevision; FileActions; Get; GetDraftRevision; GetRevision; GetSelection; IdentityMapping; InterpretationApplicability; InterpretationContentDigest; InterpretationFeatureDefinition; InterpretationLibrary; InterpretationLibraryID; InterpretationPriority; InterpretationRepository; InterpretationRevision; InterpretationRevisionID; InterpretationRule; InterpretationRuleID; InterpretationStructuralCandidate; InterpretationStructuralMatch; Is; List; LoadExplorerState; ManagementMode; Matches; Materialization; MembershipDigest; MigrateLegacyConstructionSteps; NewInterpretationContentDigest; NewInterpretationLibraryID; NewInterpretationRevisionID; NewInterpretationRuleID; NewService; Output; PopulationMemberRemovalProposalBinding; PrepareInterpretationRevision; PublicOutputColumn; PublicOutputContract; PublicOutputContracts; PublicUnitNormalization; PublicUnitRuleIdentity; PublicationMetadata; PublishAuthoring; ReceiptCompiledOutputColumn; ReceiptConstructionActiveRelatedRecord; ReceiptConstructionCodedGroupChoice; ReceiptConstructionGroupFieldOwner; ReceiptConstructionGroupFieldSourceAncestor; ReceiptConstructionGroupFieldSourceContext; ReceiptConstructionGroupFieldSourceExpansion; ReceiptConstructionGroupFieldSourceRouteStep; ReceiptConstructionOperationChoice; ReceiptConstructionRelatedExpand; ReceiptConstructionRelatedExpandAnchor; ReceiptConstructionStage; ReceiptConstructionStageColumn; ReceiptContractSupportedForExecution; ReceiptID; RepositoryRevisionID; ResolvedInterpretation; ResourceRef; Revision; RevisionIdentityEnvelope; RevisionStatus; RowDefinitionProposalBinding; SelectRule; SelectionFilter; SelectionMember; SelectionRevision; SelectionRule; SelectionRuleDigest; SelectionRuleDigestWithMembers; SelectionSource; Service; SharedFilter; StableExplorerID; StartExplicitGroupMembershipDigest; Store; StoreCompilationReceipt; TableShapeProposalBinding; Unwrap; UpsertRepositoryV2; Validate; ValidateAgainst; ValidateID; ValidateSource; ValidateSourceSelection; ViewerProjectionIntegrityError; VisitSelectionMembers; WriteExplicitGroupMembershipFrame; WriteMembershipFrame,internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/unit; internal/dataset; internal/explorer/authoringv2; internal/explorer/capability; internal/projectid,internal/dataframe/publication; internal/dataframe/recipe; internal/dataframe/unit; internal/explorer/authoringv2,cmd/explorer-config-v2-convert; cmd/loom-acceptance; internal/backends/arango/internal/explorer; internal/backends/postgres/internal/explorer; internal/explorer/artifactfs; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,internal/backends/postgres/internal/store; internal/dataframe/compiler/render/postgres; internal/dataframe/execution,—,6 exported interface(s),legacy=17; migration=2; compatibility=4,broad surface (17 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/explorer,inventory complete; semantic disposition pending internal/explorer/artifactfs,github.com/calypr/loom/internal/explorer/artifactfs,artifactfs,domain/library,Persist and serve immutable Explorer training artifacts from a dedicated filesystem root.,store.go,reconcile_test.go; store_test.go,—,—,379,621,10,Abort; Begin; Close; Commit; Get; New; Open; Reconcile; Store; Write,internal/dataframe/published; internal/explorer,internal/dataframe/published; internal/explorer,internal/server,—,—,—,—,one production importer (internal/server); inspect ownership/inline potential,go test ./internal/explorer/artifactfs,inventory complete; semantic disposition pending -internal/explorer/authoringv2,github.com/calypr/loom/internal/explorer/authoringv2,authoringv2,domain/library,"Define, validate, canonicalize, and mutate the V2 Explorer Builder authoring workspace.",availability.go; canonical.go; commands.go; construction.go; construction_change.go; construction_choice.go; construction_migration.go; construction_row_values.go; construction_source_children.go; construction_validation.go; framing.go; migration.go; route_identity.go; row_change.go; row_definition.go; semantic_command.go; semantic_types.go; table_shape.go; transformations.go; types.go; validation.go; value_transformations.go; workspace_output_dependencies.go,commands_test.go; compile_preparation_test.go; construction_choice_test.go; construction_combine_test.go; construction_expand_test.go; construction_formal_replay_test.go; construction_group_coded_pivot_test.go; construction_group_field_source_test.go; construction_projection_replay_test.go; construction_proposal_test.go; construction_related_contributor_test.go; construction_row_values_test.go; construction_source_children_test.go; construction_test.go; correlated_test.go; framing_test.go; interpretation_test.go; migration_test.go; population_member_proposal_test.go; row_change_test.go; row_definition_test.go; row_proposal_test.go; semantic_codeable_concept_fallback_test.go; semantic_coded_category_test.go; semantic_command_test.go; table_shape_proposal_test.go; table_shape_test.go; transformations_test.go; types_test.go; value_transformations_test.go; workspace_output_dependencies_test.go; workspace_test.go,—,—,13260,8548,231,Action; ActionColumn; Addable; AggregateSource; AggregateTransformationCapabilities; AggregateTransformationCapabilitiesForCapability; AggregateTransformationCapabilitiesForCatalog; AnalyzeConstructionCandidate; AnalyzeStepEdit; ApplyCommands; ApplyCommandsRequest; ApplyCommandsResponse; ApplyInterpretationCandidate; ApplyRowChange; ApplyRowReplacement; ArithmeticOperand; AssessRowChange; AssessRowReplacement; BuilderState; CanPopulateConstructionRows; CanonicalJSON; CatalogAvailability; CatalogAvailabilityReadiness; CatalogAvailabilitySourceReadiness; CatalogCandidate; CatalogDiagnostic; CatalogEdge; CatalogNode; CatalogObservedField; CatalogObservedRelationship; CatalogObservedResource; CatalogSnapshot; ChartPresentation; CodeValue; Column; ColumnOutput; ColumnSource; ColumnTransformationChange; ColumnValueTransformationCapabilities; ColumnValueTransformationCapabilitiesForCatalog; Command; CommandResult; ConceptCandidate; Construction; ConstructionCanCarrySourceProjection; ConstructionChoiceSelection; ConstructionCodedGroup; ConstructionCodedGroupSource; ConstructionCodedPivot; ConstructionCodedPivotCategory; ConstructionCodedPivotSource; ConstructionCombine; ConstructionCombineJoinType; ConstructionCombineKey; ConstructionCombineKind; ConstructionCombineMembershipMode; ConstructionCombineProjection; ConstructionCombineRightMatchPolicy; ConstructionDependencyIssue; ConstructionDerive; ConstructionDerivedOperation; ConstructionDivisionByZeroPolicy; ConstructionExpand; ConstructionExpandEmptyPolicy; ConstructionExpandedFieldSource; ConstructionFilter; ConstructionFilterOperator; ConstructionFilterValueKind; ConstructionGroup; ConstructionGroupAggregate; ConstructionGroupAggregateOp; ConstructionGroupFieldCurrentStage; ConstructionGroupFieldRelatedOccurrence; ConstructionGroupFieldRelatedRouteStep; ConstructionGroupFieldSource; ConstructionGroupFieldSourceInput; ConstructionGroupKey; ConstructionGroupMissingKeyPolicy; ConstructionGroupSemanticCategory; ConstructionID; ConstructionImpact; ConstructionInputKind; ConstructionInputRef; ConstructionLiteral; ConstructionMissingInputPolicy; ConstructionNumericKind; ConstructionOperand; ConstructionOperandKind; ConstructionOperation; ConstructionOperationKind; ConstructionOutputPresentation; ConstructionPivot; ConstructionPivotCategory; ConstructionPivotDuplicatePolicy; ConstructionPivotMissingCellPolicy; ConstructionPivotUnlistedCategoryPolicy; ConstructionRelatedContributorRule; ConstructionRelatedEligibility; ConstructionRelatedEligibilityMatch; ConstructionRelatedExpand; ConstructionRelatedField; ConstructionRelatedFieldSource; ConstructionRelatedSource; ConstructionRowValue; ConstructionRowValuePolicy; ConstructionSourceProjection; ConstructionSourceProjectionName; ConstructionStep; ConstructionUnpivot; ConstructionUnpivotInput; ConstructionUnpivotNullPolicy; ContributorCode; ContributorOperator; ContributorPredicate; ContributorQuantifier; ContributorValue; ContributorValueKind; ContributorWindowSource; DecodeWorkspace; DerivedConstruction; Digest; Document; DraftSourceContext; EmptyCollectionPolicy; ExpandedRows; ExplicitGroupRowValue; ExplicitGroupSource; ExplorerMetadata; FeatureInterpretation; FeatureInterpretationKind; FieldGroupSource; FieldPath; FieldSource; FileActions; FilterPresentation; FilterValue; FixedFilter; FrameDefinition; FrameManyPolicy; FrameZeroPolicy; GroupSource; GroupSourceKind; GroupedRows; HasMissingInputs; LegacyNilConstructionStepsDigest; LookupMatch; LookupSource; MarshalJSON; MigrateAggregateTemporalPolicy; MigrateLegacyContributors; MigrateLosslessDefaults; MissingKeyPolicy; NewFrameDefinition; NormalizePresentationOrders; Normalized; Output; OutputDependencyOrder; OwnerRecordsSource; PinnedInterpretation; PivotCategory; PivotConstruction; Population; PopulationRouteStep; PrepareWorkspaceForCompilation; ProjectionMode; ProposeStepRemoval; RecordRows; RecordsRowDefinition; RelatedSelection; RepeatedBoundary; ResolveConstructionProposal; ResolveDraftRevision; ResolveDraftSourceContext; ResolveFrameSource; ResolvePopulationMemberProposal; ResolveRowDefinitionProposal; ResolveSemanticSelectionPlan; ResolveTableShapeProposal; ResolvedConstructionChoice; ResolvedDraftSourceContext; RouteMatchMode; RouteNode; RoutePolicy; RouteRebaseChoice; RowChangeAssessment; RowChangeProposal; RowChangeRequest; RowChangeUnresolvedReference; RowDefinition; RowDefinitionKind; SemanticReadinessStatus; SemanticSelection; SemanticSelectionPlan; SemanticSelectionReadiness; SemanticSelectionResult; SharedFilterBinding; SharedFilterBindingRef; SourceWhere; StageColumn; SupportsAnchor; SupportsTimestamp; Tab; TablePresentation; TableReshape; TableScalar; TableScalarKind; TableShape; TemporalFieldChoice; TemporalOrderingSource; TemporalReductionCapabilities; UnassignedMemberPolicy; UnitNormalizationCapabilities; UnitNormalizationPolicy; UnitNormalizationPresetCapability; UnmarshalJSON; UnpivotConstruction; UnpivotInput; UpgradeDocumentToConstruction; Valid; Validate; ValidateConcreteValue; ValidateConstructionExpandedFieldSource; ValidateContributorForCatalog; ValidateForPublication; ValidatePivotCategoryKey; ValidateStructure; ValidateTableShapeOutput; ValidateWithExplicitGroups; ValueTransformationCapability; Workspace; WorkspaceOutputConsumer,internal/catalog; internal/dataframe/columntransform; internal/dataframe/lineage; internal/dataframe/unit; internal/explorer/capability; internal/fhir/schema,internal/catalog; internal/dataframe/columntransform; internal/dataframe/lineage; internal/explorer/capability; internal/fhir/schema,cmd/explorer-config-v2-convert; cmd/preview-covering-index-artifact; generated/loomapi; internal/acceptance; internal/backends/arango/internal/explorer; internal/explorer; internal/explorer/compilation; internal/explorer/lifecycle; internal/repositoryseed; internal/server,internal/backends/postgres/internal/explorer; internal/explorer/capability,—,—,legacy=50; migration=28; compatibility=3,broad surface (23 production files); inspect cohesion,go test ./internal/explorer/authoringv2,inventory complete; semantic disposition pending -internal/explorer/capability,github.com/calypr/loom/internal/explorer/capability,capability,domain/library,Build the immutable compiler-backed capability contract exposed to Explorer Builder.,builder.go; construction.go; construction_routes.go; domain.go; population_route_choice.go; repository.go; row_choice.go; value_operations.go,builder_test.go; construction_test.go; domain_test.go; row_choice_test.go; semantic_fallback_dto_test.go; value_operations_test.go,—,—,3950,1396,110,AggregateInput; AggregateOperation; AggregateOperationCapability; AggregateRowContext; Allows; Availability; AvailabilityReadiness; AvailabilitySourceReadiness; Build; Builder; Candidate; CandidateProof; CanonicalPayload; ChartAggregation; Clone; CodedGroupChoiceSource; CompilerCallbacks; ConceptCandidate; ConstructionChoice; ConstructionChoiceDecision; ConstructionChoiceFact; ConstructionChoiceForm; ConstructionChoiceIdentity; ConstructionChoiceOption; ConstructionChoicePredicateOperator; ConstructionChoicePresentation; ConstructionChoicePreservation; ConstructionChoiceRowEffect; ConstructionChoiceShape; ConstructionChoiceSource; ConstructionChoiceSourceKind; ConstructionChoiceSupport; ConstructionCodedGroupChoice; ConstructionRelatedFieldChoice; ConstructionRelatedResourceRouteChoice; ConstructionRoutePage; ConstructionRouteSearch; ConstructionRouteStep; DecodeConstructionChoiceID; DecodePopulationRouteChoiceID; DecodeRowChoiceID; DeriveAggregateOperationCapabilities; Diagnostic; Edge; EdgeProof; Evidence; FieldChoiceSource; FieldObservation; FilterOperator; GroupFieldChoiceInput; GroupFieldChoiceSource; GroupFieldCurrentStageInput; GroupFieldRelatedOccurrenceInput; GroupFieldRelatedRouteStep; IsRepeatedCardinality; IsSupportedConstructionSourceType; IsUsable; NewBuilder; NewConstructionCodedGroupChoice; NewConstructionRelatedFieldChoice; NewConstructionRelatedResourceRouteChoiceFromAnchor; NewFieldConstructionChoice; NewFieldConstructionChoiceForRoute; NewGroupCodingIdentityChoice; NewGroupFieldConstructionChoice; NewPopulationRouteChoiceID; NewRowChoice; NewSemanticConstructionChoice; NewSemanticConstructionChoiceForRoute; NewSemanticFrameConstructionChoice; NewSnapshot; Node; NodeProof; ObservedField; ObservedRelationship; ObservedResource; Operation; PlanConstructionRoutes; Policy; PopulationRouteChoiceIdentity; ProjectionMode; ProjectionPolicy; RelatedFieldChoiceSource; RelatedResourceChoiceSource; RelationshipObservation; RepeatedBoundary; RepeatedCoordinate; Repository; ResolveRowChoiceID; ResourceObservation; RoutePolicy; RowChoice; RowChoiceCardinality; RowChoiceFacts; RowChoiceIdentity; RowChoiceKind; RowChoiceOccurrence; RowChoicePathResolver; RowChoiceShape; SemanticBindingChoiceSource; SemanticFrameChoiceSource; SemanticFrameFamily; Snapshot; SnapshotIdentity; Status; UnmarshalJSON; Usable; ValidateConstructionSourceType; ValidateSnapshot; ValidateToken,internal/dataframe/recipe,generated/loomapi; internal/catalog; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/lifecycle,cmd/preview-covering-index-artifact; internal/backends/arango/internal/explorer; internal/backends/postgres/internal/explorer; internal/explorer; internal/explorer/authoringv2; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,internal/dataframe/compiler; internal/explorer/capability,—,2 exported interface(s),legacy=2,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/explorer/capability,inventory complete; semantic disposition pending +internal/explorer/authoringv2,github.com/calypr/loom/internal/explorer/authoringv2,authoringv2,domain/library,"Define, validate, canonicalize, and mutate the V2 Explorer Builder authoring workspace.",availability.go; canonical.go; commands.go; construction.go; construction_change.go; construction_choice.go; construction_migration.go; construction_row_values.go; construction_source_children.go; construction_validation.go; framing.go; migration.go; route_identity.go; row_change.go; row_definition.go; semantic_command.go; semantic_types.go; table_shape.go; transformations.go; types.go; validation.go; value_transformations.go; workspace_output_dependencies.go,commands_test.go; compile_preparation_test.go; construction_choice_test.go; construction_combine_test.go; construction_expand_test.go; construction_formal_replay_test.go; construction_group_coded_pivot_test.go; construction_group_field_source_test.go; construction_projection_replay_test.go; construction_proposal_test.go; construction_related_contributor_test.go; construction_row_values_test.go; construction_source_children_test.go; construction_test.go; correlated_test.go; framing_test.go; interpretation_test.go; migration_test.go; population_member_proposal_test.go; row_change_test.go; row_definition_test.go; row_proposal_test.go; semantic_codeable_concept_fallback_test.go; semantic_coded_category_test.go; semantic_command_test.go; table_shape_proposal_test.go; table_shape_test.go; transformations_test.go; types_test.go; value_transformations_test.go; workspace_output_dependencies_test.go; workspace_test.go,—,—,13260,8548,231,Action; ActionColumn; Addable; AggregateSource; AggregateTransformationCapabilities; AggregateTransformationCapabilitiesForCapability; AggregateTransformationCapabilitiesForCatalog; AnalyzeConstructionCandidate; AnalyzeStepEdit; ApplyCommands; ApplyCommandsRequest; ApplyCommandsResponse; ApplyInterpretationCandidate; ApplyRowChange; ApplyRowReplacement; ArithmeticOperand; AssessRowChange; AssessRowReplacement; BuilderState; CanPopulateConstructionRows; CanonicalJSON; CatalogAvailability; CatalogAvailabilityReadiness; CatalogAvailabilitySourceReadiness; CatalogCandidate; CatalogDiagnostic; CatalogEdge; CatalogNode; CatalogObservedField; CatalogObservedRelationship; CatalogObservedResource; CatalogSnapshot; ChartPresentation; CodeValue; Column; ColumnOutput; ColumnSource; ColumnTransformationChange; ColumnValueTransformationCapabilities; ColumnValueTransformationCapabilitiesForCatalog; Command; CommandResult; ConceptCandidate; Construction; ConstructionCanCarrySourceProjection; ConstructionChoiceSelection; ConstructionCodedGroup; ConstructionCodedGroupSource; ConstructionCodedPivot; ConstructionCodedPivotCategory; ConstructionCodedPivotSource; ConstructionCombine; ConstructionCombineJoinType; ConstructionCombineKey; ConstructionCombineKind; ConstructionCombineMembershipMode; ConstructionCombineProjection; ConstructionCombineRightMatchPolicy; ConstructionDependencyIssue; ConstructionDerive; ConstructionDerivedOperation; ConstructionDivisionByZeroPolicy; ConstructionExpand; ConstructionExpandEmptyPolicy; ConstructionExpandedFieldSource; ConstructionFilter; ConstructionFilterOperator; ConstructionFilterValueKind; ConstructionGroup; ConstructionGroupAggregate; ConstructionGroupAggregateOp; ConstructionGroupFieldCurrentStage; ConstructionGroupFieldRelatedOccurrence; ConstructionGroupFieldRelatedRouteStep; ConstructionGroupFieldSource; ConstructionGroupFieldSourceInput; ConstructionGroupKey; ConstructionGroupMissingKeyPolicy; ConstructionGroupSemanticCategory; ConstructionID; ConstructionImpact; ConstructionInputKind; ConstructionInputRef; ConstructionLiteral; ConstructionMissingInputPolicy; ConstructionNumericKind; ConstructionOperand; ConstructionOperandKind; ConstructionOperation; ConstructionOperationKind; ConstructionOutputPresentation; ConstructionPivot; ConstructionPivotCategory; ConstructionPivotDuplicatePolicy; ConstructionPivotMissingCellPolicy; ConstructionPivotUnlistedCategoryPolicy; ConstructionRelatedContributorRule; ConstructionRelatedEligibility; ConstructionRelatedEligibilityMatch; ConstructionRelatedExpand; ConstructionRelatedField; ConstructionRelatedFieldSource; ConstructionRelatedSource; ConstructionRowValue; ConstructionRowValuePolicy; ConstructionSourceProjection; ConstructionSourceProjectionName; ConstructionStep; ConstructionUnpivot; ConstructionUnpivotInput; ConstructionUnpivotNullPolicy; ContributorCode; ContributorOperator; ContributorPredicate; ContributorQuantifier; ContributorValue; ContributorValueKind; ContributorWindowSource; DecodeWorkspace; DerivedConstruction; Digest; Document; DraftSourceContext; EmptyCollectionPolicy; ExpandedRows; ExplicitGroupRowValue; ExplicitGroupSource; ExplorerMetadata; FeatureInterpretation; FeatureInterpretationKind; FieldGroupSource; FieldPath; FieldSource; FileActions; FilterPresentation; FilterValue; FixedFilter; FrameDefinition; FrameManyPolicy; FrameZeroPolicy; GroupSource; GroupSourceKind; GroupedRows; HasMissingInputs; LegacyNilConstructionStepsDigest; LookupMatch; LookupSource; MarshalJSON; MigrateAggregateTemporalPolicy; MigrateLegacyContributors; MigrateLosslessDefaults; MissingKeyPolicy; NewFrameDefinition; NormalizePresentationOrders; Normalized; Output; OutputDependencyOrder; OwnerRecordsSource; PinnedInterpretation; PivotCategory; PivotConstruction; Population; PopulationRouteStep; PrepareWorkspaceForCompilation; ProjectionMode; ProposeStepRemoval; RecordRows; RecordsRowDefinition; RelatedSelection; RepeatedBoundary; ResolveConstructionProposal; ResolveDraftRevision; ResolveDraftSourceContext; ResolveFrameSource; ResolvePopulationMemberProposal; ResolveRowDefinitionProposal; ResolveSemanticSelectionPlan; ResolveTableShapeProposal; ResolvedConstructionChoice; ResolvedDraftSourceContext; RouteMatchMode; RouteNode; RoutePolicy; RouteRebaseChoice; RowChangeAssessment; RowChangeProposal; RowChangeRequest; RowChangeUnresolvedReference; RowDefinition; RowDefinitionKind; SemanticReadinessStatus; SemanticSelection; SemanticSelectionPlan; SemanticSelectionReadiness; SemanticSelectionResult; SharedFilterBinding; SharedFilterBindingRef; SourceWhere; StageColumn; SupportsAnchor; SupportsTimestamp; Tab; TablePresentation; TableReshape; TableScalar; TableScalarKind; TableShape; TemporalFieldChoice; TemporalOrderingSource; TemporalReductionCapabilities; UnassignedMemberPolicy; UnitNormalizationCapabilities; UnitNormalizationPolicy; UnitNormalizationPresetCapability; UnmarshalJSON; UnpivotConstruction; UnpivotInput; UpgradeDocumentToConstruction; Valid; Validate; ValidateConcreteValue; ValidateConstructionExpandedFieldSource; ValidateContributorForCatalog; ValidateForPublication; ValidatePivotCategoryKey; ValidateStructure; ValidateTableShapeOutput; ValidateWithExplicitGroups; ValueTransformationCapability; Workspace; WorkspaceOutputConsumer,internal/catalog; internal/dataframe/columntransform; internal/dataframe/lineage; internal/dataframe/unit; internal/explorer/capability; internal/fhir/schema,internal/catalog; internal/dataframe/columntransform; internal/dataframe/lineage; internal/explorer/capability; internal/fhir/schema,cmd/explorer-config-v2-convert; cmd/preview-covering-index-artifact; generated/loomapi; internal/acceptance; internal/backends/arango/internal/explorer; internal/dataframe/benchmark; internal/explorer; internal/explorer/compilation; internal/explorer/lifecycle; internal/repositoryseed; internal/server,internal/backends/postgres/internal/explorer; internal/explorer/capability,—,—,legacy=50; migration=28; compatibility=3,broad surface (23 production files); inspect cohesion,go test ./internal/explorer/authoringv2,inventory complete; semantic disposition pending +internal/explorer/capability,github.com/calypr/loom/internal/explorer/capability,capability,domain/library,Build the immutable compiler-backed capability contract exposed to Explorer Builder.,builder.go; construction.go; construction_routes.go; domain.go; population_route_choice.go; repository.go; row_choice.go; value_operations.go,builder_test.go; construction_test.go; domain_test.go; row_choice_test.go; semantic_fallback_dto_test.go; value_operations_test.go,—,—,3950,1396,110,AggregateInput; AggregateOperation; AggregateOperationCapability; AggregateRowContext; Allows; Availability; AvailabilityReadiness; AvailabilitySourceReadiness; Build; Builder; Candidate; CandidateProof; CanonicalPayload; ChartAggregation; Clone; CodedGroupChoiceSource; CompilerCallbacks; ConceptCandidate; ConstructionChoice; ConstructionChoiceDecision; ConstructionChoiceFact; ConstructionChoiceForm; ConstructionChoiceIdentity; ConstructionChoiceOption; ConstructionChoicePredicateOperator; ConstructionChoicePresentation; ConstructionChoicePreservation; ConstructionChoiceRowEffect; ConstructionChoiceShape; ConstructionChoiceSource; ConstructionChoiceSourceKind; ConstructionChoiceSupport; ConstructionCodedGroupChoice; ConstructionRelatedFieldChoice; ConstructionRelatedResourceRouteChoice; ConstructionRoutePage; ConstructionRouteSearch; ConstructionRouteStep; DecodeConstructionChoiceID; DecodePopulationRouteChoiceID; DecodeRowChoiceID; DeriveAggregateOperationCapabilities; Diagnostic; Edge; EdgeProof; Evidence; FieldChoiceSource; FieldObservation; FilterOperator; GroupFieldChoiceInput; GroupFieldChoiceSource; GroupFieldCurrentStageInput; GroupFieldRelatedOccurrenceInput; GroupFieldRelatedRouteStep; IsRepeatedCardinality; IsSupportedConstructionSourceType; IsUsable; NewBuilder; NewConstructionCodedGroupChoice; NewConstructionRelatedFieldChoice; NewConstructionRelatedResourceRouteChoiceFromAnchor; NewFieldConstructionChoice; NewFieldConstructionChoiceForRoute; NewGroupCodingIdentityChoice; NewGroupFieldConstructionChoice; NewPopulationRouteChoiceID; NewRowChoice; NewSemanticConstructionChoice; NewSemanticConstructionChoiceForRoute; NewSemanticFrameConstructionChoice; NewSnapshot; Node; NodeProof; ObservedField; ObservedRelationship; ObservedResource; Operation; PlanConstructionRoutes; Policy; PopulationRouteChoiceIdentity; ProjectionMode; ProjectionPolicy; RelatedFieldChoiceSource; RelatedResourceChoiceSource; RelationshipObservation; RepeatedBoundary; RepeatedCoordinate; Repository; ResolveRowChoiceID; ResourceObservation; RoutePolicy; RowChoice; RowChoiceCardinality; RowChoiceFacts; RowChoiceIdentity; RowChoiceKind; RowChoiceOccurrence; RowChoicePathResolver; RowChoiceShape; SemanticBindingChoiceSource; SemanticFrameChoiceSource; SemanticFrameFamily; Snapshot; SnapshotIdentity; Status; UnmarshalJSON; Usable; ValidateConstructionSourceType; ValidateSnapshot; ValidateToken,internal/dataframe/recipe,generated/loomapi; internal/catalog; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/lifecycle,cmd/preview-covering-index-artifact; internal/backends/arango/internal/explorer; internal/backends/postgres/internal/explorer; internal/explorer; internal/explorer/authoringv2; internal/explorer/compilation; internal/explorer/lifecycle; internal/server,internal/dataframe/compiler; internal/dataframe/execution; internal/explorer/capability,—,2 exported interface(s),legacy=2,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/explorer/capability,inventory complete; semantic disposition pending internal/explorer/compilation,github.com/calypr/loom/internal/explorer/compilation,compilation,domain/library,Translate validated Explorer authoring intent into native recipe and compilation artifacts.,compile.go; construction.go; derived_columns.go; indexed_projection.go; interpretation.go; row_expansion.go; semantic_compile.go; source_row.go; table_reshape.go,acceptance_observation_aql_diagnostic_test.go; command_helpers_test.go; construction_combine_test.go; construction_related_contributor_test.go; construction_test.go; correlated_test.go; derived_columns_test.go; indexed_projection_test.go; interpretation_test.go; row_expansion_test.go; semantic_compile_expand_source_test.go; source_row_test.go; table_reshape_test.go; translate_test.go; value_transformations_test.go; workspace_output_mapping_test.go; workspace_test.go,—,—,3622,4536,24,Canonical; Compile; CompileWorkspace; Error; InterpretationCandidateMatch; InterpretationCandidateResolution; InterpretationCandidateResolutionState; Match; PopulationFor; PresentationColumn; PresentationConfig; Reason; ResolveAddressableSourceRow; ResolveInterpretationCandidate; ResolveWorkspaceInterpretations; ResolvedInputs; ResolvedInputsDigest; ResolvedInterpretation; ResolvedPopulation; Result; SourceRowDescriptor; State; Unwrap; WorkspaceResult,internal/dataframe/compiler/lower; internal/dataframe/lineage; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/fhir/schema; internal/projectid,internal/backends/arango; internal/catalog; internal/dataframe/columntransform; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/lineage; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/fhir/schema,cmd/preview-covering-index-artifact; internal/explorer/lifecycle; internal/server,—,—,1 exported interface(s),legacy=1,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/explorer/compilation,inventory complete; semantic disposition pending -internal/explorer/lifecycle,github.com/calypr/loom/internal/explorer/lifecycle,lifecycle,domain/library,"Coordinate transport-neutral Explorer authoring, preview, publication, query, and policy operations.",artifact.go; authoring.go; cell_trace.go; column_source.go; construction_category_discovery.go; construction_choice.go; construction_choice_proposal.go; construction_coded_pivot.go; construction_group_field_choices.go; construction_group_related_field_choices.go; construction_group_source.go; construction_pivot_source.go; construction_proposal.go; construction_routes.go; construction_semantic_group.go; derive_source.go; draft_restore.go; errors.go; expand_source.go; explicit_group_resolver.go; explicit_groups.go; filter_source.go; frame_source.go; interpretation.go; interpretation_apply.go; interpretation_context.go; interpretation_from_column.go; interpretation_library.go; interpretation_preview.go; policy.go; population.go; population_cursor.go; population_member_proposal.go; population_report.go; population_routes.go; preview.go; proposal_receipt.go; publish.go; query.go; repository_publish.go; row_change.go; row_choices.go; row_comparison.go; row_lineage.go; row_proposal.go; row_value_policy.go; schema_field_candidate.go; schema_fields.go; selection.go; semantic_catalog.go; service.go; table_shape_capabilities.go; table_shape_comparison.go; table_shape_proposal.go; table_shape_proposal_composition.go; table_shape_resolution.go; table_shape_saved.go; types.go; unpivot_source.go,artifact_test.go; builder_full_ui_replay_test.go; cell_trace_test.go; construction_category_discovery_errors_test.go; construction_category_discovery_test.go; construction_category_presence_test.go; construction_choice_category_search_test.go; construction_choice_proposal_test.go; construction_choice_test.go; construction_coded_pivot_saved_source_test.go; construction_group_field_choices_test.go; construction_pivot_source_test.go; construction_proposal_test.go; construction_proposal_trace_test.go; construction_related_expand_removal_test.go; construction_routes_test.go; construction_semantic_group_test.go; construction_v9_receipt_test.go; derive_source_test.go; expand_source_test.go; explicit_group_resolver_recovery_test.go; explicit_groups_test.go; filter_source_pivot_clear_test.go; filter_source_test.go; frame_source_test.go; fs1_reverse_order_collision_test.go; interpretation_context_test.go; interpretation_from_column_test.go; interpretation_library_test.go; interpretation_preview_test.go; interpretation_test.go; population_attachment_formal_replay_test.go; population_direction_test.go; population_formal_replay_test.go; population_member_proposal_test.go; population_removal_formal_replay_test.go; population_report_test.go; publish_release_requirements_classification_test.go; repository_publish_activation_conflict_test.go; repository_publish_generation_activation_classification_test.go; repository_publish_release_cas_conflict_test.go; row_change_test.go; row_choices_test.go; row_lineage_test.go; row_proposal_test.go; row_value_policy_test.go; schema_field_candidate_test.go; schema_fields_test.go; selection_test.go; semantic_authoring_test.go; semantic_catalog_test.go; semantic_category_test.go; semantic_coded_concept_test.go; semantic_field_presentation_test.go; semantic_frame_compatibility_test.go; service_test.go; table_shape_capabilities_test.go; table_shape_comparison_evidence_test.go; table_shape_proposal_test.go; unpivot_source_test.go,—,—,25545,31520,319,AcceptedConstruction; ApplyCommands; ApplyCommandsWithConstructionPreview; ApplyCommandsWithConstructionPreviewResponse; ArtifactOpenRequest; ArtifactPublishedReader; ArtifactRequest; ArtifactResult; AssessRowChange; AssessRowChangeRequest; AssessRowChangeResult; AuthorizedCapability; BrowseFrameSourceOptions; BrowseFrameSourceOptionsRequest; BrowseFrameSourceOptionsResponse; BrowseSemanticInventory; BrowseSemanticInventoryRequest; BrowseSemanticInventoryResponse; Builder; BuilderRequest; CandidatePreviewCompleteness; CandidatePreviewCounts; CandidatePreviewState; Capability; CapabilityResolver; CategoryScanner; CellTrace; CellTraceBinding; CellTraceExecutor; CellTraceFeature; CellTraceRequest; CellTraceResult; Clone; ColumnSource; ColumnSourceRequest; ColumnSourceResponse; ColumnSourceRouteStep; CompileReceiptRequest; Config; ConfiguredColumnContext; ConfiguredColumnContextRequest; ConfiguredColumnContextResult; ConfiguredColumnReady; ConfiguredColumnResolution; ConstructionCapabilitiesRequest; ConstructionCapabilitiesResponse; ConstructionCategoryDiscoveryRequest; ConstructionCategoryDiscoveryResponse; ConstructionChoiceProposalRequest; ConstructionChoiceProposalResponse; ConstructionChoiceProposalSelection; ConstructionChoiceSearchRequest; ConstructionChoiceSearchResponse; ConstructionChoiceSearchSource; ConstructionDependencyImpact; ConstructionDependencyIssue; ConstructionDeriveSourceCapability; ConstructionDeriveSourceChoice; ConstructionDeriveSourceFacts; ConstructionDeriveSourceSelection; ConstructionDiscoveredCategory; ConstructionExpandSourceCapability; ConstructionExpandSourceChoice; ConstructionExpandSourceFacts; ConstructionExpandSourceSelection; ConstructionFilterSourceCapability; ConstructionFilterSourceChoice; ConstructionFilterSourceFacts; ConstructionFilterSourceSelection; ConstructionGroupFieldChoice; ConstructionGroupRelatedOccurrenceChoice; ConstructionGroupSourceCapability; ConstructionGroupSourceChoice; ConstructionGroupSourceSelection; ConstructionPivotSourceCapability; ConstructionPivotSourceChoice; ConstructionPivotSourceSelection; ConstructionProposalRequest; ConstructionProposalResponse; ConstructionSemanticGroupChoice; ConstructionSourceProjectionAvailability; ConstructionSourceStageCompiler; ConstructionSourceStageRequest; ConstructionUnpivotSourceCapability; ConstructionUnpivotSourceChoice; ConstructionUnpivotSourceFacts; ConstructionUnpivotSourceSelection; ConstructionWorkspaceInput; ConstructionWorkspaceInputColumn; Create; CreateExplicitGroupRevision; CreateInterpretationRevision; CreateInterpretationRevisionFromColumn; CreateInterpretationRevisionFromColumnRequest; CreateInterpretationRevisionRequest; CreateRequest; CreateSelection; Decode; DiscoverConstructionCategories; DiscoverTableShapeCategories; Encode; Error; ErrorClass; ExactExecutionMaterialization; ExactSemanticInventoryItemIdentity; Execution; ExecutionOutput; ExpandedSelection; ExplicitGroupCreateRequest; ExplicitGroupCreateResult; ExplicitGroupInput; ExplicitGroupRevisionChoice; ExplicitGroupRevisionListRequest; ExplicitGroupRevisionProof; ExplicitGroupRevisionResolveRequest; ExplicitGroupRevisionResolver; ExplicitGroupSelection; ExplicitGroupSummary; FamilySemanticInventoryItemIdentity; FieldGroupSelection; FieldPresentationAccess; FieldPresentationContext; FieldPresentationDecisionKind; FieldPresentationGroupUnderSemantic; FieldPresentationKeepRaw; FieldPresentationReady; FieldPresentationReason; FieldPresentationRequest; FieldPresentationResultKind; FieldPresentationUnavailable; FrameSourceForm; FrameSourceOption; GenerationValidator; Get; GetConstructionCapabilities; GetInterpretationRevision; GetInterpretationRevisionRequest; GetTableShapeCatalog; HMACPopulationMappingCursorCodec; InterpretationCandidatePreviewSample; InterpretationLibrarySummary; InterpretationLibraryView; InterpretationRevisionSummary; List; ListExplicitGroupRevisions; ListInterpretationLibraries; ListInterpretationLibrariesResult; ListResult; ListRowChoices; ListRowDefinitionChoices; New; NewHMACPopulationMappingCursorCodec; NewRepositoryExplicitGroupRevisionResolver; NewSchemaRowChoiceResolver; OpenArtifact; PopulationMapping; PopulationMappingBinding; PopulationMappingCounts; PopulationMappingCursor; PopulationMappingCursorCodec; PopulationMappingDiagnostic; PopulationMappingExecutor; PopulationMappingReport; PopulationMappingRequest; PopulationMappingResult; PopulationMemberRemovalProposalRequest; PopulationMemberRemovalProposalResponse; PopulationRouteChoice; PopulationRoutesRequest; PopulationRoutesResponse; PrepareArtifact; Preview; PreviewInterpretationCandidate; PreviewInterpretationCandidateRequest; PreviewInterpretationCandidateResult; PreviewRequest; PreviewResult; PreviewSinkFactory; ProposeConstruction; ProposeConstructionChoice; ProposePopulationMemberRemoval; ProposeRowDefinition; ProposeTableShape; Publish; PublishRepository; PublishRequest; PublishResult; PublishedOutputIntent; ReadSelection; Ready; Reason; ReceiptCompiler; ReceiptMaterializer; ReceiptPreviewer; ReceiptReader; ReceiptStreamValidator; Reconcile; ReconcileRequest; ReconcileResult; ReconcileWithWorkspace; RelatedExpandChoiceSearchRequest; RelatedExpandChoiceSearchResponse; RelatedExpandContributorChoice; RelatedExpandContributorChoiceSearchRequest; RelatedExpandContributorChoiceSearchResponse; RelatedExpandRouteChoice; RelatedFieldChoice; RelatedFieldChoiceSearchRequest; RelatedFieldChoiceSearchResponse; ReleaseActivator; ReleasePreparer; RepositoryPublishRequest; RepositoryPublishResult; ResolveExplicitGroupRevision; ResolveRowChoiceID; ResolveTableShape; ResolvedPopulationRouteChoice; ResolvedRowChoice; RowChoiceKind; RowChoicePlanner; RowChoiceResolveRequest; RowChoiceResolver; RowDefinitionChoice; RowDefinitionChoicePolicy; RowDefinitionChoicesRequest; RowDefinitionChoicesResponse; RowDefinitionComparison; RowDefinitionComparisonExample; RowDefinitionComparisonStatus; RowDefinitionPreviewSummary; RowDefinitionProposal; RowDefinitionProposalRequest; RowDefinitionSelection; RowDefinitionSelectionKind; RowLineage; RowLineageExecutor; RowLineageRequest; RowLineageResult; SchemaFieldOption; SchemaFieldSearchResponse; SchemaRowChoiceResolver; SearchConstructionChoices; SearchPopulationRoutes; SearchRelatedExpandChoices; SearchRelatedExpandContributorChoices; SearchRelatedFieldChoices; SearchSchemaFields; SearchSchemaFieldsRequest; SelectionCreateResult; SelectionIntentCreateRequest; SelectionPublishedReader; SelectionReadIntentRequest; SelectionReadResult; SelectionReferenceValidator; SelectionSourceIntent; SelectionSourceResolver; SemanticInventoryItem; SemanticInventoryItemIdentityKind; ServerTiming; Service; State; StreamExactExecution; Suggestions; SuggestionsRequest; SuggestionsResult; TableShapeCatalogChoice; TableShapeCatalogColumn; TableShapeCatalogRequest; TableShapeCatalogResult; TableShapeCategoryDiscoveryRequest; TableShapeCategoryDiscoveryResult; TableShapeCategoryValue; TableShapeCellContributor; TableShapeCellTraceEvidence; TableShapeCellTraceState; TableShapeCellValue; TableShapeChangedCell; TableShapeChangedRow; TableShapeColumnChoice; TableShapeComparison; TableShapeComparisonStatus; TableShapeContributor; TableShapeDeclaredInformationLoss; TableShapeDerivedReference; TableShapeDerivedSelection; TableShapeDiscoveredCategory; TableShapeEvidenceLimitation; TableShapeExcludedRecord; TableShapeExclusionEvidence; TableShapeExclusionEvidenceStatus; TableShapeExclusionExecutor; TableShapeInformationLoss; TableShapeInformationLossStatus; TableShapeOperandSelection; TableShapePivotCategorySelection; TableShapePivotSelection; TableShapePostPivotOutput; TableShapePreviewSummary; TableShapeProposal; TableShapeProposalMode; TableShapeProposalRequest; TableShapeReshapeModeChoice; TableShapeResolutionRequest; TableShapeResolutionResult; TableShapeResolvedCategory; TableShapeSavedDerived; TableShapeSavedOperand; TableShapeSavedPivot; TableShapeSavedPivotCategory; TableShapeSavedProposalIntent; TableShapeSavedSupport; TableShapeSavedUnpivot; TableShapeSavedUnpivotInput; TableShapeSourceIdentity; TableShapeUnpivotSelection; UnmarshalJSON; Unwrap; Validate; ValidateCompilationReceipt,internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/tableshapecap; internal/fhir/schema; internal/projectid,internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/tableshapecap; internal/fhir/schema; internal/projectid,internal/server,internal/backends/arango/internal/explorer; internal/explorer/capability,github.com/google/uuid,8 exported interface(s),legacy=43; migration=2; compatibility=13,one production importer (internal/server); inspect ownership/inline potential; broad surface (59 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/explorer/lifecycle,inventory complete; semantic disposition pending +internal/explorer/lifecycle,github.com/calypr/loom/internal/explorer/lifecycle,lifecycle,domain/library,"Coordinate transport-neutral Explorer authoring, preview, publication, query, and policy operations.",artifact.go; authoring.go; cell_trace.go; column_source.go; construction_category_discovery.go; construction_choice.go; construction_choice_proposal.go; construction_coded_pivot.go; construction_group_field_choices.go; construction_group_related_field_choices.go; construction_group_source.go; construction_pivot_source.go; construction_proposal.go; construction_routes.go; construction_semantic_group.go; derive_source.go; draft_restore.go; errors.go; expand_source.go; explicit_group_resolver.go; explicit_groups.go; filter_source.go; frame_source.go; interpretation.go; interpretation_apply.go; interpretation_context.go; interpretation_from_column.go; interpretation_library.go; interpretation_preview.go; policy.go; population.go; population_cursor.go; population_member_proposal.go; population_report.go; population_routes.go; preview.go; proposal_receipt.go; publish.go; query.go; repository_publish.go; row_change.go; row_choices.go; row_comparison.go; row_lineage.go; row_proposal.go; row_value_policy.go; schema_field_candidate.go; schema_fields.go; selection.go; semantic_catalog.go; service.go; table_shape_capabilities.go; table_shape_comparison.go; table_shape_proposal.go; table_shape_proposal_composition.go; table_shape_resolution.go; table_shape_saved.go; types.go; unpivot_source.go,artifact_test.go; builder_full_ui_replay_test.go; cell_trace_test.go; construction_category_discovery_errors_test.go; construction_category_discovery_test.go; construction_category_presence_test.go; construction_choice_category_search_test.go; construction_choice_proposal_test.go; construction_choice_test.go; construction_coded_pivot_saved_source_test.go; construction_group_field_choices_test.go; construction_pivot_source_test.go; construction_proposal_test.go; construction_proposal_trace_test.go; construction_related_expand_removal_test.go; construction_routes_test.go; construction_semantic_group_test.go; construction_v9_receipt_test.go; derive_source_test.go; expand_source_test.go; explicit_group_resolver_recovery_test.go; explicit_groups_test.go; filter_source_pivot_clear_test.go; filter_source_test.go; frame_source_test.go; fs1_reverse_order_collision_test.go; interpretation_context_test.go; interpretation_from_column_test.go; interpretation_library_test.go; interpretation_preview_test.go; interpretation_test.go; population_attachment_formal_replay_test.go; population_direction_test.go; population_formal_replay_test.go; population_member_proposal_test.go; population_removal_formal_replay_test.go; population_report_test.go; publish_release_requirements_classification_test.go; repository_publish_activation_conflict_test.go; repository_publish_generation_activation_classification_test.go; repository_publish_release_cas_conflict_test.go; row_change_test.go; row_choices_test.go; row_lineage_test.go; row_proposal_test.go; row_value_policy_test.go; schema_field_candidate_test.go; schema_fields_test.go; selection_test.go; semantic_authoring_test.go; semantic_catalog_test.go; semantic_category_test.go; semantic_coded_concept_test.go; semantic_field_presentation_test.go; semantic_frame_compatibility_test.go; service_test.go; table_shape_capabilities_test.go; table_shape_comparison_evidence_test.go; table_shape_proposal_test.go; unpivot_source_test.go,—,—,25545,31520,319,AcceptedConstruction; ApplyCommands; ApplyCommandsWithConstructionPreview; ApplyCommandsWithConstructionPreviewResponse; ArtifactOpenRequest; ArtifactPublishedReader; ArtifactRequest; ArtifactResult; AssessRowChange; AssessRowChangeRequest; AssessRowChangeResult; AuthorizedCapability; BrowseFrameSourceOptions; BrowseFrameSourceOptionsRequest; BrowseFrameSourceOptionsResponse; BrowseSemanticInventory; BrowseSemanticInventoryRequest; BrowseSemanticInventoryResponse; Builder; BuilderRequest; CandidatePreviewCompleteness; CandidatePreviewCounts; CandidatePreviewState; Capability; CapabilityResolver; CategoryScanner; CellTrace; CellTraceBinding; CellTraceExecutor; CellTraceFeature; CellTraceRequest; CellTraceResult; Clone; ColumnSource; ColumnSourceRequest; ColumnSourceResponse; ColumnSourceRouteStep; CompileReceiptRequest; Config; ConfiguredColumnContext; ConfiguredColumnContextRequest; ConfiguredColumnContextResult; ConfiguredColumnReady; ConfiguredColumnResolution; ConstructionCapabilitiesRequest; ConstructionCapabilitiesResponse; ConstructionCategoryDiscoveryRequest; ConstructionCategoryDiscoveryResponse; ConstructionChoiceProposalRequest; ConstructionChoiceProposalResponse; ConstructionChoiceProposalSelection; ConstructionChoiceSearchRequest; ConstructionChoiceSearchResponse; ConstructionChoiceSearchSource; ConstructionDependencyImpact; ConstructionDependencyIssue; ConstructionDeriveSourceCapability; ConstructionDeriveSourceChoice; ConstructionDeriveSourceFacts; ConstructionDeriveSourceSelection; ConstructionDiscoveredCategory; ConstructionExpandSourceCapability; ConstructionExpandSourceChoice; ConstructionExpandSourceFacts; ConstructionExpandSourceSelection; ConstructionFilterSourceCapability; ConstructionFilterSourceChoice; ConstructionFilterSourceFacts; ConstructionFilterSourceSelection; ConstructionGroupFieldChoice; ConstructionGroupRelatedOccurrenceChoice; ConstructionGroupSourceCapability; ConstructionGroupSourceChoice; ConstructionGroupSourceSelection; ConstructionPivotSourceCapability; ConstructionPivotSourceChoice; ConstructionPivotSourceSelection; ConstructionProposalRequest; ConstructionProposalResponse; ConstructionSemanticGroupChoice; ConstructionSourceProjectionAvailability; ConstructionSourceStageCompiler; ConstructionSourceStageRequest; ConstructionUnpivotSourceCapability; ConstructionUnpivotSourceChoice; ConstructionUnpivotSourceFacts; ConstructionUnpivotSourceSelection; ConstructionWorkspaceInput; ConstructionWorkspaceInputColumn; Create; CreateExplicitGroupRevision; CreateInterpretationRevision; CreateInterpretationRevisionFromColumn; CreateInterpretationRevisionFromColumnRequest; CreateInterpretationRevisionRequest; CreateRequest; CreateSelection; Decode; DiscoverConstructionCategories; DiscoverTableShapeCategories; Encode; Error; ErrorClass; ExactExecutionMaterialization; ExactSemanticInventoryItemIdentity; Execution; ExecutionOutput; ExpandedSelection; ExplicitGroupCreateRequest; ExplicitGroupCreateResult; ExplicitGroupInput; ExplicitGroupRevisionChoice; ExplicitGroupRevisionListRequest; ExplicitGroupRevisionProof; ExplicitGroupRevisionResolveRequest; ExplicitGroupRevisionResolver; ExplicitGroupSelection; ExplicitGroupSummary; FamilySemanticInventoryItemIdentity; FieldGroupSelection; FieldPresentationAccess; FieldPresentationContext; FieldPresentationDecisionKind; FieldPresentationGroupUnderSemantic; FieldPresentationKeepRaw; FieldPresentationReady; FieldPresentationReason; FieldPresentationRequest; FieldPresentationResultKind; FieldPresentationUnavailable; FrameSourceForm; FrameSourceOption; GenerationValidator; Get; GetConstructionCapabilities; GetInterpretationRevision; GetInterpretationRevisionRequest; GetTableShapeCatalog; HMACPopulationMappingCursorCodec; InterpretationCandidatePreviewSample; InterpretationLibrarySummary; InterpretationLibraryView; InterpretationRevisionSummary; List; ListExplicitGroupRevisions; ListInterpretationLibraries; ListInterpretationLibrariesResult; ListResult; ListRowChoices; ListRowDefinitionChoices; New; NewHMACPopulationMappingCursorCodec; NewRepositoryExplicitGroupRevisionResolver; NewSchemaRowChoiceResolver; OpenArtifact; PopulationMapping; PopulationMappingBinding; PopulationMappingCounts; PopulationMappingCursor; PopulationMappingCursorCodec; PopulationMappingDiagnostic; PopulationMappingExecutor; PopulationMappingReport; PopulationMappingRequest; PopulationMappingResult; PopulationMemberRemovalProposalRequest; PopulationMemberRemovalProposalResponse; PopulationRouteChoice; PopulationRoutesRequest; PopulationRoutesResponse; PrepareArtifact; Preview; PreviewInterpretationCandidate; PreviewInterpretationCandidateRequest; PreviewInterpretationCandidateResult; PreviewRequest; PreviewResult; PreviewSinkFactory; ProposeConstruction; ProposeConstructionChoice; ProposePopulationMemberRemoval; ProposeRowDefinition; ProposeTableShape; Publish; PublishRepository; PublishRequest; PublishResult; PublishedOutputIntent; ReadSelection; Ready; Reason; ReceiptCompiler; ReceiptMaterializer; ReceiptPreviewer; ReceiptReader; ReceiptStreamValidator; Reconcile; ReconcileRequest; ReconcileResult; ReconcileWithWorkspace; RelatedExpandChoiceSearchRequest; RelatedExpandChoiceSearchResponse; RelatedExpandContributorChoice; RelatedExpandContributorChoiceSearchRequest; RelatedExpandContributorChoiceSearchResponse; RelatedExpandRouteChoice; RelatedFieldChoice; RelatedFieldChoiceSearchRequest; RelatedFieldChoiceSearchResponse; ReleaseActivator; ReleasePreparer; RepositoryPublishRequest; RepositoryPublishResult; ResolveExplicitGroupRevision; ResolveRowChoiceID; ResolveTableShape; ResolvedPopulationRouteChoice; ResolvedRowChoice; RowChoiceKind; RowChoicePlanner; RowChoiceResolveRequest; RowChoiceResolver; RowDefinitionChoice; RowDefinitionChoicePolicy; RowDefinitionChoicesRequest; RowDefinitionChoicesResponse; RowDefinitionComparison; RowDefinitionComparisonExample; RowDefinitionComparisonStatus; RowDefinitionPreviewSummary; RowDefinitionProposal; RowDefinitionProposalRequest; RowDefinitionSelection; RowDefinitionSelectionKind; RowLineage; RowLineageExecutor; RowLineageRequest; RowLineageResult; SchemaFieldOption; SchemaFieldSearchResponse; SchemaRowChoiceResolver; SearchConstructionChoices; SearchPopulationRoutes; SearchRelatedExpandChoices; SearchRelatedExpandContributorChoices; SearchRelatedFieldChoices; SearchSchemaFields; SearchSchemaFieldsRequest; SelectionCreateResult; SelectionIntentCreateRequest; SelectionPublishedReader; SelectionReadIntentRequest; SelectionReadResult; SelectionReferenceValidator; SelectionSourceIntent; SelectionSourceResolver; SemanticInventoryItem; SemanticInventoryItemIdentityKind; ServerTiming; Service; State; StreamExactExecution; Suggestions; SuggestionsRequest; SuggestionsResult; TableShapeCatalogChoice; TableShapeCatalogColumn; TableShapeCatalogRequest; TableShapeCatalogResult; TableShapeCategoryDiscoveryRequest; TableShapeCategoryDiscoveryResult; TableShapeCategoryValue; TableShapeCellContributor; TableShapeCellTraceEvidence; TableShapeCellTraceState; TableShapeCellValue; TableShapeChangedCell; TableShapeChangedRow; TableShapeColumnChoice; TableShapeComparison; TableShapeComparisonStatus; TableShapeContributor; TableShapeDeclaredInformationLoss; TableShapeDerivedReference; TableShapeDerivedSelection; TableShapeDiscoveredCategory; TableShapeEvidenceLimitation; TableShapeExcludedRecord; TableShapeExclusionEvidence; TableShapeExclusionEvidenceStatus; TableShapeExclusionExecutor; TableShapeInformationLoss; TableShapeInformationLossStatus; TableShapeOperandSelection; TableShapePivotCategorySelection; TableShapePivotSelection; TableShapePostPivotOutput; TableShapePreviewSummary; TableShapeProposal; TableShapeProposalMode; TableShapeProposalRequest; TableShapeReshapeModeChoice; TableShapeResolutionRequest; TableShapeResolutionResult; TableShapeResolvedCategory; TableShapeSavedDerived; TableShapeSavedOperand; TableShapeSavedPivot; TableShapeSavedPivotCategory; TableShapeSavedProposalIntent; TableShapeSavedSupport; TableShapeSavedUnpivot; TableShapeSavedUnpivotInput; TableShapeSourceIdentity; TableShapeUnpivotSelection; UnmarshalJSON; Unwrap; Validate; ValidateCompilationReceipt,internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/tableshapecap; internal/fhir/schema; internal/projectid,internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/tableshapecap; internal/fhir/schema; internal/projectid,internal/server,internal/backends/arango/internal/explorer; internal/dataframe/execution; internal/explorer/capability,github.com/google/uuid,8 exported interface(s),legacy=43; migration=2; compatibility=13,one production importer (internal/server); inspect ownership/inline potential; broad surface (59 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/explorer/lifecycle,inventory complete; semantic disposition pending internal/explorer/tableshapecap,github.com/calypr/loom/internal/explorer/tableshapecap,tableshapecap,domain/library,Define immutable table-shape capability and resolution artifacts plus their create-once repository contract.,identity.go; repository.go; types.go,artifacts_test.go,—,—,1971,503,65,AvailabilityState; Binding; BooleanScalar; CanonicalContent; CatalogChoices; CatalogLookup; CatalogReceipt; CategoryProof; CategoryScanReceipt; CategoryValuesDigest; Choice; ChoiceRole; ColumnChoice; DecimalScalar; DecodeStrict; DerivedResolution; DiscoveredCategory; FindCategoryChoice; FindColumn; FindOperand; FindOperator; FindPolicy; FrozenCategory; IntegerScalar; IntentKind; LogicalType; Matches; MissingScalar; NamedOutput; NewCatalogReceipt; NewCategoryScanReceipt; NewPivotDerivedOperandChoiceID; NewResolutionReceipt; NullScalar; OperandChoice; OperandKind; OperandRef; OperatorChoice; PivotDerivedOperand; PivotResolution; PolicyChoice; ProposalIntent; PublicColumn; RemoveIntent; Repository; ResolutionKind; ResolutionReceipt; ResolvedOperand; ResolvedOperandKind; ResolvedUnpivotInput; RoleAvailability; SavedShapeSummary; Scalar; ScalarKind; SetIntent; StringScalar; TypeFact; UnmarshalJSON; UnpivotResolution; Valid; Validate; ValidateAgainstCatalog; ValidateCategoryScanAgainstCatalog; ValidateResolutionAgainstCatalog; ValidateResolutionAgainstCategoryScan,internal/projectid,—,internal/backends/arango/internal/explorer; internal/backends/postgres/internal/explorer; internal/explorer/lifecycle; internal/server,—,—,1 exported interface(s),—,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/explorer/tableshapecap,inventory complete; semantic disposition pending internal/fhir/schema,github.com/calypr/loom/internal/fhir/schema,schema,domain/library,"Add handwritten selector, traversal, pivot, and compiler semantics to generated FHIR metadata.",compiler_semantics.go; correlated.go; fields.go; generated_metadata.go; identifier.go; index.go; pivots.go; quantity.go; row_resolution.go; selectors.go; structure.go; terminal_metadata.go; traversals.go,compiler_semantics_test.go; correlated_selection_test.go; correlated_test.go; fields_test.go; identifier_test.go; index_test.go; pivots_test.go; root_resources_test.go; row_resolution_test.go; selectors_test.go; structure_test.go; terminal_metadata_test.go; traversals_test.go,—,—,2446,1341,86,ArrayElementType; ArrayElements; At; BindingStrength; CanonicalPath; CanonicalResourceType; CanonicalizePath; ChoiceValueSelectorOptions; CompilerTraversal; ConcreteResourceType; ContainsFilter; CorrelatedBinding; CorrelatedBindingSpec; CorrelatedKey; CorrelatedSelectionScope; CorrelatedValueScope; DefaultPivotSpec; Definition; DefinitionExists; DefinitionName; DefinitionStructure; DefinitionView; Definitions; Element; ElementListView; ElementView; Elements; ExtensionBinding; ExtensionBindingSpec; FieldKind; FieldPredicateSpec; FieldSelectorSpec; FieldSelectorSpecFromPath; FieldSemantics; FieldSpec; FieldsForResource; GeneratedIndex; HasRepeatedCodingPath; HasResource; IdentifierBinding; IdentifierBindingSpec; Index; ItemJSONType; JSONType; Len; LookupField; LookupTraversal; Name; NewIndex; ParseSelector; PivotSpec; PrimitiveKind; QuantityIdentityPaths; ReadDefinition; ReferencedType; RepeatedCodingPaths; RequiredElements; ResolveCompilerTraversal; ResolveFieldSemantics; ResolvePath; ResolveRowPath; ResolveTerminalScalarMetadata; ResolvedPath; ResolvesToCodeableConcept; ResourceSpec; ResourceType; ResourceTypes; RowCardinality; RowPathFacts; RowPathShape; Selector; SelectorExpression; SelectorFromField; SelectorStep; Snapshot; Structure; StructureMember; TerminalScalarMetadata; TraversalDirection; TraversalMultiplicity; TraversalSpec; ValidateCorrelatedBinding; ValidateCorrelatedKey; ValidateExtensionBinding; ValidateIdentifierBinding; ValidatePivotSelectors,generated/fhirschema,—,cmd/arango-fhir-proto; generated/fhir; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/backends/arango/internal/catalog; internal/backends/postgres/internal/catalog; internal/catalog; internal/dataframe/compiler/capability; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/recipe; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/explorer/authoringv2; internal/explorer/compilation; internal/explorer/lifecycle; internal/fhir/semantic; internal/ingest; internal/server; scripts,conformance/compiler; internal/dataframe/compiler,—,—,legacy=1; compatibility=2,broad surface (13 production files); inspect cohesion,go test ./internal/fhir/schema,inventory complete; semantic disposition pending internal/fhir/semantic,github.com/calypr/loom/internal/fhir/semantic,semantic,domain/library,Walk generated FHIR definition indexes to enumerate typed referenced datatype occurrences.,member_walk.go; walk.go,member_walk_resource_dispatch_test.go; member_walk_test.go; walk_test.go,—,—,829,949,17,Error; Len; Less; MemberFact; Occurrence; Pop; Push; RepeatedBoundary; ResourceBoundary; Swap; Walk; WalkDiagnostic; WalkDiagnosticCode; WalkLimitError; WalkLimitKind; WalkMembers; WalkMembersWithResourceTypes,internal/fhir/schema,internal/fhir/schema,internal/catalog,—,—,—,—,one production importer (internal/catalog); inspect ownership/inline potential,go test ./internal/fhir/semantic,inventory complete; semantic disposition pending -internal/ingest,github.com/calypr/loom/internal/ingest,ingest,domain/library,"Preflight and load FHIR NDJSON into generation-scoped Arango vertices, edges, and catalog facts, and commit built resource-row and built-edge-occurrence batches to the PostgreSQL source store through the ledgered batch writer with scope verification, canonical payload encoding, and payload-backed path/ordinal recovery for repeated references.",backend.go; doc.go; documents.go; files.go; generated_load.go; generation_identity.go; generation_load.go; generation_orchestration.go; generation_workers.go; load_options.go; postgres_writer.go; preflight.go; progress.go; resource_load.go; row_builder.go; semantic_inventory_backfill.go; source_schema.go,backend_test.go; basic_observation_fixture_validation_test.go; bench_test.go; documents_test.go; file_pipeline_test.go; files_test.go; generated_load_test.go; generation_cleanup_test.go; generation_failed_cleanup_e2e_test.go; generation_identity_test.go; generation_load_postgres_durability_test.go; generation_load_postgres_test.go; generation_load_test.go; integration_test.go; keycollide_generated_e2e_test.go; load_options_test.go; postgres_writer_test.go; preflight_test.go; related_medication_fixture_test.go; resource_load_test.go; row_builder_test.go; semantic_inventory_backfill_integration_test.go; semantic_inventory_backfill_test.go; semantic_inventory_test.go; source_schema_test.go; viewer_row_file_download_fixture_test.go,—,—,5649,10567,63,ActivatePostgresGeneration; ActivationOutcomeError; BackfillSemanticInventory; Build; Complete; ConnectionOptions; DiscoverNDJSON; EdgeDocument; EdgeFromGrip; Error; EventSink; GeneratedRowBuilder; GenerationCatalogWriters; GenerationFieldProfileWriter; GenerationInventoryRebuilder; GenerationLoadIncompleteError; GenerationRelationshipDiscoverer; GenerationSemanticContributor; GenericRowBuilder; IngestionMode; Load; LoadIncompleteError; LoadOptions; LoadPostgresGeneration; LoadSummary; NewGeneratedRowBuilder; NewGenericRowBuilder; NewPostgresEdgeWriter; NewPostgresResourceWriter; OpenLineScanner; PGEdgeBatchScope; PGEdgeRowIdentityError; PGEdgeWriteResult; PGEdgeWriteScopeError; PGLoadConfig; PGResourceBatchScope; PGResourceRowIdentityError; PGResourceWriteResult; PGResourceWriteScopeError; PGSourceOptions; PostgresEdgeWriter; PostgresResourceWriter; PreflightError; PreflightFiles; PreflightIssue; PreflightReport; PreflightResource; ResourceTypeFromPath; RowBuilder; RowErrorSample; SanitizeKey; SecondsSince; SemanticInventoryBackfillCollectionResult; SemanticInventoryBackfillOptions; SemanticInventoryBackfillProgress; SemanticInventoryBackfillReport; SplitCollectionID; Unwrap; Valid; VertexDocument; VertexFromFHIRWithExtra; WriteEdgeBatch; WriteResourceBatch,generated/fhir; internal/backends/arango; internal/backends/postgres; internal/catalog; internal/dataset; internal/fhir/schema; internal/source,generated/fhir; internal/backends/arango; internal/backends/postgres; internal/catalog; internal/dataset; internal/fhir/schema; internal/source,cmd/arango-fhir-proto; cmd/loom-data; internal/api/bulk/load; internal/server,internal/backends/postgres/internal/catalog,github.com/bmeg/jsonschema/v6; github.com/bmeg/jsonschemagraph/graph; github.com/bmeg/jsonschemagraph/model; github.com/bmeg/jsonschemagraph/util; github.com/bytedance/sonic,5 exported interface(s),legacy=5; migration=9; compatibility=1,broad surface (17 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/ingest,inventory complete; semantic disposition pending +internal/ingest,github.com/calypr/loom/internal/ingest,ingest,domain/library,"Preflight and load FHIR NDJSON into generation-scoped Arango vertices, edges, and catalog facts, and commit built resource-row and built-edge-occurrence batches to the PostgreSQL source store through the ledgered batch writer with scope verification, canonical payload encoding, and payload-backed path/ordinal recovery for repeated references.",backend.go; doc.go; documents.go; files.go; generated_load.go; generation_identity.go; generation_load.go; generation_orchestration.go; generation_workers.go; load_options.go; postgres_writer.go; preflight.go; progress.go; resource_load.go; row_builder.go; semantic_inventory_backfill.go; source_schema.go,backend_test.go; basic_observation_fixture_validation_test.go; bench_test.go; documents_test.go; file_pipeline_test.go; files_test.go; generated_load_test.go; generation_cleanup_test.go; generation_failed_cleanup_e2e_test.go; generation_identity_test.go; generation_load_postgres_durability_test.go; generation_load_postgres_test.go; generation_load_test.go; integration_test.go; keycollide_generated_e2e_test.go; load_options_test.go; postgres_writer_test.go; preflight_test.go; related_medication_fixture_test.go; resource_load_test.go; row_builder_test.go; semantic_inventory_backfill_integration_test.go; semantic_inventory_backfill_test.go; semantic_inventory_test.go; source_schema_test.go; viewer_row_file_download_fixture_test.go,—,—,5649,10567,63,ActivatePostgresGeneration; ActivationOutcomeError; BackfillSemanticInventory; Build; Complete; ConnectionOptions; DiscoverNDJSON; EdgeDocument; EdgeFromGrip; Error; EventSink; GeneratedRowBuilder; GenerationCatalogWriters; GenerationFieldProfileWriter; GenerationInventoryRebuilder; GenerationLoadIncompleteError; GenerationRelationshipDiscoverer; GenerationSemanticContributor; GenericRowBuilder; IngestionMode; Load; LoadIncompleteError; LoadOptions; LoadPostgresGeneration; LoadSummary; NewGeneratedRowBuilder; NewGenericRowBuilder; NewPostgresEdgeWriter; NewPostgresResourceWriter; OpenLineScanner; PGEdgeBatchScope; PGEdgeRowIdentityError; PGEdgeWriteResult; PGEdgeWriteScopeError; PGLoadConfig; PGResourceBatchScope; PGResourceRowIdentityError; PGResourceWriteResult; PGResourceWriteScopeError; PGSourceOptions; PostgresEdgeWriter; PostgresResourceWriter; PreflightError; PreflightFiles; PreflightIssue; PreflightReport; PreflightResource; ResourceTypeFromPath; RowBuilder; RowErrorSample; SanitizeKey; SecondsSince; SemanticInventoryBackfillCollectionResult; SemanticInventoryBackfillOptions; SemanticInventoryBackfillProgress; SemanticInventoryBackfillReport; SplitCollectionID; Unwrap; Valid; VertexDocument; VertexFromFHIRWithExtra; WriteEdgeBatch; WriteResourceBatch,generated/fhir; internal/backends/arango; internal/backends/postgres; internal/catalog; internal/dataset; internal/fhir/schema; internal/source,generated/fhir; internal/backends/arango; internal/backends/postgres; internal/catalog; internal/dataset; internal/fhir/schema; internal/source,cmd/arango-fhir-proto; cmd/loom-data; internal/api/bulk/load; internal/server,internal/api/graphql/graph/query; internal/backends/postgres/internal/catalog,github.com/bmeg/jsonschema/v6; github.com/bmeg/jsonschemagraph/graph; github.com/bmeg/jsonschemagraph/model; github.com/bmeg/jsonschemagraph/util; github.com/bytedance/sonic,5 exported interface(s),legacy=5; migration=9; compatibility=1,broad surface (17 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/ingest,inventory complete; semantic disposition pending internal/projectid,github.com/calypr/loom/internal/projectid,projectid,domain/library,Define canonical and legacy-compatible public project identity normalization.,projectid.go,projectid_test.go,—,—,51,18,2,Canonical; Legacy,—,—,cmd/loom-repository; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/backends/arango/internal/explorer; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/published; internal/explorer; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/repositoryseed; internal/server,internal/dataframe/compiler,—,—,legacy=5; compatibility=1,small shared semantic contract; compare direct callers before inlining,go test ./internal/projectid,inventory complete; semantic disposition pending internal/repositoryseed,github.com/calypr/loom/internal/repositoryseed,repositoryseed,domain/library,"Validate repository metadata and configuration, produce a source manifest, and refuse unmaterialized inputs before launch.",preflight.go,preflight_test.go,—,—,281,105,6,Error; File; Manifest; Options; Prepare; UnmaterializedError,internal/explorer/authoringv2; internal/projectid,—,cmd/loom-repository,—,—,—,—,one production importer (cmd/loom-repository); inspect ownership/inline potential,go test ./internal/repositoryseed,inventory complete; semantic disposition pending -internal/server,github.com/calypr/loom/internal/server,server,domain/library,"Compose storage, domain services, generated OpenAPI handlers, GraphQL adapters, and HTTP routes.",artifact_dev.go; auth_wiring.go; construction_inputs_catalog.go; explain_wiring.go; explorer_authoring_http.go; explorer_capability.go; explorer_combine_output_metadata.go; explorer_config_route.go; explorer_lifecycle_helpers.go; explorer_lifecycle_wire.go; explorer_output_reconcile.go; explorer_preview_response.go; explorer_preview_security.go; explorer_publication.go; explorer_receipt_contract.go; explorer_receipt_validation.go; local_workspace_writeback.go; openapi_data_routes.go; openapi_explorer_artifacts.go; openapi_explorer_authoring_direct.go; openapi_explorer_construction.go; openapi_explorer_construction_choice_proposal.go; openapi_explorer_construction_inputs.go; openapi_explorer_construction_proposal.go; openapi_explorer_conversion.go; openapi_explorer_direct.go; openapi_explorer_explicit_groups.go; openapi_explorer_population_member_proposal.go; openapi_explorer_routes.go; openapi_explorer_row_definition.go; openapi_explorer_selections.go; openapi_explorer_table_shape.go; openapi_frame_source_options.go; openapi_graphql_routes.go; openapi_recipe_execution.go; openapi_routes.go; openapi_semantic_inventory.go; options.go; private_artifact_reconciler.go; receipt_preview_diagnostic.go; recipe_authorization.go; recipe_combine_resolution.go; recipe_discovery.go; recipe_publication.go; release_verifier.go; routes.go; server.go,accepted_construction_contract_test.go; append_workspace_rows_integration_test.go; artifact_reconciler_wiring_test.go; auth_wiring_trusted_issuer_test.go; backend_error_boundary_contract_test.go; case008_row_definition_preview_route_test.go; command_helpers_test.go; config_test.go; construction_category_discovery_openapi_error_test.go; construction_inputs_catalog_test.go; construction_proposal_zero_column_test.go; core_authoring_protocol_arango_conformance_test.go; core_authoring_protocol_formal_replay_test.go; core_authoring_protocol_receipts_test.go; core_authoring_protocol_restore_test.go; core_authoring_protocol_rowschema_test.go; core_authoring_protocol_scoped_compile_test.go; core_authoring_protocol_test.go; dataframe_query_error_test.go; derived_policy_rows_integration_test.go; explain_wiring_pg18_test.go; explain_wiring_preview_test.go; explorer_capability_test.go; explorer_coded_value_capability_test.go; explorer_concept_evidence_test.go; explorer_config_route_test.go; explorer_diagnostic_logging_test.go; explorer_output_reconcile_derive_source_test.go; explorer_output_reconcile_source_child_test.go; explorer_output_reconcile_unpivot_source_test.go; explorer_preview_response_test.go; explorer_preview_security_test.go; explorer_receipt_contract_error_test.go; explorer_receipt_contract_test.go; explorer_reconcile_scope_test.go; explorer_row_change_route_test.go; explorer_state_route_test.go; fs1_signed_slot_collision_test.go; http_test_helpers_test.go; local_workspace_writeback_test.go; openapi_column_id_contract_test.go; openapi_explorer_artifacts_download_test.go; openapi_explorer_authoring_write_fault_test.go; openapi_explorer_cell_trace_test.go; openapi_explorer_commands_notfound_test.go; openapi_explorer_construction_apply_preview_test.go; openapi_explorer_construction_derive_source_test.go; openapi_explorer_construction_expand_source_test.go; openapi_explorer_construction_filter_source_test.go; openapi_explorer_construction_inputs_test.go; openapi_explorer_construction_proposal_test.go; openapi_explorer_construction_unpivot_source_test.go; openapi_explorer_conversion_test.go; openapi_explorer_direct_test.go; openapi_explorer_draft_restore_test.go; openapi_explorer_legacy_details_control_test.go; openapi_explorer_population_mapping_construction_test.go; openapi_explorer_population_member_proposal_test.go; openapi_explorer_restricted_starting_collection_integration_test.go; openapi_explorer_route_roundtrip_test.go; openapi_explorer_route_rows_arango_test.go; openapi_explorer_row_definition_test.go; openapi_explorer_write_scope_test.go; openapi_field_presentation_fixture_test.go; openapi_graphql_status_test.go; openapi_graphql_transport_review_test.go; openapi_interpretation_routes_test.go; openapi_recipe_execution_test.go; openapi_route_inventory_test.go; openapi_routes_test.go; openapi_semantic_inventory_test.go; openapi_status_coverage_test.go; openapi_test_helpers_test.go; options_schemas_test.go; patient_one_construction_choice_route_test.go; population_member_receipt_test.go; postgres_refusal_contract_test.go; preview_postgres_wiring_pg18_test.go; private_artifact_reconciler_test.go; publication_rows_integration_test.go; publication_viewer_agreement_test.go; receipt_preview_diagnostic_test.go; recipe_authorization_test.go; recipe_blank_project_test.go; recipe_combine_resolution_test.go; recipe_discovery_test.go; recipe_execution_etl_failure_fields_e2e_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_header_probe_test.go; recipe_execution_status_twin_e2e_test.go; recipe_execution_twin_authz_repair_test.go; recipe_project_keying_test.go; recipe_publication_test.go; release_verifier_fault_mapping_test.go; release_verifier_test.go; repository_activation_conflict_dev_test.go; routes_test.go; row_value_policy_stream_test.go; wiring_test.go,—,—,12476,33256,92,ActivateDatasetGeneration; ApplyExplorerBuilderCommands; AssessExplorerRowChange; AuthConfig; AuthorizeRead; AuthorizeWrite; BasicAuthConfig; BrowseExplorerFrameSourceOptions; BrowseExplorerSemanticInventory; CalyprAuthConfig; CheckExplorerPopulationMapping; ClickHouseConfig; Config; CreateDatasetGeneration; CreateExplorer; CreateExplorerExplicitGroupRevision; CreateExplorerSelection; CreateInterpretationRevision; CreateInterpretationRevisionFromColumn; DataframerConfig; DefaultConfig; DiscoverExplorerConstructionCategories; DiscoverExplorerTableShapeCategories; DownloadExplorerArtifact; Error; ExactExecutionMaterialization; ExecuteDataframeGraphQL; ExecuteGraphQL; Finish; GetApolloSandbox; GetDatasetGenerationStatus; GetExplorer; GetExplorerAuthoringCapability; GetExplorerBuilder; GetExplorerColumnSource; GetExplorerConstructionCapabilities; GetExplorerConstructionInputs; GetExplorerSelection; GetExplorerTableShapeCapabilities; GetGraphQLPlayground; GetHealth; GetInterpretationRevision; GetLiveness; GetReadiness; GetRecipeExecution; HTTPRoutes; ListExplorerRowDefinitionChoices; ListExplorers; ListInterpretationLibraries; LoadConfig; PostgreSQLConfig; PrepareExplorerArtifact; PreviewCollectionScanEvidenceFromExplain; PreviewExplorer; PreviewInterpretationCandidate; ProbeCandidate; ProbeEdge; ProbeNode; ProposeExplorerConstruction; ProposeExplorerConstructionChoice; ProposeExplorerPopulationMemberRemoval; ProposeExplorerRowDefinition; ProposeExplorerTableShape; PublishExplorer; PublishRepositoryExplorerConfig; ReconcileExplorerBuilder; Resolve; ResolveConfiguredColumnContext; ResolveExplorerTableShape; ResolveForCompilation; ResolveForExecution; ResolveToken; Run; RunWithConfig; SearchExplorerCandidates; SearchExplorerConstructionChoices; SearchExplorerPopulationRoutes; SearchExplorerRelatedExpandChoices; SearchExplorerRelatedExpandContributors; SearchExplorerRelatedFieldChoices; SearchExplorerSchemaFields; ServerConfig; StreamExactExport; String; TraceExplorerCell; TraceExplorerRowLineage; Unwrap; Validate; VerifyPublication; Visit; VisitProposeExplorerTableShapeResponse; Write,generated/loomapi; internal/api/authpolicy; internal/api/bulk/load; internal/api/columncapabilities; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/artifactfs; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/fhir/schema; internal/ingest; internal/projectid; internal/store/clickhouse,generated/graphql/graph/model; generated/loomapi; internal/api/bulk/load; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/fhir/schema; internal/ingest; internal/projectid; internal/source; internal/store/clickhouse,.; cmd/arango-fhir-server,internal/backends/postgres,github.com/gofiber/fiber/v3; go.yaml.in/yaml/v3; golang.org/x/sync/singleflight,—,legacy=41; migration=9; compatibility=3,broad surface (47 production files); inspect cohesion,go test ./internal/server,inventory complete; semantic disposition pending -internal/source,github.com/calypr/loom/internal/source,source,domain/library,"Define the backend-neutral logical source identity, payload, and authorization-scope contracts shared by ingestion and store read/write paths, free of any storage or driver detail.",doc.go; resource.go,ownership_contract_test.go,—,—,80,403,2,Resource; ResourceScope,—,—,internal/backends/postgres/internal/store; internal/ingest,internal/backends/postgres; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/server,—,—,legacy=4,small shared semantic contract; compare direct callers before inlining,go test ./internal/source,inventory complete; semantic disposition pending +internal/server,github.com/calypr/loom/internal/server,server,domain/library,"Compose storage, domain services, generated OpenAPI handlers, GraphQL adapters, and HTTP routes.",artifact_dev.go; auth_wiring.go; compose.go; construction_inputs_catalog.go; explain_wiring.go; explorer_authoring_http.go; explorer_capability.go; explorer_combine_output_metadata.go; explorer_config_route.go; explorer_lifecycle_helpers.go; explorer_lifecycle_wire.go; explorer_output_reconcile.go; explorer_preview_response.go; explorer_preview_security.go; explorer_publication.go; explorer_receipt_contract.go; explorer_receipt_validation.go; local_workspace_writeback.go; openapi_data_routes.go; openapi_explorer_artifacts.go; openapi_explorer_authoring_direct.go; openapi_explorer_construction.go; openapi_explorer_construction_choice_proposal.go; openapi_explorer_construction_inputs.go; openapi_explorer_construction_proposal.go; openapi_explorer_conversion.go; openapi_explorer_direct.go; openapi_explorer_explicit_groups.go; openapi_explorer_population_member_proposal.go; openapi_explorer_routes.go; openapi_explorer_row_definition.go; openapi_explorer_selections.go; openapi_explorer_table_shape.go; openapi_frame_source_options.go; openapi_graphql_routes.go; openapi_recipe_execution.go; openapi_routes.go; openapi_semantic_inventory.go; options.go; private_artifact_reconciler.go; receipt_preview_diagnostic.go; recipe_authorization.go; recipe_combine_resolution.go; recipe_discovery.go; recipe_publication.go; release_verifier.go; routes.go; row_sources.go; runtime.go; server.go,accepted_construction_contract_test.go; append_workspace_rows_integration_test.go; artifact_reconciler_wiring_test.go; auth_wiring_trusted_issuer_test.go; backend_composition_contract_test.go; backend_error_boundary_contract_test.go; backend_neutral_diagnostics_live_test.go; case008_row_definition_preview_route_test.go; command_helpers_test.go; config_test.go; construction_category_discovery_openapi_error_test.go; construction_inputs_catalog_test.go; construction_proposal_zero_column_test.go; core_authoring_protocol_arango_conformance_test.go; core_authoring_protocol_formal_replay_test.go; core_authoring_protocol_receipts_test.go; core_authoring_protocol_restore_test.go; core_authoring_protocol_rowschema_test.go; core_authoring_protocol_scoped_compile_test.go; core_authoring_protocol_test.go; dataframe_query_error_test.go; derived_policy_rows_integration_test.go; explain_wiring_pg18_test.go; explain_wiring_preview_test.go; explorer_capability_test.go; explorer_coded_value_capability_test.go; explorer_concept_evidence_test.go; explorer_config_route_test.go; explorer_diagnostic_logging_test.go; explorer_output_reconcile_derive_source_test.go; explorer_output_reconcile_source_child_test.go; explorer_output_reconcile_unpivot_source_test.go; explorer_preview_response_test.go; explorer_preview_security_test.go; explorer_receipt_contract_error_test.go; explorer_receipt_contract_test.go; explorer_reconcile_scope_test.go; explorer_row_change_route_test.go; explorer_state_route_test.go; fs1_signed_slot_collision_test.go; health_readiness_wiring_test.go; http_test_helpers_test.go; local_workspace_writeback_test.go; openapi_column_id_contract_test.go; openapi_explorer_artifacts_download_test.go; openapi_explorer_authoring_write_fault_test.go; openapi_explorer_cell_trace_test.go; openapi_explorer_commands_notfound_test.go; openapi_explorer_construction_apply_preview_test.go; openapi_explorer_construction_derive_source_test.go; openapi_explorer_construction_expand_source_test.go; openapi_explorer_construction_filter_source_test.go; openapi_explorer_construction_inputs_test.go; openapi_explorer_construction_proposal_test.go; openapi_explorer_construction_unpivot_source_test.go; openapi_explorer_conversion_test.go; openapi_explorer_direct_test.go; openapi_explorer_draft_restore_test.go; openapi_explorer_legacy_details_control_test.go; openapi_explorer_population_mapping_construction_test.go; openapi_explorer_population_member_proposal_test.go; openapi_explorer_restricted_starting_collection_integration_test.go; openapi_explorer_route_roundtrip_test.go; openapi_explorer_route_rows_arango_test.go; openapi_explorer_row_definition_test.go; openapi_explorer_write_scope_test.go; openapi_field_presentation_fixture_test.go; openapi_graphql_status_test.go; openapi_graphql_transport_review_test.go; openapi_interpretation_routes_test.go; openapi_recipe_execution_test.go; openapi_route_inventory_test.go; openapi_routes_test.go; openapi_semantic_inventory_test.go; openapi_status_coverage_test.go; openapi_test_helpers_test.go; options_schemas_test.go; patient_one_construction_choice_route_test.go; population_member_receipt_test.go; postgres_query_test.go; postgres_refusal_contract_test.go; preview_postgres_wiring_pg18_test.go; private_artifact_reconciler_test.go; publication_rows_integration_test.go; publication_viewer_agreement_test.go; receipt_preview_diagnostic_test.go; recipe_authorization_test.go; recipe_blank_project_test.go; recipe_combine_resolution_test.go; recipe_discovery_test.go; recipe_execution_etl_failure_fields_e2e_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_header_probe_test.go; recipe_execution_status_twin_e2e_test.go; recipe_execution_twin_authz_repair_test.go; recipe_project_keying_test.go; recipe_publication_test.go; release_verifier_fault_mapping_test.go; release_verifier_test.go; repository_activation_conflict_dev_test.go; route_composition_contract_test.go; routes_test.go; row_tag_guard_contract_test.go; row_value_policy_stream_test.go; runtime_postgres_live_test.go; runtime_test.go; wiring_test.go; zz_readiness_contract_guard_test.go,—,—,14261,37352,103,Activate; ActivateDatasetGeneration; ActivateRepositoryGeneration; ApplyExplorerBuilderCommands; AssessExplorerRowChange; AuthConfig; AuthorizeRead; AuthorizeWrite; BasicAuthConfig; BrowseExplorerFrameSourceOptions; BrowseExplorerSemanticInventory; CalyprAuthConfig; CheckExplorerPopulationMapping; ClickHouseConfig; Config; CreateDatasetGeneration; CreateExplorer; CreateExplorerExplicitGroupRevision; CreateExplorerSelection; CreateInterpretationRevision; CreateInterpretationRevisionFromColumn; DataframerConfig; DefaultConfig; DiscoverExplorerConstructionCategories; DiscoverExplorerTableShapeCategories; DownloadExplorerArtifact; Error; ExactExecutionMaterialization; ExecuteDataframeGraphQL; ExecuteGraphQL; Explain; ExplainPhysical; Finish; GetApolloSandbox; GetDatasetGenerationStatus; GetExecution; GetExplorer; GetExplorerAuthoringCapability; GetExplorerBuilder; GetExplorerColumnSource; GetExplorerConstructionCapabilities; GetExplorerConstructionInputs; GetExplorerSelection; GetExplorerTableShapeCapabilities; GetGraphQLPlayground; GetHealth; GetInterpretationRevision; GetLiveness; GetPointer; GetReadiness; GetRecipeExecution; HTTPRoutes; ListExplorerRowDefinitionChoices; ListExplorers; ListInterpretationLibraries; LoadConfig; PostgreSQLConfig; PrepareExplorerArtifact; Preview; PreviewCollectionScanEvidenceFromExplain; PreviewExplorer; PreviewInterpretationCandidate; ProbeCandidate; ProbeEdge; ProbeNode; ProposeExplorerConstruction; ProposeExplorerConstructionChoice; ProposeExplorerPopulationMemberRemoval; ProposeExplorerRowDefinition; ProposeExplorerTableShape; PublishAuthoring; PublishExplorer; PublishRepositoryExplorerConfig; ReadManifest; ReconcileExplorerBuilder; Resolve; ResolveConfiguredColumnContext; ResolveExplorerTableShape; ResolveForCompilation; ResolveForExecution; ResolveToken; Run; RunGeneration; RunWithConfig; SearchExplorerCandidates; SearchExplorerConstructionChoices; SearchExplorerPopulationRoutes; SearchExplorerRelatedExpandChoices; SearchExplorerRelatedExpandContributors; SearchExplorerRelatedFieldChoices; SearchExplorerSchemaFields; ServerConfig; StreamExactExport; String; TraceExplorerCell; TraceExplorerRowLineage; Unwrap; Validate; ValidateSelectionReferences; VerifyPublication; Visit; VisitProposeExplorerTableShapeResponse; Write,generated/loomapi; internal/api/authpolicy; internal/api/bulk/load; internal/api/columncapabilities; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/artifactfs; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/fhir/schema; internal/ingest; internal/projectid; internal/store/clickhouse,generated/graphql/graph/model; generated/loomapi; internal/api/bulk/load; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/fhir/schema; internal/ingest; internal/projectid; internal/source; internal/store/clickhouse,.; cmd/arango-fhir-server,internal/backends/postgres,github.com/gofiber/fiber/v3; go.yaml.in/yaml/v3; golang.org/x/sync/singleflight,—,legacy=41; migration=11; compatibility=3,broad surface (50 production files); inspect cohesion,go test ./internal/server,inventory complete; semantic disposition pending +internal/source,github.com/calypr/loom/internal/source,source,domain/library,"Define the backend-neutral logical source identity, payload, and authorization-scope contracts shared by ingestion and store read/write paths, free of any storage or driver detail.",doc.go; resource.go,ownership_contract_test.go,—,—,80,403,2,Resource; ResourceScope,—,—,internal/backends/postgres/internal/store; internal/ingest,internal/backends/postgres; internal/dataframe/compiler; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/server,—,—,legacy=4,small shared semantic contract; compare direct callers before inlining,go test ./internal/source,inventory complete; semantic disposition pending internal/store/clickhouse,github.com/calypr/loom/internal/store/clickhouse,clickhouse,storage adapter,Provide the low-level typed ClickHouse connection and database-management boundary.,client.go,client_test.go; integration_test.go,—,—,676,712,15,AddColumn; Client; Close; Column; CreateTable; DropColumns; DropTable; EnsureDatabase; InsertRows; New; Options; Ping; QueryRowsArgs; QueryRowsArgsVisit; VerifyOutput,—,internal/dataframe/compiler/ir; internal/dataframe/compiler/render/clickhouse; internal/dataframe/published,internal/acceptance; internal/dataframe/execution/chartifact; internal/dataframe/publication/clickhouse; internal/server,internal/backends/arango/internal/chartifact; internal/backends/arango/internal/publication; internal/backends/postgres/internal/chartifact; internal/dataframe/compiler/render/clickhouse; internal/dataframe/execution; internal/dataframe/published,github.com/ClickHouse/clickhouse-go/v2; github.com/ClickHouse/clickhouse-go/v2/lib/driver,—,—,storage adapter; preserve interface and persisted-data contracts,go test ./internal/store/clickhouse,inventory complete; semantic disposition pending scripts,github.com/calypr/loom/scripts,main,domain/library,"Hold repository developer, verification, migration, and release utilities that are not part of the Go module runtime packages.",check_fhir_semantic_resource_types.go; local-preview-index-migration.go,check_fhir_semantic_resource_types_test.go; local-preview-index-migration_test.go,README.md; package-lock.json; package.json; pg-exec-03-skipset-capture.json,—,769,532,0,—,internal/fhir/schema,—,—,—,github.com/arangodb/go-driver/v2/arangodb; github.com/arangodb/go-driver/v2/arangodb/shared; github.com/arangodb/go-driver/v2/connection; github.com/arangodb/go-driver/v2/utils,build tags,legacy=8; migration=9,no in-repo production importer; verify runtime/tooling reachability; runtime/build signals present; static importer counts are insufficient for deletion,go test ./scripts,inventory complete; semantic disposition pending diff --git a/docs/PACKAGE_AUDIT.md b/docs/PACKAGE_AUDIT.md index a62863ce3..d797fa567 100644 --- a/docs/PACKAGE_AUDIT.md +++ b/docs/PACKAGE_AUDIT.md @@ -6,14 +6,14 @@ A row marked as a consolidation candidate is **not** deletion authorization. Bef ## Snapshot -- Packages: 92 -- Production Go files: 680 -- Test Go files: 801 -- Production Go lines: 540,857 -- Test Go lines: 317,374 -- Package kinds: conformance=1, domain/library=59, executable=12, generated=5, storage adapter=6, transport adapter=9 -- Non-entrypoint handwritten packages with no production importer: internal/backends/postgres/internal/chartifact; internal/ci; scripts -- Non-entrypoint handwritten packages with one production importer: internal/acceptance; internal/api/bulk/load; internal/api/graphql/graph; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/explorer; internal/backends/arango/internal/recipeexec; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/rows; internal/dataframe/compiler/render/clickhouse; internal/dataframe/compiler/render/postgres; internal/dataframe/publication/clickhouse; internal/explorer/artifactfs; internal/explorer/lifecycle; internal/fhir/semantic; internal/repositoryseed +- Packages: 94 +- Production Go files: 711 +- Test Go files: 864 +- Production Go lines: 558,568 +- Test Go lines: 351,008 +- Package kinds: conformance=1, domain/library=61, executable=12, generated=5, storage adapter=6, transport adapter=9 +- Non-entrypoint handwritten packages with no production importer: internal/ci; internal/dataframe/benchmark/cmd/benchcli; scripts +- Non-entrypoint handwritten packages with one production importer: internal/acceptance; internal/api/bulk/load; internal/api/graphql/graph; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/explorer; internal/backends/arango/internal/recipeexec; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/rows; internal/dataframe/benchmark; internal/dataframe/compiler/render/clickhouse; internal/dataframe/publication/clickhouse; internal/explorer/artifactfs; internal/explorer/lifecycle; internal/fhir/semantic; internal/repositoryseed ## How to use the table @@ -53,7 +53,7 @@ These records preserve verified package-boundary decisions for both live package | `cmd/arango-fhir-proto` | executable | Provide operator commands for FHIR loading, catalog diagnostics, generation repair, and activation. | P: main.go; T: main_test.go; resource_inventory_test.go; Other: — | 842/470 | 0: — | internal/backends/arango; internal/catalog; internal/dataset; internal/fhir/schema; internal/ingest | — | executable boundary; compare orchestration with sibling commands before combining | | `cmd/arango-fhir-server` | executable | Start the explicit Loom HTTP server binary. | P: main.go; T: —; Other: — | 7/0 | 0: — | internal/server | — | executable boundary; compare orchestration with sibling commands before combining | | `cmd/dataframe-profile` | executable | Run and compare developer-facing dataframe profiling workloads. | P: main.go; T: —; Other: — | 331/0 | 0: — | conformance/compiler; generated/graphql/graph/model; internal/api/graphql/graph/query; internal/backends/arango; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/recipe; internal/dataframe/semantic | — | executable boundary; compare orchestration with sibling commands before combining | -| `cmd/dataframe-query` | executable | Execute a checked-in GraphQL dataframe request and report response timing and diagnostics. | P: main.go; T: main_test.go; Other: — | 459/432 | 3: Error; String; Unwrap | — | — | executable boundary; compare orchestration with sibling commands before combining | +| `cmd/dataframe-query` | executable | Execute a checked-in GraphQL dataframe request and report response timing and diagnostics. | P: main.go; T: backend_neutral_diagnostics_test.go; main_test.go; Other: — | 475/508 | 3: Error; String; Unwrap | — | — | executable boundary; compare orchestration with sibling commands before combining | | `cmd/explorer-config-v2-convert` | executable | Convert legacy Explorer configuration input into the V2 authoring representation offline. | P: main.go; T: main_test.go; Other: — | 377/62 | 0: — | internal/dataframe/recipe; internal/explorer; internal/explorer/authoringv2 | — | executable boundary; compare orchestration with sibling commands before combining | | `cmd/generate` | executable | Generate FHIR models, schema metadata, validation, extraction, and GraphQL artifacts from source schemas. | P: extraction.go; graphql.go; helpers.go; main.go; metadata.go; model.go; names.go; resources.go; schema.go; validation.go; T: extraction_test.go; graphql_test.go; metadata_test.go; model_test.go; resources_test.go; root_test.go; schema_fixture_test.go; Other: fhir_helpers.go.tmpl | 1,750/522 | 10: BindingStrength; Definition; EdgeDocument; Link; Property; Result; Schema; TargetHints; TargetSchema; TemplatePointers | — | — | executable boundary; compare orchestration with sibling commands before combining; runtime/build signals present; static importer counts are insufficient for deletion | | `cmd/gqlgenfix` | executable | Post-process gqlgen output to enforce Loom-specific generated-code invariants. | P: main.go; T: main_test.go; Other: — | 173/67 | 1: MarshalJSON | — | — | executable boundary; compare orchestration with sibling commands before combining | @@ -64,8 +64,8 @@ These records preserve verified package-boundary decisions for both live package | `conformance/compiler` | conformance | Load and exercise the canonical dataframe compiler oracle corpus for conformance and performance checks. | P: types.go; T: benchmark_test.go; command_helpers_test.go; corpus_coverage_test.go; gdc_fixture_test.go; generated_semantics_coverage_test.go; physical_plan_cost_test.go; policy_ablation_test.go; run_test.go; schema_roots_test.go; types_test.go; Other: — | 119/1,279 | 4: Expected; Fixture; LoadDir; Validate | internal/dataframe/recipe | cmd/dataframe-profile | non-runtime conformance boundary | | `generated/fhir` | generated | Expose generated FHIR resource models and their extraction, validation, and GraphQL helpers. | P: extract.go; graphql.go; helpers.go; model.go; resources.go; validate.go; T: —; Other: — | 72,020/0 | 155: Address; Age; Annotation; Attachment; Availability; AvailabilityAvailableTime; AvailabilityNotAvailableTime; BodyStructure; BodyStructureIncludedStructure; BodyStructureIncludedStructureBodyLandmarkOrientation; BodyStructureIncludedStructureBodyLandmarkOrientationDistanceFromLandmark; CodeableConcept; … (+143) | internal/fhir/schema | generated/graphql/graph/executor; internal/api/graphql/graph/resolver; internal/dataframe/recipe; internal/dataframe/spec; internal/ingest | generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion | | `generated/fhirschema` | generated | Expose generated raw FHIR schema metadata tables. | P: generated.go; T: —; Other: — | 55,277/0 | 3: Definition; Property; Traversal | — | internal/fhir/schema | generated boundary; change its source/generator, not emitted code | -| `generated/graphql/graph/executor` | generated | Provide gqlgen-generated GraphQL executable schema and execution plumbing. | P: fhir_schema.generated.go; prelude.generated.go; root_.generated.go; schema.generated.go; T: —; Other: — | 155,289/0 | 328: Address; AddressResolver; Age; AgeResolver; Annotation; AnnotationResolver; Attachment; AttachmentResolver; Availability; AvailabilityAvailableTime; AvailabilityAvailableTimeResolver; AvailabilityNotAvailableTime; … (+316) | generated/fhir; generated/graphql/graph/model | internal/api/graphql/graph; internal/api/graphql/graph/resolver | generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion | -| `generated/graphql/graph/model` | generated | Provide gqlgen-generated GraphQL transport model types. | P: models.go; T: —; Other: — | 1,333/0 | 95: DataframeAggregateInput; DataframeAggregateResult; DataframeAggregationSpecInput; DataframeAggregationsInput; DataframeAggregationsResult; DataframeColumn; DataframeCompilerPlanDiagnostics; DataframeDatasetInput; DataframeFilterInput; DataframeMaterialization; DataframeMaterializationState; DataframeOptimizationDecision; … (+83) | — | cmd/dataframe-profile; generated/graphql/graph/executor; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver | generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion | +| `generated/graphql/graph/executor` | generated | Provide gqlgen-generated GraphQL executable schema and execution plumbing. | P: fhir_schema.generated.go; prelude.generated.go; root_.generated.go; schema.generated.go; T: —; Other: — | 155,331/0 | 328: Address; AddressResolver; Age; AgeResolver; Annotation; AnnotationResolver; Attachment; AttachmentResolver; Availability; AvailabilityAvailableTime; AvailabilityAvailableTimeResolver; AvailabilityNotAvailableTime; … (+316) | generated/fhir; generated/graphql/graph/model | internal/api/graphql/graph; internal/api/graphql/graph/resolver | generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion | +| `generated/graphql/graph/model` | generated | Provide gqlgen-generated GraphQL transport model types. | P: models.go; T: —; Other: — | 1,334/0 | 95: DataframeAggregateInput; DataframeAggregateResult; DataframeAggregationSpecInput; DataframeAggregationsInput; DataframeAggregationsResult; DataframeColumn; DataframeCompilerPlanDiagnostics; DataframeDatasetInput; DataframeFilterInput; DataframeMaterialization; DataframeMaterializationState; DataframeOptimizationDecision; … (+83) | — | cmd/dataframe-profile; generated/graphql/graph/executor; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver | generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion | | `generated/loomapi` | generated | Provide OpenAPI-generated HTTP request, response, and strict-server contracts. | P: api.gen.go; strict_json.go; T: construction_json_test.go; correlated_json_test.go; request_boundary_test.go; selection_json_test.go; strict_json_test.go; Other: — | 23,408/512 | 1620: APIVersion; AcceptedConstruction; AcceptedConstructionReceipt; Action; ActionColumn; ActivateDatasetGeneration; ActivateDatasetGeneration200JSONResponse; ActivateDatasetGeneration400JSONResponse; ActivateDatasetGeneration401JSONResponse; ActivateDatasetGeneration403JSONResponse; ActivateDatasetGeneration409JSONResponse; ActivateDatasetGeneration500JSONResponse; … (+1608) | internal/explorer/authoringv2 | internal/server | generated boundary; change its source/generator, not emitted code; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/acceptance` | domain/library | Fetch and cache the locked FHIR fixture, drive the real-data acceptance scenario, and verify its outputs and performance evidence. | P: fixture.go; performance.go; runner.go; scenario.go; T: fixture_test.go; performance_test.go; runner_locking_test.go; runner_status_test.go; runner_zero_value_test.go; scenario_test.go; Other: — | 2,283/1,570 | 41: Acquire; Close; ComparePerformance; ComparePerformanceReportFiles; ComparePerformanceRuns; ComputedDigest; Config; Connections; DefaultLimits; Fetcher; FixtureDigest; FixtureLock; … (+29) | internal/backends/arango; internal/dataframe/publication; internal/explorer/authoringv2; internal/store/clickhouse | cmd/loom-acceptance | one production importer (cmd/loom-acceptance); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/api/authpolicy` | transport adapter | Centralize authorization-sentinel classification for transport operations while preserving operation-specific concealment. | P: policy.go; T: policy_test.go; Other: — | 56/44 | 2: Classify; Operation | internal/authscope; internal/dataframe/errors | internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/server | no automatic consolidation signal; perform semantic review | @@ -73,45 +73,47 @@ These records preserve verified package-boundary decisions for both live package | `internal/api/columncapabilities` | transport adapter | Derive transport-neutral filter, sort, and aggregate capabilities from published ClickHouse column types. | P: capabilities.go; T: capabilities_test.go; Other: — | 56/23 | 2: FromClickHouse; Tuple | — | internal/api/graphql/graph/dataframe; internal/server | no automatic consolidation signal; perform semantic review | | `internal/api/graphql/graph` | transport adapter | Own GraphQL HTTP handler construction, error presentation, and generated executor binding. | P: doc.go; errors.go; handler.go; T: aggregate_http_integration_test.go; errors_test.go; handler_test.go; http_integration_test.go; Other: — | 511/944 | 8: Flush; Header; NewApolloSandboxHandler; NewHandler; NewPlaygroundHandler; RouteConfig; Write; WriteHeader | generated/graphql/graph/executor; internal/api/graphql/graph/resolver; internal/api/http; internal/dataframe/errors | internal/server | one production importer (internal/server); inspect ownership/inline potential | | `internal/api/graphql/graph/dataframe` | transport adapter | Adapt published ClickHouse dataframe discovery, row, aggregate, and export operations to GraphQL. | P: aggregates.go; export.go; model.go; rows.go; service.go; T: aggregates_test.go; export_test.go; fb22_principal_auth_path_control_test.go; filter_test.go; model_test.go; service_test.go; Other: — | 1,072/506 | 17: AggregateInput; AggregateRowsResult; AggregationsInput; AggregationsJSON; ColumnFromPhysical; ColumnModel; Config; Dataset; ExportDataframe; Model; NewService; PersistedFailure; … (+5) | generated/graphql/graph/model; internal/api/authpolicy; internal/api/columncapabilities; internal/api/http; internal/authscope; internal/dataframe/errors; internal/dataframe/publication; internal/dataframe/published; internal/projectid | internal/api/graphql/graph/resolver; internal/server | no automatic consolidation signal; perform semantic review | -| `internal/api/graphql/graph/query` | transport adapter | Resolve and authorize Arango-backed FHIR dataframe and graph query requests for GraphQL. | P: active_generation.go; doc.go; errors.go; fhir_service.go; fieldrefs.go; graph_service.go; helpers.go; input_resolution.go; introspection.go; recipe_mapping.go; recipe_resolve.go; service.go; types.go; T: active_generation_test.go; auth_scope_test.go; errors_test.go; fhir_service_test.go; fieldrefs_test.go; graph_service_test.go; introspection_test.go; project_identity_test.go; read_scope_ingress_control_chars_e2e_test.go; recipe_mapping_test.go; service_test.go; Other: — | 1,888/1,447 | 28: Config; ExplainFHIRGraph; FHIRGraphExplanation; FHIRGraphNode; FHIRGraphPageInfo; FHIRGraphPath; FHIRGraphQuery; FHIRGraphRelationship; FHIRGraphResult; FHIRGraphTraversal; FHIRListRequest; FHIRListResult; … (+16) | generated/graphql/graph/model; internal/api/authpolicy; internal/api/recipe; internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataset; internal/fhir/schema; internal/projectid | cmd/dataframe-profile; internal/api/graphql/graph/resolver; internal/server | broad surface (13 production files); inspect cohesion | -| `internal/api/graphql/graph/resolver` | transport adapter | Implement gqlgen field resolvers and map GraphQL values to query and lifecycle services. | P: dataframe_diagnostics.go; fhir_reference_loader.go; fhir_resolver_helpers.go; fhir_schema.resolvers.go; graph_resolvers.go; graphql_output_helpers.go; operation_context.go; recipe_control_graphql.go; recipe_execution_reader.go; recipe_revision_model.go; resolver.go; schema.resolvers.go; T: fhir_reference_loader_test.go; output_mapping_test.go; recipe_control_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_oracle_probe_test.go; recipe_execution_reader_authz_e2e_test.go; recipe_execution_twin_wire_parity_test.go; recipe_revision_register_test.go; Other: — | 5,459/2,334 | 362: AbatementDateTime; AbatementString; Active; Actual; ActualComparisonGroup; ActualNumber; Address; Age; AgeString; Alias; AllDay; AllowedBoolean; … (+350) | generated/fhir; generated/graphql/graph/executor; generated/graphql/graph/model; internal/api/authpolicy; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataset; internal/fhir/schema | internal/api/graphql/graph; internal/server | broad surface (12 production files); inspect cohesion; mixed handwritten/generated package (2 generated file(s)); preserve regeneration boundary; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/api/http` | transport adapter | Own shared Fiber server setup, middleware, request IDs, errors, health, and readiness behavior. | P: doc.go; errors.go; middleware.go; request_id.go; routes.go; server.go; T: backend_error_boundary_contract_test.go; errors_test.go; http_test.go; request_id_test.go; Other: — | 533/887 | 15: App; ContextWithRequestID; ErrorResponse; HTTPConfig; HTTPErrorBody; HTTPServer; Health; Liveness; Logger; MapDataframeError; MappedError; NewHTTPServer; … (+3) | internal/authscope; internal/dataframe/errors | internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/server | no automatic consolidation signal; perform semantic review | +| `internal/api/graphql/graph/query` | transport adapter | Resolve and authorize Arango-backed FHIR dataframe and graph query requests for GraphQL. | P: active_generation.go; doc.go; errors.go; fhir_service.go; fieldrefs.go; graph_service.go; helpers.go; input_resolution.go; introspection.go; recipe_mapping.go; recipe_resolve.go; service.go; types.go; T: active_generation_test.go; auth_scope_test.go; errors_test.go; fhir_service_test.go; fieldrefs_test.go; graph_service_test.go; introspection_test.go; postgres_query_test.go; project_identity_test.go; read_scope_ingress_control_chars_e2e_test.go; recipe_mapping_test.go; service_test.go; Other: — | 1,888/2,363 | 28: Config; ExplainFHIRGraph; FHIRGraphExplanation; FHIRGraphNode; FHIRGraphPageInfo; FHIRGraphPath; FHIRGraphQuery; FHIRGraphRelationship; FHIRGraphResult; FHIRGraphTraversal; FHIRListRequest; FHIRListResult; … (+16) | generated/graphql/graph/model; internal/api/authpolicy; internal/api/recipe; internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataset; internal/fhir/schema; internal/projectid | cmd/dataframe-profile; internal/api/graphql/graph/resolver; internal/server | broad surface (13 production files); inspect cohesion | +| `internal/api/graphql/graph/resolver` | transport adapter | Implement gqlgen field resolvers and map GraphQL values to query and lifecycle services. | P: dataframe_diagnostics.go; fhir_reference_loader.go; fhir_resolver_helpers.go; fhir_schema.resolvers.go; graph_resolvers.go; graphql_output_helpers.go; operation_context.go; recipe_control_graphql.go; recipe_execution_reader.go; recipe_revision_model.go; resolver.go; schema.resolvers.go; T: dataframe_diagnostics_test.go; fhir_reference_loader_test.go; output_mapping_test.go; recipe_control_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_oracle_probe_test.go; recipe_execution_reader_authz_e2e_test.go; recipe_execution_twin_wire_parity_test.go; recipe_revision_register_test.go; Other: — | 5,469/2,376 | 362: AbatementDateTime; AbatementString; Active; Actual; ActualComparisonGroup; ActualNumber; Address; Age; AgeString; Alias; AllDay; AllowedBoolean; … (+350) | generated/fhir; generated/graphql/graph/executor; generated/graphql/graph/model; internal/api/authpolicy; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataset; internal/fhir/schema | internal/api/graphql/graph; internal/server | broad surface (12 production files); inspect cohesion; mixed handwritten/generated package (2 generated file(s)); preserve regeneration boundary; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/api/http` | transport adapter | Own shared Fiber server setup, middleware, request IDs, errors, health, and readiness behavior. | P: doc.go; errors.go; middleware.go; request_id.go; routes.go; server.go; T: backend_error_boundary_contract_test.go; errors_test.go; health_readiness_test.go; http_test.go; request_id_test.go; Other: — | 642/1,145 | 15: App; ContextWithRequestID; ErrorResponse; HTTPConfig; HTTPErrorBody; HTTPServer; Health; Liveness; Logger; MapDataframeError; MappedError; NewHTTPServer; … (+3) | internal/authscope; internal/dataframe/errors | internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/server | no automatic consolidation signal; perform semantic review | | `internal/api/recipe` | transport adapter | Adapt scoped catalog field discovery to recipe schema resolution for one-shot and stored recipes. | P: discovery.go; T: discovery_auth_mode_test.go; discovery_test.go; Other: — | 110/126 | 3: FieldDiscovery; Fields; NewFieldDiscovery | internal/authscope; internal/catalog; internal/dataframe/errors; internal/dataframe/recipe/schema | internal/api/graphql/graph/query; internal/server | no automatic consolidation signal; perform semantic review | | `internal/authscope` | domain/library | Represent request principals and resolve authorized project, generation, and resource scopes. | P: authentication.go; doc.go; fence_resource_access.go; principal.go; scope_resolver.go; T: all_generations_cache_key_test.go; auth_path_control_ingress_test.go; auth_resource_path_options_conflict_test.go; authentication_test.go; covers_execution_paths_control_test.go; persisted_scope_fails_closed_test.go; probe_double_normalize_test.go; scope_cache_key_namespace_test.go; scope_test.go; Other: — | 1,175/2,104 | 36: AllowAllAuthorizer; Authenticate; Authenticator; AuthorizeProject; AuthorizeReadProject; AuthorizeWrite; Authorizer; BasicAuthenticator; BearerTokenAuthenticator; CalyprAuthenticator; Clone; ContextWithPrincipal; … (+24) | internal/catalog; internal/projectid | cmd/preview-covering-index-artifact; internal/api/authpolicy; internal/api/bulk/load; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/backends/arango/internal/explorer; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer/lifecycle; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/arango` | storage adapter | Expose the only public entry to the ArangoDB 3.12 backend: a single Open factory validates explicit config, owns exactly one lifecycle client with the required collection bootstrap policy, and returns the typed runtime carrying the catalog, dataset lifecycle, Explorer, recipe registry/revisions, and published-dataframe ports assembled from the private implementation tree, with alias re-exports for witness-side reassembly. | P: aliases.go; doc.go; open.go; T: ownership_contract_test.go; Other: — | 329/574 | 4: Close; Config; Open; Runtime | internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec; internal/backends/arango/internal/store | cmd/arango-fhir-proto; cmd/dataframe-profile; cmd/loom-data; internal/acceptance; internal/ingest; internal/server | storage adapter; preserve interface and persisted-data contracts | +| `internal/backends/arango` | storage adapter | Expose the only public entry to the ArangoDB 3.12 backend: a single Open factory validates explicit config, owns exactly one lifecycle client with the required collection bootstrap policy, and returns the typed runtime carrying the catalog, dataset lifecycle, Explorer, recipe registry/revisions, and published-dataframe ports assembled from the private implementation tree, with alias re-exports for witness-side reassembly. | P: aliases.go; doc.go; open.go; T: ownership_contract_test.go; Other: — | 336/800 | 4: Close; Config; Open; Runtime | internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec; internal/backends/arango/internal/store | cmd/arango-fhir-proto; cmd/dataframe-profile; cmd/loom-data; internal/acceptance; internal/ingest; internal/server | storage adapter; preserve interface and persisted-data contracts | | `internal/backends/arango/internal/catalog` | domain/library | Persist and retrieve catalog facts in ArangoDB. | P: codec.go; evidence.go; fields.go; relationships.go; resource_inventory.go; resources.go; semantic_coverage.go; semantic_coverage_envelope.go; semantic_inventory.go; store.go; T: all_generations_auth_path_conflict_test.go; all_generations_project_binding_integration_test.go; resource_inventory_integration_test.go; resource_inventory_unit_test.go; semantic_coded_category_integration_test.go; semantic_coverage_envelope_test.go; semantic_coverage_resource_integration_test.go; semantic_coverage_test.go; semantic_inventory_category_test.go; semantic_inventory_integration_test.go; semantic_inventory_selection_test.go; semantic_inventory_source_availability_test.go; store_test.go; Other: — | 5,267/5,466 | 54: AccumulateRelationshipCatalog; AddSource; AdvanceSemanticInventoryBackfill; AdvanceSemanticInventoryBuild; AuditRelationshipEdges; BeginSemanticCoverageBuild; BeginSemanticCoverageResource; BeginSemanticInventoryBuild; ClaimSemanticInventoryBackfill; CompleteRetainedSemanticCoverageResource; CompleteSemanticCoverageResource; CompleteSemanticInventoryBackfill; … (+42) | internal/backends/arango/internal/store; internal/catalog; internal/fhir/schema | internal/backends/arango | one production importer (internal/backends/arango); inspect ownership/inline potential | | `internal/backends/arango/internal/chartifact` | domain/library | Persist private ClickHouse artifact manifests and leases in a dedicated Arango collection. | P: catalog.go; T: catalog_arango_test.go; catalog_test.go; Other: — | 379/723 | 10: BootstrapSpec; Catalog; ClaimCleanup; Create; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update | internal/backends/arango/internal/store; internal/dataframe/execution/chartifact | internal/backends/arango | one production importer (internal/backends/arango); inspect ownership/inline potential | | `internal/backends/arango/internal/dataset` | domain/library | Persist immutable dataset generation lifecycle state in ArangoDB. | P: collections.go; doc.go; document_codec.go; manifest_lifecycle.go; release_store.go; T: generation_activation_cas_e2e_test.go; release_policy_change_e2e_test.go; revision_inflation_e2e_test.go; store_test.go; Other: — | 764/1,652 | 15: Activate; ActiveDocumentKey; BootstrapSpec; CollectionSpecs; CompareAndSwapActivateRelease; CompareAndSwapPublishRelease; CreateManifest; ManifestDocumentKey; New; ReadActiveRelease; ReadManifest; ResolveActiveManifest; … (+3) | internal/backends/arango/internal/store; internal/dataset | internal/backends/arango; internal/backends/arango/internal/explorer | no automatic consolidation signal; perform semantic review | | `internal/backends/arango/internal/explorer` | domain/library | Persist Explorer drafts, revisions, receipts, publications, capabilities, and repository state in ArangoDB. | P: activation.go; capability_snapshot_store.go; codec.go; collections.go; drafts.go; explicit_groups.go; explorers.go; interpretations.go; publication.go; receipts.go; repository.go; revisions.go; selection_references.go; selections.go; store.go; table_shape_capabilities.go; T: capability_snapshot_store_test.go; collections_test.go; cross_generation_publish_e2e_test.go; draft_conflict_test.go; draft_transaction_formal_replay_test.go; drafts_integration_test.go; drafts_test.go; explicit_group_storage_integration_test.go; explicit_groups_test.go; interpretations_test.go; receipt_store_test.go; repository_test.go; selection_references_test.go; selection_scale_integration_test.go; selection_storage_integration_test.go; selections_cleanup_e2e_test.go; store_test.go; table_shape_capabilities_test.go; Other: — | 3,366/4,552 | 56: AbortExplicitGroupRevision; AbortSelection; ActivateRepositoryGeneration; AppendExplicitGroupMemberships; AppendSelectionMembers; BeginExplicitGroupRevision; BeginSelection; BootstrapSpec; CapabilitySnapshotCollectionSpec; CapabilitySnapshotStore; CleanupExplicitGroupStaging; CleanupSelectionStaging; … (+44) | internal/authscope; internal/backends/arango/internal/dataset; internal/backends/arango/internal/publication; internal/backends/arango/internal/store; internal/dataframe/publication; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/tableshapecap; internal/projectid | internal/backends/arango | one production importer (internal/backends/arango); inspect ownership/inline potential; broad surface (16 production files); inspect cohesion | | `internal/backends/arango/internal/publication` | domain/library | Persist dataframe publication registry and bundle metadata in ArangoDB. | P: bundle_registry.go; registry.go; T: bundle_registry_test.go; dangling_binding_e2e_test.go; execution_read_pin_integration_test.go; name_latest_binding_e2e_test.go; publish_atomicity_e2e_test.go; release_dangle_reconcile_e2e_test.go; Other: — | 533/2,990 | 22: AcquireBundleLease; AcquireExecutionReadPin; BootstrapSpec; ClaimExecutionCleanup; Client; CompareAndSwapPointer; FindExecutionByKey; FindExecutionBySelector; GetExecution; GetPointer; ListExecutions; New; … (+10) | internal/backends/arango/internal/store; internal/dataframe/publication | internal/backends/arango; internal/backends/arango/internal/explorer | no automatic consolidation signal; perform semantic review | | `internal/backends/arango/internal/recipeexec` | domain/library | Persist recipe versions and execution-use records in ArangoDB. | P: registry.go; revisions.go; T: registry_test.go; revisions_test.go; revisions_user_faults_test.go; Other: — | 294/555 | 15: BootstrapSpec; Client; Get; List; LoadRecipe; LoadRecipeVersion; New; NewRevisionRegistry; RecipeVersionUsed; Register; Registry; ReplaceRecipeVersion; … (+3) | internal/backends/arango/internal/store; internal/dataframe/errors; internal/dataframe/recipe; internal/dataframe/recipe/exec | internal/backends/arango | one production importer (internal/backends/arango); inspect ownership/inline potential | -| `internal/backends/arango/internal/store` | domain/library | Provide the low-level typed ArangoDB client, query streaming, profiling, and Explain assessment boundary. | P: client.go; errors.go; explain.go; explain_assessment.go; explain_client.go; options.go; preview_index.go; profile.go; profile_client.go; types.go; T: client_query_memory_limit_test.go; client_queryrows_test.go; client_revision_test.go; errors_test.go; explain_assessment_test.go; explain_client_test.go; explain_test.go; population_profile_integration_test.go; preview_index_test.go; profile_client_test.go; profile_integration_test.go; profile_test.go; Other: — | 1,861/2,210 | 70: AQLExecutor; AssessExplainResult; BatchInserter; Bootstrap; BootstrapSpec; Client; Close; CollectionCount; CollectionExists; CollectionRevision; CollectionSpec; ConnectionOptions; … (+58) | internal/dataframe/errors | internal/backends/arango; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec | runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/postgres` | storage adapter | Expose the only public entry to the PostgreSQL 18 backend: a single Open factory validates explicit config, owns exactly one readiness-gated pool with own-prefix connection tagging, and returns the typed runtime carrying the SourceRows and PlanJSON data-plane ports over tagged compiled commands plus every control-plane port assembled from the private implementation tree. | P: aliases.go; doc.go; open.go; plan_executor.go; T: engine_postgres_command_boundary_pg18_test.go; engine_postgres_pg18_test.go; explain_postgres_test.go; open_live_test.go; ownership_contract_test.go; plan_executor_test.go; Other: — | 317/4,349 | 4: NewPlanJSONExecutor; NewSourceRows; Open; Runtime | internal/backends/postgres/control; internal/backends/postgres/internal/rows; internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/execution | cmd/loom-data; internal/ingest | storage adapter; preserve interface and persisted-data contracts | -| `internal/backends/postgres/control` | domain/library | Host the cycle-free public control plane of the PostgreSQL backend: configuration and series-assignment validation, the bare-pool and batch-vocabulary re-exports, the dataset manifest lifecycle and catalog evidence port assembly, and the structural query surface that backend-adjacent witnesses drive without importing the execution bridge. | P: config.go; errors.go; runtime.go; vocabulary.go; vocabulary_control.go; T: —; Other: — | 893/0 | 42: AllSeries; CatalogStore; Close; Config; ConnSource; Error; FieldProfileScope; GenerationAdvisoryLockKey; Health; MigrateDatasetReleaseSchema; MigrateResult; MigrateSchema; … (+30) | internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/store; internal/catalog; internal/dataset | internal/backends/postgres | one production importer (internal/backends/postgres); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/postgres/internal/catalog` | domain/library | Persist and retrieve catalog facts in PostgreSQL 18: publish-last generation-scoped resource inventory builds, bounded field-profile records with completeness and truncation metadata, leased compare-and-swap-guarded semantic inventory build state, deterministic semantic-inventory contribution rows with content-addressed replay and conflict classification, byte-order-stable keyset pages over retained resources, and authorization-scoped semantic-inventory pages and selection resolution served directly from contribution aggregates. | P: field_profile.go; relationships.go; resource_inventory.go; semantic_build.go; semantic_contribution.go; semantic_page.go; store.go; T: field_profile_test.go; relationships_pg_test.go; relationships_test.go; resource_inventory_test.go; semantic_build_test.go; semantic_contribution_test.go; semantic_page_test.go; Other: — | 4,133/10,664 | 52: AdvanceSemanticInventoryBackfill; ClaimSemanticInventoryBackfill; CompleteSemanticInventoryBackfill; DiscoverFieldEnrichment; DiscoverRelationshipObservations; DiscoverResourceInventory; EnsureSemanticInventoryEntries; Error; FailSemanticInventoryBackfill; FieldProfileStore; FieldProfileUnavailableError; FieldProfileWriteError; … (+40) | internal/backends/postgres/internal/store; internal/catalog; internal/fhir/schema | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/postgres/internal/chartifact` | domain/library | Persist private ClickHouse artifact manifests and cleanup leases in the PostgreSQL 18 control plane with CAS progress writes, bounded expired scans, and one-winner cleanup claims while physical tables stay in ClickHouse. | P: catalog.go; T: catalog_manager_test.go; catalog_test.go; Other: — | 535/1,605 | 10: Catalog; ClaimCleanup; Create; DB; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update | internal/dataframe/execution/chartifact | — | no in-repo production importer; verify runtime/tooling reachability; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/postgres/internal/dataset` | domain/library | Persist dataset generation manifests, the active-generation compare-and-swap pointer, and immutable project release candidates with the active-release compare-and-swap pointer in PostgreSQL 18. | P: commands.go; manifest_lifecycle.go; migrate.go; release_store.go; store.go; T: commands_test.go; manifest_lifecycle_pg_test.go; release_store_pg_test.go; Other: — | 1,699/2,488 | 27: ActivateGeneration; Backend; Command; CommandBackend; CompareAndSwapActivateRelease; ConnSource; CreateManifest; EnsureGenerationPrerequisites; GenerationOwnerAdapter; MigrateSchema; New; NewPostgreSQLCommand; … (+15) | internal/backends/postgres/internal/store; internal/dataset | internal/backends/postgres/control; internal/backends/postgres/internal/explorer | runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/postgres/internal/explorer` | domain/library | Persist Explorer owner records, draft saves with atomic version/digest CAS and exact preimage history, insert-once compilation receipts, immutable Explorer revisions, create-once immutable capability snapshots and table-shape capability receipts, project-scoped interpretation libraries advancing heads over immutable content-addressed revisions by transactional parent CAS, the staged selection lifecycle with project-scoped idempotency, an expiring writer owner, and insert-once membership staged in bounded idempotent batches with the digest/count finalized under the current writer fence and bounded cancellation-aware keyset membership paging, atomic publish authoring that commits a verified compilation receipt, the immutable Explorer revision, the active Explorer owner pointer, and the project release activation CAS in one PostgreSQL transaction, and staging explicit-group revision headers with ordered definition rows and insert-once group membership (one row per revision/group/reference so an overlap is several rows, bounded keyset paging, and a fenced digest/count-verified STAGING to COMPLETE transition) under guarded writer-lease, abort, and expired-cleanup lifecycle in PostgreSQL 18 with unique project/Explorer identity, typed columns, lossless JSONB payloads, DB-enforced row freezing and content-digest collision checks, guard-triggered immutability, and CAS-guarded revision lifecycle on the shared pg-store pool and migration runner. | P: capabilities.go; drafts.go; explicit_group_memberships.go; explicit_groups.go; explorers.go; interpretations.go; publication.go; receipts.go; revisions.go; selection_members.go; selections.go; T: capabilities_test.go; drafts_test.go; explicit_groups_readpath_test.go; explicit_groups_test.go; explorers_test.go; interpretations_test.go; pgstate02_fixture_test.go; pubauth_fixture_test.go; publication_golden_test.go; publication_guarddrop_test.go; publication_race_test.go; publication_retry_test.go; publication_test.go; publication_zz_test.go; receipts_test.go; revisions_test.go; selection_members_test.go; selections_test.go; Other: — | 7,405/21,659 | 59: AbortExplicitGroupRevision; AbortSelection; AppendExplicitGroupMemberships; AppendSelectionMembers; BeginExplicitGroupRevision; BeginSelection; CapabilitySnapshotStore; CleanupExplicitGroupStaging; CleanupSelectionStaging; CompleteExplicitGroupRevision; CompleteSelection; Create; … (+47) | internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/store; internal/dataframe/publication; internal/dataframe/recipe; internal/dataset; internal/explorer; internal/explorer/capability; internal/explorer/tableshapecap | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/postgres/internal/publication` | domain/library | Persist dataframe publication execution metadata, exact logical pointers, fenced bundle leases, owner-conditional execution checkpoints, read retention pins arbitrated against cleanup claims on a shared retention anchor, and page-scoped stale execution reconciliation with owner-conditional cleanup claims, plus the single-transaction publish commit of PUBLISHED state with its reader pointer, in the PostgreSQL 18 control plane while ClickHouse remains the row store. | P: leases.go; migrate.go; publish.go; read_pins.go; reconcile.go; registry.go; T: leases_test.go; publish_atomicity_test.go; read_pins_test.go; reconcile_test.go; registry_test.go; Other: — | 2,482/7,214 | 43: AcquireBundleLease; AcquireExecutionReadPin; AcquireLease; BundleCatalog; Checkpoint; CheckpointRecord; ClaimExecutionCleanup; CompareAndSwapPointer; DB; ExpireLease; FencedLeases; FindExecutionByKey; … (+31) | internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/publication; internal/dataset | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/postgres/internal/recipeexec` | domain/library | Persist immutable recipe registrations and fenced execution-occurrence usage markers in the PostgreSQL 18 control plane. | P: registry.go; T: registry_test.go; Other: — | 408/1,087 | 11: DB; LoadRecipe; LoadRecipeVersion; MigrateSchema; New; RecipeVersionUsed; RecordRecipeExecutionOccurrence; Registry; RegistryMigrations; ReplaceRecipeVersion; SaveRecipe | internal/backends/postgres/internal/store; internal/dataframe/recipe/exec | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/postgres/internal/recipes` | domain/library | Persist project recipe revision chains with CAS head tracking in the PostgreSQL 18 control plane. | P: revisions.go; T: revisions_test.go; Other: — | 389/1,072 | 8: ChainMigrations; DB; Get; List; MigrateSchema; New; Register; RevisionRegistry | internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/recipe | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/arango/internal/store` | domain/library | Provide the low-level typed ArangoDB client, query streaming, profiling, and Explain assessment boundary. | P: client.go; errors.go; explain.go; explain_assessment.go; explain_client.go; options.go; preview_index.go; profile.go; profile_client.go; types.go; T: client_query_memory_limit_test.go; client_queryrows_test.go; client_revision_test.go; errors_test.go; explain_assessment_test.go; explain_client_test.go; explain_test.go; population_profile_integration_test.go; preview_index_test.go; profile_client_test.go; profile_integration_test.go; profile_test.go; Other: — | 1,950/2,210 | 70: AQLExecutor; AssessExplainResult; BatchInserter; Bootstrap; BootstrapSpec; Client; Close; CollectionCount; CollectionExists; CollectionRevision; CollectionSpec; ConnectionOptions; … (+58) | internal/dataframe/errors | internal/backends/arango; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec | runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres` | storage adapter | Expose the only public entry to the PostgreSQL 18 backend: a single Open factory validates explicit config, owns exactly one readiness-gated pool with own-prefix connection tagging, and returns the typed runtime carrying the SourceRows and PlanJSON data-plane ports over tagged compiled commands plus every control-plane port assembled from the private implementation tree. | P: aliases.go; doc.go; open.go; plan_executor.go; T: engine_postgres_command_boundary_pg18_test.go; engine_postgres_pg18_test.go; explain_postgres_test.go; open_live_test.go; ownership_contract_test.go; plan_executor_test.go; sentinel_identity_test.go; Other: — | 354/4,406 | 4: NewPlanJSONExecutor; NewSourceRows; Open; Runtime | internal/backends/postgres/control; internal/backends/postgres/internal/rows; internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/execution | cmd/loom-data; internal/ingest; internal/server | storage adapter; preserve interface and persisted-data contracts | +| `internal/backends/postgres/control` | domain/library | Host the cycle-free public control plane of the PostgreSQL backend: configuration and series-assignment validation, the bare-pool and batch-vocabulary re-exports, the dataset manifest lifecycle and catalog evidence port assembly, and the structural query surface that backend-adjacent witnesses drive without importing the execution bridge. | P: catalog_read.go; config.go; errors.go; runtime.go; vocabulary.go; vocabulary_control.go; T: catalog_read_test.go; Other: — | 1,385/284 | 65: AllSeries; Begin; CatalogStore; Close; Commit; Config; ConnSource; DiscoverExistingAuthResourcePaths; DiscoverFields; Error; Exec; FieldProfileScope; … (+53) | internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/store; internal/catalog; internal/dataset | internal/backends/postgres; internal/dataframe/benchmark/cmd/benchcli | runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres/internal/catalog` | domain/library | Persist and retrieve catalog facts in PostgreSQL 18: publish-last generation-scoped resource inventory builds, bounded field-profile records with completeness and truncation metadata, leased compare-and-swap-guarded semantic inventory build state, deterministic semantic-inventory contribution rows with content-addressed replay and conflict classification, byte-order-stable keyset pages over retained resources, and authorization-scoped semantic-inventory pages and selection resolution served directly from contribution aggregates. | P: field_discovery.go; field_profile.go; relationships.go; resource_inventory.go; semantic_build.go; semantic_contribution.go; semantic_page.go; store.go; T: field_discovery_test.go; field_profile_test.go; relationships_pg_test.go; relationships_test.go; resource_inventory_test.go; semantic_build_test.go; semantic_contribution_test.go; semantic_page_test.go; Other: — | 4,340/11,088 | 54: AdvanceSemanticInventoryBackfill; ClaimSemanticInventoryBackfill; CompleteSemanticInventoryBackfill; DiscoverExistingAuthResourcePaths; DiscoverFieldEnrichment; DiscoverFields; DiscoverRelationshipObservations; DiscoverResourceInventory; EnsureSemanticInventoryEntries; Error; FailSemanticInventoryBackfill; FieldProfileStore; … (+42) | internal/backends/postgres/internal/store; internal/catalog; internal/fhir/schema | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres/internal/chartifact` | domain/library | Persist private ClickHouse artifact manifests and cleanup leases in the PostgreSQL 18 control plane with CAS progress writes, bounded expired scans, and one-winner cleanup claims while physical tables stay in ClickHouse. | P: catalog.go; T: catalog_manager_test.go; catalog_test.go; Other: — | 535/1,605 | 10: Catalog; ClaimCleanup; Create; DB; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update | internal/dataframe/execution/chartifact | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres/internal/dataset` | domain/library | Persist dataset generation manifests, the active-generation compare-and-swap pointer, and immutable project release candidates with the active-release compare-and-swap pointer in PostgreSQL 18. | P: commands.go; manifest_lifecycle.go; migrate.go; release_store.go; store.go; T: commands_test.go; manifest_lifecycle_pg_test.go; release_store_pg_test.go; Other: — | 1,725/2,488 | 28: ActivateGeneration; Backend; Command; CommandBackend; CompareAndSwapActivateRelease; ConnSource; CreateManifest; EnsureGenerationPrerequisites; GenerationOwnerAdapter; MigrateSchema; New; NewPostgreSQLCommand; … (+16) | internal/backends/postgres/internal/store; internal/dataset | internal/backends/postgres/control; internal/backends/postgres/internal/explorer | runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres/internal/explorer` | domain/library | Persist Explorer owner records, draft saves with atomic version/digest CAS and exact preimage history, insert-once compilation receipts, immutable Explorer revisions, create-once immutable capability snapshots and table-shape capability receipts, project-scoped interpretation libraries advancing heads over immutable content-addressed revisions by transactional parent CAS, the staged selection lifecycle with project-scoped idempotency, an expiring writer owner, and insert-once membership staged in bounded idempotent batches with the digest/count finalized under the current writer fence and bounded cancellation-aware keyset membership paging, atomic publish authoring that commits a verified compilation receipt, the immutable Explorer revision, the active Explorer owner pointer, and the project release activation CAS in one PostgreSQL transaction, and staging explicit-group revision headers with ordered definition rows and insert-once group membership (one row per revision/group/reference so an overlap is several rows, bounded keyset paging, and a fenced digest/count-verified STAGING to COMPLETE transition) under guarded writer-lease, abort, and expired-cleanup lifecycle in PostgreSQL 18 with unique project/Explorer identity, typed columns, lossless JSONB payloads, DB-enforced row freezing and content-digest collision checks, guard-triggered immutability, and CAS-guarded revision lifecycle on the shared pg-store pool and migration runner. | P: capabilities.go; drafts.go; explicit_group_memberships.go; explicit_groups.go; explorers.go; interpretations.go; publication.go; receipts.go; revisions.go; selection_members.go; selections.go; T: capabilities_test.go; drafts_test.go; explicit_groups_readpath_test.go; explicit_groups_test.go; explorers_test.go; interpretations_test.go; pgstate02_fixture_test.go; pubauth_fixture_test.go; publication_golden_test.go; publication_guarddrop_test.go; publication_race_test.go; publication_retry_test.go; publication_test.go; publication_zz_test.go; receipts_test.go; revisions_test.go; selection_members_test.go; selections_test.go; Other: — | 7,425/21,659 | 60: AbortExplicitGroupRevision; AbortSelection; AppendExplicitGroupMemberships; AppendSelectionMembers; BeginExplicitGroupRevision; BeginSelection; CapabilitySnapshotStore; CleanupExplicitGroupStaging; CleanupSelectionStaging; CompleteExplicitGroupRevision; CompleteSelection; Create; … (+48) | internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/store; internal/dataframe/publication; internal/dataframe/recipe; internal/dataset; internal/explorer; internal/explorer/capability; internal/explorer/tableshapecap | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres/internal/publication` | domain/library | Persist dataframe publication execution metadata, exact logical pointers, fenced bundle leases, owner-conditional execution checkpoints, read retention pins arbitrated against cleanup claims on a shared retention anchor, and page-scoped stale execution reconciliation with owner-conditional cleanup claims, plus the single-transaction publish commit of PUBLISHED state with its reader pointer, in the PostgreSQL 18 control plane while ClickHouse remains the row store. | P: leases.go; migrate.go; publish.go; read_pins.go; reconcile.go; registry.go; T: leases_test.go; publish_atomicity_test.go; read_pins_test.go; reconcile_test.go; registry_test.go; Other: — | 2,502/7,214 | 44: AcquireBundleLease; AcquireExecutionReadPin; AcquireLease; BundleCatalog; Checkpoint; CheckpointRecord; ClaimExecutionCleanup; CompareAndSwapPointer; DB; ExpireLease; FencedLeases; FindExecutionByKey; … (+32) | internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/publication; internal/dataset | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres/internal/recipeexec` | domain/library | Persist immutable recipe registrations and fenced execution-occurrence usage markers in the PostgreSQL 18 control plane. | P: registry.go; T: registry_test.go; Other: — | 428/1,087 | 12: DB; LoadRecipe; LoadRecipeVersion; MigrateSchema; New; RecipeVersionUsed; RecordRecipeExecutionOccurrence; Registry; RegistryMigrations; ReplaceRecipeVersion; SaveRecipe; VerifySchema | internal/backends/postgres/internal/store; internal/dataframe/recipe/exec | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres/internal/recipes` | domain/library | Persist project recipe revision chains with CAS head tracking in the PostgreSQL 18 control plane. | P: revisions.go; T: revisions_test.go; Other: — | 409/1,072 | 9: ChainMigrations; DB; Get; List; MigrateSchema; New; Register; RevisionRegistry; VerifySchema | internal/backends/postgres/internal/store; internal/dataframe/errors; internal/dataframe/recipe | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/backends/postgres/internal/redact` | domain/library | Scrub DSN passwords and secret parameter values from pool-open diagnostics so credential material never reaches rendered error text while the cause chain stays intact. | P: redact.go; T: redact_internal_test.go; Other: — | 79/84 | 3: ExtractDSNPassword; RedactDSN; ScrubSecretText | — | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential | | `internal/backends/postgres/internal/rows` | domain/library | Stream typed rows from tagged compiled PostgreSQL commands over pgx connections, preserving bound-value integrity and three-state typed decoding, enforcing output column contracts, and classifying cancellation, visitor stops, contract violations, and driver failures with identifiable wrapped causes. | P: rows.go; T: rows_boundary_pg18_ab03_test.go; rows_postgres_test.go; rows_postquery_postgres_test.go; rows_test.go; Other: — | 483/3,537 | 8: Error; Executor; NewExecutor; Querier; QueryError; RowVisitor; SourceRows; Unwrap | internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/execution | internal/backends/postgres | one production importer (internal/backends/postgres); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/backends/postgres/internal/store` | domain/library | Provide the bounded pgx/v5-backed PostgreSQL 18 source store: readiness-pinged pool, advisory-lock serialized migration runner, scoped FHIR resource repository that accepts the logical internal/source contract on writes and answers reads with its store-owned physical row under the server-assigned keyset sequence, scoped typed edge-occurrence repository, publish-last inventory repository, scoped bounded field-profile record persistence, the ledgered idempotent COPY batch writer for resource and edge rows, and resource-only and edge-only batch facades over that writer. | P: advisory.go; batch_writer.go; client.go; commit_outcome.go; edge.go; edge_writer.go; errors.go; field_profile.go; inventory.go; migrate.go; migration_ownership.go; resource.go; resource_writer.go; T: batch_writer_test.go; client_test.go; edge_test.go; edge_writer_test.go; errors_test.go; field_profile_ddl_test.go; inventory_test.go; migrate_test.go; ownership_contract_test.go; resource_test.go; resource_writer_test.go; Other: — | 5,761/13,350 | 110: Acquire; AsUniqueConflict; Assignment; AssignmentConflict; Batch; BatchAccountingError; BatchEdgeConflictError; BatchIdentityConflictError; BatchResourceConflictError; BatchResult; BatchWriteError; BatchWriter; … (+98) | internal/catalog; internal/dataframe/errors; internal/source | internal/backends/postgres; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/rows | broad surface (13 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres/internal/store` | domain/library | Provide the bounded pgx/v5-backed PostgreSQL 18 source store: readiness-pinged pool, advisory-lock serialized migration runner, scoped FHIR resource repository that accepts the logical internal/source contract on writes and answers reads with its store-owned physical row under the server-assigned keyset sequence, scoped typed edge-occurrence repository, publish-last inventory repository, scoped bounded field-profile record persistence, the ledgered idempotent COPY batch writer for resource and edge rows, and resource-only and edge-only batch facades over that writer. | P: advisory.go; batch_writer.go; client.go; commit_outcome.go; edge.go; edge_writer.go; errors.go; field_profile.go; inventory.go; migrate.go; migration_ownership.go; resource.go; resource_writer.go; T: batch_writer_test.go; client_test.go; edge_test.go; edge_writer_test.go; errors_test.go; field_profile_ddl_test.go; field_profile_filter_golden_test.go; inventory_test.go; migrate_test.go; ownership_contract_test.go; resource_test.go; resource_writer_test.go; verify_classify_test.go; verify_readonly_test.go; Other: — | 5,898/13,789 | 112: Acquire; AsUniqueConflict; Assignment; AssignmentConflict; Batch; BatchAccountingError; BatchEdgeConflictError; BatchIdentityConflictError; BatchResourceConflictError; BatchResult; BatchWriteError; BatchWriter; … (+100) | internal/catalog; internal/dataframe/errors; internal/source | internal/backends/postgres; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/rows | broad surface (13 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/catalog` | domain/library | Own persistence-neutral observed FHIR field, pivot, relationship, and capability facts. | P: auth_scope.go; backend_contract.go; cache.go; capability_evidence.go; generation.go; helpers.go; limits.go; rebuild.go; relationship_evidence.go; relationships.go; resource_inventory.go; retained_semantic_page.go; semantic.go; semantic_coverage.go; semantic_coverage_contract.go; semantic_inventory.go; semantic_schema.go; types.go; write_profiler.go; T: auth_scope_test.go; cache_test.go; capability_evidence_test.go; generation_test.go; relationship_evidence_census_test.go; relationship_evidence_test.go; relationships_test.go; semantic_coding_display_projection_test.go; semantic_coverage_diagnostics_test.go; semantic_coverage_test.go; semantic_inventory_buildid_cache_test.go; semantic_inventory_category_test.go; semantic_inventory_emitter_anchor_test.go; semantic_inventory_test.go; semantic_schema_identifier_type_test.go; semantic_schema_test.go; semantic_test.go; write_profiler_coverage_test.go; write_profiler_test.go; Other: — | 6,471/3,946 | 146: AuthResourcePathOptions; Cache; CacheOptions; CacheStats; CapabilityEvidence; CapabilityEvidenceOptions; CapabilityEvidenceReader; CensusSemanticCoverage; ConceptCandidate; DecodeSemanticInventoryCursor; DefaultProfileLimits; DiscoverFields; … (+134) | internal/fhir/schema; internal/fhir/semantic | cmd/arango-fhir-proto; cmd/loom-data; internal/api/graphql/graph/query; internal/api/recipe; internal/authscope; internal/backends/arango/internal/catalog; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/explorer/authoringv2; internal/explorer/lifecycle; internal/ingest; internal/server | broad surface (19 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/ci` | domain/library | Exercise repository-wide Go formatting and vet gates, including injected fault detection. | P: —; T: gofmt_gate_test.go; vet_gate_test.go; Other: — | 0/283 | 0: — | — | — | no in-repo production importer; verify runtime/tooling reachability | +| `internal/dataframe/benchmark` | domain/library | Run the Arango-versus-PostgreSQL matched dataframe benchmark: deterministic fixtures, canonical bags and correctness gates evaluated before any timing is claimed, control runs, and report artifacts that a re-computing verifier must accept, with ClickHouse-backed publication legs disclosed as planned/not_run when the service is absent. | P: armplan.go; authoring.go; canonical.go; controls.go; driver.go; fixtures.go; gates.go; harness.go; matrix.go; measure.go; oracle.go; provision.go; report.go; report_md.go; T: armplan_test.go; authoring_test.go; canonical_test.go; controls_test.go; driver_test.go; fixtures_fhir_test.go; fixtures_test.go; gates_test.go; harness_scope_test.go; harness_test.go; matrix_test.go; measure_test.go; oracle_test.go; publication_test.go; report_test.go; scratch_authrepro_test.go; scratch_liveupload_test.go; Other: README.md | 5,072/2,964 | 146: Apply; ArangoBackendYAML; Arm; ArmConfig; ArmEvidence; AuthorWorkload; AuthoringStatus; AuthoringVerification; BackendResult; Bag; BothArmsPlan; BuildAllWorkspaces; … (+134) | internal/explorer/authoringv2 | internal/dataframe/benchmark/cmd/benchcli | one production importer (internal/dataframe/benchmark/cmd/benchcli); inspect ownership/inline potential; broad surface (14 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/dataframe/benchmark/cmd/benchcli` | domain/library | Own the benchmark command-line entry point: fail-closed live-run preflight, planned disclosure artifacts, report verification authority, and the loom_bench PostgreSQL database lifecycle. | P: deployer.go; main.go; probes.go; setup.go; T: guards_test.go; Other: — | 949/322 | 0: — | internal/backends/postgres/control; internal/dataframe/benchmark | — | no in-repo production importer; verify runtime/tooling reachability | | `internal/dataframe/columntransform` | domain/library | Define closed, backend-neutral transformations over one selected output column value. | P: types.go; T: types_test.go; Other: — | 79/61 | 7: CategoryMapping; Clone; ExactCategoryRecode; Kind; UnknownPolicy; Validate; ValueTransformation | — | internal/dataframe/compiler/lower; internal/dataframe/recipe; internal/explorer/authoringv2 | no automatic consolidation signal; perform semantic review | -| `internal/dataframe/compiler` | domain/library | Orchestrate semantic planning, physical lowering, optimization, rendering, and compiled-query assembly. | P: api.go; category_scan.go; category_scan_filter_pushdown.go; category_scan_related.go; cell_trace.go; clone_helpers.go; compiled_query.go; construction_preview_root_id_filter.go; graph_execution.go; ir_helpers.go; optimizer.go; output.go; population_mapping.go; postgres_target.go; preview_covering_index.go; recipe_execution.go; recipe_paging.go; row_lineage.go; semantic_group_projection_preview.go; table_shape_exclusions.go; target.go; T: backend_leaf_guard_test.go; category_scan_arango_test.go; category_scan_parent_presence_arango_test.go; category_scan_related_collection_identity_test.go; category_scan_related_presence_arango_test.go; category_scan_related_test.go; category_scan_test.go; cell_trace_test.go; cohort_drop_boundary_arango_test.go; command_helpers_external_test.go; command_helpers_test.go; compiled_command_test.go; construction_arango_test.go; construction_coded_group_arango_test.go; construction_coded_group_execution_test.go; construction_coded_pivot_test.go; construction_execution_test.go; construction_group_expand_arango_test.go; construction_group_preview_liveness_arango_test.go; construction_group_related_summary_arango_test.go; construction_group_source_streaming_test.go; construction_pivot_lineage_arango_test.go; construction_preview_root_id_filter_test.go; construction_related_count_arango_test.go; construction_related_expand_arango_test.go; construction_related_group_preview_arango_test.go; correlated_arango_test.go; correlated_legacy_compat_test.go; correlated_physical_test.go; correlated_selection_test.go; correlated_system_version_arango_test.go; explicit_cohort_values_arango_test.go; filter_semantics_test.go; filter_test.go; generic_physical_plan_test.go; grain_test.go; group_connected_field_source_arango_test.go; group_field_source_arango_test.go; nested_expansion_arango_test.go; operator_oracles_arango_test.go; physical_diagnostics_test.go; physical_lowering_test.go; physical_optimize_test.go; physical_plan_test.go; physical_projection_reserved_name_arango_test.go; physical_render_test.go; physical_required_match_test.go; physical_scope_test.go; pivot_presence_arango_audit_test.go; pivot_presence_audit_test.go; plan_test_helpers_test.go; population_frontier_arango_test.go; population_mapping_filter_exists_test.go; population_mapping_identity_guard_test.go; population_mapping_routed_group_arango_test.go; population_mapping_test.go; postgres_coded_group_compile_test.go; postgres_parity_fixture_test.go; postgres_target_fingerprint_golden_test.go; public_columns_freeze_test.go; recipe_paging_related_filter_sequence_test.go; recipe_paging_routed_exists_arango_test.go; recipe_paging_test.go; root_page_related_seed_arango_test.go; row_expansion_test.go; row_item_projection_arango_test.go; row_lineage_cohort_filter_arango_test.go; row_lineage_composed_arango_test.go; row_lineage_group_pivot_arango_test.go; row_lineage_group_rows_arango_test.go; row_lineage_test.go; selected_source_contract_test.go; selection_semantics_test.go; selector_first_limit_test.go; semantic_group_arango_test.go; semantic_group_preview_arango_test.go; semantic_group_projection_factorization_arango_test.go; semantic_validation_test.go; source_group_preview_arango_test.go; streaming_missing_category_pivot_test.go; table_reshape_arango_test.go; table_reshape_execution_window_test.go; table_shape_exclusions_test.go; workspace_output_capture_test.go; Other: — | 7,647/30,756 | 42: ArangoTarget; Backend; CategoryOverflowWitness; CategoryScanProof; CategoryScanRefusal; CategoryScanRefusalCode; CategoryScanRefusalCodeOf; CompileCategoryScanOutput; CompileCategoryScanStage; CompileCellTraceOutput; CompilePopulationMappingOutput; CompileRecipeOutput; … (+30) | internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec | cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/dataframe/execution; internal/explorer/lifecycle; internal/server | broad surface (21 production files); inspect cohesion | +| `internal/dataframe/compiler` | domain/library | Orchestrate semantic planning, physical lowering, optimization, rendering, and compiled-query assembly. | P: api.go; category_scan.go; category_scan_filter_pushdown.go; category_scan_related.go; cell_trace.go; clone_helpers.go; compiled_query.go; construction_preview_root_id_filter.go; graph_execution.go; ir_helpers.go; optimizer.go; output.go; population_mapping.go; postgres_target.go; preview_covering_index.go; recipe_execution.go; recipe_paging.go; row_lineage.go; semantic_group_projection_preview.go; table_shape_exclusions.go; target.go; T: backend_leaf_guard_test.go; category_scan_arango_test.go; category_scan_parent_presence_arango_test.go; category_scan_related_collection_identity_test.go; category_scan_related_presence_arango_test.go; category_scan_related_test.go; category_scan_test.go; cell_trace_test.go; cohort_drop_boundary_arango_test.go; command_helpers_external_test.go; command_helpers_test.go; compiled_command_test.go; construction_arango_test.go; construction_coded_group_arango_test.go; construction_coded_group_execution_test.go; construction_coded_pivot_test.go; construction_execution_test.go; construction_group_expand_arango_test.go; construction_group_preview_liveness_arango_test.go; construction_group_related_summary_arango_test.go; construction_group_source_streaming_test.go; construction_pivot_lineage_arango_test.go; construction_preview_root_id_filter_test.go; construction_related_count_arango_test.go; construction_related_expand_arango_test.go; construction_related_group_preview_arango_test.go; correlated_arango_test.go; correlated_legacy_compat_test.go; correlated_physical_test.go; correlated_selection_test.go; correlated_system_version_arango_test.go; explicit_cohort_values_arango_test.go; filter_semantics_test.go; filter_test.go; generic_physical_plan_test.go; grain_test.go; group_connected_field_source_arango_test.go; group_field_source_arango_test.go; nested_expansion_arango_test.go; operator_oracles_arango_test.go; physical_diagnostics_test.go; physical_lowering_test.go; physical_optimize_test.go; physical_plan_test.go; physical_projection_reserved_name_arango_test.go; physical_render_test.go; physical_required_match_test.go; physical_scope_test.go; pivot_presence_arango_audit_test.go; pivot_presence_audit_test.go; plan_test_helpers_test.go; population_frontier_arango_test.go; population_mapping_filter_exists_test.go; population_mapping_identity_guard_test.go; population_mapping_routed_group_arango_test.go; population_mapping_test.go; postgres_coded_group_compile_test.go; postgres_parity_fixture_test.go; postgres_target_fingerprint_golden_test.go; public_columns_freeze_test.go; recipe_execution_pg18_test.go; recipe_execution_test.go; recipe_paging_related_filter_sequence_test.go; recipe_paging_routed_exists_arango_test.go; recipe_paging_test.go; root_page_related_seed_arango_test.go; row_expansion_test.go; row_item_projection_arango_test.go; row_lineage_cohort_filter_arango_test.go; row_lineage_composed_arango_test.go; row_lineage_group_pivot_arango_test.go; row_lineage_group_rows_arango_test.go; row_lineage_test.go; selected_source_contract_test.go; selection_semantics_test.go; selector_first_limit_test.go; semantic_group_arango_test.go; semantic_group_preview_arango_test.go; semantic_group_projection_factorization_arango_test.go; semantic_validation_test.go; source_group_preview_arango_test.go; streaming_missing_category_pivot_test.go; table_reshape_arango_test.go; table_reshape_execution_window_test.go; table_shape_exclusions_test.go; workspace_output_capture_test.go; Other: — | 8,120/32,667 | 44: ArangoTarget; Backend; CategoryOverflowWitness; CategoryScanProof; CategoryScanRefusal; CategoryScanRefusalCode; CategoryScanRefusalCodeOf; CompileCategoryScanOutput; CompileCategoryScanStage; CompileCellTraceOutput; CompileClickHousePrefix; CompilePopulationMappingOutput; … (+32) | internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec | cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/dataframe/execution; internal/explorer/lifecycle; internal/server | broad surface (21 production files); inspect cohesion | | `internal/dataframe/compiler/capability` | domain/library | Probe which dataframe language capabilities the compiler can prove and support. | P: probe.go; schema_fields.go; T: probe_test.go; schema_fields_test.go; Other: — | 803/313 | 22: CandidateCapability; CandidateRequest; Chart; Filter; Options; OwnerRecordsCapability; OwnerRecordsRequest; ProbeCandidate; ProbeOwnerRecords; ProbeProjection; ProbeRoot; ProbeTraversal; … (+10) | internal/authscope; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/fhir/schema | internal/explorer/lifecycle; internal/server | no automatic consolidation signal; perform semantic review | -| `internal/dataframe/compiler/command` | domain/library | Own the closed dispatch-tagged executable command types, rendered PostgreSQL plan validation, and constructor guarantees shared by compilers and row adapters. | P: command.go; rendered_postgres.go; T: compiled_command_test.go; imports_guard_test.go; rendered_postgres_test.go; Other: — | 433/526 | 15: AQLCommand; AQLParameters; Backend; CompiledBackend; CompiledCommand; Dispatch; NewAQLCommand; NewPostgreSQLCommand; NewRenderedPostgresPhysicalPlan; PostgreSQLCommand; PostgreSQLParameter; PostgreSQLParameterType; … (+3) | — | internal/backends/postgres; internal/backends/postgres/internal/rows; internal/dataframe/compiler; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/server | no automatic consolidation signal; perform semantic review | -| `internal/dataframe/compiler/ir` | domain/library | Define typed physical operations, values, scope proofs, policies, and validation. | P: clone.go; dataset_generation.go; diagnostics.go; doc.go; navigation.go; physical_combine.go; physical_combine_scalar.go; physical_operations.go; physical_stage_sequence.go; physical_stage_validation.go; physical_table_shape.go; physical_types.go; physical_validation.go; physical_validation_expr.go; physical_validation_refs.go; physical_validation_shapes.go; physical_values.go; policy.go; postgres_storage.go; prefix.go; scope.go; selector_helpers.go; whole_relation_scope_evidence.go; workspace_combine_scope_operand.go; T: group_related_field_source_validation_test.go; physical_combine_scalar_test.go; physical_combine_test.go; physical_group_pivot_lineage_validation_test.go; physical_root_page_candidate_seed_test.go; physical_scope_cycle_test.go; physical_stage_validation_test.go; population_mapping_test.go; population_root_test.go; related_coding_group_lineage_validation_test.go; row_lineage_trace_validation_test.go; subplan_test.go; unit_validation_test.go; unnest_test.go; workspace_output_validation_test.go; Other: — | 12,210/1,691 | 214: AddDecision; CanonicalExecutionPhysicalPlan; ClickHouseArtifactIdentity; ClickHouseCombineScalarBaseType; ClonePhysicalExpression; ClonePhysicalOperation; ClonePhysicalOperations; ClonePhysicalPlan; ClonePhysicalPredicateExpression; ClonePhysicalSubplan; Column; CompilerPlanDiagnostics; … (+202) | internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema; internal/projectid | cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/clickhouse; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/explorer/lifecycle; internal/server | broad surface (24 production files); inspect cohesion | -| `internal/dataframe/compiler/lower` | domain/library | Lower logical FHIR dataframe plans into storage-aware physical IR. | P: auth_scope.go; child_sets.go; column_naming.go; correlated_binding.go; doc.go; filter_literal.go; graph_plan.go; group_field_source_context.go; group_field_source_related.go; group_related_occurrence_lower.go; group_rows.go; helpers.go; plan_orchestration.go; population.go; recipe_column_transformations.go; recipe_compile.go; recipe_construction.go; recipe_construction_coded_group.go; recipe_construction_coded_pivot.go; recipe_construction_combine.go; recipe_construction_combine_metadata.go; recipe_construction_composite.go; recipe_construction_group_sources.go; recipe_construction_related_eligibility.go; recipe_construction_related_expand.go; recipe_construction_related_field.go; recipe_construction_reshape.go; recipe_derived_columns.go; recipe_dynamic_columns.go; recipe_expression.go; recipe_expression_lowering.go; recipe_schema.go; recipe_table_reshape.go; required_match.go; root_projection.go; row_expansion.go; storage_route.go; traversal.go; typed_predicate.go; whole_relation_scope.go; workspace_output_dependencies.go; T: bound_scalar_coding_group_test.go; child_set_selector_demand_test.go; construction_group_streaming_test.go; correlated_binding_test.go; empty_bound_group_source_test.go; graph_plan_test.go; group_field_source_context_test.go; group_related_occurrence_lower_test.go; group_rows_test.go; group_specimen_inbound_coding_test.go; owner_records_test.go; physical_nested_scope_test.go; population_test.go; recipe_construction_coded_group_test.go; recipe_construction_coded_pivot_display_test.go; recipe_construction_coded_pivot_test.go; recipe_construction_combine_test.go; recipe_construction_composite_test.go; recipe_construction_related_count_test.go; recipe_construction_reshape_test.go; recipe_construction_row_values_test.go; recipe_construction_test.go; recipe_derived_columns_test.go; recipe_expression_test.go; recipe_output_test.go; recipe_schema_cardinality_test.go; recipe_semantic_path_test.go; recipe_table_reshape_test.go; recipe_unpivot_capability_test.go; root_id_filter_test.go; row_expansion_test.go; traversal_test.go; whole_relation_scope_evidence_test.go; workspace_combine_scope_evidence_test.go; workspace_output_compile_test.go; Other: — | 12,185/10,741 | 40: BindGroupRelatedOccurrenceSource; BuildGenericPhysicalPlanWithPolicy; BuildGraphPhysicalPlan; BuildPhysicalTraversal; CloneCompiledOutputSchema; CompileResolvedGraphPlan; CompileResolvedRecipePlan; CompiledActiveRelatedRecordStage; CompiledGroupFieldOwner; CompiledGroupFieldSourceAncestor; CompiledGroupFieldSourceContext; CompiledGroupFieldSourceExpansion; … (+28) | internal/authscope; internal/dataframe/columntransform; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema; internal/projectid | internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/explorer/compilation; internal/explorer/lifecycle; internal/server | broad surface (41 production files); inspect cohesion | +| `internal/dataframe/compiler/command` | domain/library | Own the closed dispatch-tagged executable command types, rendered PostgreSQL plan validation, and constructor guarantees shared by compilers and row adapters. | P: command.go; rendered_postgres.go; T: compiled_command_test.go; imports_guard_test.go; rendered_postgres_test.go; Other: — | 433/526 | 15: AQLCommand; AQLParameters; Backend; CompiledBackend; CompiledCommand; Dispatch; NewAQLCommand; NewPostgreSQLCommand; NewRenderedPostgresPhysicalPlan; PostgreSQLCommand; PostgreSQLParameter; PostgreSQLParameterType; … (+3) | — | internal/api/graphql/graph/resolver; internal/backends/postgres; internal/backends/postgres/internal/rows; internal/dataframe/compiler; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/server | no automatic consolidation signal; perform semantic review | +| `internal/dataframe/compiler/ir` | domain/library | Define typed physical operations, values, scope proofs, policies, and validation. | P: clone.go; dataset_generation.go; diagnostics.go; doc.go; navigation.go; physical_combine.go; physical_combine_scalar.go; physical_operations.go; physical_stage_sequence.go; physical_stage_validation.go; physical_table_shape.go; physical_types.go; physical_validation.go; physical_validation_expr.go; physical_validation_refs.go; physical_validation_shapes.go; physical_values.go; policy.go; postgres_storage.go; prefix.go; scope.go; selector_helpers.go; whole_relation_scope_evidence.go; workspace_combine_scope_operand.go; T: group_related_field_source_validation_test.go; physical_combine_scalar_test.go; physical_combine_test.go; physical_group_pivot_lineage_validation_test.go; physical_root_page_candidate_seed_test.go; physical_scope_cycle_test.go; physical_stage_validation_test.go; population_mapping_test.go; population_root_test.go; related_coding_group_lineage_validation_test.go; row_lineage_trace_validation_test.go; subplan_test.go; unit_validation_test.go; unnest_test.go; workspace_output_validation_test.go; Other: — | 12,283/1,804 | 215: AddDecision; CanonicalExecutionPhysicalPlan; ClickHouseArtifactIdentity; ClickHouseCombineScalarBaseType; ClonePhysicalExpression; ClonePhysicalOperation; ClonePhysicalOperations; ClonePhysicalPlan; ClonePhysicalPredicateExpression; ClonePhysicalSubplan; Column; CompilerPlanDiagnostics; … (+203) | internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema; internal/projectid | cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/clickhouse; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/explorer/lifecycle; internal/server | broad surface (24 production files); inspect cohesion | +| `internal/dataframe/compiler/lower` | domain/library | Lower logical FHIR dataframe plans into storage-aware physical IR. | P: auth_scope.go; child_sets.go; column_naming.go; correlated_binding.go; doc.go; filter_literal.go; graph_plan.go; group_field_source_context.go; group_field_source_related.go; group_related_occurrence_lower.go; group_rows.go; helpers.go; plan_orchestration.go; population.go; recipe_column_transformations.go; recipe_compile.go; recipe_construction.go; recipe_construction_coded_group.go; recipe_construction_coded_pivot.go; recipe_construction_combine.go; recipe_construction_combine_metadata.go; recipe_construction_composite.go; recipe_construction_group_sources.go; recipe_construction_related_eligibility.go; recipe_construction_related_expand.go; recipe_construction_related_field.go; recipe_construction_reshape.go; recipe_derived_columns.go; recipe_dynamic_columns.go; recipe_expression.go; recipe_expression_lowering.go; recipe_schema.go; recipe_table_reshape.go; required_match.go; root_projection.go; row_expansion.go; storage_route.go; traversal.go; typed_predicate.go; whole_relation_scope.go; workspace_output_dependencies.go; T: bound_scalar_coding_group_test.go; child_set_selector_demand_test.go; construction_group_streaming_test.go; correlated_binding_test.go; empty_bound_group_source_test.go; graph_plan_test.go; group_field_source_context_test.go; group_related_occurrence_lower_test.go; group_rows_test.go; group_specimen_inbound_coding_test.go; owner_records_test.go; physical_nested_scope_test.go; population_test.go; recipe_construction_coded_group_test.go; recipe_construction_coded_pivot_display_test.go; recipe_construction_coded_pivot_test.go; recipe_construction_combine_test.go; recipe_construction_composite_test.go; recipe_construction_related_count_test.go; recipe_construction_reshape_test.go; recipe_construction_row_values_test.go; recipe_construction_test.go; recipe_derived_columns_test.go; recipe_expression_test.go; recipe_output_test.go; recipe_schema_cardinality_test.go; recipe_semantic_path_test.go; recipe_table_reshape_test.go; recipe_unpivot_capability_test.go; root_id_filter_test.go; row_expansion_test.go; traversal_test.go; whole_relation_scope_evidence_test.go; workspace_combine_scope_evidence_test.go; workspace_output_compile_test.go; Other: — | 12,219/10,802 | 40: BindGroupRelatedOccurrenceSource; BuildGenericPhysicalPlanWithPolicy; BuildGraphPhysicalPlan; BuildPhysicalTraversal; CloneCompiledOutputSchema; CompileResolvedGraphPlan; CompileResolvedRecipePlan; CompiledActiveRelatedRecordStage; CompiledGroupFieldOwner; CompiledGroupFieldSourceAncestor; CompiledGroupFieldSourceContext; CompiledGroupFieldSourceExpansion; … (+28) | internal/authscope; internal/dataframe/columntransform; internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema; internal/projectid | internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/explorer/compilation; internal/explorer/lifecycle; internal/server | broad surface (41 production files); inspect cohesion | | `internal/dataframe/compiler/optimize` | domain/library | Apply explainable semantics-preserving rewrites to validated physical IR. | P: doc.go; optimize.go; T: optimize_test.go; Other: — | 438/124 | 1: OptimizePhysicalPlanWithPolicy | internal/dataframe/compiler/ir | internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/execution | no automatic consolidation signal; perform semantic review | -| `internal/dataframe/compiler/render/aql` | domain/library | Render validated physical IR as deterministic parameterized AQL. | P: calls.go; cell_trace.go; collections.go; combine_prefix.go; construction_coded_group.go; construction_expand.go; construction_group.go; construction_related_expand.go; correlation_value_scope.go; doc.go; graph_render.go; group_row_value_preview.go; group_rows.go; navigation_layout.go; navigation_render.go; owner_records.go; population_mapping.go; predicates.go; related_count.go; related_group_preview.go; render.go; row_lineage.go; row_lineage_coded_group.go; row_lineage_composed.go; row_lineage_group_pivot.go; row_lineage_group_rows.go; row_lineage_pivot.go; row_lineage_related_coding.go; row_values.go; selector.go; selectors.go; source_group_preview.go; stage_sequence.go; table_reshape.go; table_reshape_dynamic.go; table_reshape_streaming.go; table_shape_exclusion.go; validation.go; values.go; T: call_test.go; command_helpers_ext_test.go; construction_group_related_occurrence_test.go; construction_group_test.go; construction_related_expand_test.go; correlated_selection_test.go; graph_test.go; group_row_value_preview_test.go; navigation_layout_test.go; related_count_category_test.go; row_lineage_composed_test.go; selectors_test.go; source_group_preview_test.go; stage_sequence_test.go; table_reshape_dynamic_test.go; table_reshape_streaming_scalar_test.go; two_scan_preview_test.go; unnest_test.go; Other: — | 14,514/3,483 | 22: RenderClickHousePrefix; RenderPhysicalCohortGroupRowsLineage; RenderPhysicalGroupRowsLineage; RenderPhysicalPlan; RenderPhysicalPlanWithCategoryScanPresenceMarker; RenderPhysicalPlanWithDynamicCategoryPivotPreview; RenderPhysicalPlanWithRelatedExpandPreviewLimit; RenderPhysicalPlanWithRootIndexDisabled; RenderPhysicalPlanWithRootIndexHint; RenderPhysicalPlanWithRootPageKeys; RenderPhysicalPlanWithSemanticGroupProjectionAssessment; RenderPhysicalPlanWithSemanticGroupProjectionFactorization; … (+10) | internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema | internal/dataframe/compiler; internal/dataframe/compiler/capability | broad surface (39 production files); inspect cohesion | -| `internal/dataframe/compiler/render/clickhouse` | storage adapter | Render typed Combine plans and resolved published or private table inputs as deterministic ClickHouse SQL. | P: combine.go; workspace_combine.go; T: combine_test.go; membership_nullable_integration_test.go; workspace_combine_test.go; Other: — | 930/1,092 | 6: RenderCombine; RenderCombineWithLimit; RenderCompositeClickHouseCombine; RenderWorkspaceClickHouseCombine; RenderedCombine; WorkspaceArtifact | internal/dataframe/compiler/ir | internal/dataframe/execution | one production importer (internal/dataframe/execution); inspect ownership/inline potential; storage adapter; preserve interface and persisted-data contracts | -| `internal/dataframe/compiler/render/postgres` | storage adapter | Render the supported physical IR read spine and typed selector extractions with two-valued JSONB path-presence evidence onto allowlisted PostgreSQL storage bindings as deterministic parameterized SQL with ordered typed arguments, failing closed on every unsupported plan shape. | P: calls.go; coded_group.go; graph.go; group.go; null_semantics.go; objects.go; pivot.go; population.go; predicates.go; render.go; root_scan.go; scope.go; scope_ops.go; selectors.go; sets.go; traversal.go; unit_normalization.go; unnest.go; unpivot.go; window.go; T: calls_live_test.go; calls_test.go; coded_group_test.go; coded_pivot_test.go; graph_test.go; group_test.go; main_test.go; null_semantics_test.go; obj11_production_test.go; objects_test.go; pivot14_noncoded_test.go; pivot_test.go; population_live_test.go; population_test.go; predicates_pg18_test.go; predicates_test.go; render_pg18_test.go; render_test.go; root_scan_test.go; scope_ops_witness_test.go; scope_test.go; selectors_test.go; sets_live_test.go; sets_test.go; traversal_test.go; unit_normalization_production_test.go; unit_normalization_test.go; unit_normalization_tie_test.go; unnest_test.go; unpivot_test.go; window_test.go; Other: — | 16,217/31,618 | 13: NewPostgresPopulationRootBinding; PostgresPopulationRootBinding; RenderGraphPhysicalPlan; RenderGroupedCodedPivot; RenderGroupedPivot; RenderPhysicalPlan; RenderPopulationRootScan; RenderRootScan; RenderSelectorExtraction; RenderUnnest; RenderUnpivot; Validate; … (+1) | internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema | internal/dataframe/compiler | one production importer (internal/dataframe/compiler); inspect ownership/inline potential; broad surface (20 production files); inspect cohesion; storage adapter; preserve interface and persisted-data contracts | +| `internal/dataframe/compiler/render/aql` | domain/library | Render validated physical IR as deterministic parameterized AQL. | P: calls.go; cell_trace.go; collections.go; combine_prefix.go; construction_coded_group.go; construction_expand.go; construction_group.go; construction_related_expand.go; correlation_value_scope.go; doc.go; graph_render.go; group_row_value_preview.go; group_rows.go; navigation_layout.go; navigation_render.go; owner_records.go; population_mapping.go; predicates.go; related_count.go; related_group_preview.go; render.go; row_lineage.go; row_lineage_coded_group.go; row_lineage_composed.go; row_lineage_group_pivot.go; row_lineage_group_rows.go; row_lineage_pivot.go; row_lineage_related_coding.go; row_values.go; selector.go; selectors.go; source_group_preview.go; stage_sequence.go; table_reshape.go; table_reshape_dynamic.go; table_reshape_streaming.go; table_shape_exclusion.go; validation.go; values.go; T: call_test.go; command_helpers_ext_test.go; construction_group_related_occurrence_test.go; construction_group_test.go; construction_related_expand_test.go; correlated_selection_test.go; graph_test.go; group_row_value_preview_test.go; navigation_layout_test.go; recipe_unpivot_identity_test.go; related_count_category_test.go; row_lineage_composed_test.go; selectors_test.go; source_group_preview_test.go; stage_sequence_test.go; table_reshape_dynamic_test.go; table_reshape_streaming_scalar_test.go; two_scan_preview_test.go; unnest_test.go; Other: — | 14,519/3,608 | 22: RenderClickHousePrefix; RenderPhysicalCohortGroupRowsLineage; RenderPhysicalGroupRowsLineage; RenderPhysicalPlan; RenderPhysicalPlanWithCategoryScanPresenceMarker; RenderPhysicalPlanWithDynamicCategoryPivotPreview; RenderPhysicalPlanWithRelatedExpandPreviewLimit; RenderPhysicalPlanWithRootIndexDisabled; RenderPhysicalPlanWithRootIndexHint; RenderPhysicalPlanWithRootPageKeys; RenderPhysicalPlanWithSemanticGroupProjectionAssessment; RenderPhysicalPlanWithSemanticGroupProjectionFactorization; … (+10) | internal/dataframe/compiler/ir; internal/dataframe/errors; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema | internal/dataframe/compiler; internal/dataframe/compiler/capability | broad surface (39 production files); inspect cohesion | +| `internal/dataframe/compiler/render/clickhouse` | storage adapter | Render typed Combine plans and resolved published or private table inputs as deterministic ClickHouse SQL. | P: combine.go; workspace_combine.go; T: combine_test.go; membership_nullable_integration_test.go; workspace_combine_test.go; Other: — | 930/1,093 | 6: RenderCombine; RenderCombineWithLimit; RenderCompositeClickHouseCombine; RenderWorkspaceClickHouseCombine; RenderedCombine; WorkspaceArtifact | internal/dataframe/compiler/ir | internal/dataframe/execution | one production importer (internal/dataframe/execution); inspect ownership/inline potential; storage adapter; preserve interface and persisted-data contracts | +| `internal/dataframe/compiler/render/postgres` | storage adapter | Render the supported physical IR read spine and typed selector extractions with two-valued JSONB path-presence evidence onto allowlisted PostgreSQL storage bindings as deterministic parameterized SQL with ordered typed arguments, failing closed on every unsupported plan shape. | P: calls.go; category_scan.go; cell_trace.go; coded_group.go; graph.go; group.go; null_semantics.go; objects.go; pivot.go; population.go; population_mapping.go; predicates.go; render.go; root_scan.go; row_lineage.go; scope.go; scope_ops.go; selectors.go; sets.go; stage_related_occurrence.go; stage_semantic.go; stage_sequence.go; table_shape_exclusions.go; traversal.go; unit_normalization.go; unnest.go; unpivot.go; window.go; T: calls_live_test.go; calls_test.go; category_scan_test.go; cell_trace_test.go; coded_group_test.go; coded_pivot_test.go; graph_test.go; group_test.go; main_test.go; null_semantics_test.go; obj11_production_test.go; objects_test.go; pivot14_noncoded_test.go; pivot_test.go; population_live_test.go; population_mapping_test.go; population_test.go; predicates_pg18_test.go; predicates_test.go; render_pg18_test.go; render_test.go; root_scan_test.go; row_lineage_test.go; scope_ops_witness_test.go; scope_test.go; selectors_test.go; sets_live_test.go; sets_test.go; stage_related_occurrence_p4_test.go; stage_related_occurrence_production_test.go; stage_related_occurrence_test.go; stage_semantic_production_test.go; stage_semantic_test.go; stage_sequence_production_test.go; stage_sequence_test.go; table_shape_exclusions_live_test.go; table_shape_exclusions_test.go; traversal_test.go; unit_normalization_production_test.go; unit_normalization_test.go; unit_normalization_tie_test.go; unnest_test.go; unpivot_test.go; window_test.go; Other: — | 23,444/42,018 | 18: NewPostgresPopulationRootBinding; PostgresPopulationRootBinding; RenderCategoryScan; RenderGraphPhysicalPlan; RenderGroupedCodedPivot; RenderGroupedPivot; RenderPhysicalPlan; RenderPopulationMappingPlan; RenderPopulationRootScan; RenderRootScan; RenderSelectorExtraction; RenderTableShapeExclusions; … (+6) | internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema | internal/dataframe/compiler; internal/dataframe/execution | broad surface (28 production files); inspect cohesion; storage adapter; preserve interface and persisted-data contracts | | `internal/dataframe/errors` | domain/library | Define and normalize stable structured dataframe errors for transport presentation. | P: errors.go; T: errors_test.go; Other: — | 488/172 | 21: AsUserError; Code; Details; Error; ErrorCode; ErrorOption; FieldPath; IsFeatureResolutionCode; IsRetryableCode; NewError; Normalize; PublicMessage; … (+9) | — | internal/api/authpolicy; internal/api/bulk/load; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/backends/arango/internal/recipeexec; internal/backends/arango/internal/store; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/store; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/execution; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/semantic; internal/dataframe/spec; internal/explorer/lifecycle; internal/server | small shared semantic contract; compare direct callers before inlining; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/dataframe/execution` | domain/library | Prepare, authorize, execute, explain, and post-process compiled dataframe requests. | P: category_scan.go; cell_trace.go; clickhouse_artifact.go; control.go; control_explain_types.go; control_types.go; engine.go; execution_service.go; explain.go; explain_postgres.go; group_preview_cache.go; group_preview_scan.go; helpers.go; named_uuid.go; population_mapping.go; postquery.go; preview_capabilities.go; query_budget.go; related_preview_paging.go; row_lineage.go; service.go; table_shape_exclusions.go; types.go; T: auth_scope_test.go; boundary_helpers_test.go; category_scan_test.go; cell_trace_row_not_found_class_test.go; cell_trace_test.go; clickhouse_artifact_test.go; cohort_preview_paging_arango_test.go; command_boundary_contract_test.go; construction_cell_trace_arango_test.go; control_validate_digest_test.go; engine_blank_project_review_test.go; engine_clickhouse_test.go; engine_postgres_postquery_pg18_test.go; engine_postgres_preview_pg18_test.go; engine_postgres_test.go; engine_run_output_class_test.go; engine_test.go; engine_workspace_artifact_test.go; execution_test.go; explain_test.go; group_preview_cache_test.go; group_preview_scan_test.go; helpers_test.go; identity_test.go; named_uuid_test.go; population_group_preview_test.go; population_mapping_test.go; postgres_explain_parser_internal_test.go; postgres_target_fixture_test.go; postquery_test.go; preview_capability_boundary_test.go; recipe_paging_postgres_adapter_test.go; recipe_paging_postgres_test.go; recipe_preview_paging_postgres_internal_test.go; row_lineage_test.go; selected_source_contract_test.go; table_shape_exclusions_test.go; vendor_import_guard_test.go; verify_arch01_pins_test.go; Other: — | 6,401/16,663 | 108: BuildPostgreSQLExplainCommand; CategoryScanRequest; CategoryScanResult; CategoryValue; CellTrace; CellTraceCompiled; CellTraceContribution; CellTraceRequest; CellTraceResult; CellTraceStatus; ClickHouseQueryRows; CompileResolvedBundle; … (+96) | internal/authscope; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/clickhouse; internal/dataframe/errors; internal/dataframe/execution/chartifact; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/projectid | internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/backends/postgres; internal/backends/postgres/internal/rows; internal/explorer/lifecycle; internal/server | broad surface (23 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/dataframe/execution` | domain/library | Prepare, authorize, execute, explain, and post-process compiled dataframe requests. | P: category_scan.go; cell_trace.go; clickhouse_artifact.go; control.go; control_explain_types.go; control_types.go; engine.go; execution_service.go; explain.go; explain_postgres.go; group_preview_cache.go; group_preview_scan.go; helpers.go; named_uuid.go; population_mapping.go; postquery.go; preview_capabilities.go; query_budget.go; related_preview_paging.go; row_lineage.go; service.go; table_shape_exclusions.go; types.go; T: auth_scope_test.go; backend_neutral_diagnostics_test.go; boundary_helpers_test.go; category_scan_postgres_test.go; category_scan_test.go; cell_trace_postgres_test.go; cell_trace_row_not_found_class_test.go; cell_trace_test.go; clickhouse_artifact_test.go; cohort_preview_paging_arango_test.go; command_boundary_contract_test.go; construction_cell_trace_arango_test.go; control_validate_digest_test.go; engine_blank_project_review_test.go; engine_clickhouse_test.go; engine_postgres_capture_pg18_test.go; engine_postgres_capture_test.go; engine_postgres_category_pg18_test.go; engine_postgres_cell_trace_pg18_test.go; engine_postgres_exclusions_pg18_test.go; engine_postgres_postquery_pg18_test.go; engine_postgres_preview_pg18_test.go; engine_postgres_test.go; engine_run_output_class_test.go; engine_test.go; engine_workspace_artifact_test.go; execution_test.go; explain_test.go; group_preview_cache_test.go; group_preview_scan_test.go; helpers_test.go; identity_test.go; named_uuid_test.go; population_group_preview_test.go; population_mapping_postgres_pg18_test.go; population_mapping_test.go; postgres_explain_parser_internal_test.go; postgres_target_fixture_test.go; postquery_test.go; preview_capability_boundary_test.go; recipe_paging_postgres_adapter_test.go; recipe_paging_postgres_test.go; recipe_preview_paging_postgres_internal_test.go; row_lineage_postgres_pg18_test.go; row_lineage_test.go; selected_source_contract_test.go; table_shape_exclusions_test.go; vendor_import_guard_test.go; verify_arch01_pins_test.go; Other: — | 7,241/27,582 | 110: BuildPostgreSQLExplainCommand; CategoryScanRequest; CategoryScanResult; CategoryValue; CellTrace; CellTraceCompiled; CellTraceContribution; CellTraceRequest; CellTraceResult; CellTraceStatus; ClickHouseQueryRows; CombinePortsConfigured; … (+98) | internal/authscope; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/clickhouse; internal/dataframe/compiler/render/postgres; internal/dataframe/errors; internal/dataframe/execution/chartifact; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/projectid | internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/backends/postgres; internal/backends/postgres/internal/rows; internal/explorer/lifecycle; internal/server | broad surface (23 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/dataframe/execution/chartifact` | domain/library | Manage private ClickHouse staging tables and leases used to cross execution boundaries without publishing outputs. | P: artifact.go; T: artifact_test.go; Other: — | 1,038/490 | 24: Abort; Artifact; Begin; Catalog; CheckLease; ClickHouse; Column; ColumnsFromCompiledOutput; Config; Finalize; Identity; LeaseContext; … (+12) | internal/authscope; internal/dataframe/compiler/lower; internal/dataframe/expression; internal/store/clickhouse | internal/backends/arango/internal/chartifact; internal/backends/postgres/internal/chartifact; internal/dataframe/execution; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | | `internal/dataframe/expression` | domain/library | Define and validate the typed backend-neutral expression AST used by recipes. | P: expression_checking.go; expression_types.go; recipe_adapter.go; T: expression_test.go; recipe_adapter_test.go; Other: — | 856/235 | 26: AllowsMany; Call; Cardinality; Check; CheckedExpression; Constant; Document; DocumentRef; Expression; FromRecipe; FromRecipeInContexts; Function; … (+14) | internal/dataframe/recipe | internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution/chartifact; internal/dataframe/semantic; internal/explorer; internal/explorer/lifecycle; internal/server | no automatic consolidation signal; perform semantic review | | `internal/dataframe/lineage` | domain/library | Define stable identities and provenance for columns expanded from one authored source slot. | P: source_child.go; T: source_child_test.go; Other: — | 163/77 | 7: CloneSourceChild; Coordinate; IsSourceChildID; ParseSourceChildID; SourceChild; SourceChildKind; StableSourceChildID | — | internal/dataframe/compiler/lower; internal/dataframe/recipe; internal/explorer/authoringv2; internal/explorer/compilation | no automatic consolidation signal; perform semantic review | @@ -121,13 +123,13 @@ These records preserve verified package-boundary decisions for both live package | `internal/dataframe/recipe` | domain/library | Define, canonicalize, expand, encode, and validate persistence-neutral recipe documents. | P: canonical.go; construction.go; construction_combine.go; construction_reshape.go; derived.go; derived_validation.go; document_types.go; document_validation.go; dynamic_validation.go; fragments.go; json_encoding.go; revisions.go; strict_json.go; table_reshape.go; validation_rich.go; workspace_output_dependencies.go; T: construction_combine_test.go; construction_group_policy_test.go; construction_test.go; correlated_selection_test.go; derived_validation_test.go; documentation_test.go; fragments_test.go; publication_identity_nul_http_test.go; table_reshape_test.go; types_test.go; workspace_output_dependencies_test.go; Other: — | 5,611/1,930 | 147: Aggregate; AggregateOperation; ArrayQuantifier; Bundle; CanonicalBundle; CanonicalJSON; CanonicalJSONWithWorkspaceOutputs; CatalogProjection; Clone; CodeValue; ColumnNaming; ColumnTransformation; … (+135) | generated/fhir; internal/authscope; internal/dataframe/columntransform; internal/dataframe/lineage; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema | cmd/dataframe-profile; cmd/explorer-config-v2-convert; cmd/preview-covering-index-artifact; conformance/compiler; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/backends/arango/internal/recipeexec; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/recipes; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/explorer; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/server | broad surface (16 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/dataframe/recipe/exec` | domain/library | Define durable recipe-version storage contracts and immutable registration behavior. | P: persistent.go; T: persistent_test.go; Other: — | 84/102 | 5: Entry; PersistentRegistry; Reader; RegisterVersion; Store | internal/dataframe/recipe | internal/api/graphql/graph/resolver; internal/backends/arango/internal/recipeexec; internal/backends/postgres/internal/recipeexec; internal/dataframe/execution; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | | `internal/dataframe/recipe/schema` | domain/library | Resolve catalog-backed recipe declarations into concrete validated schemas. | P: discovery.go; extension_columns.go; resolve.go; T: resolve_test.go; Other: — | 882/293 | 8: Discovery; ExtensionValueObservation; FieldCandidate; Fields; Resolve; Resolved; Scope; SourceSnapshot | internal/dataframe/recipe; internal/fhir/schema | internal/api/graphql/graph/query; internal/api/recipe; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/dataframe/semantic` | domain/library | Convert validated dataframe requests and recipes into backend-neutral logical FHIR plans. | P: doc.go; dynamic_schema.go; execution_context.go; recipe_filter.go; recipe_plan_build.go; recipe_plan_output.go; recipe_plan_types.go; recipe_projection.go; recipe_rich_shaping.go; recipe_traversal.go; resolved_plan.go; selection_semantics.go; selector_spec.go; semantic_nodes.go; semantic_validation.go; T: dynamic_schema_test.go; recipe_plan_test.go; recipe_plan_user_fault_class_test.go; recipe_rich_shaping_test.go; resolved_plan_test.go; Other: — | 3,022/931 | 47: BuildRecipePlan; Candidate; Column; DynamicSpec; ExecutionContext; ExpansionEmptyPolicy; ExpansionExplanation; Explain; ExpressionExplanation; FallbackSelectors; Freeze; FrozenSchema; … (+35) | internal/authscope; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema | cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution | broad surface (15 production files); inspect cohesion | +| `internal/dataframe/semantic` | domain/library | Convert validated dataframe requests and recipes into backend-neutral logical FHIR plans. | P: doc.go; dynamic_schema.go; execution_context.go; recipe_filter.go; recipe_plan_build.go; recipe_plan_output.go; recipe_plan_types.go; recipe_projection.go; recipe_rich_shaping.go; recipe_traversal.go; resolved_plan.go; selection_semantics.go; selector_spec.go; semantic_nodes.go; semantic_validation.go; T: dynamic_schema_test.go; recipe_plan_test.go; recipe_plan_user_fault_class_test.go; recipe_rich_shaping_test.go; resolved_plan_test.go; Other: — | 3,022/931 | 47: BuildRecipePlan; Candidate; Column; DynamicSpec; ExecutionContext; ExpansionEmptyPolicy; ExpansionExplanation; Explain; ExpressionExplanation; FallbackSelectors; Freeze; FrozenSchema; … (+35) | internal/authscope; internal/dataframe/errors; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/fhir/schema | cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/server | broad surface (15 production files); inspect cohesion | | `internal/dataframe/spec` | domain/library | Define the backend-independent dataframe request, filter, grain, relationship, and selector language. | P: doc.go; filter.go; filter_semantics.go; grain.go; relationship_match.go; selector_validation.go; selectors.go; T: correlated_selection_test.go; grain_classification_test.go; Other: — | 745/71 | 31: AllowsMany; ArrayQuantifier; Cardinality; Clone; CodeValue; ContainsFilter; DefaultRowIdentity; ExpandsRows; FilterOperator; FilterValue; FilterValueKind; InferRowGrain; … (+19) | generated/fhir; internal/dataframe/errors; internal/fhir/schema | internal/api/graphql/graph/query; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer/compilation; internal/explorer/lifecycle; internal/server | no automatic consolidation signal; perform semantic review | | `internal/dataframe/unit` | domain/library | Define unit identity, dimensional compatibility, and approved conversion rules for quantity values. | P: arithmetic.go; unit.go; T: arithmetic_test.go; unit_test.go; Other: — | 442/203 | 27: ApprovedUnitPolicies; ApprovedUnitPolicy; ArithmeticOperand; ArithmeticOperation; ArithmeticUnitError; ArithmeticUnitErrorReason; ColumnArithmeticOperand; Equal; Error; LiteralArithmeticOperand; ResolveApprovedUnitPolicy; ResolveApprovedUnitRules; … (+15) | — | internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer; internal/explorer/authoringv2; internal/explorer/compilation; internal/explorer/lifecycle; internal/server | no automatic consolidation signal; perform semantic review | | `internal/dataset` | domain/library | Define immutable dataset generation manifests, releases, active pointers, and lifecycle validation. | P: active_resolver.go; dataframe_selector.go; doc.go; manifest.go; manifest_port.go; ref.go; release.go; schema.go; validation.go; T: active_resolver_test.go; manifest_test.go; memory_store_test.go; ref_test.go; release_id_framing_test.go; release_idempotency_test.go; release_requirements_e2e_test.go; release_test.go; release_verifier_fault_classification_test.go; schema_test.go; Other: README.md | 802/2,253 | 34: Activate; ActivationRequest; Active; ActiveRelease; ActiveReleaseResolver; ActiveResolver; ContractVerification; Create; DataframeSelector; Error; IsStaged; Key; … (+22) | — | cmd/arango-fhir-proto; cmd/loom-data; internal/api/bulk/load; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/postgres/control; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/explorer; internal/explorer/lifecycle; internal/ingest; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | | `internal/explorer` | domain/library | Define Explorer aggregate identities, configs, revisions, receipts, output contracts, stores, and core service operations. | P: artifact.go; authoring_errors.go; canonical_json.go; compilation.go; compilation_receipt.go; config_v2.go; explicit_group_repository.go; explicit_groups.go; identity.go; interpretation.go; output_contract.go; selection.go; service.go; state_v1.go; store.go; types.go; viewer_projection.go; T: artifact_test.go; compilation_receipt_test.go; explicit_groups_test.go; interpretation_test.go; output_contract_test.go; repository_revision_id_test.go; selection_test.go; types_test.go; viewer_projection_order_test.go; Other: — | 4,335/1,587 | 165: AbortSelection; ActivateRepositoryGeneration; ActiveRevision; AppendSelectionMembers; ApplyWorkspaceCommands; ApplyWorkspaceCommandsChecked; ArtifactCompletion; ArtifactID; ArtifactRecord; ArtifactStage; ArtifactState; ArtifactStore; … (+153) | internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/unit; internal/dataset; internal/explorer/authoringv2; internal/explorer/capability; internal/projectid | cmd/explorer-config-v2-convert; cmd/loom-acceptance; internal/backends/arango/internal/explorer; internal/backends/postgres/internal/explorer; internal/explorer/artifactfs; internal/explorer/compilation; internal/explorer/lifecycle; internal/server | broad surface (17 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/explorer/artifactfs` | domain/library | Persist and serve immutable Explorer training artifacts from a dedicated filesystem root. | P: store.go; T: reconcile_test.go; store_test.go; Other: — | 379/621 | 10: Abort; Begin; Close; Commit; Get; New; Open; Reconcile; Store; Write | internal/dataframe/published; internal/explorer | internal/server | one production importer (internal/server); inspect ownership/inline potential | -| `internal/explorer/authoringv2` | domain/library | Define, validate, canonicalize, and mutate the V2 Explorer Builder authoring workspace. | P: availability.go; canonical.go; commands.go; construction.go; construction_change.go; construction_choice.go; construction_migration.go; construction_row_values.go; construction_source_children.go; construction_validation.go; framing.go; migration.go; route_identity.go; row_change.go; row_definition.go; semantic_command.go; semantic_types.go; table_shape.go; transformations.go; types.go; validation.go; value_transformations.go; workspace_output_dependencies.go; T: commands_test.go; compile_preparation_test.go; construction_choice_test.go; construction_combine_test.go; construction_expand_test.go; construction_formal_replay_test.go; construction_group_coded_pivot_test.go; construction_group_field_source_test.go; construction_projection_replay_test.go; construction_proposal_test.go; construction_related_contributor_test.go; construction_row_values_test.go; construction_source_children_test.go; construction_test.go; correlated_test.go; framing_test.go; interpretation_test.go; migration_test.go; population_member_proposal_test.go; row_change_test.go; row_definition_test.go; row_proposal_test.go; semantic_codeable_concept_fallback_test.go; semantic_coded_category_test.go; semantic_command_test.go; table_shape_proposal_test.go; table_shape_test.go; transformations_test.go; types_test.go; value_transformations_test.go; workspace_output_dependencies_test.go; workspace_test.go; Other: — | 13,260/8,548 | 231: Action; ActionColumn; Addable; AggregateSource; AggregateTransformationCapabilities; AggregateTransformationCapabilitiesForCapability; AggregateTransformationCapabilitiesForCatalog; AnalyzeConstructionCandidate; AnalyzeStepEdit; ApplyCommands; ApplyCommandsRequest; ApplyCommandsResponse; … (+219) | internal/catalog; internal/dataframe/columntransform; internal/dataframe/lineage; internal/dataframe/unit; internal/explorer/capability; internal/fhir/schema | cmd/explorer-config-v2-convert; cmd/preview-covering-index-artifact; generated/loomapi; internal/acceptance; internal/backends/arango/internal/explorer; internal/explorer; internal/explorer/compilation; internal/explorer/lifecycle; internal/repositoryseed; internal/server | broad surface (23 production files); inspect cohesion | +| `internal/explorer/authoringv2` | domain/library | Define, validate, canonicalize, and mutate the V2 Explorer Builder authoring workspace. | P: availability.go; canonical.go; commands.go; construction.go; construction_change.go; construction_choice.go; construction_migration.go; construction_row_values.go; construction_source_children.go; construction_validation.go; framing.go; migration.go; route_identity.go; row_change.go; row_definition.go; semantic_command.go; semantic_types.go; table_shape.go; transformations.go; types.go; validation.go; value_transformations.go; workspace_output_dependencies.go; T: commands_test.go; compile_preparation_test.go; construction_choice_test.go; construction_combine_test.go; construction_expand_test.go; construction_formal_replay_test.go; construction_group_coded_pivot_test.go; construction_group_field_source_test.go; construction_projection_replay_test.go; construction_proposal_test.go; construction_related_contributor_test.go; construction_row_values_test.go; construction_source_children_test.go; construction_test.go; correlated_test.go; framing_test.go; interpretation_test.go; migration_test.go; population_member_proposal_test.go; row_change_test.go; row_definition_test.go; row_proposal_test.go; semantic_codeable_concept_fallback_test.go; semantic_coded_category_test.go; semantic_command_test.go; table_shape_proposal_test.go; table_shape_test.go; transformations_test.go; types_test.go; value_transformations_test.go; workspace_output_dependencies_test.go; workspace_test.go; Other: — | 13,260/8,548 | 231: Action; ActionColumn; Addable; AggregateSource; AggregateTransformationCapabilities; AggregateTransformationCapabilitiesForCapability; AggregateTransformationCapabilitiesForCatalog; AnalyzeConstructionCandidate; AnalyzeStepEdit; ApplyCommands; ApplyCommandsRequest; ApplyCommandsResponse; … (+219) | internal/catalog; internal/dataframe/columntransform; internal/dataframe/lineage; internal/dataframe/unit; internal/explorer/capability; internal/fhir/schema | cmd/explorer-config-v2-convert; cmd/preview-covering-index-artifact; generated/loomapi; internal/acceptance; internal/backends/arango/internal/explorer; internal/dataframe/benchmark; internal/explorer; internal/explorer/compilation; internal/explorer/lifecycle; internal/repositoryseed; internal/server | broad surface (23 production files); inspect cohesion | | `internal/explorer/capability` | domain/library | Build the immutable compiler-backed capability contract exposed to Explorer Builder. | P: builder.go; construction.go; construction_routes.go; domain.go; population_route_choice.go; repository.go; row_choice.go; value_operations.go; T: builder_test.go; construction_test.go; domain_test.go; row_choice_test.go; semantic_fallback_dto_test.go; value_operations_test.go; Other: — | 3,950/1,396 | 110: AggregateInput; AggregateOperation; AggregateOperationCapability; AggregateRowContext; Allows; Availability; AvailabilityReadiness; AvailabilitySourceReadiness; Build; Builder; Candidate; CandidateProof; … (+98) | internal/dataframe/recipe | cmd/preview-covering-index-artifact; internal/backends/arango/internal/explorer; internal/backends/postgres/internal/explorer; internal/explorer; internal/explorer/authoringv2; internal/explorer/compilation; internal/explorer/lifecycle; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | | `internal/explorer/compilation` | domain/library | Translate validated Explorer authoring intent into native recipe and compilation artifacts. | P: compile.go; construction.go; derived_columns.go; indexed_projection.go; interpretation.go; row_expansion.go; semantic_compile.go; source_row.go; table_reshape.go; T: acceptance_observation_aql_diagnostic_test.go; command_helpers_test.go; construction_combine_test.go; construction_related_contributor_test.go; construction_test.go; correlated_test.go; derived_columns_test.go; indexed_projection_test.go; interpretation_test.go; row_expansion_test.go; semantic_compile_expand_source_test.go; source_row_test.go; table_reshape_test.go; translate_test.go; value_transformations_test.go; workspace_output_mapping_test.go; workspace_test.go; Other: — | 3,622/4,536 | 24: Canonical; Compile; CompileWorkspace; Error; InterpretationCandidateMatch; InterpretationCandidateResolution; InterpretationCandidateResolutionState; Match; PopulationFor; PresentationColumn; PresentationConfig; Reason; … (+12) | internal/dataframe/compiler/lower; internal/dataframe/lineage; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/fhir/schema; internal/projectid | cmd/preview-covering-index-artifact; internal/explorer/lifecycle; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | | `internal/explorer/lifecycle` | domain/library | Coordinate transport-neutral Explorer authoring, preview, publication, query, and policy operations. | P: artifact.go; authoring.go; cell_trace.go; column_source.go; construction_category_discovery.go; construction_choice.go; construction_choice_proposal.go; construction_coded_pivot.go; construction_group_field_choices.go; construction_group_related_field_choices.go; construction_group_source.go; construction_pivot_source.go; construction_proposal.go; construction_routes.go; construction_semantic_group.go; derive_source.go; draft_restore.go; errors.go; expand_source.go; explicit_group_resolver.go; explicit_groups.go; filter_source.go; frame_source.go; interpretation.go; interpretation_apply.go; interpretation_context.go; interpretation_from_column.go; interpretation_library.go; interpretation_preview.go; policy.go; population.go; population_cursor.go; population_member_proposal.go; population_report.go; population_routes.go; preview.go; proposal_receipt.go; publish.go; query.go; repository_publish.go; row_change.go; row_choices.go; row_comparison.go; row_lineage.go; row_proposal.go; row_value_policy.go; schema_field_candidate.go; schema_fields.go; selection.go; semantic_catalog.go; service.go; table_shape_capabilities.go; table_shape_comparison.go; table_shape_proposal.go; table_shape_proposal_composition.go; table_shape_resolution.go; table_shape_saved.go; types.go; unpivot_source.go; T: artifact_test.go; builder_full_ui_replay_test.go; cell_trace_test.go; construction_category_discovery_errors_test.go; construction_category_discovery_test.go; construction_category_presence_test.go; construction_choice_category_search_test.go; construction_choice_proposal_test.go; construction_choice_test.go; construction_coded_pivot_saved_source_test.go; construction_group_field_choices_test.go; construction_pivot_source_test.go; construction_proposal_test.go; construction_proposal_trace_test.go; construction_related_expand_removal_test.go; construction_routes_test.go; construction_semantic_group_test.go; construction_v9_receipt_test.go; derive_source_test.go; expand_source_test.go; explicit_group_resolver_recovery_test.go; explicit_groups_test.go; filter_source_pivot_clear_test.go; filter_source_test.go; frame_source_test.go; fs1_reverse_order_collision_test.go; interpretation_context_test.go; interpretation_from_column_test.go; interpretation_library_test.go; interpretation_preview_test.go; interpretation_test.go; population_attachment_formal_replay_test.go; population_direction_test.go; population_formal_replay_test.go; population_member_proposal_test.go; population_removal_formal_replay_test.go; population_report_test.go; publish_release_requirements_classification_test.go; repository_publish_activation_conflict_test.go; repository_publish_generation_activation_classification_test.go; repository_publish_release_cas_conflict_test.go; row_change_test.go; row_choices_test.go; row_lineage_test.go; row_proposal_test.go; row_value_policy_test.go; schema_field_candidate_test.go; schema_fields_test.go; selection_test.go; semantic_authoring_test.go; semantic_catalog_test.go; semantic_category_test.go; semantic_coded_concept_test.go; semantic_field_presentation_test.go; semantic_frame_compatibility_test.go; service_test.go; table_shape_capabilities_test.go; table_shape_comparison_evidence_test.go; table_shape_proposal_test.go; unpivot_source_test.go; Other: — | 25,545/31,520 | 319: AcceptedConstruction; ApplyCommands; ApplyCommandsWithConstructionPreview; ApplyCommandsWithConstructionPreviewResponse; ArtifactOpenRequest; ArtifactPublishedReader; ArtifactRequest; ArtifactResult; AssessRowChange; AssessRowChangeRequest; AssessRowChangeResult; AuthorizedCapability; … (+307) | internal/authscope; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/tableshapecap; internal/fhir/schema; internal/projectid | internal/server | one production importer (internal/server); inspect ownership/inline potential; broad surface (59 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | @@ -137,7 +139,7 @@ These records preserve verified package-boundary decisions for both live package | `internal/ingest` | domain/library | Preflight and load FHIR NDJSON into generation-scoped Arango vertices, edges, and catalog facts, and commit built resource-row and built-edge-occurrence batches to the PostgreSQL source store through the ledgered batch writer with scope verification, canonical payload encoding, and payload-backed path/ordinal recovery for repeated references. | P: backend.go; doc.go; documents.go; files.go; generated_load.go; generation_identity.go; generation_load.go; generation_orchestration.go; generation_workers.go; load_options.go; postgres_writer.go; preflight.go; progress.go; resource_load.go; row_builder.go; semantic_inventory_backfill.go; source_schema.go; T: backend_test.go; basic_observation_fixture_validation_test.go; bench_test.go; documents_test.go; file_pipeline_test.go; files_test.go; generated_load_test.go; generation_cleanup_test.go; generation_failed_cleanup_e2e_test.go; generation_identity_test.go; generation_load_postgres_durability_test.go; generation_load_postgres_test.go; generation_load_test.go; integration_test.go; keycollide_generated_e2e_test.go; load_options_test.go; postgres_writer_test.go; preflight_test.go; related_medication_fixture_test.go; resource_load_test.go; row_builder_test.go; semantic_inventory_backfill_integration_test.go; semantic_inventory_backfill_test.go; semantic_inventory_test.go; source_schema_test.go; viewer_row_file_download_fixture_test.go; Other: — | 5,649/10,567 | 63: ActivatePostgresGeneration; ActivationOutcomeError; BackfillSemanticInventory; Build; Complete; ConnectionOptions; DiscoverNDJSON; EdgeDocument; EdgeFromGrip; Error; EventSink; GeneratedRowBuilder; … (+51) | generated/fhir; internal/backends/arango; internal/backends/postgres; internal/catalog; internal/dataset; internal/fhir/schema; internal/source | cmd/arango-fhir-proto; cmd/loom-data; internal/api/bulk/load; internal/server | broad surface (17 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/projectid` | domain/library | Define canonical and legacy-compatible public project identity normalization. | P: projectid.go; T: projectid_test.go; Other: — | 51/18 | 2: Canonical; Legacy | — | cmd/loom-repository; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/backends/arango/internal/explorer; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/published; internal/explorer; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/repositoryseed; internal/server | small shared semantic contract; compare direct callers before inlining | | `internal/repositoryseed` | domain/library | Validate repository metadata and configuration, produce a source manifest, and refuse unmaterialized inputs before launch. | P: preflight.go; T: preflight_test.go; Other: — | 281/105 | 6: Error; File; Manifest; Options; Prepare; UnmaterializedError | internal/explorer/authoringv2; internal/projectid | cmd/loom-repository | one production importer (cmd/loom-repository); inspect ownership/inline potential | -| `internal/server` | domain/library | Compose storage, domain services, generated OpenAPI handlers, GraphQL adapters, and HTTP routes. | P: artifact_dev.go; auth_wiring.go; construction_inputs_catalog.go; explain_wiring.go; explorer_authoring_http.go; explorer_capability.go; explorer_combine_output_metadata.go; explorer_config_route.go; explorer_lifecycle_helpers.go; explorer_lifecycle_wire.go; explorer_output_reconcile.go; explorer_preview_response.go; explorer_preview_security.go; explorer_publication.go; explorer_receipt_contract.go; explorer_receipt_validation.go; local_workspace_writeback.go; openapi_data_routes.go; openapi_explorer_artifacts.go; openapi_explorer_authoring_direct.go; openapi_explorer_construction.go; openapi_explorer_construction_choice_proposal.go; openapi_explorer_construction_inputs.go; openapi_explorer_construction_proposal.go; openapi_explorer_conversion.go; openapi_explorer_direct.go; openapi_explorer_explicit_groups.go; openapi_explorer_population_member_proposal.go; openapi_explorer_routes.go; openapi_explorer_row_definition.go; openapi_explorer_selections.go; openapi_explorer_table_shape.go; openapi_frame_source_options.go; openapi_graphql_routes.go; openapi_recipe_execution.go; openapi_routes.go; openapi_semantic_inventory.go; options.go; private_artifact_reconciler.go; receipt_preview_diagnostic.go; recipe_authorization.go; recipe_combine_resolution.go; recipe_discovery.go; recipe_publication.go; release_verifier.go; routes.go; server.go; T: accepted_construction_contract_test.go; append_workspace_rows_integration_test.go; artifact_reconciler_wiring_test.go; auth_wiring_trusted_issuer_test.go; backend_error_boundary_contract_test.go; case008_row_definition_preview_route_test.go; command_helpers_test.go; config_test.go; construction_category_discovery_openapi_error_test.go; construction_inputs_catalog_test.go; construction_proposal_zero_column_test.go; core_authoring_protocol_arango_conformance_test.go; core_authoring_protocol_formal_replay_test.go; core_authoring_protocol_receipts_test.go; core_authoring_protocol_restore_test.go; core_authoring_protocol_rowschema_test.go; core_authoring_protocol_scoped_compile_test.go; core_authoring_protocol_test.go; dataframe_query_error_test.go; derived_policy_rows_integration_test.go; explain_wiring_pg18_test.go; explain_wiring_preview_test.go; explorer_capability_test.go; explorer_coded_value_capability_test.go; explorer_concept_evidence_test.go; explorer_config_route_test.go; explorer_diagnostic_logging_test.go; explorer_output_reconcile_derive_source_test.go; explorer_output_reconcile_source_child_test.go; explorer_output_reconcile_unpivot_source_test.go; explorer_preview_response_test.go; explorer_preview_security_test.go; explorer_receipt_contract_error_test.go; explorer_receipt_contract_test.go; explorer_reconcile_scope_test.go; explorer_row_change_route_test.go; explorer_state_route_test.go; fs1_signed_slot_collision_test.go; http_test_helpers_test.go; local_workspace_writeback_test.go; openapi_column_id_contract_test.go; openapi_explorer_artifacts_download_test.go; openapi_explorer_authoring_write_fault_test.go; openapi_explorer_cell_trace_test.go; openapi_explorer_commands_notfound_test.go; openapi_explorer_construction_apply_preview_test.go; openapi_explorer_construction_derive_source_test.go; openapi_explorer_construction_expand_source_test.go; openapi_explorer_construction_filter_source_test.go; openapi_explorer_construction_inputs_test.go; openapi_explorer_construction_proposal_test.go; openapi_explorer_construction_unpivot_source_test.go; openapi_explorer_conversion_test.go; openapi_explorer_direct_test.go; openapi_explorer_draft_restore_test.go; openapi_explorer_legacy_details_control_test.go; openapi_explorer_population_mapping_construction_test.go; openapi_explorer_population_member_proposal_test.go; openapi_explorer_restricted_starting_collection_integration_test.go; openapi_explorer_route_roundtrip_test.go; openapi_explorer_route_rows_arango_test.go; openapi_explorer_row_definition_test.go; openapi_explorer_write_scope_test.go; openapi_field_presentation_fixture_test.go; openapi_graphql_status_test.go; openapi_graphql_transport_review_test.go; openapi_interpretation_routes_test.go; openapi_recipe_execution_test.go; openapi_route_inventory_test.go; openapi_routes_test.go; openapi_semantic_inventory_test.go; openapi_status_coverage_test.go; openapi_test_helpers_test.go; options_schemas_test.go; patient_one_construction_choice_route_test.go; population_member_receipt_test.go; postgres_refusal_contract_test.go; preview_postgres_wiring_pg18_test.go; private_artifact_reconciler_test.go; publication_rows_integration_test.go; publication_viewer_agreement_test.go; receipt_preview_diagnostic_test.go; recipe_authorization_test.go; recipe_blank_project_test.go; recipe_combine_resolution_test.go; recipe_discovery_test.go; recipe_execution_etl_failure_fields_e2e_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_header_probe_test.go; recipe_execution_status_twin_e2e_test.go; recipe_execution_twin_authz_repair_test.go; recipe_project_keying_test.go; recipe_publication_test.go; release_verifier_fault_mapping_test.go; release_verifier_test.go; repository_activation_conflict_dev_test.go; routes_test.go; row_value_policy_stream_test.go; wiring_test.go; Other: — | 12,476/33,256 | 92: ActivateDatasetGeneration; ApplyExplorerBuilderCommands; AssessExplorerRowChange; AuthConfig; AuthorizeRead; AuthorizeWrite; BasicAuthConfig; BrowseExplorerFrameSourceOptions; BrowseExplorerSemanticInventory; CalyprAuthConfig; CheckExplorerPopulationMapping; ClickHouseConfig; … (+80) | generated/loomapi; internal/api/authpolicy; internal/api/bulk/load; internal/api/columncapabilities; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/artifactfs; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/fhir/schema; internal/ingest; internal/projectid; internal/store/clickhouse | .; cmd/arango-fhir-server | broad surface (47 production files); inspect cohesion | +| `internal/server` | domain/library | Compose storage, domain services, generated OpenAPI handlers, GraphQL adapters, and HTTP routes. | P: artifact_dev.go; auth_wiring.go; compose.go; construction_inputs_catalog.go; explain_wiring.go; explorer_authoring_http.go; explorer_capability.go; explorer_combine_output_metadata.go; explorer_config_route.go; explorer_lifecycle_helpers.go; explorer_lifecycle_wire.go; explorer_output_reconcile.go; explorer_preview_response.go; explorer_preview_security.go; explorer_publication.go; explorer_receipt_contract.go; explorer_receipt_validation.go; local_workspace_writeback.go; openapi_data_routes.go; openapi_explorer_artifacts.go; openapi_explorer_authoring_direct.go; openapi_explorer_construction.go; openapi_explorer_construction_choice_proposal.go; openapi_explorer_construction_inputs.go; openapi_explorer_construction_proposal.go; openapi_explorer_conversion.go; openapi_explorer_direct.go; openapi_explorer_explicit_groups.go; openapi_explorer_population_member_proposal.go; openapi_explorer_routes.go; openapi_explorer_row_definition.go; openapi_explorer_selections.go; openapi_explorer_table_shape.go; openapi_frame_source_options.go; openapi_graphql_routes.go; openapi_recipe_execution.go; openapi_routes.go; openapi_semantic_inventory.go; options.go; private_artifact_reconciler.go; receipt_preview_diagnostic.go; recipe_authorization.go; recipe_combine_resolution.go; recipe_discovery.go; recipe_publication.go; release_verifier.go; routes.go; row_sources.go; runtime.go; server.go; T: accepted_construction_contract_test.go; append_workspace_rows_integration_test.go; artifact_reconciler_wiring_test.go; auth_wiring_trusted_issuer_test.go; backend_composition_contract_test.go; backend_error_boundary_contract_test.go; backend_neutral_diagnostics_live_test.go; case008_row_definition_preview_route_test.go; command_helpers_test.go; config_test.go; construction_category_discovery_openapi_error_test.go; construction_inputs_catalog_test.go; construction_proposal_zero_column_test.go; core_authoring_protocol_arango_conformance_test.go; core_authoring_protocol_formal_replay_test.go; core_authoring_protocol_receipts_test.go; core_authoring_protocol_restore_test.go; core_authoring_protocol_rowschema_test.go; core_authoring_protocol_scoped_compile_test.go; core_authoring_protocol_test.go; dataframe_query_error_test.go; derived_policy_rows_integration_test.go; explain_wiring_pg18_test.go; explain_wiring_preview_test.go; explorer_capability_test.go; explorer_coded_value_capability_test.go; explorer_concept_evidence_test.go; explorer_config_route_test.go; explorer_diagnostic_logging_test.go; explorer_output_reconcile_derive_source_test.go; explorer_output_reconcile_source_child_test.go; explorer_output_reconcile_unpivot_source_test.go; explorer_preview_response_test.go; explorer_preview_security_test.go; explorer_receipt_contract_error_test.go; explorer_receipt_contract_test.go; explorer_reconcile_scope_test.go; explorer_row_change_route_test.go; explorer_state_route_test.go; fs1_signed_slot_collision_test.go; health_readiness_wiring_test.go; http_test_helpers_test.go; local_workspace_writeback_test.go; openapi_column_id_contract_test.go; openapi_explorer_artifacts_download_test.go; openapi_explorer_authoring_write_fault_test.go; openapi_explorer_cell_trace_test.go; openapi_explorer_commands_notfound_test.go; openapi_explorer_construction_apply_preview_test.go; openapi_explorer_construction_derive_source_test.go; openapi_explorer_construction_expand_source_test.go; openapi_explorer_construction_filter_source_test.go; openapi_explorer_construction_inputs_test.go; openapi_explorer_construction_proposal_test.go; openapi_explorer_construction_unpivot_source_test.go; openapi_explorer_conversion_test.go; openapi_explorer_direct_test.go; openapi_explorer_draft_restore_test.go; openapi_explorer_legacy_details_control_test.go; openapi_explorer_population_mapping_construction_test.go; openapi_explorer_population_member_proposal_test.go; openapi_explorer_restricted_starting_collection_integration_test.go; openapi_explorer_route_roundtrip_test.go; openapi_explorer_route_rows_arango_test.go; openapi_explorer_row_definition_test.go; openapi_explorer_write_scope_test.go; openapi_field_presentation_fixture_test.go; openapi_graphql_status_test.go; openapi_graphql_transport_review_test.go; openapi_interpretation_routes_test.go; openapi_recipe_execution_test.go; openapi_route_inventory_test.go; openapi_routes_test.go; openapi_semantic_inventory_test.go; openapi_status_coverage_test.go; openapi_test_helpers_test.go; options_schemas_test.go; patient_one_construction_choice_route_test.go; population_member_receipt_test.go; postgres_query_test.go; postgres_refusal_contract_test.go; preview_postgres_wiring_pg18_test.go; private_artifact_reconciler_test.go; publication_rows_integration_test.go; publication_viewer_agreement_test.go; receipt_preview_diagnostic_test.go; recipe_authorization_test.go; recipe_blank_project_test.go; recipe_combine_resolution_test.go; recipe_discovery_test.go; recipe_execution_etl_failure_fields_e2e_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_header_probe_test.go; recipe_execution_status_twin_e2e_test.go; recipe_execution_twin_authz_repair_test.go; recipe_project_keying_test.go; recipe_publication_test.go; release_verifier_fault_mapping_test.go; release_verifier_test.go; repository_activation_conflict_dev_test.go; route_composition_contract_test.go; routes_test.go; row_tag_guard_contract_test.go; row_value_policy_stream_test.go; runtime_postgres_live_test.go; runtime_test.go; wiring_test.go; zz_readiness_contract_guard_test.go; Other: — | 14,261/37,352 | 103: Activate; ActivateDatasetGeneration; ActivateRepositoryGeneration; ApplyExplorerBuilderCommands; AssessExplorerRowChange; AuthConfig; AuthorizeRead; AuthorizeWrite; BasicAuthConfig; BrowseExplorerFrameSourceOptions; BrowseExplorerSemanticInventory; CalyprAuthConfig; … (+91) | generated/loomapi; internal/api/authpolicy; internal/api/bulk/load; internal/api/columncapabilities; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/artifactfs; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/fhir/schema; internal/ingest; internal/projectid; internal/store/clickhouse | .; cmd/arango-fhir-server | broad surface (50 production files); inspect cohesion | | `internal/source` | domain/library | Define the backend-neutral logical source identity, payload, and authorization-scope contracts shared by ingestion and store read/write paths, free of any storage or driver detail. | P: doc.go; resource.go; T: ownership_contract_test.go; Other: — | 80/403 | 2: Resource; ResourceScope | — | internal/backends/postgres/internal/store; internal/ingest | small shared semantic contract; compare direct callers before inlining | | `internal/store/clickhouse` | storage adapter | Provide the low-level typed ClickHouse connection and database-management boundary. | P: client.go; T: client_test.go; integration_test.go; Other: — | 676/712 | 15: AddColumn; Client; Close; Column; CreateTable; DropColumns; DropTable; EnsureDatabase; InsertRows; New; Options; Ping; … (+3) | — | internal/acceptance; internal/dataframe/execution/chartifact; internal/dataframe/publication/clickhouse; internal/server | storage adapter; preserve interface and persisted-data contracts | | `scripts` | domain/library | Hold repository developer, verification, migration, and release utilities that are not part of the Go module runtime packages. | P: check_fhir_semantic_resource_types.go; local-preview-index-migration.go; T: check_fhir_semantic_resource_types_test.go; local-preview-index-migration_test.go; Other: README.md; package-lock.json; package.json; pg-exec-03-skipset-capture.json | 769/532 | 0: — | internal/fhir/schema | — | no in-repo production importer; verify runtime/tooling reachability; runtime/build signals present; static importer counts are insufficient for deletion | diff --git a/internal/dataframe/benchmark/cmd/benchcli/probes.go b/internal/dataframe/benchmark/cmd/benchcli/probes.go index ec35b46bb..c65d4af28 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/probes.go +++ b/internal/dataframe/benchmark/cmd/benchcli/probes.go @@ -8,23 +8,46 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" "time" - "github.com/jackc/pgx/v5" + backendspostgres "github.com/calypr/loom/internal/backends/postgres/control" ) // Live probes: the report's backend versions must be observed from the -// services themselves, never written by hand. +// services themselves, never written by hand. The PostgreSQL driver stays +// confined to internal/backends (ownership contract): every probe here +// reaches the database through the backend's own public factory surface, +// never through a driver import. + +// openProbeRuntime opens the backend factory's single readiness-gated pool +// with no schema assignment (the empty placement is the valid probe shape) +// and SetupNone, then hands back the runtime whose ConnSource is the +// factory's public single-connection port. +func openProbeRuntime(ctx context.Context, dsn string) (*backendspostgres.Runtime, error) { + return backendspostgres.Open(ctx, backendspostgres.Config{ + DSN: dsn, + MaxConns: 2, + MinConns: 1, + AcquireTimeout: 15 * time.Second, + Setup: backendspostgres.SetupNone, + }) +} func probePostgresVersion(ctx context.Context, dsn string) (string, error) { - connCtx, cancel := context.WithTimeout(ctx, 15*time.Second) + connCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - conn, err := pgx.Connect(connCtx, dsn) + runtime, err := openProbeRuntime(connCtx, dsn) if err != nil { - return "", fmt.Errorf("postgres probe connect: %w", scrub(err, dsn)) + return "", fmt.Errorf("postgres probe open: %w", scrub(err, dsn)) } - defer conn.Close(context.Background()) + defer func() { _ = runtime.Close() }() + conn, err := runtime.ConnSource().Acquire(connCtx) + if err != nil { + return "", fmt.Errorf("postgres probe acquire: %w", scrub(err, dsn)) + } + defer conn.Release() var version string if err := conn.QueryRow(connCtx, "SELECT version()").Scan(&version); err != nil { return "", fmt.Errorf("postgres probe query: %w", scrub(err, dsn)) @@ -93,14 +116,29 @@ func redact(text, secret string) (string, bool) { // a benchmark run outside its namespace. const benchmarkNamespacePrefix = "loom_bench_" +// parsePostgresURL enforces the URL-form law this lane accepts everywhere +// (the same shape provision, arms.json evidence, and the admin DSN use) and +// returns the parsed URL with a non-empty database named. +func parsePostgresURL(dsn string) (*url.URL, string, error) { + parsed, err := url.Parse(strings.TrimSpace(dsn)) + if err != nil || (parsed.Scheme != "postgres" && parsed.Scheme != "postgresql") { + return nil, "", fmt.Errorf("postgres dsn unparsable: URL form postgres://… is required (credentials redacted)") + } + database := strings.TrimPrefix(parsed.Path, "/") + if database == "" { + return nil, "", fmt.Errorf("postgres dsn unparsable: no database named (credentials redacted)") + } + return parsed, database, nil +} + func guardPostgresNamespace(dsn string) error { - config, err := pgx.ParseConfig(dsn) + _, database, err := parsePostgresURL(dsn) if err != nil { - return fmt.Errorf("postgres dsn unparsable: %s", redactSecretText(err.Error(), dsn)) + return err } - if !strings.HasPrefix(config.Database, benchmarkNamespacePrefix) { + if !strings.HasPrefix(database, benchmarkNamespacePrefix) { return fmt.Errorf("postgres database %q is outside the benchmark namespace %s*; refusing to run against it", - redactSecretText(config.Database, dsn), benchmarkNamespacePrefix) + redactSecretText(database, dsn), benchmarkNamespacePrefix) } return nil } @@ -119,22 +157,34 @@ func randomRunIDSuffix() string { } func parsePostgresDSN(dsn string) (host string, port int, database, user, password, sslmode string, err error) { - config, err := pgx.ParseConfig(dsn) + parsed, database, err := parsePostgresURL(dsn) if err != nil { return "", 0, "", "", "", "", fmt.Errorf("unparsable postgres dsn") } - host = config.Host - port = int(config.Port) - if port == 0 { - port = 5432 - } - database, user, password = config.Database, config.User, string(config.Password) - sslmode = "disable" - if config.TLSConfig != nil { - sslmode = config.TLSConfig.ServerName - if sslmode == "" { - sslmode = "require" + host = parsed.Hostname() + if host == "" { + return "", 0, "", "", "", "", fmt.Errorf("unparsable postgres dsn") + } + port = 5432 + if rawPort := parsed.Port(); rawPort != "" { + p, perr := strconv.Atoi(rawPort) + if perr != nil || p <= 0 || p > 65535 { + return "", 0, "", "", "", "", fmt.Errorf("unparsable postgres dsn") } + port = p + } + if parsed.User != nil { + user = parsed.User.Username() + password, _ = parsed.User.Password() + } + if user == "" { + return "", 0, "", "", "", "", fmt.Errorf("unparsable postgres dsn") + } + // sslmode rides the query parameter; the lane's DSNs state it + // explicitly, and disable is the honest local default. + sslmode = parsed.Query().Get("sslmode") + if sslmode == "" { + sslmode = "disable" } return host, port, database, user, password, sslmode, nil } diff --git a/internal/dataframe/benchmark/cmd/benchcli/setup.go b/internal/dataframe/benchmark/cmd/benchcli/setup.go index 2b79c6df4..0fd255749 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/setup.go +++ b/internal/dataframe/benchmark/cmd/benchcli/setup.go @@ -10,8 +10,6 @@ import ( "strings" "time" - "github.com/jackc/pgx/v5" - backendspostgres "github.com/calypr/loom/internal/backends/postgres/control" ) @@ -23,9 +21,26 @@ import ( // requires: the caller CREATES the six assigned schemas, then the public // factory bootstraps them with SetupMigrate; the server later opens the // same placement with SetupNone (the factory's arrangement law). +// +// DDL runs on the factory's public ConnSource port: the PostgreSQL driver +// stays confined to internal/backends (ownership contract), and every +// identifier interpolated into DDL first passes a closed regex gate, then +// carries SQL-standard double-quote escaping. var benchDatabaseRe = regexp.MustCompile(`^` + benchmarkNamespacePrefix + `[a-z0-9_]{4,48}$`) +var benchSchemaIdentRe = regexp.MustCompile(`^[a-z_][a-z0-9_]{0,62}$`) + +// quoteBenchIdent renders name as a quoted SQL identifier. It refuses any +// name outside the closed benchmark shapes: no unvalidated text ever +// reaches DDL. +func quoteBenchIdent(name string) (string, error) { + if !benchSchemaIdentRe.MatchString(name) { + return "", fmt.Errorf("identifier %q is outside the benchmark shape", name) + } + return `"` + strings.ReplaceAll(name, `"`, `""`) + `"`, nil +} + func adminDSNFromEnv() (string, error) { dsn := strings.TrimSpace(os.Getenv("LOOM_BENCH_ADMIN_DSN")) if dsn == "" { @@ -84,46 +99,65 @@ func setupPG(ctx context.Context, adminDSN, database, schemaPrefix string) error !strings.HasPrefix(schemaPrefix, benchmarkNamespacePrefix) { return fmt.Errorf("schema prefix %q produces schemas outside the benchmark namespace", schemaPrefix) } - admin, err := pgx.Connect(ctx, adminDSN) + quotedDatabase, err := quoteBenchIdent(database) + if err != nil { + return err + } + admin, err := openProbeRuntime(ctx, adminDSN) + if err != nil { + return fmt.Errorf("admin connect (credentials redacted): %w", err) + } + defer func() { _ = admin.Close() }() + adminConn, err := admin.ConnSource().Acquire(ctx) if err != nil { - return fmt.Errorf("admin connect (credentials redacted)") + return fmt.Errorf("admin acquire (credentials redacted): %w", err) } - defer admin.Close(ctx) + releaseAdmin := true + defer func() { + if releaseAdmin { + adminConn.Release() + } + }() var exists bool - if err := admin.QueryRow(ctx, "SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1)", database).Scan(&exists); err != nil { + if err := adminConn.QueryRow(ctx, "SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1)", database).Scan(&exists); err != nil { return err } if exists { // Fresh database per run keeps digests and timings attributable. return fmt.Errorf("database %s already exists; drop-pg first (fresh per run keeps evidence attributable)", database) } - if _, err := admin.Exec(ctx, "CREATE DATABASE "+pgx.Identifier{database}.Sanitize()); err != nil { + if _, err := adminConn.Exec(ctx, "CREATE DATABASE "+quotedDatabase); err != nil { return fmt.Errorf("create database: %w", err) } - if err := admin.Close(ctx); err != nil { - return err - } + adminConn.Release() + releaseAdmin = false // explicit release; the guard defers the rest targetDSN, err := withDatabase(adminDSN, database) if err != nil { return err } - target, err := pgx.Connect(ctx, targetDSN) + target, err := openProbeRuntime(ctx, targetDSN) + if err != nil { + return fmt.Errorf("target connect (credentials redacted): %w", err) + } + defer func() { _ = target.Close() }() + targetConn, err := target.ConnSource().Acquire(ctx) if err != nil { - return fmt.Errorf("target connect (credentials redacted)") + return fmt.Errorf("target acquire (credentials redacted): %w", err) } + defer targetConn.Release() schemas := map[backendspostgres.Series]string{} for _, series := range backendspostgres.AllSeries() { schema := schemaPrefix + "_" + string(series) - if _, err := target.Exec(ctx, "CREATE SCHEMA "+pgx.Identifier{schema}.Sanitize()); err != nil { - target.Close(ctx) + quotedSchema, err := quoteBenchIdent(schema) + if err != nil { + return err + } + if _, err := targetConn.Exec(ctx, "CREATE SCHEMA "+quotedSchema); err != nil { return fmt.Errorf("create schema %s: %w", schema, err) } schemas[series] = schema } - if err := target.Close(ctx); err != nil { - return err - } // Canonical bootstrap: the public factory with SetupMigrate, exactly as // the server's live composition fixtures arrange it. @@ -142,12 +176,21 @@ func setupPG(ctx context.Context, adminDSN, database, schemaPrefix string) error } func dropPG(ctx context.Context, adminDSN, database string) error { - admin, err := pgx.Connect(ctx, adminDSN) + quotedDatabase, err := quoteBenchIdent(database) + if err != nil { + return err + } + admin, err := openProbeRuntime(ctx, adminDSN) + if err != nil { + return fmt.Errorf("admin connect (credentials redacted): %w", err) + } + defer func() { _ = admin.Close() }() + adminConn, err := admin.ConnSource().Acquire(ctx) if err != nil { - return fmt.Errorf("admin connect (credentials redacted)") + return fmt.Errorf("admin acquire (credentials redacted): %w", err) } - defer admin.Close(ctx) - _, err = admin.Exec(ctx, "DROP DATABASE IF EXISTS "+pgx.Identifier{database}.Sanitize()+" WITH (FORCE)") + defer adminConn.Release() + _, err = adminConn.Exec(ctx, "DROP DATABASE IF EXISTS "+quotedDatabase+" WITH (FORCE)") return err } diff --git a/scripts/package_audit.py b/scripts/package_audit.py index ece82def5..7b60b7d86 100644 --- a/scripts/package_audit.py +++ b/scripts/package_audit.py @@ -67,6 +67,8 @@ "internal/backends/arango/internal/catalog": "Persist and retrieve catalog facts in ArangoDB.", "internal/backends/postgres/internal/catalog": "Persist and retrieve catalog facts in PostgreSQL 18: publish-last generation-scoped resource inventory builds, bounded field-profile records with completeness and truncation metadata, leased compare-and-swap-guarded semantic inventory build state, deterministic semantic-inventory contribution rows with content-addressed replay and conflict classification, byte-order-stable keyset pages over retained resources, and authorization-scoped semantic-inventory pages and selection resolution served directly from contribution aggregates.", "internal/ci": "Exercise repository-wide Go formatting and vet gates, including injected fault detection.", + "internal/dataframe/benchmark": "Run the Arango-versus-PostgreSQL matched dataframe benchmark: deterministic fixtures, canonical bags and correctness gates evaluated before any timing is claimed, control runs, and report artifacts that a re-computing verifier must accept, with ClickHouse-backed publication legs disclosed as planned/not_run when the service is absent.", + "internal/dataframe/benchmark/cmd/benchcli": "Own the benchmark command-line entry point: fail-closed live-run preflight, planned disclosure artifacts, report verification authority, and the loom_bench PostgreSQL database lifecycle.", "internal/dataframe/columntransform": "Define closed, backend-neutral transformations over one selected output column value.", "internal/dataframe/compiler": "Orchestrate semantic planning, physical lowering, optimization, rendering, and compiled-query assembly.", "internal/dataframe/compiler/capability": "Probe which dataframe language capabilities the compiler can prove and support.", From 51fad8e8f78226f1c2b509a5d8cc5cce628ffc23 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 03:58:28 +0000 Subject: [PATCH 23/45] fix(benchmark): exclude unproven workloads from live legs with named reasons Addresses review r4559430560: derive-age-band was unpassable by construction (the workspace authors DISTINCT_VALUES over raw valueQuantity.value while the oracle emits band labels; fixture ages [1000,10000) make the young/middle branches dead), and sort-limit-topn carries an unproven first-only assumption over multi-valued name[].family. Live battery law: a live leg runs exactly the authoring-proven workloads. Registry-excluded workloads are never authored, previewed, or timed; they ship as blocked gate rows with the LiveBatteryExclusion marker, one disclosure row per running leg naming the exact defect, and zero timing evidence. VerifyReport ties marker, disclosure text, arm posture, and sample-freedom back to LiveBatteryExclusions(); RunStatusScoped skips disclosed exclusions; benchcli's plan matcher and both report fixtures follow the same law. The pre-timing authorization gate moves to a proven workload (group-count-by-patient) since filter-active-gender is now registry-excluded. Validation: go test ./internal/dataframe/benchmark/... green (both packages, incl. 8 new live-battery forgery subtests and TestLiveBatteryExclusionsRegistry); node scripts/benchmark-backends.test.mjs 12/12; go build/vet clean. --- internal/dataframe/benchmark/armplan.go | 117 ++++++++++++++- internal/dataframe/benchmark/armplan_test.go | 33 +++-- internal/dataframe/benchmark/authoring.go | 57 ++++++-- .../dataframe/benchmark/authoring_test.go | 43 ++++++ .../benchmark/cmd/benchcli/guards_test.go | 15 ++ .../dataframe/benchmark/cmd/benchcli/main.go | 33 ++++- internal/dataframe/benchmark/driver.go | 34 ++++- internal/dataframe/benchmark/driver_test.go | 17 ++- .../dataframe/benchmark/publication_test.go | 47 ++++-- internal/dataframe/benchmark/report.go | 71 ++++++++- internal/dataframe/benchmark/report_test.go | 136 +++++++++++++++--- 11 files changed, 538 insertions(+), 65 deletions(-) diff --git a/internal/dataframe/benchmark/armplan.go b/internal/dataframe/benchmark/armplan.go index 85d72d4fb..fcaeabe63 100644 --- a/internal/dataframe/benchmark/armplan.go +++ b/internal/dataframe/benchmark/armplan.go @@ -84,6 +84,24 @@ type PublicationPlan struct { Capability string `json:"capability,omitempty"` } +// ApplyExcludedWorkloadGateReports replaces any gate report for an +// excluded workload with its disclosed blocked row, so no construction +// path can leave a marker-less (or worse, claiming) row for a workload +// that was never allowed to run. +func ApplyExcludedWorkloadGateReports(gates, excludedReports []WorkloadGateReport) []WorkloadGateReport { + excludedIDs := map[string]bool{} + for _, g := range excludedReports { + excludedIDs[g.WorkloadID] = true + } + out := make([]WorkloadGateReport, 0, len(gates)+len(excludedReports)) + for _, g := range gates { + if !excludedIDs[g.WorkloadID] { + out = append(out, g) + } + } + return append(out, excludedReports...) +} + // PublicationExcluded reports whether the plan declares the publication // chain not run. A nil plan means the field predates the exclusion model // and is treated as run for leg/scope checks; VerifyReport still rejects a @@ -96,6 +114,90 @@ func (p RunPlan) PublicationExcluded() bool { // the publication chain; VerifyReport recognizes the exclusion by it. const PublicationChainReason = "publication chain not run: " +// WorkloadNotRunReason prefixes disclosure rows for a single workload +// excluded from live legs (disproven or unproven oracle/output agreement); +// VerifyReport recognizes the exclusion by it. +const WorkloadNotRunReason = "workload not run: " + +// LegNotRunReason prefixes disclosure rows and blocked-arm details for a +// leg that did not execute at all. +const LegNotRunReason = "leg not run: " + +// LiveBatteryExclusionMarker prefixes the ExclusionReason of gate reports +// built by ExcludedWorkloadGateReports. RunStatusScoped skips these rows +// (they are disclosed absences, not failures); VerifyReport is what ties +// the marker back to LiveBatteryExclusions() so it cannot be forged onto +// a workload the harness could have run. +const LiveBatteryExclusionMarker = "live battery exclusion: " + +// AppendWorkloadDisclosures adds the disclosure rows for excluded +// workloads on every running leg, skipping rows already present. Both +// benchcli (preflight) and the driver (final plan copy) call it, so it +// must be idempotent. +func AppendWorkloadDisclosures(plan *RunPlan, excluded map[string]string) { + seen := map[string]bool{} + for _, row := range plan.PlannedNotRun { + seen[row.WorkloadID+"|"+row.Backend+"|"+row.Reason] = true + } + for _, row := range WorkloadDisclosures(plan.Legs, excluded) { + if !seen[row.WorkloadID+"|"+row.Backend+"|"+row.Reason] { + plan.PlannedNotRun = append(plan.PlannedNotRun, row) + } + } +} + +// WorkloadDisclosures expands the per-workload exclusions across every +// running leg, so an excluded workload cannot quietly keep gate or timing +// rows on a leg that was never allowed to run it. +func WorkloadDisclosures(legs []LegPlan, excluded map[string]string) []PlannedNotRun { + var rows []PlannedNotRun + for _, leg := range legs { + if leg.Mode != "run" { + continue + } + for _, spec := range Battery { + if reason, ok := excluded[spec.ID]; ok { + rows = append(rows, PlannedNotRun{ + WorkloadID: spec.ID, Backend: leg.Backend, + Reason: WorkloadNotRunReason + reason, + }) + } + } + } + return rows +} + +// ExcludedWorkloadGateReports builds the blocked gate reports for +// excluded workloads: running legs blocked by the workload reason, +// not-run legs by their leg capability. Claimable stays false. +func ExcludedWorkloadGateReports(legs []LegPlan, excluded map[string]string) []WorkloadGateReport { + legCapability := map[string]string{} + for _, leg := range legs { + if leg.Mode != "run" { + legCapability[leg.Backend] = leg.Capability + } + } + detail := func(backend, workloadID string) string { + if cap, blocked := legCapability[backend]; blocked { + return LegNotRunReason + cap + } + return WorkloadNotRunReason + excluded[workloadID] + } + var reports []WorkloadGateReport + for _, spec := range Battery { + if _, ok := excluded[spec.ID]; !ok { + continue + } + reports = append(reports, WorkloadGateReport{ + WorkloadID: spec.ID, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelBlocked, Detail: detail(BackendArango, spec.ID)}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelBlocked, Detail: detail(BackendPostgres, spec.ID)}, + ExclusionReason: LiveBatteryExclusionMarker + detail(BackendArango, spec.ID) + " | " + detail(BackendPostgres, spec.ID), + }) + } + return reports +} + // PublicationDisclosures expands the publication-chain exclusion across // every battery workload of every running leg, so "publish excluded" // cannot quietly leave workload rows claiming measurement on a chain that @@ -130,7 +232,7 @@ func PublicationGateReports(legs []LegPlan, capability string) []WorkloadGateRep } detail := func(backend string) string { if cap, blocked := legCapability[backend]; blocked { - return "leg not run: " + cap + return LegNotRunReason + cap } return PublicationChainReason + capability } @@ -231,7 +333,7 @@ func PlannedGateReports(legs []LegPlan) []WorkloadGateReport { for _, leg := range legs { capability[leg.Backend] = leg.Capability } - detail := func(backend string) string { return "leg not run: " + capability[backend] } + detail := func(backend string) string { return LegNotRunReason + capability[backend] } var reports []WorkloadGateReport for _, spec := range Battery { reports = append(reports, WorkloadGateReport{ @@ -327,7 +429,7 @@ func (p RunPlan) NotRunBackends() []string { // did not run, so gates can distinguish "expectedly absent" from "failed". func ExpectedOutcomeForScope(leg LegPlan) (WorkloadOutcome, string) { if leg.Mode == "not_run" { - return OutcomeLabelBlocked, "leg not run: " + leg.Capability + return OutcomeLabelBlocked, LegNotRunReason + leg.Capability } return "", "" } @@ -365,6 +467,15 @@ func RunStatusScoped(reports []WorkloadGateReport, scope string) (string, []stri } armOutcomes = []WorkloadOutcome{report.Postgres.Outcome} } + if strings.HasPrefix(report.ExclusionReason, LiveBatteryExclusionMarker) { + // A disclosed live-battery exclusion is neither a pass nor + // a failure: it is a named absence. VerifyReport ties the + // marker to LiveBatteryExclusions() and requires the + // blocked posture, a disclosure row, and zero samples for + // every such row; a marker on anything else is refused + // there, so skipping here cannot launder a blocked run. + continue + } blocked := false for _, outcome := range armOutcomes { if outcome == OutcomeLabelBlocked { diff --git a/internal/dataframe/benchmark/armplan_test.go b/internal/dataframe/benchmark/armplan_test.go index 0316077ce..62a39e0c7 100644 --- a/internal/dataframe/benchmark/armplan_test.go +++ b/internal/dataframe/benchmark/armplan_test.go @@ -48,13 +48,22 @@ func validPostgresOnlyReport(t *testing.T) *Report { for i := range plan.PlannedNotRun { plan.PlannedNotRun[i].Reason = "leg not run: " + testArangoCapability } - gate := postgresOnlyGate(WorkloadFilterActiveGender, strings.Repeat("c", 64)) + liveExcluded := LiveBatteryExclusions() + var gates []WorkloadGateReport var samples []Sample - samples = append(samples, sample(BackendPostgres, WorkloadFilterActiveGender, OutcomePassed, 5, true)) - for i := 0; i < 4; i++ { - samples = append(samples, sample(BackendPostgres, WorkloadFilterActiveGender, OutcomePassed, 10+float64(i), false)) + for _, spec := range Battery { + if _, excluded := liveExcluded[spec.ID]; excluded { + continue + } + gates = append(gates, postgresOnlyGate(spec.ID, strings.Repeat("c", 64))) + samples = append(samples, sample(BackendPostgres, spec.ID, OutcomePassed, 5, true)) + for i := 0; i < 4; i++ { + samples = append(samples, sample(BackendPostgres, spec.ID, OutcomePassed, 10+float64(i), false)) + } } - report, err := BuildReport(identity, plan, controls, []WorkloadGateReport{gate}, samples) + gates = ApplyExcludedWorkloadGateReports(gates, ExcludedWorkloadGateReports(plan.Legs, liveExcluded)) + AppendWorkloadDisclosures(&plan, liveExcluded) + report, err := BuildReport(identity, plan, controls, gates, samples) if err != nil { t.Fatalf("honest postgres-only report must build: %v", err) } @@ -132,10 +141,10 @@ func TestPostgresOnlyForgerySuite(t *testing.T) { r.GateReports[0].Arango.Outcome = OutcomeLabelPassed }, "arango-samples-smuggled": func(r *Report) { - r.Samples = append(r.Samples, sample(BackendArango, WorkloadFilterActiveGender, OutcomePassed, 20, false)) + r.Samples = append(r.Samples, sample(BackendArango, primaryTiming(), OutcomePassed, 20, false)) r.Summaries = append(r.Summaries, SummaryRow{ - WorkloadID: WorkloadFilterActiveGender, Backend: BackendArango, Cache: CacheWarm, - Summary: Summarize([]Sample{sample(BackendArango, WorkloadFilterActiveGender, OutcomePassed, 20, false)}), + WorkloadID: primaryTiming(), Backend: BackendArango, Cache: CacheWarm, + Summary: Summarize([]Sample{sample(BackendArango, primaryTiming(), OutcomePassed, 20, false)}), }) }, "disclosure-rows-dropped": func(r *Report) { @@ -152,9 +161,9 @@ func TestPostgresOnlyForgerySuite(t *testing.T) { }, "speed-claim-injected-without-arango": func(r *Report) { r.Claims = append(r.Claims, Claim{ - WorkloadID: WorkloadFilterActiveGender, - Arango: Summarize([]Sample{sample(BackendArango, WorkloadFilterActiveGender, OutcomePassed, 40, false)}), - Postgres: Summarize([]Sample{sample(BackendPostgres, WorkloadFilterActiveGender, OutcomePassed, 10, false)}), + WorkloadID: primaryTiming(), + Arango: Summarize([]Sample{sample(BackendArango, primaryTiming(), OutcomePassed, 40, false)}), + Postgres: Summarize([]Sample{sample(BackendPostgres, primaryTiming(), OutcomePassed, 10, false)}), Ratio: SpeedRatio{ArangoOverPostgres: 4.0}, }) }, @@ -170,7 +179,7 @@ func TestPostgresOnlyForgerySuite(t *testing.T) { } // The converse: disclosure rows for a leg that DID run are lies too. t.Run("disclosure-for-ran-leg", forgeWith(t, validReport, func(r *Report) { - r.Plan.PlannedNotRun = []PlannedNotRun{{WorkloadID: WorkloadFilterActiveGender, Backend: BackendArango, Reason: "pretend we skipped it"}} + r.Plan.PlannedNotRun = []PlannedNotRun{{WorkloadID: primaryTiming(), Backend: BackendArango, Reason: WorkloadNotRunReason + LiveBatteryExclusions()[WorkloadDeriveAgeBand]}} })) } diff --git a/internal/dataframe/benchmark/authoring.go b/internal/dataframe/benchmark/authoring.go index 13fd0fa9f..d498e061d 100644 --- a/internal/dataframe/benchmark/authoring.go +++ b/internal/dataframe/benchmark/authoring.go @@ -263,8 +263,10 @@ func BuildWorkspace(workloadID string) ([]byte, error) { }, nil, nil), nil case WorkloadDeriveAgeBand: // No acceptance precedent for value transformations in the workspace - // wire shape: authored best-effort, decoder verdict decides; on - // rejection the workload ships planned/not_run with this reason. + // wire shape, and the authored approximation cannot pass its own + // oracle (raw distinct values vs band labels): LiveBatteryExclusions + // ships this workload planned/not_run with the named defect until + // the ClickHouse lane rehearses a proven derivation. return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ aggregateCol("observation_id", "Observation id", "string", "observation", "DISTINCT_VALUES", "id", "code.coding[].code", ObservationCodeAge, 0), @@ -317,13 +319,52 @@ func AuthoringVerification() map[string]AuthoringStatus { // unverifiedWorkloads lists shapes without acceptance-fixture precedent; // rehearsal classifies them and the outcome is recorded in the report. +// Anything not proven ships planned/not_run (live battery law below), so +// the live legs can never fail a gate a shape could not pass by +// construction (review r4559430560). var unverifiedWorkloads = map[string]bool{ - WorkloadFilterActiveGender: true, // fixedFilters over a boolean field column - WorkloadDistinctFamily: true, // DISTINCT_VALUES at the root occurrence - WorkloadDeriveAgeBand: true, // transformation intent approximated - WorkloadStudyCorrelation: true, // extensionByUrl returning a reference string - WorkloadStageCounts: true, // DISTINCT_VALUES + COUNT pair at one occurrence (per-row grain expectation) - WorkloadSortLimitTopN: false, // field-only columns: proven shape + WorkloadFilterActiveGender: true, // fixedFilters over a boolean field column + WorkloadDistinctFamily: true, // DISTINCT_VALUES at the root occurrence + WorkloadDeriveAgeBand: true, // transformation intent approximated + WorkloadStudyCorrelation: true, // extensionByUrl returning a reference string + WorkloadStageCounts: true, // DISTINCT_VALUES + COUNT pair at one occurrence (per-row grain expectation) + WorkloadSortLimitTopN: true, // VALUE over multi-valued name[].family: row expansion unproven (review r4559430560) +} + +// workloadNotRunReasons names why each not-proven workload is excluded +// from live legs. Every non-proven verdict must carry an entry: an +// exclusion without a named reason is a silent coverage hole, which is +// the exact deception class the report verifier exists to refuse. +var workloadNotRunReasons = map[string]string{ + WorkloadFilterActiveGender: "shape has no acceptance-fixture precedent: fixedFilters over a boolean field column; live rehearsal (ClickHouse lane) must prove the oracle/output agreement before this leg runs", + WorkloadDistinctFamily: "shape has no acceptance-fixture precedent: DISTINCT_VALUES at the root occurrence; live rehearsal must prove the oracle/output agreement before this leg runs", + WorkloadDeriveAgeBand: "oracle/output agreement disproven in source: the workspace authors age_band as DISTINCT_VALUES over raw valueQuantity.value while the oracle emits band labels, and fixture ages [1000,10000) make the young/middle branches dead; needs a proven case/if derivation (see AgeBandYoungUnder/AgeBandMiddleUnder) or an oracle re-model, rehearsed in the ClickHouse lane", + WorkloadStudyCorrelation: "shape has no acceptance-fixture precedent: extensionByUrl returning a reference string; live rehearsal must prove the oracle/output agreement before this leg runs", + WorkloadStageCounts: "shape has no acceptance-fixture precedent: DISTINCT_VALUES + COUNT pair at one occurrence with a per-row grain expectation; live rehearsal must prove the oracle/output agreement before this leg runs", + WorkloadSortLimitTopN: "oracle/output agreement unproven: VALUE columns over multi-valued name[].family with a second family entry on every 4th patient; the oracle assumes first-only while preview row expansion (first-only vs row-per-value) has no acceptance precedent; live rehearsal must pin the scalarization contract", +} + +// LiveBatteryExclusions returns the workloads that may not run in a live +// leg and the named reason for each: anything the decoder rejects, and +// anything without an acceptance-proven shape or disproven oracle/output +// agreement. The driver skips authoring, publication, preview, and timing +// for these; the report verifier requires one disclosure row per excluded +// workload per running leg and refuses any timing evidence for them. +func LiveBatteryExclusions() map[string]string { + out := map[string]string{} + for workloadID, status := range AuthoringVerification() { + if status == AuthoringProven { + continue + } + reason := workloadNotRunReasons[workloadID] + if reason == "" { + // Fail closed in the disclosure itself: an unproven verdict + // with no reason is a bug, not a licence to run silently. + reason = "verdict " + string(status) + " without a named reason (harness bug; refusing to run it)" + } + out[workloadID] = reason + } + return out } // BuildAllWorkspaces returns every battery workspace keyed by workload id. diff --git a/internal/dataframe/benchmark/authoring_test.go b/internal/dataframe/benchmark/authoring_test.go index c5c45a81b..e352b9997 100644 --- a/internal/dataframe/benchmark/authoring_test.go +++ b/internal/dataframe/benchmark/authoring_test.go @@ -102,3 +102,46 @@ func TestAuthoringVerificationNoneRejected(t *testing.T) { } } } + +// TestLiveBatteryExclusionsRegistry pins the live battery law from review +// r4559430560: a live leg runs exactly the authoring-proven workloads, +// and every exclusion carries a named reason. A silent exclusion is the +// same deception class the report verifier exists to refuse. +func TestLiveBatteryExclusionsRegistry(t *testing.T) { + exclusions := LiveBatteryExclusions() + statuses := AuthoringVerification() + live := 0 + for _, spec := range Battery { + reason, excluded := exclusions[spec.ID] + if statuses[spec.ID] == AuthoringProven && !unverifiedWorkloads[spec.ID] { + if excluded { + t.Errorf("%s: proven workload excluded from live legs: %q", spec.ID, reason) + } + live++ + continue + } + if !excluded { + t.Errorf("%s: unproven verdict %q not excluded from live legs", spec.ID, statuses[spec.ID]) + continue + } + if strings.TrimSpace(reason) == "" { + t.Errorf("%s: exclusion without a named reason", spec.ID) + } + } + if live == 0 { + t.Fatal("live battery is empty: nothing would ever be timed") + } + if live == len(Battery) { + t.Fatal("nothing is excluded: the disproven oracle rows must stay out of live legs") + } + // The two review findings must be named in the registry with their + // actual defects, not generic prose. + if r := exclusions[WorkloadDeriveAgeBand]; !strings.Contains(r, "DISTINCT_VALUES") || + !strings.Contains(r, "band labels") || !strings.Contains(r, "valueQuantity.value") { + t.Errorf("derive-age-band exclusion must name the oracle/workspace defect: %q", r) + } + if r := exclusions[WorkloadSortLimitTopN]; !strings.Contains(r, "name[].family") || + !strings.Contains(r, "first-only") { + t.Errorf("sort-limit-topn exclusion must name the multi-family risk: %q", r) + } +} diff --git a/internal/dataframe/benchmark/cmd/benchcli/guards_test.go b/internal/dataframe/benchmark/cmd/benchcli/guards_test.go index a1d756f0f..b0037a7bb 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/guards_test.go +++ b/internal/dataframe/benchmark/cmd/benchcli/guards_test.go @@ -153,6 +153,8 @@ func TestPlanNotRunMatchesBatteryBothWays(t *testing.T) { } only := benchmark.PostgresOnlyPlan("cap a") only.PlannedNotRun = benchmark.PlannedNotRunForBackends(only.NotRunBackends()) + // the ran leg must also carry one disclosure row per excluded workload + benchmark.AppendWorkloadDisclosures(&only, benchmark.LiveBatteryExclusions()) if !planNotRunMatchesBattery(only) { t.Fatal("complete postgres-only disclosure rejected") } @@ -164,6 +166,19 @@ func TestPlanNotRunMatchesBatteryBothWays(t *testing.T) { if planNotRunMatchesBattery(only) { t.Fatal("disclosure row for a ran leg accepted") } + // a ran-leg workload row must match the registry exactly + forged := benchmark.PostgresOnlyPlan("cap a") + forged.PlannedNotRun = benchmark.PlannedNotRunForBackends(forged.NotRunBackends()) + benchmark.AppendWorkloadDisclosures(&forged, benchmark.LiveBatteryExclusions()) + for i := range forged.PlannedNotRun { + if strings.HasPrefix(forged.PlannedNotRun[i].Reason, benchmark.WorkloadNotRunReason) { + forged.PlannedNotRun[i].Reason = benchmark.WorkloadNotRunReason + "because I said so" + break + } + } + if planNotRunMatchesBattery(forged) { + t.Fatal("registry-mismatched workload disclosure accepted") + } } func TestWriteRunArtifactsRoundTripsThroughVerifier(t *testing.T) { diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go index 5956fa3ad..1ad8bd2db 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/main.go +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -218,6 +218,9 @@ func runLive() { default: preflight("unknown --publication %q (want excluded | run)", f.publication) } + // Live battery law: registry-excluded workloads ride along as + // disclosed planned/not_run rows on every ran leg. + benchmark.AppendWorkloadDisclosures(&plan, benchmark.LiveBatteryExclusions()) if !planNotRunMatchesBattery(plan) { fmt.Fprintln(os.Stderr, "benchcli run: internal plan inconsistency") os.Exit(1) @@ -376,17 +379,41 @@ func planNotRunMatchesBattery(plan benchmark.RunPlan) bool { for _, leg := range plan.Legs { mode[leg.Backend] = leg.Mode } + liveExcluded := benchmark.LiveBatteryExclusions() counts := map[string]int{} for _, row := range plan.PlannedNotRun { - if mode[row.Backend] == "run" && !strings.HasPrefix(row.Reason, benchmark.PublicationChainReason) { - return false // only publication-chain disclosures may sit on a ran leg + switch { + case mode[row.Backend] != "run": + // Not-run legs disclose exactly their leg capability. + if !strings.HasPrefix(row.Reason, "leg not run") { + return false + } + case strings.HasPrefix(row.Reason, benchmark.PublicationChainReason): + if !plan.PublicationExcluded() { + return false + } + case strings.HasPrefix(row.Reason, benchmark.WorkloadNotRunReason): + // Only registry-excluded workloads may sit on a ran leg, + // under their exact named reason. + reason, ok := liveExcluded[row.WorkloadID] + if !ok || row.Reason != benchmark.WorkloadNotRunReason+reason { + return false + } + default: + return false // only the three honest classes above } counts[row.Backend]++ } for _, leg := range plan.Legs { want := 0 - if leg.Mode == "not_run" || plan.PublicationExcluded() { + switch leg.Mode { + case "not_run": want = len(benchmark.Battery) + default: + if plan.PublicationExcluded() { + want += len(benchmark.Battery) + } + want += len(liveExcluded) } if counts[leg.Backend] != want { return false diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index 669fb44cb..f5200ea9b 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -116,6 +116,10 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden } else if opts.Plan.Publication != nil && opts.Plan.Publication.Run && !opts.ClickHouseEnabled { return nil, nil, fmt.Errorf("benchmark: the publication chain is ClickHouse-backed end to end; refusing to claim it ran without ClickHouseEnabled") } + // Live battery law: workloads whose oracle/output agreement is + // disproven or unproven are never authored, previewed, or timed on a + // live leg; they ship as disclosed blocked rows with named reasons. + excludedWorkloads := LiveBatteryExclusions() if err := opts.Config.Validate(); err != nil { return nil, nil, err } @@ -214,8 +218,13 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden identity.PostgresVersion = version } } - report, err := BuildReport(identity, opts.Plan, ControlRuns{}, - PublicationGateReports(opts.Plan.Legs, opts.Plan.Publication.Capability), nil) + plan := opts.Plan + plan.PlannedNotRun = append([]PlannedNotRun(nil), opts.Plan.PlannedNotRun...) + AppendWorkloadDisclosures(&plan, excludedWorkloads) + report, err := BuildReport(identity, plan, ControlRuns{}, + ApplyExcludedWorkloadGateReports( + PublicationGateReports(opts.Plan.Legs, opts.Plan.Publication.Capability), + ExcludedWorkloadGateReports(opts.Plan.Legs, excludedWorkloads)), nil) if err != nil { return nil, nil, fmt.Errorf("benchmark: excluded-publication report would not verify (refusing to emit): %w", err) } @@ -238,6 +247,9 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden outputIDs[p.backend] = map[string]string{} } for _, spec := range Battery { + if _, skip := excludedWorkloads[spec.ID]; skip { + continue // disclosed absence: never authored, never timed + } workspace, err := BuildWorkspace(spec.ID) if err != nil { return nil, nil, err @@ -283,8 +295,11 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden // Pre-timing authorization gate (ruling iii). If the scoped principal // sees anything it was not granted, timings are meaningless: abort - // before producing any artifact. - authWorkload := WorkloadFilterActiveGender + // before producing any artifact. The gate runs on a live-battery + // (authoring-proven) workload: an excluded workload is never authored, + // so it cannot host the control. Any authored workload exercises the + // same fence — the whole fixture set uploads under one auth path. + authWorkload := WorkloadGroupCountByPatient baseOpts := RunOptions{ Project: opts.Params.Project, Generation: opts.Params.Generation, AuthResourcePath: opts.Params.AuthResourcePath, @@ -331,6 +346,9 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden } } for _, spec := range Battery { + if _, skip := excludedWorkloads[spec.ID]; skip { + continue // disclosed absence: never authored, never timed + } ra := RunOptions{Project: baseOpts.Project, Generation: baseOpts.Generation, AuthResourcePath: baseOpts.AuthResourcePath, UnrestrictedToken: baseOpts.UnrestrictedToken, ScopedToken: baseOpts.ScopedToken, PreviewLimit: baseOpts.PreviewLimit, Config: baseOpts.Config, SourceCommit: baseOpts.SourceCommit, Explorer: explorerIDByWorkload[spec.ID]} rc := map[string]string{} @@ -380,6 +398,9 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden } gateReports = append(gateReports, report) } + // Registry-excluded workloads appear as disclosed blocked rows, never + // as missing rows: the report says exactly what did not run and why. + gateReports = append(gateReports, ExcludedWorkloadGateReports(opts.Plan.Legs, excludedWorkloads)...) // Control runs. controls := ControlRuns{ @@ -438,7 +459,10 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden } } - report, err := BuildReport(identity, opts.Plan, controls, gateReports, allSamples) + plan := opts.Plan + plan.PlannedNotRun = append([]PlannedNotRun(nil), opts.Plan.PlannedNotRun...) + AppendWorkloadDisclosures(&plan, excludedWorkloads) + report, err := BuildReport(identity, plan, controls, gateReports, allSamples) if err != nil { return nil, nil, fmt.Errorf("benchmark: report would not verify (refusing to emit): %w", err) } diff --git a/internal/dataframe/benchmark/driver_test.go b/internal/dataframe/benchmark/driver_test.go index 2f7d4d2f6..dc02ff85e 100644 --- a/internal/dataframe/benchmark/driver_test.go +++ b/internal/dataframe/benchmark/driver_test.go @@ -100,13 +100,24 @@ func TestDriverBothArmsProtocol(t *testing.T) { if len(evidence) != 2 || evidence[0].Backend != BackendArango { t.Fatalf("evidence: %+v", evidence) } - // both arms must have uploaded the dataset and authored every workload + // both arms must have uploaded the dataset and authored every + // live-battery (proven) workload; registry-excluded workloads are + // never authored (review r4559430560) + live := 0 + for _, spec := range Battery { + if _, excluded := LiveBatteryExclusions()[spec.ID]; !excluded { + live++ + } + } + if live == 0 || live == len(Battery) { + t.Fatalf("expected a partially excluded live battery, got %d of %d", live, len(Battery)) + } for name, f := range map[string]*fakeArm{"arango": fa, "postgres": fp} { if f.uploads != 1 { t.Fatalf("%s: uploads=%d want 1", name, f.uploads) } - if f.publish != len(Battery)+1 { // + empty floor - t.Fatalf("%s: publish=%d want %d", name, f.publish, len(Battery)+1) + if f.publish != live+1 { // + empty floor + t.Fatalf("%s: publish=%d want %d", name, f.publish, live+1) } } // round-trip the artifact diff --git a/internal/dataframe/benchmark/publication_test.go b/internal/dataframe/benchmark/publication_test.go index 44aac814f..c2107be19 100644 --- a/internal/dataframe/benchmark/publication_test.go +++ b/internal/dataframe/benchmark/publication_test.go @@ -28,9 +28,16 @@ func excludedPlan(scope string) RunPlan { plan.PlannedNotRun[i].Reason = "leg not run: " + plan.Legs[0].Capability } plan.PlannedNotRun = append(plan.PlannedNotRun, PublicationDisclosures(plan.Legs, PublicationCapabilityCH)...) + AppendWorkloadDisclosures(&plan, LiveBatteryExclusions()) return plan } +func excludedGates(plan RunPlan) []WorkloadGateReport { + return ApplyExcludedWorkloadGateReports( + PublicationGateReports(plan.Legs, PublicationCapabilityCH), + ExcludedWorkloadGateReports(plan.Legs, LiveBatteryExclusions())) +} + func excludedIdentity(runID string) RunIdentity { return RunIdentity{ RunID: runID, StartedAt: time.Unix(1700000000, 0).UTC(), @@ -48,8 +55,7 @@ func TestExcludedChainReportIsHonestAndVerifies(t *testing.T) { identity := excludedIdentity("bench-excl-a") identity.ArangoVersion = "3.12.x-fake" identity.PostgresVersion = "PostgreSQL 18.x-fake" - report, err := BuildReport(identity, plan, ControlRuns{}, - PublicationGateReports(plan.Legs, PublicationCapabilityCH), nil) + report, err := BuildReport(identity, plan, ControlRuns{}, excludedGates(plan), nil) if err != nil { t.Fatalf("honest excluded-chain report must build: %v", err) } @@ -57,18 +63,31 @@ func TestExcludedChainReportIsHonestAndVerifies(t *testing.T) { t.Fatalf("excluded chain must carry zero timing evidence: %d samples, %d summaries, %d claims", len(report.Samples), len(report.Summaries), len(report.Claims)) } + liveExcluded := LiveBatteryExclusions() for _, gate := range report.GateReports { + _, isExcludedWorkload := liveExcluded[gate.WorkloadID] for _, arm := range []BackendResult{gate.Arango, gate.Postgres} { if arm.Outcome != OutcomeLabelBlocked { t.Fatalf("%s/%s must be blocked, got %s", gate.WorkloadID, arm.Backend, arm.Outcome) } + if isExcludedWorkload { + // Both exclusions are true; the row must name the + // workload reason and carry the live-battery marker. + if !strings.Contains(arm.Detail, WorkloadNotRunReason) { + t.Fatalf("%s/%s detail must name the workload exclusion: %q", gate.WorkloadID, arm.Backend, arm.Detail) + } + if !strings.HasPrefix(gate.ExclusionReason, LiveBatteryExclusionMarker) { + t.Fatalf("%s must carry the live-battery marker: %q", gate.WorkloadID, gate.ExclusionReason) + } + continue + } if !strings.Contains(arm.Detail, PublicationCapabilityCH) { t.Fatalf("%s/%s detail must name the capability: %q", gate.WorkloadID, arm.Backend, arm.Detail) } } } - if want := 2 * len(Battery); len(report.Plan.PlannedNotRun) != want { - t.Fatalf("both running legs must each disclose the whole battery: %d rows want %d", len(report.Plan.PlannedNotRun), want) + if want := 2*len(Battery) + 2*len(liveExcluded); len(report.Plan.PlannedNotRun) != want { + t.Fatalf("both running legs must each disclose the whole battery plus the exclusions: %d rows want %d", len(report.Plan.PlannedNotRun), want) } // The artifact must survive marshal + LoadReport verification. if _, err := LoadReport(mustMarshal(t, report)); err != nil { @@ -82,8 +101,7 @@ func TestExcludedChainForgeryPaths(t *testing.T) { plan := excludedPlan(RunScopePostgresOnly) identity := excludedIdentity("bench-excl-forge") identity.PostgresVersion = "PostgreSQL 18.x-fake" - report, err := BuildReport(identity, plan, ControlRuns{}, - PublicationGateReports(plan.Legs, PublicationCapabilityCH), nil) + report, err := BuildReport(identity, plan, ControlRuns{}, excludedGates(plan), nil) if err != nil { t.Fatalf("baseline excluded report: %v", err) } @@ -107,11 +125,22 @@ func TestExcludedChainForgeryPaths(t *testing.T) { r.Controls.SelfRatioPostgres = 1.0 }, "control values claimed although the publication chain was excluded"}, {"samples fabricated into an excluded run", func(t *testing.T, r *Report) { - r.Samples = append(r.Samples, sample(BackendPostgres, WorkloadFilterActiveGender, OutcomePassed, 10, false)) + r.Samples = append(r.Samples, sample(BackendPostgres, primaryTiming(), OutcomePassed, 10, false)) }, "timing evidence claimed although the publication chain was excluded"}, {"one disclosure row quietly dropped", func(t *testing.T, r *Report) { - // drop a postgres publication-chain row (the last leg's block) - r.Plan.PlannedNotRun = r.Plan.PlannedNotRun[:len(r.Plan.PlannedNotRun)-1] + // drop the last postgres publication-chain row + drop, kept := -1, make([]PlannedNotRun, 0, len(r.Plan.PlannedNotRun)) + for i, row := range r.Plan.PlannedNotRun { + if drop < 0 && row.Backend == BackendPostgres && strings.HasPrefix(row.Reason, PublicationChainReason) { + drop = i + continue + } + kept = append(kept, row) + } + if drop < 0 { + t.Fatal("baseline lacks a postgres publication disclosure row") + } + r.Plan.PlannedNotRun = kept }, "without a disclosure row"}, {"one gate arm flipped to passed", func(t *testing.T, r *Report) { r.GateReports[0].Postgres.Outcome = OutcomeLabelPassed diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go index 5c2e7423f..e29040bff 100644 --- a/internal/dataframe/benchmark/report.go +++ b/internal/dataframe/benchmark/report.go @@ -316,16 +316,33 @@ func VerifyReport(report *Report) error { // A not-run leg must be disclosed per workload and honest in the gates: // blocked arm, zero samples, zero claims. Anything else is a forgery // path ("silently measured nothing and claimed it anyway"). + // Live battery exclusions: workloads whose oracle/output agreement is + // disproven or unproven may never run on a live leg. The registry is + // deterministic (decoder verdicts only); VerifyReport is what ties + // every claim of exclusion back to it. + liveExcluded := LiveBatteryExclusions() disclosed := map[string]bool{} + wlDisclosed := map[string]bool{} for _, row := range report.Plan.PlannedNotRun { if _, absent := notRun[row.Backend]; !absent { // A disclosure row on a ran leg is only honest when it // declares the excluded publication chain under an excluding - // plan; any other row on a ran leg is a forgery path. + // plan, or a registry-excluded workload under its exact + // named reason; any other row on a ran leg is a forgery + // path. if publicationExcluded && strings.HasPrefix(row.Reason, PublicationChainReason) { pubDisclosed[row.WorkloadID+"|"+row.Backend] = true continue } + if reason, excludedWorkload := liveExcluded[row.WorkloadID]; excludedWorkload && + strings.HasPrefix(row.Reason, WorkloadNotRunReason) { + if row.Reason != WorkloadNotRunReason+reason { + return fmt.Errorf("benchmark report: %s/%s workload-not_run disclosure does not match the exclusion registry", + row.WorkloadID, row.Backend) + } + wlDisclosed[row.WorkloadID+"|"+row.Backend] = true + continue + } return fmt.Errorf("benchmark report: planned/not_run row for %s/%s but its leg ran", row.WorkloadID, row.Backend) } disclosed[row.WorkloadID+"|"+row.Backend] = true @@ -346,6 +363,58 @@ func VerifyReport(report *Report) error { } } } + // Live battery law: every battery workload must appear in the gate + // reports of every ran leg (a silently shrunk live matrix is the same + // deception as a silently passed one). Excluded workloads must be + // blocked, marker-carrying, disclosure-backed, and sample-free; the + // marker may never sit on a workload the harness was allowed to run. + liveGate := map[string]WorkloadGateReport{} + for _, gate := range report.GateReports { + if _, dup := liveGate[gate.WorkloadID]; dup { + return fmt.Errorf("benchmark report: duplicate gate report for %s", gate.WorkloadID) + } + liveGate[gate.WorkloadID] = gate + } + for _, leg := range report.Plan.Legs { + if leg.Mode != "run" { + continue + } + for _, spec := range Battery { + gate, ok := liveGate[spec.ID] + if !ok { + return fmt.Errorf("benchmark report: %s leg ran without a gate report for %s", leg.Backend, spec.ID) + } + arm := gate.Arango + if leg.Backend == BackendPostgres { + arm = gate.Postgres + } + if _, excludedWorkload := liveExcluded[spec.ID]; excludedWorkload { + if arm.Outcome != OutcomeLabelBlocked { + return fmt.Errorf("benchmark report: %s/%s shows %s although the workload is excluded from live legs", + spec.ID, leg.Backend, arm.Outcome) + } + if !strings.HasPrefix(gate.ExclusionReason, LiveBatteryExclusionMarker) { + return fmt.Errorf("benchmark report: excluded workload %s on %s without the live-battery exclusion marker", + spec.ID, leg.Backend) + } + if !wlDisclosed[spec.ID+"|"+leg.Backend] { + return fmt.Errorf("benchmark report: %s/%s excluded from live legs without a disclosure row", + spec.ID, leg.Backend) + } + continue + } + if strings.HasPrefix(gate.ExclusionReason, LiveBatteryExclusionMarker) { + return fmt.Errorf("benchmark report: %s carries the live-battery exclusion marker but is not an excluded workload", + spec.ID) + } + } + } + for _, s := range report.Samples { + if _, excludedWorkload := liveExcluded[s.WorkloadID]; excludedWorkload { + return fmt.Errorf("benchmark report: timing samples exist for %s although it is excluded from live legs", + s.WorkloadID) + } + } for _, gate := range report.GateReports { for _, arm := range []BackendResult{gate.Arango, gate.Postgres} { capability, absent := notRun[arm.Backend] diff --git a/internal/dataframe/benchmark/report_test.go b/internal/dataframe/benchmark/report_test.go index e05c95f47..e82e7822c 100644 --- a/internal/dataframe/benchmark/report_test.go +++ b/internal/dataframe/benchmark/report_test.go @@ -14,7 +14,7 @@ import ( func validReport(t *testing.T) *Report { t.Helper() identity := RunIdentity{ - RunID: "bench-2026-11-13-a", StartedAt: time.Unix(1700000000, 0).UTC(), + RunID: "bench-2026-11-13-a", StartedAt: time.Unix(1700000, 0).UTC(), Preset: PresetTiny, SourceCommit: strings.Repeat("a", 40), DatasetDigest: strings.Repeat("b", 64), ArangoVersion: "3.12.x", PostgresVersion: "PostgreSQL 18.x", @@ -29,32 +29,64 @@ func validReport(t *testing.T) *Report { SelfRatioArango: 1.0, SelfRatioPostgres: 1.0, EmptyFloorMillis: 0.4, RatioPolicy: "ratio-only-both-passed", } - gate := WorkloadGateReport{ - WorkloadID: WorkloadFilterActiveGender, Claimable: true, - Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelPassed, RowCount: 5, BagDigest: strings.Repeat("c", 64)}, - Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelPassed, RowCount: 5, BagDigest: strings.Repeat("c", 64)}, - Gates: []GateResult{{Gate: GateBagEquality, Pass: true}}, - } + // Live battery law: proven workloads carry passing gates and samples; + // registry-excluded workloads carry disclosed blocked rows and + // disclosure rows, never samples. + liveExcluded := LiveBatteryExclusions() + var gates []WorkloadGateReport var samples []Sample - for _, backend := range []string{BackendArango, BackendPostgres} { - samples = append(samples, sample(backend, WorkloadFilterActiveGender, OutcomePassed, 5, true)) // warmup - base := 20.0 - if backend == BackendPostgres { - base = 10.0 + for _, spec := range Battery { + if _, excluded := liveExcluded[spec.ID]; excluded { + continue } - for i := 0; i < 4; i++ { - samples = append(samples, sample(backend, WorkloadFilterActiveGender, OutcomePassed, base+float64(i), false)) + gates = append(gates, WorkloadGateReport{ + WorkloadID: spec.ID, Claimable: true, + Arango: BackendResult{Backend: BackendArango, Outcome: OutcomeLabelPassed, RowCount: 5, BagDigest: strings.Repeat("c", 64)}, + Postgres: BackendResult{Backend: BackendPostgres, Outcome: OutcomeLabelPassed, RowCount: 5, BagDigest: strings.Repeat("c", 64)}, + Gates: []GateResult{{Gate: GateBagEquality, Pass: true}}, + }) + for _, backend := range []string{BackendArango, BackendPostgres} { + samples = append(samples, sample(backend, spec.ID, OutcomePassed, 5, true)) // warmup + base := 20.0 + if backend == BackendPostgres { + base = 10.0 + } + for i := 0; i < 4; i++ { + samples = append(samples, sample(backend, spec.ID, OutcomePassed, base+float64(i), false)) + } } } timingPlan := BothArmsPlan() timingPlan.Publication = &PublicationPlan{Run: true} - report, err := BuildReport(identity, timingPlan, controls, []WorkloadGateReport{gate}, samples) + gates = append(gates, ExcludedWorkloadGateReports(timingPlan.Legs, liveExcluded)...) + AppendWorkloadDisclosures(&timingPlan, liveExcluded) + report, err := BuildReport(identity, timingPlan, controls, gates, samples) if err != nil { t.Fatalf("valid report build failed: %v", err) } return report } +// primaryTiming returns the workload validReport puts first among the +// proven (timed) workloads; forgery cases mutate GateReports[0]. +func primaryTiming() string { + for _, spec := range Battery { + if _, excluded := LiveBatteryExclusions()[spec.ID]; !excluded { + return spec.ID + } + } + panic("live battery has no proven workloads") +} + +func gateForID(r *Report, workloadID string) *WorkloadGateReport { + for i := range r.GateReports { + if r.GateReports[i].WorkloadID == workloadID { + return &r.GateReports[i] + } + } + panic("no gate report for " + workloadID) +} + func roundTrip(t *testing.T, report *Report) *Report { t.Helper() data, err := MarshalReport(report) @@ -71,8 +103,8 @@ func roundTrip(t *testing.T, report *Report) *Report { func TestReportRoundTrips(t *testing.T) { report := validReport(t) loaded := roundTrip(t, report) - if loaded.Status != "passed" || len(loaded.Claims) != 1 { - t.Fatalf("expected one claim on a passed run: %+v", loaded) + if loaded.Status != "passed" || len(loaded.Claims) != len(Battery)-len(LiveBatteryExclusions()) { + t.Fatalf("expected one claim per proven workload on a passed run: %+v", loaded) } if loaded.Claims[0].Ratio.ArangoOverPostgres <= 0 { t.Fatal("ratio missing") @@ -102,7 +134,7 @@ func TestReportForgerySuite(t *testing.T) { "failed-sample-deleted-from-raw": func(r *Report) { // Append a timed-out sample so the summary legitimately has a // failure, then delete it from raw: summary says FailedCount 1. - r.Samples = append(r.Samples, sample(BackendArango, WorkloadFilterActiveGender, OutcomeTimeout, 9000, false)) + r.Samples = append(r.Samples, sample(BackendArango, primaryTiming(), OutcomeTimeout, 9000, false)) sums := r.Summaries r.Summaries = nil for _, row := range sums { @@ -139,20 +171,82 @@ func TestReportForgerySuite(t *testing.T) { r.GateReports[0].Claimable = false r.GateReports[0].ExclusionReason = "forged exclusion" r.Status = "failed" - r.StatusReasons = []string{"filter-active-gender: forged exclusion"} + r.StatusReasons = []string{primaryTiming() + ": forged exclusion"} // Claims left in place: a claim on a non-claimable workload // must not verify even when the status was honestly flipped. }, "self-ratio-forged-high": func(r *Report) { r.Controls.SelfRatioArango = 3.0 }, "empty-floor-never-run": func(r *Report) { r.Controls.EmptyFloorMillis = 0 }, "stability-limit-never-set": func(r *Report) { r.Controls.StabilityCVLimit = 0 }, - "unsupported-list-drifted": func(r *Report) { r.Unsupported = []string{"filter-active-gender/postgres"} }, + "unsupported-list-drifted": func(r *Report) { r.Unsupported = []string{primaryTiming() + "/postgres"} }, "exclusion-note-drifted": func(r *Report) { r.Exclusions = "no workloads excluded from performance claims (lies)" }, "unknown-backend-in-samples": func(r *Report) { r.Samples[0].Backend = "clickhouse" }, "duplicate-summary-cell": func(r *Report) { r.Summaries = append(r.Summaries, r.Summaries[0]) }, "schema-renamed": func(r *Report) { r.Schema = "loom.benchmark-report/v0" }, "runid-blank": func(r *Report) { r.Identity.RunID = "" }, "config-zeroed": func(r *Report) { r.Identity.Config.Iterations = 0 }, + // Live battery law (review r4559430560): excluded workloads must + // stay blocked, disclosed, and sample-free; the marker may never + // move; the matrix may never shrink silently. + "excluded-arm-flipped-to-passed": func(r *Report) { + g := gateForID(r, WorkloadDeriveAgeBand) + g.Arango.Outcome = OutcomeLabelPassed + }, + "excluded-gate-report-deleted": func(r *Report) { + var kept []WorkloadGateReport + for _, g := range r.GateReports { + if g.WorkloadID != WorkloadDeriveAgeBand { + kept = append(kept, g) + } + } + r.GateReports = kept + }, + "proven-gate-report-deleted": func(r *Report) { + var kept []WorkloadGateReport + for _, g := range r.GateReports { + if g.WorkloadID != primaryTiming() { + kept = append(kept, g) + } + } + r.GateReports = kept + }, + "excluded-workload-samples-injected": func(r *Report) { + r.Samples = append(r.Samples, sample(BackendPostgres, WorkloadDistinctFamily, OutcomePassed, 10, false)) + }, + "excluded-disclosure-row-deleted": func(r *Report) { + var kept []PlannedNotRun + dropped := false + for _, row := range r.Plan.PlannedNotRun { + if !dropped && row.WorkloadID == WorkloadDeriveAgeBand && row.Backend == BackendArango { + dropped = true + continue + } + kept = append(kept, row) + } + if !dropped { + panic("valid report lacks the exclusion disclosure row") + } + r.Plan.PlannedNotRun = kept + }, + "excluded-disclosure-reason-forged": func(r *Report) { + for i := range r.Plan.PlannedNotRun { + if r.Plan.PlannedNotRun[i].WorkloadID == WorkloadDeriveAgeBand { + r.Plan.PlannedNotRun[i].Reason = WorkloadNotRunReason + "because I said so" + return + } + } + panic("valid report lacks the exclusion disclosure row") + }, + "marker-forged-on-proven-workload": func(r *Report) { + gateForID(r, primaryTiming()).ExclusionReason = LiveBatteryExclusionMarker + "hiding a slow workload" + }, + "excluded-row-forged-for-proven-workload": func(r *Report) { + reason := LiveBatteryExclusions()[WorkloadDeriveAgeBand] + r.Plan.PlannedNotRun = append(r.Plan.PlannedNotRun, PlannedNotRun{ + WorkloadID: primaryTiming(), Backend: BackendArango, + Reason: WorkloadNotRunReason + reason, + }) + }, } for name, mutate := range cases { t.Run(name, forge(t, mutate)) @@ -165,7 +259,7 @@ func TestReportBlockedStatusRoundTrips(t *testing.T) { report.GateReports[0].Claimable = false report.GateReports[0].ExclusionReason = "postgres: blocked" report.Status = "failed" - report.StatusReasons = []string{"filter-active-gender: blocked"} + report.StatusReasons = []string{primaryTiming() + ": blocked"} report.Exclusions = FormatExclusionList(report.GateReports) report.Claims = nil if _, err := LoadReport(mustMarshal(t, report)); err != nil { From 7d0d620964cbbece87c17d2cfa43d591ee64985b Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:15:13 +0000 Subject: [PATCH 24/45] fix(benchmark): truthful cache labels and regime-aware controls (review r4559430564/568) MeasureArms labels process-cold only on the sample that directly follows that arm's own successful restart; attached arms (no Proc) are labeled service-warm regardless of the peer being spawned; a failed cold restart records a labeled error sample at its position with Round/Iteration set. Controls split into passedStable (claims + CV, steady regimes only) and passedMeasured (round-half self-ratio across all regimes, so a spawned timing leg never aborts for lack of cells). buildClaims and VerifyReport share claimCellPair: warm preferred, service-warm fallback, never process-cold. Tests: TestMeasureArmsColdLabelContract spawns a real sleeper process as the arm's process owner and pins restart counts, per-round cold-first labeling, and service-warm attached arms; TestMeasureArmsFailedColdRestart IsLabeled pins the failure-sample contract. --- internal/dataframe/benchmark/controls.go | 51 +++++-- internal/dataframe/benchmark/harness.go | 51 +++++-- internal/dataframe/benchmark/harness_test.go | 150 +++++++++++++++++++ internal/dataframe/benchmark/report.go | 44 +++++- 4 files changed, 262 insertions(+), 34 deletions(-) diff --git a/internal/dataframe/benchmark/controls.go b/internal/dataframe/benchmark/controls.go index 69fde8718..5ff0dd1cf 100644 --- a/internal/dataframe/benchmark/controls.go +++ b/internal/dataframe/benchmark/controls.go @@ -10,12 +10,38 @@ import ( // block; the verifier independently re-checks their bounds, so a run that // skips a control cannot forge one. -// passedWarm returns the samples that carry timing meaning: warm cache, -// measured (not warmup), successful. -func passedWarm(samples []Sample) []Sample { +// passedStable returns samples whose timing carries steady-state meaning: +// a measured (not warmup), successful sample in a non-cold regime — warm +// (harness-owned process at steady state) or service-warm (attached +// external service, no restart ownership). Process-cold samples (review +// r4559430568) are honest measurements but belong to a different regime: +// they legitimately cost more, so they neither seed stability controls nor +// feed speed claims. +func passedStable(samples []Sample) []Sample { var out []Sample for _, s := range samples { - if s.Warmup || s.Cache != CacheWarm || s.Outcome != OutcomePassed { + if s.Warmup || s.Outcome != OutcomePassed { + continue + } + if s.Cache != CacheWarm && s.Cache != CacheServiceWarm { + continue + } + out = append(out, s) + } + return out +} + +// passedMeasured returns every measured (not warmup), successful sample in +// any regime. The round-robin protocol gives every round an identical cache +// composition (process-cold first iteration after a restart, steady +// afterwards), so comparing round halves across all regimes is a fair +// environment-stability check for spawned and attached arms alike — and a +// spawned run, which produces no warm-only cells, still gets a self-ratio +// (a spawned timing leg must never abort for lack of cells). +func passedMeasured(samples []Sample) []Sample { + var out []Sample + for _, s := range samples { + if s.Warmup || s.Outcome != OutcomePassed { continue } out = append(out, s) @@ -33,15 +59,15 @@ func cellSamples(samples []Sample, workload, backend string) []Sample { return out } -// ControlUnstableCells returns "workload/backend" cells whose passed-warm -// coefficient of variation exceeds the ceiling. The driver excludes these +// ControlUnstableCells returns "workload/backend" cells whose passed +// steady-regime coefficient of variation exceeds the ceiling. The driver excludes these // cells from claims; with fewer than 3 samples a cell cannot demonstrate // stability and is reported unstable (never silently trusted). func ControlUnstableCells(samples []Sample, limit float64) []string { type cell struct{ workload, backend string } seen := map[cell]bool{} var cells []cell - for _, s := range passedWarm(samples) { + for _, s := range passedStable(samples) { c := cell{s.WorkloadID, s.Backend} if !seen[c] { seen[c] = true @@ -66,7 +92,8 @@ func ControlUnstableCells(samples []Sample, limit float64) []string { // ControlSelfRatio computes the backend-vs-itself sanity ratio across // halves of the iteration sequence: for every cell, medians of the first -// and second half of passed-warm samples ordered by (round, iteration); +// and second half of measured samples (any cache regime; rounds are regime- +// symmetric under the interleaved protocol) ordered by (round, iteration); // the reported value is the WORST deviation across cells (always >= 1). // ok=false when any evaluated cell cannot be split (fewer than 4 samples) // or no cells exist: a missing self-ratio control cannot pass the verifier. @@ -74,7 +101,7 @@ func ControlSelfRatio(samples []Sample, backend string) (float64, bool) { type cell struct{ workload, backend string } seen := map[cell]bool{} var cells []cell - for _, s := range passedWarm(samples) { + for _, s := range passedMeasured(samples) { if s.Backend != backend { continue } @@ -90,7 +117,7 @@ func ControlSelfRatio(samples []Sample, backend string) (float64, bool) { worst := 1.0 for _, c := range cells { cellSamplesSorted := cellSamples(samples, c.workload, c.backend) - passed := passedWarm(cellSamplesSorted) + passed := passedMeasured(cellSamplesSorted) if len(passed) < 4 { return 0, false } @@ -118,9 +145,9 @@ func ControlSelfRatio(samples []Sample, backend string) (float64, bool) { } // ControlEmptyFloorMillis turns empty-result floor samples into the control -// value (median of passed warm samples); ok=false without evidence. +// value (median of passed steady-regime samples); ok=false without evidence. func ControlEmptyFloorMillis(samples []Sample) (float64, bool) { - passed := passedWarm(samples) + passed := passedStable(samples) if len(passed) == 0 { return 0, false } diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go index b1a7c5418..e4365f42c 100644 --- a/internal/dataframe/benchmark/harness.go +++ b/internal/dataframe/benchmark/harness.go @@ -239,10 +239,21 @@ type RunOptions struct { } // MeasureArms runs the interleaved round-robin timing protocol for one -// workload on both arms. A non-nil coldRestart is invoked before the first -// measured iteration of each round to earn the process-cold label; otherwise -// samples are warm. Rounds interleave arm-by-arm so clock and host drift hit -// both sides alike. +// workload on both arms. Cache labels follow the contract in measure.go +// exactly (review r4559430564): +// +// - an arm the harness owns (Proc != nil) is restarted before the first +// measured iteration of each round; ONLY the single sample that +// directly follows a successful restart is process-cold, the rest of +// the round is warm; +// - an attached arm (Proc == nil) never restarts and is therefore +// service-warm on every sample, regardless of whether the peer arm is +// spawned; +// - a failed restart yields an OutcomeError sample carrying its Round, +// Iteration, and the process-cold regime it attempted, never a silent +// hole. +// +// Rounds interleave arm-by-arm so clock and host drift hit both sides alike. func MeasureArms(ctx context.Context, clock Clock, workloadID string, arango, postgres *Arm, opts RunOptions, receiptIDs map[string]string, outputIDs map[string]string, coldRestart func(ctx context.Context, arm *Arm) error) ([]Sample, map[string]PreviewResult, error) { if err := opts.Config.Validate(); err != nil { return nil, nil, err @@ -254,14 +265,23 @@ func MeasureArms(ctx context.Context, clock Clock, workloadID string, arango, po runPhase := func(warmup bool, rounds, iters int) { iteration := 0 for round := 1; round <= rounds; round++ { + coldEarned := map[string]bool{} if !warmup && coldRestart != nil { for _, backend := range []string{BackendArango, BackendPostgres} { - if arms[backend] == nil { - continue // leg not participating in this scope + arm := arms[backend] + if arm == nil || arm.Proc == nil { + continue // not participating, or no restart ownership } - if err := coldRestart(ctx, arms[backend]); err != nil { - samples = append(samples, Sample{WorkloadID: workloadID, Backend: backend, Outcome: OutcomeError, ErrorCode: "cold restart: " + err.Error()}) + if err := coldRestart(ctx, arm); err != nil { + samples = append(samples, Sample{ + WorkloadID: workloadID, Backend: backend, + Round: round, Iteration: iteration + 1, + StartedAt: clock.Now(), Cache: CacheProcessCold, + Outcome: OutcomeError, ErrorCode: "cold restart: " + err.Error(), + }) + continue } + coldEarned[backend] = true } } for i := 0; i < iters; i++ { @@ -271,18 +291,21 @@ func MeasureArms(ctx context.Context, clock Clock, workloadID string, arango, po if arm == nil { continue } + cache := CacheWarm + if arm.Proc == nil { + cache = CacheServiceWarm // external service: never claimed cold + } + if coldEarned[backend] && i == 0 { + cache = CacheProcessCold // the sample that directly followed the restart + coldEarned[backend] = false + } started := clock.Now() result, err := arm.ExecuteOutput(ctx, tokenFor(backend, opts), opts.Project, opts.Explorer, receiptIDs[backend], outputIDs[backend], opts.PreviewLimit) sample := Sample{ WorkloadID: workloadID, Backend: backend, Round: round, Iteration: iteration, Warmup: warmup, StartedAt: started, - Cache: func() CacheLabel { - if coldRestart != nil && !warmup { - return CacheProcessCold - } - return CacheWarm - }(), + Cache: cache, } if err != nil { sample.Outcome = classifyArmError(err) diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index 0beba5c9e..d33ff6c75 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" "os" + "path/filepath" "strings" "sync" "testing" @@ -443,3 +444,152 @@ func TestFetchBuilderStateRefusesMissingToken(t *testing.T) { t.Fatalf("missing snapshot token must fail closed, got %v", err) } } + +// --- cache-label contract (review r4559430564) -------------------------------- + +// spawnSleeperProcess starts a real OS process (sh+sleep) that stands in +// for a spawned arm whose HTTP surface is the fakeArm server: restarts are +// genuine process restarts, observable by the harness without any backend. +func spawnSleeperProcess(t *testing.T, readyURL, backend string) *ServerProcess { + t.Helper() + dir := t.TempDir() + script := filepath.Join(dir, "dummy-arm.sh") + if err := os.WriteFile(script, []byte("#!/bin/sh\nexec sleep 300\n"), 0o755); err != nil { + t.Fatal(err) + } + spec := ServerSpec{ + Backend: backend, BinaryPath: script, ConfigYAML: "listen: unused", + ReadyURL: readyURL, Timeout: 15 * time.Second, StderrDir: dir, + } + proc, err := SpawnServer(context.Background(), spec, nil, "127.0.0.1:0") + if err != nil { + t.Fatalf("spawn dummy arm: %v", err) + } + t.Cleanup(proc.Stop) + return proc +} + +func TestMeasureArmsColdLabelContract(t *testing.T) { + ready := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer ready.Close() + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fa.rows["w"] = []map[string]any{{"patient_id": "p", "gender": "female"}} + fp.rows["w"] = []map[string]any{{"patient_id": "p", "gender": "female"}} + arango := fa.arm(BackendArango) + arango.Proc = spawnSleeperProcess(t, ready.URL, BackendArango) + postgres := fp.arm(BackendPostgres) // attached arm: Proc == nil, never cold + + restarts := 0 + cold := func(ctx context.Context, arm *Arm) error { + if arm == nil || arm.Proc == nil { + return nil + } + restarts++ + return arm.Proc.Restart(ctx) + } + clock := &scriptedClock{base: time.Unix(1700000000, 0)} + opts := RunOptions{Project: "p", Generation: "g", Explorer: "w", UnrestrictedToken: "Bearer x", PreviewLimit: 100, + Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}} + samples, finals, err := MeasureArms(context.Background(), clock, "w", arango, postgres, opts, + map[string]string{BackendArango: "r-a", BackendPostgres: "r-p"}, + map[string]string{BackendArango: "w", BackendPostgres: "w"}, cold) + if err != nil { + t.Fatal(err) + } + if restarts != 2 { + t.Fatalf("restarts=%d want 2 (one per round, owned arms only)", restarts) + } + if _, ok := finals[BackendArango]; !ok { + t.Fatal("arango final preview missing") + } + for _, s := range samples { + if s.Outcome != OutcomePassed { + t.Fatalf("sample outcome %s: %s", s.Outcome, s.ErrorCode) + } + switch s.Backend { + case BackendArango: + if s.Warmup { + if s.Cache != CacheWarm { + t.Fatalf("warmup sample on owned arm must be warm, got %s", s.Cache) + } + continue + } + // itersPerRound == 2: the first iteration of each round + // directly followed a successful restart; the second is warm. + firstOfRound := (s.Iteration-1)%2 == 0 + want := CacheWarm + if firstOfRound { + want = CacheProcessCold + } + if s.Cache != want { + t.Fatalf("arango round %d iteration %d: cache %s want %s", s.Round, s.Iteration, s.Cache, want) + } + case BackendPostgres: + if s.Cache != CacheServiceWarm { + t.Fatalf("attached arm labeled %s on round %d iteration %d: attached arms are service-warm regardless of the peer being spawned", + s.Cache, s.Round, s.Iteration) + } + } + } +} + +func TestMeasureArmsFailedColdRestartIsLabeled(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fa.rows["w"] = []map[string]any{{"patient_id": "p", "gender": "female"}} + fp.rows["w"] = []map[string]any{{"patient_id": "p", "gender": "female"}} + arango := fa.arm(BackendArango) + // A process owner that never started: every Restart attempt fails with + // "arm never started", the cheapest honest reproduction of a failed + // cold restart. + arango.Proc = &ServerProcess{} + postgres := fp.arm(BackendPostgres) + cold := func(ctx context.Context, arm *Arm) error { + if arm == nil || arm.Proc == nil { + return nil + } + return arm.Proc.Restart(ctx) + } + clock := &scriptedClock{base: time.Unix(1700000000, 0)} + opts := RunOptions{Project: "p", Generation: "g", Explorer: "w", UnrestrictedToken: "Bearer x", PreviewLimit: 100, + Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}} + samples, _, err := MeasureArms(context.Background(), clock, "w", arango, postgres, opts, + map[string]string{BackendArango: "r-a", BackendPostgres: "r-p"}, + map[string]string{BackendArango: "w", BackendPostgres: "w"}, cold) + if err != nil { + t.Fatal(err) + } + failures := 0 + for _, s := range samples { + if s.Backend != BackendArango || s.Warmup { + continue + } + if s.Outcome == OutcomeError && strings.Contains(s.ErrorCode, "cold restart:") { + failures++ + // The failed attempt must sit at its position with the + // regime it attempted, never as a naked hole (review: + // empty Cache, no Round/Iteration). + if s.Cache != CacheProcessCold { + t.Fatalf("cold-restart failure sample lacks its regime label: %q", s.Cache) + } + if s.Round < 1 || s.Iteration < 1 { + t.Fatalf("cold-restart failure sample lacks Round/Iteration: %+v", s) + } + if s.Millis != 0 { + t.Fatalf("failed restart must not report a duration: %v", s.Millis) + } + continue + } + if s.Cache != CacheWarm { + t.Fatalf("arango sample after a failed restart attempt must be warm (no cold was earned), got %s", s.Cache) + } + } + if failures != 2 { + t.Fatalf("cold-restart failures=%d want 2 (one per round)", failures) + } +} diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go index e29040bff..f5d80929f 100644 --- a/internal/dataframe/benchmark/report.go +++ b/internal/dataframe/benchmark/report.go @@ -137,6 +137,36 @@ func BuildReport(identity RunIdentity, plan RunPlan, controls ControlRuns, gateR return report, nil } +// claimCellPair resolves the steady-regime cell a claim may cite for each +// arm (review r4559430568): warm preferred; service-warm for an attached +// arm that never restarts; process-cold cells are diagnostic only and are +// never claimed. The same helper drives buildClaims and VerifyReport, so a +// claim can only ever cite what the policy would choose. +func claimCellPair(summaryOf map[string]Summary, workloadID string) (Summary, Summary, bool) { + var a, p Summary + ok := true + for _, backend := range []string{BackendArango, BackendPostgres} { + found := false + for _, cache := range []CacheLabel{CacheWarm, CacheServiceWarm} { + s := summaryOf[workloadID+"|"+backend+"|"+string(cache)] + if s.Count > 0 && s.FailedCount == 0 { + if backend == BackendArango { + a = s + } else { + p = s + } + found = true + break + } + } + ok = ok && found + } + if !ok { + return Summary{}, Summary{}, false + } + return a, p, true +} + func buildClaims(gateReports []WorkloadGateReport, summaries []SummaryRow, controls ControlRuns) []Claim { byCell := map[string]Summary{} for _, row := range summaries { @@ -154,9 +184,8 @@ func buildClaims(gateReports []WorkloadGateReport, summaries []SummaryRow, contr if unstable[gate.WorkloadID+"/"+BackendArango] || unstable[gate.WorkloadID+"/"+BackendPostgres] { continue // stability control } - a := byCell[gate.WorkloadID+"|"+BackendArango+"|"+string(CacheWarm)] - p := byCell[gate.WorkloadID+"|"+BackendPostgres+"|"+string(CacheWarm)] - if a.Count == 0 || p.Count == 0 || a.FailedCount != 0 || p.FailedCount != 0 { + a, p, ok := claimCellPair(byCell, gate.WorkloadID) + if !ok { continue } ratio, err := NewSpeedRatio(a.MedianMillis, p.MedianMillis) @@ -526,14 +555,13 @@ func VerifyReport(report *Report) error { if !ok || !gate.Claimable { return fmt.Errorf("benchmark report: claim for %s is not gate-claimable", claim.WorkloadID) } - na := summaryOf[claim.WorkloadID+"|"+BackendArango+"|"+string(CacheWarm)] - np := summaryOf[claim.WorkloadID+"|"+BackendPostgres+"|"+string(CacheWarm)] + na, np, pairOK := claimCellPair(summaryOf, claim.WorkloadID) + if !pairOK { + return fmt.Errorf("benchmark report: claim for %s rests on cells with failures or no samples", claim.WorkloadID) + } if !summaryNearEqual(na, claim.Arango) || !summaryNearEqual(np, claim.Postgres) { return fmt.Errorf("benchmark report: claim for %s cites summaries that do not recompute", claim.WorkloadID) } - if na.Count == 0 || np.Count == 0 || na.FailedCount != 0 || np.FailedCount != 0 { - return fmt.Errorf("benchmark report: claim for %s rests on cells with failures or no samples", claim.WorkloadID) - } expected, err := NewSpeedRatio(na.MedianMillis, np.MedianMillis) if err != nil || math.Abs(expected.ArangoOverPostgres-claim.Ratio.ArangoOverPostgres) > 1e-9 { return fmt.Errorf("benchmark report: claim ratio for %s does not match medians", claim.WorkloadID) From 9d188d9de9cc9b274e06709703b0f60e30507b04 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:15:13 +0000 Subject: [PATCH 25/45] fix(benchmark): derive-age-band authors as a closed category recode Age observations now carry a days identifier under urn:loom:benchmark:age-days alongside valueQuantity; the fixture domain is [1,90] days so the corpus straddles both band thresholds. The workspace is Observation-rooted and derives age_band via EXACT_CATEGORY_RECODE with an exhaustive map generated from the shared AgeBandFor() the oracle uses, so author and oracle cannot drift; unknownPolicy is ERROR and the fixed filter selects the three band labels. The workload stays authoring-unverified (recode has no acceptance precedent) and remains a disclosed live-battery exclusion until the ClickHouse lane rehearses it; TestDeriveAgeBandOfflineWitness proves the authored shape reproduces the oracle bag offline against the uploaded fixture. --- internal/dataframe/benchmark/authoring.go | 101 ++++++++--- .../dataframe/benchmark/authoring_test.go | 166 +++++++++++++++++- internal/dataframe/benchmark/fixtures.go | 19 +- internal/dataframe/benchmark/fixtures_test.go | 11 +- internal/dataframe/benchmark/matrix.go | 2 +- internal/dataframe/benchmark/oracle.go | 46 +++-- 6 files changed, 293 insertions(+), 52 deletions(-) diff --git a/internal/dataframe/benchmark/authoring.go b/internal/dataframe/benchmark/authoring.go index d498e061d..48a83f02e 100644 --- a/internal/dataframe/benchmark/authoring.go +++ b/internal/dataframe/benchmark/authoring.go @@ -3,6 +3,7 @@ package benchmark import ( "encoding/json" "fmt" + "strconv" "strings" "github.com/calypr/loom/internal/explorer/authoringv2" @@ -43,12 +44,13 @@ type authoringNote struct { // wire shapes, verbatim families from the acceptance fixture type wsColumnDef struct { - Column string `json:"column"` - Label string `json:"label"` - LogicalType string `json:"logicalType"` - OccurrenceID string `json:"occurrenceId"` - Source map[string]any `json:"source"` - Table wsTable `json:"table"` + Column string `json:"column"` + Label string `json:"label"` + LogicalType string `json:"logicalType"` + OccurrenceID string `json:"occurrenceId"` + Source map[string]any `json:"source"` + ValueTransformation map[string]any `json:"valueTransformation,omitempty"` + Table wsTable `json:"table"` } type wsTable struct { @@ -156,6 +158,18 @@ func extensionCol(column, occurrence, matchURL string, order int) wsColumnDef { } } +// identifierCol projects identifier[].value where system == matchSystem via +// the acceptance-proven identifierBySystem source shape. +func identifierCol(column, occurrence, matchSystem string, order int) wsColumnDef { + return wsColumnDef{ + Column: column, Label: column, LogicalType: "string", OccurrenceID: occurrence, + Source: map[string]any{"kind": "identifierBySystem", "lookup": map[string]any{ + "match": matchSystem, "path": "identifier[]", "projectionMode": "FIRST", + }}, + Table: wsTable{Visible: true, Order: order}, + } +} + func observationChildren() []wsChild { return []wsChild{ {OccurrenceID: "observation", ResourceType: "Observation", Relationship: "subject_Patient"}, @@ -165,6 +179,16 @@ func observationChildren() []wsChild { } func patientWorkspace(outputID string, columns []wsColumnDef, children []wsChild, filters []wsFixed) []byte { + return rootWorkspace("Patient", outputID, columns, children, filters) +} + +// observationRootWorkspace authors an output whose rows are the Observation +// resources themselves (one row per observation, no child occurrences). +func observationRootWorkspace(outputID string, columns []wsColumnDef, filters []wsFixed) []byte { + return rootWorkspace("Observation", outputID, columns, nil, filters) +} + +func rootWorkspace(rootType, outputID string, columns []wsColumnDef, children []wsChild, filters []wsFixed) []byte { if children == nil { children = []wsChild{} } @@ -174,8 +198,8 @@ func patientWorkspace(outputID string, columns []wsColumnDef, children []wsChild Documents: []wsDoc{{ Kind: wsDocKind, Output: wsOutputDef{ID: outputID, Title: outputID, RowLabel: "row"}, - RootResourceType: "Patient", - Route: wsRouteNode{OccurrenceID: "base", ResourceType: "Patient", Children: children}, + RootResourceType: rootType, + Route: wsRouteNode{OccurrenceID: "base", ResourceType: rootType, Children: children}, Columns: columns, FixedFilters: filters, }}, @@ -262,25 +286,52 @@ func BuildWorkspace(workloadID string) ([]byte, error) { extensionCol("study_ref", "base", partOfStudyURL, 1), }, nil, nil), nil case WorkloadDeriveAgeBand: - // No acceptance precedent for value transformations in the workspace - // wire shape, and the authored approximation cannot pass its own - // oracle (raw distinct values vs band labels): LiveBatteryExclusions - // ships this workload planned/not_run with the named defect until - // the ClickHouse lane rehearses a proven derivation. - return patientWorkspace(OutputIDFor(workloadID), []wsColumnDef{ - aggregateCol("observation_id", "Observation id", "string", "observation", "DISTINCT_VALUES", - "id", "code.coding[].code", ObservationCodeAge, 0), - {Column: "age_band", Label: "Age band", LogicalType: "string", OccurrenceID: "observation", - Source: map[string]any{"kind": "aggregate", "aggregate": map[string]any{ - "operation": "DISTINCT_VALUES", "path": "valueQuantity.value", - "where": map[string]any{"path": "code.coding[].code", "equals": ObservationCodeAge}, - }}, - Table: wsTable{Visible: true, Order: 1}}, - }, observationChildren(), nil), nil + // Derivation intent, authored with the repository's closed value + // transformation: age_band recodes the scalar-string age identifier + // (fixtures.go) through the exhaustive exact-category map generated + // from the same AgeBandFor function the oracle thresholds + // independently. unknownPolicy ERROR: any value outside the pinned + // fixture domain is a contract violation, never silent misbanding. + // The fixed filter keeps exactly the age observations (missing-value + // observations have no identifier, so their null band is excluded + // and the oracle skips them too). The map itself has no + // acceptance-fixture precedent, so the workload stays + // AuthoringUnverified — planned/not_run with a named reason — until + // the ClickHouse-lane rehearsal; TestDeriveAgeBandOfflineWitness + // proves offline that its output bag equals the oracle's. + ageBandCol := identifierCol("age_band", "base", BenchAgeIdentifierSystem, 1) + ageBandCol.ValueTransformation = map[string]any{ + "kind": "EXACT_CATEGORY_RECODE", + "exactCategoryRecode": map[string]any{ + "mappings": ageRecodeMappings(), + "unknownPolicy": "ERROR", + }, + } + return observationRootWorkspace(OutputIDFor(workloadID), []wsColumnDef{ + fieldCol("observation_id", "base", "id", "VALUE", "string", 0), + ageBandCol, + }, []wsFixed{ + {Column: "age_band", Values: []string{"young", "middle", "senior"}}, + }), nil } return nil, fmt.Errorf("benchmark: no workspace template for workload %q", workloadID) } +// ageRecodeMappings enumerates the exhaustive exact-category map over the +// pinned fixture domain [1, AgeBandFixtureMax], deriving every label from +// AgeBandFor — the same function the oracle thresholds independently. A +// drift between workspace and oracle is unrepresentable by construction; +// TestDeriveAgeBandOfflineWitness re-checks coverage against the fixtures. +func ageRecodeMappings() []map[string]string { + mappings := make([]map[string]string, 0, AgeBandFixtureMax) + for v := 1; v <= AgeBandFixtureMax; v++ { + mappings = append(mappings, map[string]string{ + "from": strconv.Itoa(v), "to": AgeBandFor(float64(v)), + }) + } + return mappings +} + // BuildEmptyFloorWorkspace authors the identical-request empty-result floor // control: the same shape as filter-active-gender whose fixed filter cannot // match any patient, so the expectation is provably zero rows. @@ -325,7 +376,7 @@ func AuthoringVerification() map[string]AuthoringStatus { var unverifiedWorkloads = map[string]bool{ WorkloadFilterActiveGender: true, // fixedFilters over a boolean field column WorkloadDistinctFamily: true, // DISTINCT_VALUES at the root occurrence - WorkloadDeriveAgeBand: true, // transformation intent approximated + WorkloadDeriveAgeBand: true, // EXACT_CATEGORY_RECODE map: no acceptance precedent (passability witnessed offline) WorkloadStudyCorrelation: true, // extensionByUrl returning a reference string WorkloadStageCounts: true, // DISTINCT_VALUES + COUNT pair at one occurrence (per-row grain expectation) WorkloadSortLimitTopN: true, // VALUE over multi-valued name[].family: row expansion unproven (review r4559430560) @@ -338,7 +389,7 @@ var unverifiedWorkloads = map[string]bool{ var workloadNotRunReasons = map[string]string{ WorkloadFilterActiveGender: "shape has no acceptance-fixture precedent: fixedFilters over a boolean field column; live rehearsal (ClickHouse lane) must prove the oracle/output agreement before this leg runs", WorkloadDistinctFamily: "shape has no acceptance-fixture precedent: DISTINCT_VALUES at the root occurrence; live rehearsal must prove the oracle/output agreement before this leg runs", - WorkloadDeriveAgeBand: "oracle/output agreement disproven in source: the workspace authors age_band as DISTINCT_VALUES over raw valueQuantity.value while the oracle emits band labels, and fixture ages [1000,10000) make the young/middle branches dead; needs a proven case/if derivation (see AgeBandYoungUnder/AgeBandMiddleUnder) or an oracle re-model, rehearsed in the ClickHouse lane", + WorkloadDeriveAgeBand: "shape has no acceptance-fixture precedent: the EXACT_CATEGORY_RECODE map (identifierBySystem source, ERROR unknown policy, fixed filter on the derived column) has never run end to end; TestDeriveAgeBandOfflineWitness proves offline that its output bag equals the independently thresholded oracle over the pinned fixture domain [1,90], so the workload can pass; the ClickHouse-lane rehearsal must confirm the backends apply recode+filter as specified before this leg runs", WorkloadStudyCorrelation: "shape has no acceptance-fixture precedent: extensionByUrl returning a reference string; live rehearsal must prove the oracle/output agreement before this leg runs", WorkloadStageCounts: "shape has no acceptance-fixture precedent: DISTINCT_VALUES + COUNT pair at one occurrence with a per-row grain expectation; live rehearsal must prove the oracle/output agreement before this leg runs", WorkloadSortLimitTopN: "oracle/output agreement unproven: VALUE columns over multi-valued name[].family with a second family entry on every 4th patient; the oracle assumes first-only while preview row expansion (first-only vs row-per-value) has no acceptance precedent; live rehearsal must pin the scalarization contract", diff --git a/internal/dataframe/benchmark/authoring_test.go b/internal/dataframe/benchmark/authoring_test.go index e352b9997..f25a6ade8 100644 --- a/internal/dataframe/benchmark/authoring_test.go +++ b/internal/dataframe/benchmark/authoring_test.go @@ -1,6 +1,8 @@ package benchmark import ( + "encoding/json" + "strconv" "strings" "testing" @@ -59,8 +61,11 @@ func TestBatteryWorkspacesDecode(t *testing.T) { } func TestBatteryWorkspaceRouteShape(t *testing.T) { - // Root is always Patient with base occurrence; child occurrences use - // the relationship spellings proven by the acceptance fixture. + // Root is Patient with base occurrence for every workload except + // derive-age-band, whose rows are observations at an Observation root + // (one row per observation, acceptance-proven single-node route form); + // child occurrences use the relationship spellings proven by the + // acceptance fixture. workspaces, err := BuildAllWorkspaces() if err != nil { t.Fatal(err) @@ -72,7 +77,11 @@ func TestBatteryWorkspaceRouteShape(t *testing.T) { t.Fatalf("%s: %v", id, err) } route := ws.Documents[0].Route - if route.OccurrenceID != "base" || route.ResourceType != "Patient" { + wantRoot := "Patient" + if id == WorkloadDeriveAgeBand { + wantRoot = "Observation" + } + if route.OccurrenceID != "base" || route.ResourceType != wantRoot { t.Fatalf("%s: route root: %+v", id, route) } var walk func(children []authoringv2.RouteNode) @@ -103,6 +112,151 @@ func TestAuthoringVerificationNoneRejected(t *testing.T) { } } +// TestDeriveAgeBandOfflineWitness proves the derive-age-band workload CAN +// reach passed, offline and without any server: the workspace decodes, its +// exhaustive recode map agrees with the independently thresholded oracle on +// every fixture value, and simulating the authored shape (identifier FIRST +// + recode + fixed filter) against the uploaded documents reproduces the +// oracle bag byte-for-byte. A backend that faithfully applies recode and +// filter therefore cannot fail this workload — the ClickHouse-lane +// rehearsal only has to confirm faithfulness, not discover a mismatch. +func TestDeriveAgeBandOfflineWitness(t *testing.T) { + params := DatasetParams{Preset: PresetTiny, Seed: 5, Project: "bench-proj", Generation: "gen-w", AuthResourcePath: "/lox/lox-w"} + files, err := GenerateFiles(params) + if err != nil { + t.Fatal(err) + } + raw, err := BuildWorkspace(WorkloadDeriveAgeBand) + if err != nil { + t.Fatal(err) + } + if _, err := authoringv2.DecodeWorkspace(raw); err != nil { + t.Fatalf("derive-age-band workspace must decode: %v", err) + } + + // Extract the recode map from the authored workspace, not from the + // helper, so the witness checks the actual wire artifact. + var doc struct { + Documents []struct { + Columns []struct { + Column string `json:"column"` + ValueTransformation struct { + Kind string `json:"kind"` + ExactCategoryRecode struct { + Mappings []map[string]string `json:"mappings"` + UnknownPolicy string `json:"unknownPolicy"` + } `json:"exactCategoryRecode"` + } `json:"valueTransformation"` + } `json:"columns"` + FixedFilters []struct { + Column string `json:"column"` + Values []string `json:"values"` + } `json:"fixedFilters"` + } `json:"documents"` + } + if err := json.Unmarshal(raw, &doc); err != nil { + t.Fatal(err) + } + recode := map[string]string{} + for _, col := range doc.Documents[0].Columns { + if col.Column != "age_band" { + continue + } + if col.ValueTransformation.Kind != "EXACT_CATEGORY_RECODE" || + col.ValueTransformation.ExactCategoryRecode.UnknownPolicy != "ERROR" { + t.Fatalf("age_band must recode with unknownPolicy ERROR: %+v", col.ValueTransformation) + } + for _, m := range col.ValueTransformation.ExactCategoryRecode.Mappings { + if _, dup := recode[m["from"]]; dup { + t.Fatalf("duplicate recode source %q", m["from"]) + } + recode[m["from"]] = m["to"] + } + } + if len(recode) != AgeBandFixtureMax { + t.Fatalf("recode map must cover [1,%d] exhaustively: %d entries", AgeBandFixtureMax, len(recode)) + } + for v := 1; v <= AgeBandFixtureMax; v++ { + if recode[strconv.Itoa(v)] != AgeBandFor(float64(v)) { + t.Fatalf("recode map disagrees with the oracle banding at %d: %q vs %q", + v, recode[strconv.Itoa(v)], AgeBandFor(float64(v))) + } + } + if got := doc.Documents[0].FixedFilters; len(got) != 1 || got[0].Column != "age_band" || + strings.Join(got[0].Values, ",") != "young,middle,senior" { + t.Fatalf("fixed filter must keep exactly the banded rows: %+v", got) + } + + // Simulate the authored shape against the uploaded documents: one row + // per observation with a bench age identifier, band = recode[value]. + var simulated []map[string]any + seenAge := 0 + for _, line := range strings.Split(strings.TrimRight(files.Files["Observation.ndjson"], "\n"), "\n") { + var o struct { + ID string `json:"id"` + Code struct { + Coding []struct { + Code string `json:"code"` + } `json:"coding"` + } `json:"code"` + Identifier []struct { + System string `json:"system"` + Value string `json:"value"` + } `json:"identifier"` + ValueQuantity *struct { + Value float64 `json:"value"` + } `json:"valueQuantity"` + } + if err := json.Unmarshal([]byte(line), &o); err != nil { + t.Fatal(err) + } + isAge := len(o.Code.Coding) == 1 && o.Code.Coding[0].Code == ObservationCodeAge + var bench []string + for _, id := range o.Identifier { + if id.System == BenchAgeIdentifierSystem { + bench = append(bench, id.Value) + } + } + switch { + case !isAge && len(bench) != 0: + t.Fatalf("%s: non-age observation must not carry the bench age identifier", o.ID) + case isAge && o.ValueQuantity == nil && len(bench) != 0: + t.Fatalf("%s: missing-value age observation must not carry the identifier either", o.ID) + case isAge && o.ValueQuantity != nil: + if len(bench) != 1 { + t.Fatalf("%s: age observation needs exactly one identifier value, got %d", o.ID, len(bench)) + } + if bench[0] != strconv.FormatFloat(o.ValueQuantity.Value, 'f', -1, 64) { + t.Fatalf("%s: identifier %q diverged from valueQuantity %v", o.ID, bench[0], o.ValueQuantity.Value) + } + band, ok := recode[bench[0]] + if !ok { + t.Fatalf("%s: fixture age %s outside the recode domain", o.ID, bench[0]) + } + simulated = append(simulated, map[string]any{"observation_id": o.ID, "age_band": band}) + seenAge++ + } + } + if seenAge == 0 { + t.Fatal("no valued age observations in the tiny preset") + } + columns, _ := workloadColumns(WorkloadDeriveAgeBand) + simulatedBag := Bag(CanonicalizeRows(columns, simulated)) + expected, err := OracleFor(WorkloadDeriveAgeBand, files.Facts) + if err != nil { + t.Fatal(err) + } + if expected == nil { + t.Fatal("derive-age-band must have an oracle") + } + if a, b, diff := FirstDifference(*expected, simulatedBag); diff { + t.Fatalf("workspace-simulated bag differs from the oracle bag: first divergence expected %q, simulated %q", a, b) + } + if expected.Digest() != simulatedBag.Digest() { + t.Fatalf("bag digests differ: oracle %s simulated %s", expected.Digest(), simulatedBag.Digest()) + } +} + // TestLiveBatteryExclusionsRegistry pins the live battery law from review // r4559430560: a live leg runs exactly the authoring-proven workloads, // and every exclusion carries a named reason. A silent exclusion is the @@ -136,9 +290,9 @@ func TestLiveBatteryExclusionsRegistry(t *testing.T) { } // The two review findings must be named in the registry with their // actual defects, not generic prose. - if r := exclusions[WorkloadDeriveAgeBand]; !strings.Contains(r, "DISTINCT_VALUES") || - !strings.Contains(r, "band labels") || !strings.Contains(r, "valueQuantity.value") { - t.Errorf("derive-age-band exclusion must name the oracle/workspace defect: %q", r) + if r := exclusions[WorkloadDeriveAgeBand]; !strings.Contains(r, "EXACT_CATEGORY_RECODE") || + !strings.Contains(r, "acceptance-fixture precedent") || !strings.Contains(r, "TestDeriveAgeBandOfflineWitness") { + t.Errorf("derive-age-band exclusion must name the unproven-recode shape and the offline witness: %q", r) } if r := exclusions[WorkloadSortLimitTopN]; !strings.Contains(r, "name[].family") || !strings.Contains(r, "first-only") { diff --git a/internal/dataframe/benchmark/fixtures.go b/internal/dataframe/benchmark/fixtures.go index 19904d02d..9bd6522c9 100644 --- a/internal/dataframe/benchmark/fixtures.go +++ b/internal/dataframe/benchmark/fixtures.go @@ -72,6 +72,14 @@ const ( ObservationCodeGender = "BENCH-CODE-GENDER" // valueString feeds Unpivot column keys ) +// BenchAgeIdentifierSystem is the identifier namespace on age observations +// whose value is the age as a decimal string. It is the scalar-string source +// the derive-age-band workspace recodes through EXACT_CATEGORY_RECODE. It is +// emitted exactly when valueQuantity.value is (the missing-value observations +// carry neither, so the oracle's skip and the workspace's null-exclusion +// agree by construction). +const BenchAgeIdentifierSystem = "https://bench.example/identifier/age-days" + // Specimen type codes (duplicate-contributor and filter workloads). const ( SpecimenTypePrimary = "PRIMARY" @@ -319,7 +327,11 @@ func GenerateFacts(params DatasetParams) (*Facts, error) { } switch obs.Code { case ObservationCodeAge: - value := float64(1000 + hashU64(params.Seed, "age", obs.ID)%9000) + // Integer ages in [1, AgeBandFixtureMax] days: the domain + // straddles both band thresholds so young/middle/senior all + // occur, and it is exactly the domain the workspace recode + // map enumerates (fixtures tests pin both invariants). + value := float64(1 + hashU64(params.Seed, "age", obs.ID)%uint64(AgeBandFixtureMax)) if p%19 == 0 { obs.ValueNumber = nil // missing numeric: aggregation must skip, not zero } else { @@ -475,6 +487,11 @@ func (d DatasetParams) buildObservationDocument(f ObservationFacts) map[string]a "value": *f.ValueNumber, "unit": "days", "system": "http://unitsofmeasure.org", "code": "d", } + // Scalar-string twin of the quantity for the recode workload. + doc["identifier"] = []any{map[string]any{ + "use": "secondary", "system": BenchAgeIdentifierSystem, + "value": strconv.FormatFloat(*f.ValueNumber, 'f', -1, 64), + }} } case ObservationCodeStage: doc["valueCodeableConcept"] = map[string]any{"coding": []any{map[string]any{ diff --git a/internal/dataframe/benchmark/fixtures_test.go b/internal/dataframe/benchmark/fixtures_test.go index ab6dd00fd..0fd8d5fbd 100644 --- a/internal/dataframe/benchmark/fixtures_test.go +++ b/internal/dataframe/benchmark/fixtures_test.go @@ -153,13 +153,20 @@ func TestFactsAgeValuesAreExactIntegers(t *testing.T) { t.Fatal(err) } maxSum := 0.0 + bands := map[string]bool{} for _, o := range facts.Observations { if o.Code != ObservationCodeAge || o.ValueNumber == nil { continue } v := *o.ValueNumber - if v != float64(int64(v)) || v < 1000 || v >= 10000 { - t.Fatalf("age value %s breaks the integer [1000,10000) invariant", strconv.FormatFloat(v, 'g', -1, 64)) + if v != float64(int64(v)) || v < 1 || v > AgeBandFixtureMax { + t.Fatalf("age value %s breaks the integer [1,%d] invariant", strconv.FormatFloat(v, 'g', -1, 64), AgeBandFixtureMax) + } + bands[AgeBandFor(v)] = true + } + for _, band := range []string{"young", "middle", "senior"} { + if !bands[band] { + t.Fatalf("fixture age domain never produces band %q: the derivation branches would be dead", band) } } for _, sum := range facts.SumAgeByPatient { diff --git a/internal/dataframe/benchmark/matrix.go b/internal/dataframe/benchmark/matrix.go index e2f74297a..9fa1eb8f7 100644 --- a/internal/dataframe/benchmark/matrix.go +++ b/internal/dataframe/benchmark/matrix.go @@ -59,7 +59,7 @@ var Battery = []WorkloadSpec{ Description: "active patients with gender=female"}, {ID: WorkloadDeriveAgeBand, Category: CategoryDerive, Columns: []string{"observation_id", "age_band"}, OracleAvailable: true, - Description: "if/case derivation of an age band from BENCH-CODE-AGE quantities"}, + Description: "closed category-recode derivation of an age band from BENCH-CODE-AGE identifiers over [1,90] days"}, {ID: WorkloadAggregateSumAgeByPatient, Category: CategoryAggregate, Columns: []string{"patient_id", "sum_age", "count_age", "min_age", "max_age"}, OracleAvailable: true, Description: "sum/count/min/max of age quantities grouped by patient"}, diff --git a/internal/dataframe/benchmark/oracle.go b/internal/dataframe/benchmark/oracle.go index a24da7b3d..bb8d61e6e 100644 --- a/internal/dataframe/benchmark/oracle.go +++ b/internal/dataframe/benchmark/oracle.go @@ -228,31 +228,43 @@ func oracleDeriveAgeBand(facts *Facts) []map[string]any { if o.Code != ObservationCodeAge || o.ValueNumber == nil { continue } - v := *o.ValueNumber - band := "unknown" - switch { - case v < 30: - band = "young" - case v < 60: - band = "middle" - default: - band = "senior" - } - rows = append(rows, map[string]any{"observation_id": o.ID, "age_band": band}) + rows = append(rows, map[string]any{"observation_id": o.ID, "age_band": AgeBandFor(*o.ValueNumber)}) } return rows } -// AgeBandThresholds are shared between the recipe (matrix.go) and the oracle -// so the two cannot drift apart silently; a change is a single edit here. +// AgeBandThresholds are shared between the recipe (authoring recode +// mappings, matrix domain) and the oracle so the two cannot drift apart +// silently; a change is a single edit here. const ( AgeBandYoungUnder = 30 AgeBandMiddleUnder = 60 TopNLimit = 25 + // AgeBandFixtureMax is the inclusive upper bound of the fixture age + // domain [1, AgeBandFixtureMax]: it straddles both thresholds so all + // three bands occur, and it is the domain the workspace recode map + // enumerates (the offline witness pins this coverage). + AgeBandFixtureMax = 90 ) +// AgeBandFor is the single banding function: the oracle applies it to the +// numeric quantity, the workspace recode maps are generated from it, and +// the fixture domain is checked against it. The derived labels therefore +// cannot drift from the independent threshold expectation by construction. +func AgeBandFor(v float64) string { + switch { + case v < AgeBandYoungUnder: + return "young" + case v < AgeBandMiddleUnder: + return "middle" + default: + return "senior" + } +} + // Exact float equality note: age valueQuantity values are integers in -// [1000, 10000) (fixtures.go), and per-patient sums stay far inside float64 -// exact integer range at every preset, so canonicalNumberText's -// shortest-round-trip spelling matches backend output exactly; the fixtures -// test re-asserts the integer invariant so the oracle cannot drift silently. +// [1, AgeBandFixtureMax] (fixtures.go), and per-patient sums stay far +// inside float64 exact integer range at every preset, so +// canonicalNumberText's shortest-round-trip spelling matches backend output +// exactly; the fixtures test re-asserts the integer invariant so the oracle +// cannot drift silently. From 4e500c1faf6d9488900133ed5a3ae1333039ff8b Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:34:41 +0000 Subject: [PATCH 26/45] feat(benchmark): paired real-driver timing lane, timed publication mode, live exit contract Operator acceptance amendment (2026-11-13): the benchmark must actually compare the two real drivers. The claim that timing sat behind ClickHouse was stale - the backend-conformance lane runs the whole authoring battery on both real drivers with clickhouse.enabled=false, and the PostgreSQL backend forbids clickhouse.enabled in this slice. What sits behind the service is only the dataset->bundle->ClickHouseBundleStore chain. - PublicationPlan gains the timed mode (Battery): chain disclosed not_run under PublicationCapabilityTimed while the battery - authoring, preview, timing, gates, controls - runs for real. VerifyReport ties timing evidence to the flag: flipping battery off under a report that carries samples is refused (forgery test). - benchcli runLive now classifies by report status + measured-sample presence (finding 4): passed -> 0; measured-but-failed -> 1; non-passed zero-timing (the default publication=excluded shape) -> 3 with the status reasons printed - disclosed not_run, never the success shape of exit 0. Pinned by TestClassifyLiveExit. - scripts/benchmark-backends.mjs gains --paired: discovers both services from LOOM_TEST_ARANGO_URL + LOOM_TEST_POSTGRES_URL, runs the matched battery through benchcli (spawned arms, benchmark fence, timed mode), hands authority to the Go verifier, then fails unless BOTH drivers ran and each carries >=5 non-warmup passed timings; writes comparison.md with median/min/max/range per workload per driver plus versions, source SHA, fixture digest, and warmup/cache/rounds/concurrency conditions. Missing services, planned/not_run/excluded arms, and zero timing are hard failures, never skips or passes (20 node tests). - tests.yaml registers job factory/paired-driver-benchmark on the backend-conformance service pattern (arangodb:3.12 + postgres:18 in one job): builds loom-server + benchcli from the SHA, runs the exact paired command with --iterations 5 --warmup 1 --rounds 5, prints the comparison table in the log, and uploads the artifact set. The job goes red rather than report scaffolding as a comparison. - README: exit-code table, the three publication modes, the paired lane, and the corrected chain-vs-battery ClickHouse claim. --- .github/workflows/tests.yaml | 92 ++++++ internal/dataframe/benchmark/README.md | 73 +++-- internal/dataframe/benchmark/armplan.go | 40 ++- .../benchmark/cmd/benchcli/guards_test.go | 37 +++ .../dataframe/benchmark/cmd/benchcli/main.go | 52 +++- internal/dataframe/benchmark/driver.go | 17 +- internal/dataframe/benchmark/report.go | 14 +- internal/dataframe/benchmark/report_test.go | 73 +++++ scripts/benchmark-backends.mjs | 263 +++++++++++++++++- scripts/benchmark-backends.test.mjs | 166 +++++++++++ 10 files changed, 778 insertions(+), 49 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b63697296..dd7463e9c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -423,6 +423,98 @@ jobs: path: ${{ runner.temp }}/backend-conformance if-no-files-found: warn + paired-driver-benchmark: + name: factory/paired-driver-benchmark + runs-on: ubuntu-latest + # Operator acceptance (2026-11-13): the actual speed comparison between + # the two real drivers. Both arms run the SAME matched battery against + # actual ArangoDB and PostgreSQL services -- the same images, ports, + # and health options the backend-conformance lane adjudicates -- with + # clickhouse.enabled=false, which is honest for the dataframe legs: + # the timing battery never consumes the ClickHouse-backed publication + # chain (that chain stays disclosed not_run in the timed lane). The + # gate fails closed if either arm is planned/not_run/excluded or + # produces no real timing samples; a disclosure artifact can never + # turn this check green. + timeout-minutes: 20 + services: + arangodb: + image: arangodb:3.12 + env: + ARANGO_NO_AUTH: '1' + ports: + - 8529:8529 + options: >- + --health-cmd "wget --quiet --tries=1 --spider http://127.0.0.1:8529/_api/version" + --health-interval 3s + --health-timeout 5s + --health-retries 40 + postgres18: + image: postgres:18 + env: + POSTGRES_DB: loom_bench_ci + POSTGRES_USER: loom_bench + POSTGRES_PASSWORD: loom_ci_test_only + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -h 127.0.0.1 -U loom_bench -d loom_bench_ci" + --health-interval 3s + --health-timeout 5s + --health-retries 40 + env: + LOOM_TEST_ARANGO_URL: http://127.0.0.1:8529 + LOOM_TEST_POSTGRES_URL: postgresql://loom_bench:loom_ci_test_only@127.0.0.1:5432/loom_bench_ci?sslmode=disable + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.26.5 + cache: true + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 24.x + - name: Build the server binary and benchcli from this SHA + run: | + mkdir -p "$RUNNER_TEMP/paired-bin" + go build -o "$RUNNER_TEMP/paired-bin/loom-server" ./cmd/arango-fhir-server + go build -o "$RUNNER_TEMP/paired-bin/benchcli" ./internal/dataframe/benchmark/cmd/benchcli + - name: Run the paired benchmark against both services + # Five alternating measured samples per driver, one warmup, tiny + # matched fixture. The script verifies the artifact through the Go + # verifier, classifies realness (both arms ran, both timed), and + # writes comparison.md; it exits nonzero on any shortfall, so the + # job goes red rather than reporting scaffolding as a comparison. + run: >- + node scripts/benchmark-backends.mjs --paired + --binary "$RUNNER_TEMP/paired-bin/loom-server" + --benchcli "$RUNNER_TEMP/paired-bin/benchcli" + --profile tiny + --iterations 5 + --warmup 1 + --rounds 5 + --source-commit "$GITHUB_SHA" + --window-note "granted by the paired-driver-benchmark CI job (isolated services, $GITHUB_RUN_ID)" + --out "$RUNNER_TEMP/paired-benchmark" + - name: Show the comparison table in the job log + if: always() + run: | + if [ -f "$RUNNER_TEMP/paired-benchmark/comparison.md" ]; then + cat "$RUNNER_TEMP/paired-benchmark/comparison.md" + else + echo "no comparison table: the paired check did not reach a real both-driver comparison" >&2 + fi + - name: Preserve paired benchmark evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: paired-driver-benchmark + path: ${{ runner.temp }}/paired-benchmark + if-no-files-found: error + node-unit: name: Node unit tests runs-on: ubuntu-latest diff --git a/internal/dataframe/benchmark/README.md b/internal/dataframe/benchmark/README.md index 1e972d80e..b3198f63c 100644 --- a/internal/dataframe/benchmark/README.md +++ b/internal/dataframe/benchmark/README.md @@ -52,21 +52,27 @@ planned capabilities). ## Exit codes (`run`) -- `0` — live legs ran and the artifact verifies. -- `1` — a live run executed and failed. -- `3` — disclosed not_run: preflight refused (missing `LOOM_BENCH_LIVE=1`, - window note, source commit, namespace guard, existing output) or the - planned disclosure artifact was written. - -## Publication chain (ClickHouse-backed) - -The publication chain is ClickHouse-backed end to end: `publication.Target` -has exactly one production implementation (`ClickHouseBundleStore`), and -`POST /generations/{g}/explorer-config` materializes through it *before* -the revision that seeds the authoring draft persists. Publish → authoring → -reconcile → preview receipts → workload gates → timing → controls is one -chain behind that service. (The preview *execution* path is ClickHouse-free; -it is the draft-seeding publish that needs it.) + - `0` — the live legs ran and the report status is `passed`. + - `1` — the run measured something and failed (a correctness gate failed, + or any leg failed after real timings were captured). + - `3` — disclosed not_run, never a pass: preflight refused (missing + `LOOM_BENCH_LIVE=1`, window note, source commit, namespace guard, + existing output), the planned disclosure artifact was written, or a + live run ended non-passed with zero measured samples (for example the + default `--publication=excluded` path, where every gate arm is + blocked). A run that timed nothing must not look like a success. + +## Publication chain (ClickHouse-backed) and the timed lane + +The publication *chain* — dataset → bundle → +`publication.Target`/`ClickHouseBundleStore`, the chain's only production +implementation — is ClickHouse-backed. The timing battery is not: the +authoring battery (explorer publish, builder, reconcile, preview, +receipts, workload gates, timing, controls) runs for real against both +drivers with `server.clickhouse.enabled=false`, the same posture the +`backend-conformance` CI lane adjudicates on the actual services. The +PostgreSQL backend forbids `clickhouse.enabled` in this slice outright, +so the chain and the battery must be disclosed separately. - `--publication=excluded` (default): spawn, upload, and version probes run for real on every ran leg; the whole publication chain ships as @@ -76,10 +82,39 @@ it is the draft-seeding publish that needs it.) `.github/workflows/tests.yaml` — currently 25.3). `VerifyReport` then refuses any samples, summaries, claims, or control values, requires every gate arm blocked, and one disclosure row per battery workload per ran leg. + This mode measures nothing and can never satisfy a benchmark check. +- `--publication=timed`: the paired timing lane. The battery executes for + real on every ran leg with `clickhouse.enabled=false`; only the chain + ships disclosed not_run under `PublicationCapabilityTimed` + (`CH-not-deployed-for-timed-dataframe-legs`). Reports from this lane + carry real samples, summaries, controls, and claims; `VerifyReport` + still refuses them if the battery flag is flipped off while evidence + is present. - `--publication=run`: requires `--clickhouse-enabled`, i.e. the CI lane with the pinned service. A report whose identity says ClickHouse is disabled while claiming the chain ran fails verification. +## Paired speed comparison (operator acceptance, 2026-11-13) + +`node scripts/benchmark-backends.mjs --paired --binary --benchcli + --out [--profile tiny|small|representative] +[--iterations N>=5 --warmup N>=1 --rounds N] [--source-commit <40-hex>]` +runs the same matched battery against BOTH real drivers discovered from +`LOOM_TEST_ARANGO_URL` + `LOOM_TEST_POSTGRES_URL` (the same variables the +`backend-conformance` job exports; the CI job +`factory/paired-driver-benchmark` in `.github/workflows/tests.yaml` runs +this exact command with services `arangodb:3.12` + `postgres:18`). +Correctness gates decide first; then >= 5 alternating measured timings per +driver are required. The check FAILS — never skips, never passes — when a +service is missing, an arm is planned/not_run/excluded (beyond the two +reviewer-accepted live-battery exclusions), a driver produced no usable +timing samples, or the Go verifier rejects the artifact. On success it +writes `comparison.md` (median/min/max/range in milliseconds per workload +per driver, plus versions, source SHA, fixture digest, warmup/cache/ +rounds/concurrency conditions) beside the raw `report.json`. No speedup is +claimed unless the claim clears correctness and the stability control; +either driver may win. + ## Live-run guards (all fail closed, exit 3) `LOOM_BENCH_LIVE=1` (coordinator-granted exclusive window), `--window-note`, @@ -90,10 +125,10 @@ from the MockFence. ## Current blockers (cited in planned artifacts) -- ClickHouse service: the publication chain (and everything timed behind - it) needs `clickhouse/clickhouse-server:25.3` as provisioned by the CI - lane; no container engine exists in the worker session, so live worker - runs use `--publication=excluded`. Owner: CI lane. +- ClickHouse service: needed only by the publication *chain* + (`--publication=run`), which requires `clickhouse/clickhouse-server:25.3` + as provisioned by the CI lane. Paired timing uses + `--publication=timed` and does not wait on it. Owner: CI lane. - `PG-PG-AUTH-PATH-DISCOVERY-ALLGENS` — FIXED in PR #156 (verified live: upload accepted, fence hit once, no 403); the branch base predates the library-side fix, so live legs go green when it merges. Evidence: diff --git a/internal/dataframe/benchmark/armplan.go b/internal/dataframe/benchmark/armplan.go index fcaeabe63..325f6b007 100644 --- a/internal/dataframe/benchmark/armplan.go +++ b/internal/dataframe/benchmark/armplan.go @@ -64,14 +64,21 @@ const ClickHouseCIImage = "clickhouse/clickhouse-server:" + ClickHouseCIVersion // PublicationCapabilityCH names the missing capability when the // publication chain cannot execute because ClickHouse is not deployed. // Publication is structurally ClickHouse-backed: publication.Target has -// exactly one production implementation (ClickHouseBundleStore) and -// POST .../explorer-config materializes through it BEFORE persisting the -// revision that seeds the authoring draft, so publish, reconcile, preview, -// workload gates, timing, and controls form one chain behind ClickHouse. -// The preview EXECUTION path itself stays ClickHouse-free (matched reader -// path ruling); it is the draft-seeding publish that needs the service. +// exactly one production implementation (ClickHouseBundleStore). +// The dataset→bundle→ClickHouse CHAIN needs the service; the authoring +// battery (explorer publish, builder, reconcile, preview, timing, gates, +// controls) does not — the backend-conformance lane runs it on both real +// drivers with clickhouse.enabled=false, and the PostgreSQL backend +// forbids clickhouse.enabled in this slice outright. const PublicationCapabilityCH = "CH-" + ClickHouseCIVersion + "-unavailable-in-container" +// PublicationCapabilityTimed names why the chain stays not_run on the +// paired timing lane: the dataframe legs run both drivers with +// clickhouse.enabled=false (the matched reader path ruling; the chain +// belongs to the ClickHouse CI lane), and the timing battery provably +// does not consume the chain. +const PublicationCapabilityTimed = "CH-not-deployed-for-timed-dataframe-legs" + // PublicationPlan declares whether the publication chain ran. It is a // plan-level property so VerifyReport, not the driver's self-description, // decides whether a report may carry timing evidence. @@ -82,6 +89,24 @@ type PublicationPlan struct { // PublicationCapabilityCH is the canonical value for a ClickHouse-less // deployment. Capability string `json:"capability,omitempty"` + // Battery is the timed mode: the chain stays not_run (Run false) but + // the timing battery — authoring, preview, timing, gates, controls — + // executes for real. Only the chain itself ships disclosed not_run. + // Run and Battery together is contradictory and refused. + Battery bool `json:"battery,omitempty"` +} + +// PublicationTimed reports the timed mode: chain excluded, battery run. +func (p RunPlan) PublicationTimed() bool { + return p.Publication != nil && !p.Publication.Run && p.Publication.Battery +} + +// PublicationZeroEvidence reports the pure excluded mode: chain excluded +// AND the battery excluded, so the run measured nothing and a report +// carrying samples, summaries, claims, control values, or non-blocked +// arms would be forged. This is what VerifyReport pins. +func (p RunPlan) PublicationZeroEvidence() bool { + return p.PublicationExcluded() && !p.Publication.Battery } // ApplyExcludedWorkloadGateReports replaces any gate report for an @@ -252,6 +277,9 @@ func PublicationGateReports(legs []LegPlan, capability string) []WorkloadGateRep // shape: an excluded chain must name its missing capability. func ValidatePublicationPlan(plan RunPlan) error { if plan.Publication == nil || plan.Publication.Run { + if plan.Publication != nil && plan.Publication.Run && plan.Publication.Battery { + return fmt.Errorf("benchmark: publication plan declares run and battery together; battery names the timed mode where the chain did not run") + } return nil } if strings.TrimSpace(plan.Publication.Capability) == "" { diff --git a/internal/dataframe/benchmark/cmd/benchcli/guards_test.go b/internal/dataframe/benchmark/cmd/benchcli/guards_test.go index b0037a7bb..2892f912d 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/guards_test.go +++ b/internal/dataframe/benchmark/cmd/benchcli/guards_test.go @@ -335,3 +335,40 @@ func fenceGrants(t *testing.T, d *deployer, token string) map[string]any { } const dsnForTest = "postgresql://u:p@127.0.0.1:55432/loom_bench_clitest?sslmode=disable" + +// --- live exit-code contract (review r4559430568, finding 4) ------------------- + +func liveSample(warmup bool) benchmark.Sample { + return benchmark.Sample{Backend: benchmark.BackendPostgres, WorkloadID: "select-filter", Warmup: warmup, Outcome: benchmark.OutcomePassed, Cache: benchmark.CacheWarm, Millis: 1} +} + +func TestClassifyLiveExit(t *testing.T) { + cases := []struct { + name string + status string + sample []benchmark.Sample + code int + }{ + {"passed exits 0", "passed", []benchmark.Sample{liveSample(false)}, 0}, + {"passed with no samples still 0", "passed", nil, 0}, + {"failed with measured samples exits 1 (ran and failed)", "failed", []benchmark.Sample{liveSample(false)}, 1}, + {"blocked with measured samples exits 1", "blocked", []benchmark.Sample{liveSample(false)}, 1}, + {"failed with zero samples exits 3 (disclosed not_run)", "failed", nil, 3}, + {"default publication=excluded shape: failed, zero samples", "failed", []benchmark.Sample{}, 3}, + {"warmup-only samples are not measured evidence", "failed", []benchmark.Sample{liveSample(true), liveSample(true)}, 3}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + code, verdict := classifyLiveExit(c.status, c.sample) + if code != c.code { + t.Fatalf("classifyLiveExit(%q, %d samples) = %d (%s), want %d", c.status, len(c.sample), code, verdict, c.code) + } + if c.code == 0 && verdict != "OK" { + t.Fatalf("exit 0 must print OK, got %q", verdict) + } + if c.code == 3 && verdict == "OK" { + t.Fatal("a disclosed not_run must never carry the success verdict") + } + }) + } +} diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go index 1ad8bd2db..8e42715d2 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/main.go +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -128,7 +128,7 @@ func runLive() { fs.IntVar(&f.emptyFloorIters, "empty-floor-iterations", 5, "iterations for the empty-result floor control") fs.StringVar(&f.arangoCapability, "arango-capability", defaultArangoCapability, "capability cited by the arango not_run legs (planned scope)") fs.StringVar(&f.postgresCapability, "postgres-capability", defaultPostgresCapability, "capability cited by the postgres not_run legs (planned scope)") - fs.StringVar(&f.publication, "publication", "excluded", "publication chain: excluded | run. The chain is ClickHouse-backed end to end; run requires --clickhouse-enabled and a CI lane with "+benchmark.ClickHouseCIImage) + fs.StringVar(&f.publication, "publication", "excluded", "publication chain: excluded | timed | run. excluded runs nothing downstream of upload (never satisfies a benchmark check); timed runs the full battery with clickhouse.enabled=false and discloses only the chain not_run; run asserts the chain executed and requires --clickhouse-enabled and a CI lane with "+benchmark.ClickHouseCIImage) fs.BoolVar(&f.clickHouseEnabled, "clickhouse-enabled", false, "assert the arms were deployed with ClickHouse ("+benchmark.ClickHouseCIImage+"); required for --publication=run") _ = fs.Parse(os.Args[2:]) @@ -206,17 +206,25 @@ func runLive() { } switch f.publication { case "excluded": - // Coordinator ruling 2026-11-13: no ClickHouse in the worker - // container; the whole chain ships disclosed planned/not_run. + // Pure excluded mode: no ClickHouse and no timing either; the + // whole downstream battery ships disclosed planned/not_run. A + // run like this can never satisfy a benchmark check (exit 3). plan.Publication = &benchmark.PublicationPlan{Run: false, Capability: benchmark.PublicationCapabilityCH} plan.PlannedNotRun = append(plan.PlannedNotRun, benchmark.PublicationDisclosures(plan.Legs, benchmark.PublicationCapabilityCH)...) + case "timed": + // Paired timing lane: the battery — authoring, preview, timing, + // gates, controls — runs for real against both drivers with + // clickhouse.enabled=false; only the ClickHouse-backed + // publication chain ships disclosed not_run. + plan.Publication = &benchmark.PublicationPlan{Run: false, Battery: true, Capability: benchmark.PublicationCapabilityTimed} + plan.PlannedNotRun = append(plan.PlannedNotRun, benchmark.PublicationDisclosures(plan.Legs, benchmark.PublicationCapabilityTimed)...) case "run": if !f.clickHouseEnabled { preflight("--publication=run requires --clickhouse-enabled: the chain is ClickHouse-backed end to end (%s)", benchmark.ClickHouseCIImage) } plan.Publication = &benchmark.PublicationPlan{Run: true} default: - preflight("unknown --publication %q (want excluded | run)", f.publication) + preflight("unknown --publication %q (want excluded | timed | run)", f.publication) } // Live battery law: registry-excluded workloads ride along as // disclosed planned/not_run rows on every ran leg. @@ -267,8 +275,40 @@ func runLive() { manifest["fenceRequests"] = d.fence.Requests() } writeRunArtifacts(f.out, report, manifest) - fmt.Printf("benchcli run: OK run=%s scope=%s status=%s claims=%d out=%s\n", - report.Identity.RunID, report.Plan.Scope, report.Status, len(report.Claims), f.out) + code, verdict := classifyLiveExit(report.Status, report.Samples) + fmt.Printf("benchcli run: %s run=%s scope=%s status=%s claims=%d out=%s\n", + verdict, report.Identity.RunID, report.Plan.Scope, report.Status, len(report.Claims), f.out) + if code == 0 { + return + } + for _, reason := range report.StatusReasons { + fmt.Fprintf(os.Stderr, "benchcli run: status reason: %s\n", reason) + } + if code == 3 { + fmt.Fprintln(os.Stderr, "benchcli run: exit 3 = disclosed not_run: the run captured zero measured timings; it must never be read as a benchmark pass") + } + os.Exit(code) +} + +// classifyLiveExit implements the documented CLI exit contract for a live +// run that produced its artifacts (README): exit 0 only when the live +// legs ran and the report status is passed; exit 1 when the run measured +// something and failed; exit 3 when nothing was measured at all, so a +// non-passed zero-timing report (for example the default +// --publication=excluded path, where every gate arm is blocked) discloses +// not_run exactly like planned scope instead of wearing the success shape +// of exit 0 (review r4559430568, finding 4). Warmup samples never count +// as measured: a run whose only evidence is warmup measured nothing. +func classifyLiveExit(status string, samples []benchmark.Sample) (int, string) { + if status == "passed" { + return 0, "OK" + } + for _, s := range samples { + if !s.Warmup { + return 1, "FAILED" + } + } + return 3, "DISCLOSED NOT_RUN" } // writeRunArtifacts emits the canonical three-file artifact set. Every diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index f5200ea9b..711cec928 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -191,13 +191,16 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden } } - // Excluded publication chain (coordinator ruling 2026-11-13): the - // chain is ClickHouse-backed end to end and ClickHouse is not - // deployed here, so spawn and upload have run for real and every - // publication-dependent stage ships as planned/not_run with the - // capability named. Nothing below this point executes, so the report - // carries zero samples, zero claims, and zero control values. - if opts.Plan.PublicationExcluded() { + // Excluded publication chain (pure excluded mode): spawn and upload + // have run for real and every downstream stage ships as + // planned/not_run with the capability named. Nothing below this point + // executes, so the report carries zero samples, zero claims, and zero + // control values. The timed mode (Battery) skips this return: the + // timing battery provably does not consume the chain (the + // backend-conformance lane runs authoring+publish on both drivers + // with clickhouse.enabled=false), so only the chain itself is + // disclosed not_run while timing is collected for real. + if opts.Plan.PublicationZeroEvidence() { identity := RunIdentity{ RunID: opts.RunID, StartedAt: opts.StartedAt.UTC(), Preset: opts.Preset, SourceCommit: opts.SourceCommit, DatasetDigest: files.Digest, diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go index f5d80929f..95d78e97f 100644 --- a/internal/dataframe/benchmark/report.go +++ b/internal/dataframe/benchmark/report.go @@ -257,13 +257,13 @@ func VerifyReport(report *Report) error { return fmt.Errorf("benchmark report: publication chain claimed to run although ClickHouse is disabled") } if runLegs > 0 { - if publicationExcluded { - // The publication chain is ClickHouse-backed end to end: its - // only publication.Target is ClickHouseBundleStore, and the - // workspace seeding every receipt persists only after - // materialization. With the chain excluded nothing downstream - // measured, so any control value is forged (stolen limits, - // borrowed floors, ratio cover). + if report.Plan.PublicationZeroEvidence() { + // Pure excluded mode: the battery itself was excluded, so + // nothing measured and any control value or timing evidence + // is forged (stolen limits, borrowed floors, ratio cover). + // The timed mode (chain excluded, battery run) belongs to the + // else branch below: its evidence is real and must satisfy + // the control-presence rules. c := report.Controls if c.StabilityCVLimit != 0 || c.EmptyFloorMillis != 0 || c.SelfRatioArango != 0 || c.SelfRatioPostgres != 0 || c.RatioPolicy != "" || len(c.UnstableCells) != 0 { diff --git a/internal/dataframe/benchmark/report_test.go b/internal/dataframe/benchmark/report_test.go index e82e7822c..48a762cb4 100644 --- a/internal/dataframe/benchmark/report_test.go +++ b/internal/dataframe/benchmark/report_test.go @@ -284,3 +284,76 @@ func TestBuildReportRefusesInconsistentConfig(t *testing.T) { t.Fatal("zero config must be refused at build, not defaulted") } } + +// --- timed publication lane (operator amendment 2026-11-13) -------------------- + +// timedReport builds the paired-timing shape: the ClickHouse-backed +// publication chain is disclosed not_run, but the battery ran for real, +// so samples, summaries, controls, and claims are legitimate evidence +// rather than forgery. +func timedReport(t *testing.T) *Report { + t.Helper() + report := validReport(t) + report.Identity.ClickHouseDisabled = true + plan := report.Plan + plan.Publication = &PublicationPlan{Run: false, Battery: true, Capability: PublicationCapabilityTimed} + plan.PlannedNotRun = append(plan.PlannedNotRun, PublicationDisclosures(plan.Legs, PublicationCapabilityTimed)...) + built, err := BuildReport(report.Identity, plan, report.Controls, report.GateReports, report.Samples) + if err != nil { + t.Fatalf("timed publication report must verify: %v", err) + } + return built +} + +func TestTimedPublicationCarriesTimingEvidence(t *testing.T) { + report := timedReport(t) + if report.Status != "passed" { + t.Fatalf("timed lane status: %s (%v)", report.Status, report.StatusReasons) + } + if len(report.Samples) == 0 || len(report.Summaries) == 0 { + t.Fatal("timed lane must carry real timing evidence") + } + if len(report.Claims) != len(Battery)-len(LiveBatteryExclusions()) { + t.Fatalf("timed lane claims: %d want %d", len(report.Claims), len(Battery)-len(LiveBatteryExclusions())) + } + if !report.Plan.PublicationTimed() || report.Plan.PublicationZeroEvidence() { + t.Fatalf("timed lane classification wrong: %+v", report.Plan.Publication) + } + for _, leg := range report.Plan.Legs { + disclosed := 0 + for _, row := range report.Plan.PlannedNotRun { + if row.Backend == leg.Backend && strings.HasPrefix(row.Reason, PublicationChainReason) { + disclosed++ + } + } + if disclosed != len(Battery) { + t.Fatalf("%s: %d publication-chain disclosure rows want %d (one per workload)", leg.Backend, disclosed, len(Battery)) + } + } +} + +func TestPublicationBatteryFlagMustNotBeFalseToPass(t *testing.T) { + report := timedReport(t) + plan := report.Plan + plan.Publication.Battery = false + report.Plan = plan + if err := VerifyReport(report); err == nil { + t.Fatal("battery=false on a report with timing evidence must be refused") + } +} + +func TestPublicationBatteryRequiresExcludedChain(t *testing.T) { + plan := BothArmsPlan() + plan.Publication = &PublicationPlan{Run: true, Battery: true, Capability: PublicationCapabilityTimed} + if err := ValidatePublicationPlan(plan); err == nil { + t.Fatal("run=true with battery=true must be refused as contradictory") + } +} + +func TestTimedPublicationRequiresNamedCapability(t *testing.T) { + plan := BothArmsPlan() + plan.Publication = &PublicationPlan{Run: false, Battery: true} + if err := ValidatePublicationPlan(plan); err == nil { + t.Fatal("timed lane must name the capability it discloses") + } +} diff --git a/scripts/benchmark-backends.mjs b/scripts/benchmark-backends.mjs index ea1c108b2..0f780b537 100644 --- a/scripts/benchmark-backends.mjs +++ b/scripts/benchmark-backends.mjs @@ -14,35 +14,227 @@ // --harness-selftest // Node-only sanity pass over this script's own logic. Labelled // selftest; it can never satisfy a live check. +// --paired --binary --out [--profile tiny|small|representative] +// [--iterations N>=5 --warmup N>=1 --rounds N] [--source-commit SHA] +// [--benchcli ] [--window-note ] +// The operator-mandated comparison: the same matched battery runs +// against BOTH real drivers (LOOM_TEST_ARANGO_URL + +// LOOM_TEST_POSTGRES_URL), correctness gates first, then >=5 +// alternating timings per driver. Writes comparison.md next to the +// verified report. FAILS (never skips) when a service is missing, +// an arm is planned/not_run/excluded, or a driver has no timings. import { spawnSync } from 'node:child_process'; -import { existsSync, readFileSync } from 'node:fs'; +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; export const CHECK_HARNESS = 'benchmark-harness-tests'; export const CHECK_LIVE = 'live-two-backend-correctness'; +export const CHECK_PAIRED = 'paired-driver-benchmark'; + +// Operator acceptance (2026-11-13 amendment): a paired benchmark is real +// only when BOTH drivers executed matched workloads and produced actual +// timing. Five alternating measured samples per backend is the floor; +// warmup samples never count. +export const PAIRED_MIN_SAMPLES_PER_BACKEND = 5; +export const PAIRED_PROFILES = ['tiny', 'small', 'representative']; export function parseArgs(argv) { - const flags = { mode: null, report: null, benchcli: null, out: null, runArgs: [], selftest: false }; + const flags = { + mode: null, report: null, benchcli: null, out: null, runArgs: [], selftest: false, + paired: false, binary: null, profile: 'tiny', iterations: 5, warmup: 1, rounds: null, + concurrency: 1, previewLimit: 1000, sourceCommit: null, seed: 7, windowNote: null, + }; let i = 0; + const int = (name, raw, min) => { + const value = Number(raw); + if (!Number.isInteger(value) || value < min) throw new Error(`${name} must be an integer >= ${min}, got ${raw}`); + return value; + }; while (i < argv.length) { const arg = argv[i]; if (arg === '--verify-only') flags.mode = 'verify'; else if (arg === '--run') flags.mode = 'run'; + else if (arg === '--paired') flags.paired = true; else if (arg === '--harness-selftest') flags.selftest = true; else if (arg === '--report') { i += 1; flags.report = argv[i]; } else if (arg === '--benchcli') { i += 1; flags.benchcli = argv[i]; } + else if (arg === '--binary') { i += 1; flags.binary = argv[i]; } + else if (arg === '--profile') { i += 1; flags.profile = argv[i]; } + else if (arg === '--iterations') { i += 1; flags.iterations = int('--iterations', argv[i], PAIRED_MIN_SAMPLES_PER_BACKEND); } + else if (arg === '--warmup') { i += 1; flags.warmup = int('--warmup', argv[i], 1); } + else if (arg === '--rounds') { i += 1; flags.rounds = int('--rounds', argv[i], 1); } + else if (arg === '--concurrency') { i += 1; flags.concurrency = int('--concurrency', argv[i], 1); } + else if (arg === '--preview-limit') { i += 1; flags.previewLimit = int('--preview-limit', argv[i], 1); } + else if (arg === '--seed') { i += 1; flags.seed = int('--seed', argv[i], 0); } + else if (arg === '--source-commit') { i += 1; flags.sourceCommit = argv[i]; } + else if (arg === '--window-note') { i += 1; flags.windowNote = argv[i]; } else if (arg === '--out') { i += 1; flags.out = argv[i]; } else if (arg === '--') { flags.runArgs = argv.slice(i + 1); break; } else throw new Error(`unknown argument: ${arg}`); i += 1; } - if (flags.selftest && flags.mode) throw new Error('--harness-selftest cannot combine with --verify-only/--run'); - if (!flags.selftest && !flags.mode) throw new Error('choose --verify-only, --run, or --harness-selftest'); + if (flags.selftest && (flags.mode || flags.paired)) throw new Error('--harness-selftest cannot combine with a run mode'); + if (flags.paired && flags.mode) throw new Error('--paired cannot combine with --verify-only/--run'); + if (!flags.selftest && !flags.mode && !flags.paired) throw new Error('choose --verify-only, --run, --paired, or --harness-selftest'); if (flags.mode === 'verify' && !flags.report) throw new Error('--verify-only requires --report'); if (flags.mode === 'run' && !flags.out) throw new Error('--run requires --out'); + if (flags.paired) { + // Default: fully alternating — one measured iteration per round, so + // every driver's sample sits at the same position in the schedule. + if (flags.rounds === null) flags.rounds = flags.iterations; + if (!flags.out) throw new Error('--paired requires --out'); + if (!flags.binary) throw new Error('--paired requires --binary (the server binary the arms are spawned from)'); + if (!PAIRED_PROFILES.includes(flags.profile)) throw new Error(`--profile must be one of ${PAIRED_PROFILES.join(', ')}, got ${flags.profile}`); + if (flags.iterations < PAIRED_MIN_SAMPLES_PER_BACKEND) { + throw new Error(`--paired requires --iterations >= ${PAIRED_MIN_SAMPLES_PER_BACKEND}: fewer alternating runs per driver is not a comparison`); + } + if (flags.warmup < 1) throw new Error('--paired requires --warmup >= 1 so cached timings are meaningful'); + if (flags.rounds < 1) throw new Error('--paired requires --rounds >= 1'); + if (flags.iterations % flags.rounds !== 0) throw new Error(`--iterations ${flags.iterations} must split evenly into --rounds ${flags.rounds}`); + if (!/^[0-9a-f]{40}$/.test(flags.sourceCommit ?? '')) throw new Error('--paired requires --source-commit as the full 40-hex SHA the arms were built from'); + } return flags; } +// Paired service discovery. The CI lane exports the same two variables the +// backend-conformance job uses; the local dual-URL path uses them too. A +// missing service is a hard failure: silently benchmarking one driver and +// calling it a comparison is exactly what the operator rejected. +export function pairedServices(env) { + const arangoBase = (env.LOOM_TEST_ARANGO_URL ?? '').trim(); + const postgresDsn = (env.LOOM_TEST_POSTGRES_URL ?? '').trim(); + const problems = []; + if (!arangoBase) problems.push('LOOM_TEST_ARANGO_URL is not set: the Arango/AQL driver has no service to run against'); + if (!postgresDsn) problems.push('LOOM_TEST_POSTGRES_URL is not set: the PostgreSQL/SQL driver has no service to run against'); + // benchcli refuses an Arango DSN that does not name a loom_bench* + // database; the CI service URL carries no path, so the lane's database + // name is appended here (the server creates it on first use). + let arangoUrl = arangoBase; + if (arangoUrl) { + const withoutScheme = arangoUrl.replace(/^[a-z]+:\/\//, ''); + if (!withoutScheme.includes('/')) arangoUrl = `${arangoUrl.replace(/\/$/, '')}/loom_bench_ci`; + } + return { arangoUrl, postgresDsn, problems }; +} + +export function buildPairedInvocation(flags, services) { + const bin = flags.benchcli ?? 'go'; + const args = [ + '--scope=both-arms', + // The ClickHouse-backed publication chain is disclosed not_run; the + // timing battery itself is ClickHouse-free and runs for real on both + // drivers (the backend-conformance lane proves that posture). + '--publication=timed', + `--preset=${flags.profile}`, + `--seed=${flags.seed}`, + `--warmup=${flags.warmup}`, + `--iterations=${flags.iterations}`, + `--rounds=${flags.rounds}`, + `--concurrency=${flags.concurrency}`, + `--preview-limit=${flags.previewLimit}`, + `--binary=${flags.binary}`, + `--arango-dsn=${services.arangoUrl}`, + `--postgres-dsn=${services.postgresDsn}`, + `--source-commit=${flags.sourceCommit}`, + `--out=${flags.out}`, + ]; + if (flags.windowNote) args.push(`--window-note=${flags.windowNote}`); + return { cmd: bin, args: flags.benchcli ? ['run', ...args] : ['run', './internal/dataframe/benchmark/cmd/benchcli', 'run', ...args] }; +} + +// A paired report counts as a real comparison only when both arms actually +// ran matched workloads and each carries at least PAIRED_MIN_SAMPLES_PER_BACKEND +// non-warmup timings. Anything else — a planned, not_run, or excluded arm, +// an empty timing set, a correctness failure — is a fail, never a pass. +export function classifyPairedReport(report, minSamples = PAIRED_MIN_SAMPLES_PER_BACKEND) { + const problems = []; + const legs = report.plan?.legs ?? []; + const ranBackends = legs.filter((leg) => leg.mode === 'run').map((leg) => leg.backend); + for (const backend of ['arango', 'postgres']) { + if (!legs.some((leg) => leg.backend === backend)) problems.push(`no leg declared for ${backend}`); + else if (!ranBackends.includes(backend)) { + const leg = legs.find((l) => l.backend === backend); + problems.push(`${backend} arm is ${leg.mode}${leg.capability ? ` (${leg.capability})` : ''}: a planned or not_run arm is not a benchmark`); + } + } + const excludedArms = (report.gateReports ?? []).filter((gate) => + [gate.arango, gate.postgres].some((arm) => arm?.outcome === 'blocked' + && !(gate.exclusionReason ?? '').startsWith('live battery exclusion: '))); + if (excludedArms.length > 0) { + problems.push(`${excludedArms.length} gate report(s) carry blocked arms without a live-battery exclusion marker: ${excludedArms.map((g) => g.workloadId).join(', ')}`); + } + const measured = {}; + for (const backend of ['arango', 'postgres']) measured[backend] = 0; + for (const sample of report.samples ?? []) { + if (sample.warmup) continue; + if (sample.outcome !== 'passed') continue; + if (!(sample.backend in measured)) continue; + if (!Number.isFinite(sample.millis) || sample.millis < 0) continue; + measured[sample.backend] += 1; + } + for (const backend of ['arango', 'postgres']) { + if (measured[backend] < minSamples) { + problems.push(`${backend} produced ${measured[backend]} usable timing sample(s), need >= ${minSamples}: without real timing on both drivers there is no comparison`); + } + } + if (report.status !== 'passed') problems.push(`report status is ${report.status ?? 'unknown'}, not passed`); + if (report.identity?.clickhouseDisabled !== true) { + problems.push('report does not record ClickHouse as disabled: the timed lane must state that honestly'); + } + return problems.length === 0 ? { status: 'pass', disclosure: null, measured } : { status: 'fail', disclosure: problems.join('; '), measured }; +} + +// renderComparisonTable turns the verified summaries into the readable +// table the acceptance gate asks for: units, median, range, and the +// conditions the numbers were taken under. It reads only report fields, +// so a reviewer can rerun the command and reproduce the same rows. +export function renderComparisonTable(report) { + const identity = report.identity ?? {}; + const rows = report.summaries ?? []; + const byWorkload = new Map(); + for (const row of rows) { + const key = row.workloadId; + if (!byWorkload.has(key)) byWorkload.set(key, {}); + byWorkload.get(key)[row.backend] = row.summary; + } + const num = (value) => (typeof value === 'number' ? value.toFixed(2) : 'n/a'); + const lines = []; + lines.push('# Paired driver benchmark — Arango/AQL vs PostgreSQL/SQL'); + lines.push(''); + lines.push(`Run \`${identity.runId ?? 'n/a'}\` — matched workload, both arms, ClickHouse disabled: \`${identity.clickHouseDisabled === true ? 'yes (timed lane)' : 'no'}\`.`); + lines.push(''); + lines.push('## Conditions'); + lines.push(''); + lines.push(`- source commit: \`${identity.sourceCommit ?? 'n/a'}\``); + lines.push(`- dataset preset: \`${identity.preset ?? 'n/a'}\`, fixture digest \`${identity.datasetDigest ?? 'n/a'}\``); + lines.push(`- ArangoDB version: ${identity.arangoVersion ?? 'n/a'}; PostgreSQL version: ${identity.postgresVersion ?? 'n/a'}`); + const config = identity.config ?? {}; + lines.push(`- warmup ${config.Warmup ?? 'n/a'} per workload x backend (discarded), ${config.Iterations ?? 'n/a'} measured iterations, ${config.Rounds ?? 'n/a'} interleaved rounds, concurrency ${config.Concurrency ?? 'n/a'}`); + lines.push('- cache: steady-regime samples (warm for a spawned arm at rest, service-warm for an attached service) feed the medians; process-cold samples are reported separately and never claimed'); + lines.push(''); + lines.push('Timings are milliseconds per preview request, lower is faster. Neither driver is expected to win; a gap smaller than run-to-run variation is reported as such, not as a speedup.'); + lines.push(''); + lines.push('## Timings (ms)'); + lines.push(''); + lines.push('| workload | backend | n | median | min | max | range |'); + lines.push('| --- | --- | --- | --- | --- | --- | --- |'); + for (const [workload, perBackend] of [...byWorkload.entries()].sort()) { + for (const backend of ['arango', 'postgres']) { + const s = perBackend[backend]; + if (!s) { lines.push(`| ${workload} | ${backend} | 0 | n/a | n/a | n/a | n/a |`); continue; } + const range = (typeof s.maxMillis === 'number' && typeof s.minMillis === 'number') ? (s.maxMillis - s.minMillis).toFixed(2) : 'n/a'; + lines.push(`| ${workload} | ${backend} | ${s.count ?? 0} | ${num(s.medianMillis)} | ${num(s.minMillis)} | ${num(s.maxMillis)} | ${range} |`); + } + } + const claims = report.claims ?? []; + if (claims.length === 0) { + lines.push(''); + lines.push('No claim cleared the correctness gates plus the stability control, so no speedup is claimed. That is an acceptable outcome; the measurements above are the deliverable.'); + } + return `${lines.join('\n')}\n`; +} + + export function buildVerifyInvocation(flags) { const bin = flags.benchcli ?? 'go'; const args = flags.benchcli ? ['verify', '--report', flags.report] @@ -112,9 +304,14 @@ export function classifyPlannedDisclosure(summary) { export function run(flags, deps = {}) { const exec = deps.exec ?? spawnSync; const log = deps.log ?? ((line) => console.log(line)); + const env = deps.env ?? process.env; let failures = 0; const live = (invocation) => exec(invocation.cmd, invocation.args, { stdio: 'inherit' }); + if (flags.paired) { + return runPaired(flags, { exec, log, env }); + } + if (flags.mode === 'verify') { const result = live(buildVerifyInvocation(flags)); const outcome = classifyVerifyExit(result.status); @@ -168,6 +365,64 @@ export function run(flags, deps = {}) { return failures; } +// runPaired is the operator-mandated comparison: one matched workload set, +// both real drivers, correctness first, then alternating timings. It +// refuses to call anything a benchmark when a service is missing, an arm +// did not run, or a driver produced no timings — the check goes red rather +// than green-by-scaffolding. The comparison table is written next to the +// verified report so the artifact set is self-explanatory. +function runPaired(flags, { exec, log, env }) { + const { arangoUrl, postgresDsn, problems } = pairedServices(env); + if (problems.length > 0) { + log(formatCheckLine(CHECK_PAIRED, 'fail', problems.join('; '))); + return 1; + } + const invocation = buildPairedInvocation(flags, { arangoUrl, postgresDsn }); + // The paired lane is its own granted window: it runs in a CI job with + // isolated services, so it sets the live gate the CLI demands. + const result = exec(invocation.cmd, invocation.args, { + stdio: 'inherit', + env: { ...env, LOOM_BENCH_LIVE: '1' }, + }); + const runOutcome = classifyRunExit(result.status); + log(formatCheckLine(`${CHECK_PAIRED}/run`, runOutcome.status, runOutcome.disclosure ?? undefined)); + const reportPath = `${flags.out}/report.json`; + if (!existsSync(reportPath)) { + log(formatCheckLine(CHECK_PAIRED, 'fail', `no report artifact at ${reportPath}; nothing was compared`)); + return 1; + } + // Authority stays with the Go verifier: a report this script could + // classify as fine but the verifier rejects is a fail, never a pass. + const verify = liveVerify(exec, flags, reportPath, log); + if (verify !== 0) { + log(formatCheckLine(CHECK_PAIRED, 'fail', `benchcli verify rejected the paired artifact (exit ${verify})`)); + return 1; + } + if (runOutcome.status !== 'pass') { + log(formatCheckLine(CHECK_PAIRED, 'fail', `paired run exited ${result.status}; a comparison needs a clean run on both drivers`)); + return 1; + } + const report = JSON.parse(readFileSync(reportPath, 'utf8')); + const paired = classifyPairedReport(report); + if (paired.status !== 'pass') { + log(formatCheckLine(CHECK_PAIRED, 'fail', paired.disclosure)); + return 1; + } + const comparisonPath = `${flags.out}/comparison.md`; + writeFileSync(comparisonPath, renderComparisonTable(report), 'utf8'); + log(formatCheckLine(CHECK_PAIRED, 'pass', + `matched battery on both drivers: arango ${paired.measured.arango} + postgres ${paired.measured.postgres} measured timings; table: ${comparisonPath}`)); + return 0; +} + +function liveVerify(exec, flags, reportPath, log) { + const invocation = buildVerifyInvocation({ ...flags, report: reportPath }); + const result = exec(invocation.cmd, invocation.args, { stdio: 'inherit' }); + const outcome = classifyVerifyExit(result.status); + log(formatCheckLine(CHECK_LIVE, outcome.status, outcome.disclosure ?? `verified ${reportPath}`)); + return result.status; +} + const isMain = process.argv[1] && import.meta.url.endsWith(process.argv[1].split('/').pop()); if (isMain) { try { diff --git a/scripts/benchmark-backends.test.mjs b/scripts/benchmark-backends.test.mjs index 8fcb629d2..871ab3855 100644 --- a/scripts/benchmark-backends.test.mjs +++ b/scripts/benchmark-backends.test.mjs @@ -190,3 +190,169 @@ test('planned artifact with escaped samples fails the check', async () => { assert.equal(calls.length, 1, 'a failing disclosure is not verified green'); assert.ok(lines.some((l) => l.includes(`check ${CHECK_LIVE}: fail`) && l.includes('measurement evidence'))); }); + +// --- paired-driver benchmark (operator amendment 2026-11-13) ------------------- + +import { + pairedServices, + buildPairedInvocation, + classifyPairedReport, + renderComparisonTable, + PAIRED_MIN_SAMPLES_PER_BACKEND, + CHECK_PAIRED, +} from './benchmark-backends.mjs'; + +const SHA40 = 'a'.repeat(40); + +function pairedReportFixture(overrides = {}) { + const samples = []; + for (const backend of ['arango', 'postgres']) { + for (let i = 0; i < PAIRED_MIN_SAMPLES_PER_BACKEND; i += 1) { + samples.push({ backend, workloadId: 'select-filter', round: 1, iteration: i, warmup: false, outcome: 'passed', millis: 10 + i, cache: 'warm' }); + } + samples.push({ backend, workloadId: 'select-filter', round: 0, iteration: 0, warmup: true, outcome: 'passed', millis: 20, cache: 'warm' }); + } + return { + status: 'passed', + identity: { + runId: 'bench-paired-1', preset: 'tiny', sourceCommit: SHA40, datasetDigest: 'd'.repeat(64), + arangoVersion: '3.12.4', postgresVersion: 'PostgreSQL 18.0', clickhouseDisabled: true, + config: { Warmup: 1, Iterations: 5, Concurrency: 1, Rounds: 1 }, + }, + plan: { scope: 'both-arms', legs: [{ backend: 'arango', mode: 'run' }, { backend: 'postgres', mode: 'run' }] }, + gateReports: [{ workloadId: 'select-filter', claimable: true, arango: { outcome: 'passed' }, postgres: { outcome: 'passed' } }], + samples, + summaries: [ + { workloadId: 'select-filter', backend: 'arango', cache: 'warm', summary: { count: 5, failedCount: 0, medianMillis: 12, minMillis: 10, maxMillis: 14 } }, + { workloadId: 'select-filter', backend: 'postgres', cache: 'warm', summary: { count: 5, failedCount: 0, medianMillis: 22, minMillis: 20, maxMillis: 24 } }, + ], + claims: [], + ...overrides, + }; +} + +test('paired: missing services fail the check, never skip it', () => { + const seen = pairedServices({}); + assert.equal(seen.problems.length, 2); + const logs = []; + let spawned = false; + const failures = run(parseArgs(['--paired', '--binary', '/bin/loom', '--out', 'out', '--source-commit', SHA40]), { + exec: () => { spawned = true; return { status: 0 }; }, + log: (line) => logs.push(line), + env: {}, + }); + assert.equal(failures, 1); + assert.equal(spawned, false, 'benchcli must not even start without both services'); + assert.match(logs.join('\n'), new RegExp(`check ${CHECK_PAIRED}: fail`)); +}); + +test('paired: arango dsn gets a benchmark-namespaced database', () => { + const seen = pairedServices({ LOOM_TEST_ARANGO_URL: 'http://127.0.0.1:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@127.0.0.1:5432/loom_bench_ci?sslmode=disable' }); + assert.deepEqual(seen.problems, []); + assert.match(seen.arangoUrl, /\/loom_bench_ci$/); +}); + +test('paired invocation: both arms, timed lane, real iterations', () => { + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', 'out', '--profile', 'tiny', '--iterations', '5', '--warmup', '1', '--rounds', '5', '--source-commit', SHA40]); + const inv = buildPairedInvocation(flags, { arangoUrl: 'http://127.0.0.1:8529/loom_bench_ci', postgresDsn: 'postgresql://u:p@h:5432/loom_bench_ci' }); + const joined = inv.args.join(' '); + assert.match(joined, /--scope=both-arms/); + assert.match(joined, /--publication=timed/); + assert.match(joined, /--iterations=5/); + assert.match(joined, /--warmup=1/); + assert.match(joined, /--arango-dsn=http:\/\/127\.0\.0\.1:8529\/loom_bench_ci/); +}); + +test('paired flags refuse a non-comparison', () => { + const base = ['--paired', '--binary', '/bin/loom', '--out', 'out', '--source-commit', SHA40]; + const without = (flag) => { + const i = base.indexOf(flag); + return [...base.slice(0, i), ...base.slice(i + 2)]; + }; + assert.throws(() => parseArgs(without('--out')), /requires --out/); + assert.throws(() => parseArgs(without('--binary')), /requires --binary/); + assert.throws(() => parseArgs(without('--source-commit')), /source-commit/); + assert.throws(() => parseArgs([...base, '--iterations', '3']), /integer >= 5/); + assert.throws(() => parseArgs([...base, '--warmup', '0']), /warmup/); + assert.throws(() => parseArgs([...base, '--profile', 'smoke']), /--profile must be one of/); +}); + +test('paired classification: only real timing on both drivers passes', () => { + assert.equal(classifyPairedReport(pairedReportFixture()).status, 'pass'); + + const oneColdArm = pairedReportFixture(); + oneColdArm.samples = oneColdArm.samples.filter((s) => s.backend !== 'postgres' || s.warmup); + assert.equal(classifyPairedReport(oneColdArm).status, 'fail', 'postgres with only warmups is not timed'); + + const notRunArm = pairedReportFixture(); + notRunArm.plan.legs = [{ backend: 'arango', mode: 'run' }, { backend: 'postgres', mode: 'not_run', capability: 'CH-x' }]; + assert.equal(classifyPairedReport(notRunArm).status, 'fail', 'a not_run arm must fail the check'); + + const plannedBoth = pairedReportFixture({ status: 'failed' }); + plannedBoth.plan.legs = [{ backend: 'arango', mode: 'not_run' }, { backend: 'postgres', mode: 'not_run' }]; + const plannedVerdict = classifyPairedReport(plannedBoth); + assert.equal(plannedVerdict.status, 'fail'); + assert.match(plannedVerdict.disclosure, /planned or not_run arm is not a benchmark/); + + const failedStatus = pairedReportFixture({ status: 'failed' }); + assert.equal(classifyPairedReport(failedStatus).status, 'fail', 'correctness failure cannot be read as fast'); + + const chClaimed = pairedReportFixture(); + chClaimed.identity.clickhouseDisabled = false; + assert.equal(classifyPairedReport(chClaimed).status, 'fail', 'the timed lane must record ClickHouse as disabled'); + + const blockedArm = pairedReportFixture(); + blockedArm.gateReports = [{ workloadId: 'select-filter', arango: { outcome: 'blocked' }, postgres: { outcome: 'passed' } }]; + assert.equal(classifyPairedReport(blockedArm).status, 'fail', 'a blocked arm without an exclusion marker is a fail'); + + const excludedOk = pairedReportFixture(); + excludedOk.gateReports = [ + { workloadId: 'select-filter', arango: { outcome: 'passed' }, postgres: { outcome: 'passed' } }, + { workloadId: 'derive-age-band', exclusionReason: 'live battery exclusion: unproven recode', arango: { outcome: 'blocked' }, postgres: { outcome: 'blocked' } }, + ]; + assert.equal(classifyPairedReport(excludedOk).status, 'pass', 'the two reviewer-accepted exclusions stand'); +}); + +test('paired table reports medians, ranges, units, versions, and SHA', () => { + const table = renderComparisonTable(pairedReportFixture()); + assert.match(table, /\| select-filter \| arango \| 5 \| 12\.00 \| 10\.00 \| 14\.00 \| 4\.00 \|/); + assert.match(table, /\| select-filter \| postgres \| 5 \| 22\.00 \| 20\.00 \| 24\.00 \| 4\.00 \|/); + assert.match(table, /milliseconds/); + assert.match(table, new RegExp(SHA40)); + assert.match(table, /3\.12\.4/); + assert.match(table, /PostgreSQL 18\.0/); + assert.match(table, /no speedup is claimed/i); +}); + +test('paired run end-to-end: clean both-arm report passes and writes the table', async () => { + const dir = await mkdtemp(join(tmpdir(), 'paired-')); + const report = pairedReportFixture(); + const logs = []; + const exec = (cmd, args) => { + if (args.includes('verify')) return { status: 0 }; + return { status: 0 }; + }; + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', dir, '--source-commit', SHA40]); + await writeFile(join(dir, 'report.json'), JSON.stringify(report)); + const failures = run(flags, { exec, log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_ci' } }); + assert.equal(failures, 0, logs.join('\n')); + const { readFile } = await import('node:fs/promises'); + const table = await readFile(join(dir, 'comparison.md'), 'utf8'); + assert.match(table, /select-filter/); +}); + +test('paired run end-to-end: zero-timing report fails the check even with exit 0', async () => { + const dir = await mkdtemp(join(tmpdir(), 'paired-')); + const report = pairedReportFixture(); + report.samples = []; + report.summaries = []; + report.claims = []; + report.status = 'failed'; + const exec = () => ({ status: 0 }); + const logs = []; + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', dir, '--source-commit', SHA40]); + await writeFile(join(dir, 'report.json'), JSON.stringify(report)); + const failures = run(flags, { exec, log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_ci' } }); + assert.equal(failures, 1); + assert.match(logs.join('\n'), /check paired-driver-benchmark: fail/); +}); From b4a2240bdf212baa1b9c0e66f3949ba8a535cf9a Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:42:03 +0000 Subject: [PATCH 27/45] fix(benchmark): paired lane provisions its own fresh benchmark database CI run 43366558961 of the new factory/paired-driver-benchmark job proved the lane end to end up to the first real posture the PostgreSQL backend takes: readiness answers schema_missing -- the caller owns schema lifecycle. The Arango arm reached ready 200; the Postgres arm could not, so the check correctly went red with no comparison table. The paired run now bootstraps like the server's live composition fixtures do: when LOOM_BENCH_ADMIN_DSN is set it runs benchcli drop-pg (best effort) and setup-pg for the DSN's loom_bench_* database -- database, six-series schema ladder, migrated tables -- before spawning arms, and fails the check before any timing attempt if setup-pg fails. The workflow supplies the admin DSN and lets the script own provisioning so the documented command is the whole lane. Verified against the failed run's artifact: the arm log's 503 reason and the rendered config were byte-checked against PostgresBackendYAML's six-series contract. --- .github/workflows/tests.yaml | 4 +- internal/dataframe/benchmark/README.md | 8 +++- scripts/benchmark-backends.mjs | 56 ++++++++++++++++++++++++-- scripts/benchmark-backends.test.mjs | 49 ++++++++++++++++++++++ 4 files changed, 110 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index dd7463e9c..aaaa7a4cc 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -452,19 +452,19 @@ jobs: postgres18: image: postgres:18 env: - POSTGRES_DB: loom_bench_ci POSTGRES_USER: loom_bench POSTGRES_PASSWORD: loom_ci_test_only ports: - 5432:5432 options: >- - --health-cmd "pg_isready -h 127.0.0.1 -U loom_bench -d loom_bench_ci" + --health-cmd "pg_isready -h 127.0.0.1 -U loom_bench" --health-interval 3s --health-timeout 5s --health-retries 40 env: LOOM_TEST_ARANGO_URL: http://127.0.0.1:8529 LOOM_TEST_POSTGRES_URL: postgresql://loom_bench:loom_ci_test_only@127.0.0.1:5432/loom_bench_ci?sslmode=disable + LOOM_BENCH_ADMIN_DSN: postgresql://loom_bench:loom_ci_test_only@127.0.0.1:5432/postgres?sslmode=disable steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/internal/dataframe/benchmark/README.md b/internal/dataframe/benchmark/README.md index b3198f63c..f80784da5 100644 --- a/internal/dataframe/benchmark/README.md +++ b/internal/dataframe/benchmark/README.md @@ -103,7 +103,13 @@ runs the same matched battery against BOTH real drivers discovered from `LOOM_TEST_ARANGO_URL` + `LOOM_TEST_POSTGRES_URL` (the same variables the `backend-conformance` job exports; the CI job `factory/paired-driver-benchmark` in `.github/workflows/tests.yaml` runs -this exact command with services `arangodb:3.12` + `postgres:18`). +this exact command with services `arangodb:3.12` + `postgres:18`). With +`LOOM_BENCH_ADMIN_DSN` set it first provisions its own fresh +`loom_bench_*` database via `benchcli drop-pg`/`setup-pg` (database, +six-series schema ladder, migrated tables) — the PostgreSQL backend +readiness answers `schema_missing ... the caller owns schema lifecycle`, +so the lane, not the server, bootstraps. A failed `setup-pg` fails the +check before any timing attempt. Correctness gates decide first; then >= 5 alternating measured timings per driver are required. The check FAILS — never skips, never passes — when a service is missing, an arm is planned/not_run/excluded (beyond the two diff --git a/scripts/benchmark-backends.mjs b/scripts/benchmark-backends.mjs index 0f780b537..47d24d82e 100644 --- a/scripts/benchmark-backends.mjs +++ b/scripts/benchmark-backends.mjs @@ -18,11 +18,15 @@ // [--iterations N>=5 --warmup N>=1 --rounds N] [--source-commit SHA] // [--benchcli ] [--window-note ] // The operator-mandated comparison: the same matched battery runs -// against BOTH real drivers (LOOM_TEST_ARANGO_URL + +// against BOTH real drivers (LOOM_TEST_ARANGO_URL + // LOOM_TEST_POSTGRES_URL), correctness gates first, then >=5 -// alternating timings per driver. Writes comparison.md next to the -// verified report. FAILS (never skips) when a service is missing, -// an arm is planned/not_run/excluded, or a driver has no timings. +// alternating timings per driver. With LOOM_BENCH_ADMIN_DSN set it +// first provisions its own fresh loom_bench_* database (drop-pg + +// setup-pg: database, six-series schema ladder, migrated tables), +// because the PostgreSQL backend does not own schema lifecycle. +// Writes comparison.md next to the verified report. FAILS (never +// skips) when a service is missing, an arm is +// planned/not_run/excluded, or a driver has no timings. import { spawnSync } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; @@ -117,6 +121,29 @@ export function pairedServices(env) { return { arangoUrl, postgresDsn, problems }; } +// The PostgreSQL backend refuses to own schema lifecycle ("the caller owns +// schema lifecycle" is its readiness posture). When the lane supplies an +// admin DSN, the paired run provisions its own fresh database first: +// drop-pg (best effort) then setup-pg, which creates the database, the +// six-series schema ladder, and the migrated tables — the same bootstrap +// the server's live composition fixtures use. Both subcommands refuse +// anything outside the loom_bench_* namespace. +export function pairedDatabaseFromDsn(dsn) { + const match = /^[a-z]+:\/\/[^/]*\/([^?#]+)/.exec(dsn ?? ''); + const database = match ? match[1] : ''; + if (!database.startsWith('loom_bench_')) { + return { database, problem: `paired postgres database ${JSON.stringify(database)} is outside the loom_bench_* namespace` }; + } + return { database, problem: null }; +} + +export function buildSetupInvocation(flags, action, database) { + const bin = flags.benchcli ?? 'go'; + const args = flags.benchcli ? [action, '--database', database] + : ['run', './internal/dataframe/benchmark/cmd/benchcli', action, '--database', database]; + return { cmd: bin, args }; +} + export function buildPairedInvocation(flags, services) { const bin = flags.benchcli ?? 'go'; const args = [ @@ -377,6 +404,27 @@ function runPaired(flags, { exec, log, env }) { log(formatCheckLine(CHECK_PAIRED, 'fail', problems.join('; '))); return 1; } + const { database, problem } = pairedDatabaseFromDsn(postgresDsn); + if (problem) { + log(formatCheckLine(CHECK_PAIRED, 'fail', problem)); + return 1; + } + // Idempotent provisioning: the PostgreSQL backend does not create its + // own schemas, so the lane drops and re-creates its own loom_bench_* + // database when an admin DSN is supplied. A failed setup is a hard + // failure, not a silently unprovisioned arm timing out at readiness. + if ((env.LOOM_BENCH_ADMIN_DSN ?? '').trim() !== '') { + exec(buildSetupInvocation(flags, 'drop-pg', database).cmd, buildSetupInvocation(flags, 'drop-pg', database).args, { + stdio: 'inherit', env, + }); // best effort: absent database is the normal first-run case + const setup = exec(buildSetupInvocation(flags, 'setup-pg', database).cmd, buildSetupInvocation(flags, 'setup-pg', database).args, { + stdio: 'inherit', env, + }); + if (setup.status !== 0) { + log(formatCheckLine(CHECK_PAIRED, 'fail', `benchcli setup-pg exited ${setup.status}: the PostgreSQL arm cannot serve without its six-series schema ladder`)); + return 1; + } + } const invocation = buildPairedInvocation(flags, { arangoUrl, postgresDsn }); // The paired lane is its own granted window: it runs in a CI job with // isolated services, so it sets the live gate the CLI demands. diff --git a/scripts/benchmark-backends.test.mjs b/scripts/benchmark-backends.test.mjs index 871ab3855..d120d602f 100644 --- a/scripts/benchmark-backends.test.mjs +++ b/scripts/benchmark-backends.test.mjs @@ -195,6 +195,7 @@ test('planned artifact with escaped samples fails the check', async () => { import { pairedServices, + pairedDatabaseFromDsn, buildPairedInvocation, classifyPairedReport, renderComparisonTable, @@ -356,3 +357,51 @@ test('paired run end-to-end: zero-timing report fails the check even with exit 0 assert.equal(failures, 1); assert.match(logs.join('\n'), /check paired-driver-benchmark: fail/); }); + +test('paired: postgres dsn must name a loom_bench_ database', () => { + assert.equal(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_bench_ci?sslmode=disable').problem, null); + assert.equal(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_bench_ci?sslmode=disable').database, 'loom_bench_ci'); + assert.match(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_test?sslmode=disable').problem, /loom_bench_\*/); +}); + +test('paired: admin DSN drives drop-pg then setup-pg before the benchmark', () => { + const dir = '/tmp/paired-prov-test'; + const calls = []; + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--benchcli', '/bin/benchcli', '--out', dir, '--source-commit', SHA40]); + const exec = (cmd, args) => { calls.push([cmd, args.join(' ')]); return { status: 0 }; }; + const logs = []; + run(flags, { + exec, + log: (l) => logs.push(l), + env: { + LOOM_TEST_ARANGO_URL: 'http://h:8529', + LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_ci?sslmode=disable', + LOOM_BENCH_ADMIN_DSN: 'postgresql://u:p@h:5432/postgres?sslmode=disable', + }, + }); + assert.match(calls[0][1], /^drop-pg --database loom_bench_ci$/, `first call was: ${calls[0]?.[1]}`); + assert.match(calls[1][1], /^setup-pg --database loom_bench_ci$/); + assert.match(calls[2][1], /run --scope=both-arms/); +}); + +test('paired: failed setup-pg fails the check before any timing attempt', () => { + const calls = []; + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--benchcli', '/bin/benchcli', '--out', '/tmp/paired-fail', '--source-commit', SHA40]); + const logs = []; + const failures = run(flags, { + exec: (cmd, args) => { + calls.push(args.join(' ')); + if (args[0] === 'setup-pg') return { status: 1 }; + return { status: 0 }; + }, + log: (l) => logs.push(l), + env: { + LOOM_TEST_ARANGO_URL: 'http://h:8529', + LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_ci?sslmode=disable', + LOOM_BENCH_ADMIN_DSN: 'postgresql://u:p@h:5432/postgres', + }, + }); + assert.equal(failures, 1); + assert.equal(calls.some((c) => c.includes('both-arms')), false, 'no benchmark may start when provisioning failed'); + assert.match(logs.join('\n'), /setup-pg exited 1/); +}); From 44e8757e162a55385530aba2e2a8f4dd307032ab Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:47:21 +0000 Subject: [PATCH 28/45] fix(ci): paired-driver-benchmark job honours the ten-minute budget scripts/tests/test_ui_ci_workflow.py pins every tests.yaml job to timeout-minutes: 10; the new paired job shipped at 20 and failed the retained Python contract. The job's own measured wall time (Go builds, provisioning, both services, the tiny battery) sits well inside ten minutes -- the 20 was unmeasured cushion, not evidence. --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index aaaa7a4cc..994d3d4c6 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -436,7 +436,7 @@ jobs: # gate fails closed if either arm is planned/not_run/excluded or # produces no real timing samples; a disclosure artifact can never # turn this check green. - timeout-minutes: 20 + timeout-minutes: 10 services: arangodb: image: arangodb:3.12 From 44f25685a71136b60e725577c256fe2040a6ddf7 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 04:55:14 +0000 Subject: [PATCH 29/45] fix(benchmark): paired lane uses a benchcli-legal database name CI run 43367221616 proved the fail-closed provisioning gate: benchcli answered 'database loom_bench_ci is outside the benchmark namespace' (its rule is loom_bench_[a-z0-9_]{4,48}) and the check reported the setup failure before any timing attempt, exactly as designed. The lane had been inventing its own looser loom_bench_* prefix rule; it now mirrors the benchcli regex, and the workflow's postgres DSN names loom_bench_paired. A test pins the CI-unlegal two-char suffix case. --- .github/workflows/tests.yaml | 2 +- internal/dataframe/benchmark/README.md | 3 ++- scripts/benchmark-backends.mjs | 5 +++-- scripts/benchmark-backends.test.mjs | 23 ++++++++++++----------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 994d3d4c6..f59394a3b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -463,7 +463,7 @@ jobs: --health-retries 40 env: LOOM_TEST_ARANGO_URL: http://127.0.0.1:8529 - LOOM_TEST_POSTGRES_URL: postgresql://loom_bench:loom_ci_test_only@127.0.0.1:5432/loom_bench_ci?sslmode=disable + LOOM_TEST_POSTGRES_URL: postgresql://loom_bench:loom_ci_test_only@127.0.0.1:5432/loom_bench_paired?sslmode=disable LOOM_BENCH_ADMIN_DSN: postgresql://loom_bench:loom_ci_test_only@127.0.0.1:5432/postgres?sslmode=disable steps: - name: Checkout repository diff --git a/internal/dataframe/benchmark/README.md b/internal/dataframe/benchmark/README.md index f80784da5..fcb3b54d3 100644 --- a/internal/dataframe/benchmark/README.md +++ b/internal/dataframe/benchmark/README.md @@ -105,7 +105,8 @@ runs the same matched battery against BOTH real drivers discovered from `factory/paired-driver-benchmark` in `.github/workflows/tests.yaml` runs this exact command with services `arangodb:3.12` + `postgres:18`). With `LOOM_BENCH_ADMIN_DSN` set it first provisions its own fresh -`loom_bench_*` database via `benchcli drop-pg`/`setup-pg` (database, +`loom_bench_*` database (name must match `loom_bench_[a-z0-9_]{4,48}`, +the rule benchcli enforces) via `benchcli drop-pg`/`setup-pg` (database, six-series schema ladder, migrated tables) — the PostgreSQL backend readiness answers `schema_missing ... the caller owns schema lifecycle`, so the lane, not the server, bootstraps. A failed `setup-pg` fails the diff --git a/scripts/benchmark-backends.mjs b/scripts/benchmark-backends.mjs index 47d24d82e..ac827c981 100644 --- a/scripts/benchmark-backends.mjs +++ b/scripts/benchmark-backends.mjs @@ -131,8 +131,9 @@ export function pairedServices(env) { export function pairedDatabaseFromDsn(dsn) { const match = /^[a-z]+:\/\/[^/]*\/([^?#]+)/.exec(dsn ?? ''); const database = match ? match[1] : ''; - if (!database.startsWith('loom_bench_')) { - return { database, problem: `paired postgres database ${JSON.stringify(database)} is outside the loom_bench_* namespace` }; + // Same shape benchcli enforces: loom_bench_ plus a 4-48 char suffix. + if (!/^loom_bench_[a-z0-9_]{4,48}$/.test(database)) { + return { database, problem: `paired postgres database ${JSON.stringify(database)} must match loom_bench_[a-z0-9_]{4,48} (benchcli enforces the same rule)` }; } return { database, problem: null }; } diff --git a/scripts/benchmark-backends.test.mjs b/scripts/benchmark-backends.test.mjs index d120d602f..ae1730b68 100644 --- a/scripts/benchmark-backends.test.mjs +++ b/scripts/benchmark-backends.test.mjs @@ -248,14 +248,14 @@ test('paired: missing services fail the check, never skip it', () => { }); test('paired: arango dsn gets a benchmark-namespaced database', () => { - const seen = pairedServices({ LOOM_TEST_ARANGO_URL: 'http://127.0.0.1:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@127.0.0.1:5432/loom_bench_ci?sslmode=disable' }); + const seen = pairedServices({ LOOM_TEST_ARANGO_URL: 'http://127.0.0.1:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@127.0.0.1:5432/loom_bench_paired?sslmode=disable' }); assert.deepEqual(seen.problems, []); assert.match(seen.arangoUrl, /\/loom_bench_ci$/); }); test('paired invocation: both arms, timed lane, real iterations', () => { const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', 'out', '--profile', 'tiny', '--iterations', '5', '--warmup', '1', '--rounds', '5', '--source-commit', SHA40]); - const inv = buildPairedInvocation(flags, { arangoUrl: 'http://127.0.0.1:8529/loom_bench_ci', postgresDsn: 'postgresql://u:p@h:5432/loom_bench_ci' }); + const inv = buildPairedInvocation(flags, { arangoUrl: 'http://127.0.0.1:8529/loom_bench_ci', postgresDsn: 'postgresql://u:p@h:5432/loom_bench_paired' }); const joined = inv.args.join(' '); assert.match(joined, /--scope=both-arms/); assert.match(joined, /--publication=timed/); @@ -335,7 +335,7 @@ test('paired run end-to-end: clean both-arm report passes and writes the table', }; const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', dir, '--source-commit', SHA40]); await writeFile(join(dir, 'report.json'), JSON.stringify(report)); - const failures = run(flags, { exec, log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_ci' } }); + const failures = run(flags, { exec, log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_paired' } }); assert.equal(failures, 0, logs.join('\n')); const { readFile } = await import('node:fs/promises'); const table = await readFile(join(dir, 'comparison.md'), 'utf8'); @@ -353,15 +353,16 @@ test('paired run end-to-end: zero-timing report fails the check even with exit 0 const logs = []; const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', dir, '--source-commit', SHA40]); await writeFile(join(dir, 'report.json'), JSON.stringify(report)); - const failures = run(flags, { exec, log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_ci' } }); + const failures = run(flags, { exec, log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_paired' } }); assert.equal(failures, 1); assert.match(logs.join('\n'), /check paired-driver-benchmark: fail/); }); test('paired: postgres dsn must name a loom_bench_ database', () => { - assert.equal(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_bench_ci?sslmode=disable').problem, null); - assert.equal(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_bench_ci?sslmode=disable').database, 'loom_bench_ci'); - assert.match(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_test?sslmode=disable').problem, /loom_bench_\*/); + assert.equal(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_bench_paired?sslmode=disable').problem, null); + assert.equal(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_bench_paired?sslmode=disable').database, 'loom_bench_paired'); + assert.match(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_test?sslmode=disable').problem, /loom_bench_/); + assert.match(pairedDatabaseFromDsn('postgresql://u:p@h:5432/loom_bench_ci?sslmode=disable').problem, /4,48/, 'two-char suffix must fail like benchcli does'); }); test('paired: admin DSN drives drop-pg then setup-pg before the benchmark', () => { @@ -375,12 +376,12 @@ test('paired: admin DSN drives drop-pg then setup-pg before the benchmark', () = log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', - LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_ci?sslmode=disable', + LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_paired?sslmode=disable', LOOM_BENCH_ADMIN_DSN: 'postgresql://u:p@h:5432/postgres?sslmode=disable', }, }); - assert.match(calls[0][1], /^drop-pg --database loom_bench_ci$/, `first call was: ${calls[0]?.[1]}`); - assert.match(calls[1][1], /^setup-pg --database loom_bench_ci$/); + assert.match(calls[0][1], /^drop-pg --database loom_bench_paired$/, `first call was: ${calls[0]?.[1]}`); + assert.match(calls[1][1], /^setup-pg --database loom_bench_paired$/); assert.match(calls[2][1], /run --scope=both-arms/); }); @@ -397,7 +398,7 @@ test('paired: failed setup-pg fails the check before any timing attempt', () => log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', - LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_ci?sslmode=disable', + LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_paired?sslmode=disable', LOOM_BENCH_ADMIN_DSN: 'postgresql://u:p@h:5432/postgres', }, }); From 10dc37e30899b3206665e21992b230e0bd674225 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 05:49:11 +0000 Subject: [PATCH 30/45] fix(arango): discovering auth paths on a never-ingested database answers no paths CI run 43368609701 of factory/paired-driver-benchmark was the first upload ever attempted against a spawned arm, and the Arango arm refused it: POST /api/v1/datasets/loom-bench/generations/... returned 403 FORBIDDEN in 1 ms with no fence involvement in the arm log. Root cause is a chicken-and-egg at the first authorized write. The authscope F-B27 project-binding gate asks DiscoverExistingAuthResource- Paths which paths the project has recorded before ingest can bootstrap fhir_field_catalog -- that collection is created only by the ingest bootstrap spec. On a fresh database the collection does not exist, the discovery AQL faults, the handler wraps any discovery error as CodeForbidden, and the zero-path first-generation bootstrap branch (the branch this gate is built for) is never reached. The database can never receive its first generation. The PostgreSQL arm never showed this because setup-pg's SetupMigrate pre-creates the table. Discovery now answers zero recorded paths when the field catalog collection is absent, the same posture retainedSourceFieldProfileExists already takes for a missing collection, and the same posture an empty table gives the postgres backend. A missing collection is no data, not an authorization-backend outage; a mid-request deletion still fails the query honestly. Controls: offline fake extended so accepted requests pin exactly one existence probe plus one query, rejected requests pin zero of both, and a new TestFB27MissingFieldCatalogIsNoRecordedPaths proves no AQL fires when the collection is absent; a live LOOM_TEST_ARANGO_URL proof opens a fresh database without bootstrapping and asserts both discovery shapes return empty. The gen-bind control header already named this exact failure shape: a storage fault masked as 403. --- docs/PACKAGE_AUDIT.csv | 14 +-- docs/PACKAGE_AUDIT.md | 20 ++--- ...all_generations_auth_path_conflict_test.go | 52 +++++++++-- .../missing_field_catalog_integration_test.go | 90 +++++++++++++++++++ .../arango/internal/catalog/resources.go | 18 +++- 5 files changed, 169 insertions(+), 25 deletions(-) create mode 100644 internal/backends/arango/internal/catalog/missing_field_catalog_integration_test.go diff --git a/docs/PACKAGE_AUDIT.csv b/docs/PACKAGE_AUDIT.csv index a4db941ea..d48cba5dc 100644 --- a/docs/PACKAGE_AUDIT.csv +++ b/docs/PACKAGE_AUDIT.csv @@ -29,15 +29,15 @@ internal/api/http,github.com/calypr/loom/internal/api/http,httpapi,transport ada internal/api/recipe,github.com/calypr/loom/internal/api/recipe,recipeapi,transport adapter,Adapt scoped catalog field discovery to recipe schema resolution for one-shot and stored recipes.,discovery.go,discovery_auth_mode_test.go; discovery_test.go,—,—,110,126,3,FieldDiscovery; Fields; NewFieldDiscovery,internal/authscope; internal/catalog; internal/dataframe/errors; internal/dataframe/recipe/schema,internal/authscope; internal/catalog; internal/dataframe/errors; internal/dataframe/recipe/schema,internal/api/graphql/graph/query; internal/server,—,—,—,legacy=1; compatibility=1,no automatic consolidation signal; perform semantic review,go test ./internal/api/recipe,inventory complete; semantic disposition pending internal/authscope,github.com/calypr/loom/internal/authscope,authscope,domain/library,"Represent request principals and resolve authorized project, generation, and resource scopes.",authentication.go; doc.go; fence_resource_access.go; principal.go; scope_resolver.go,all_generations_cache_key_test.go; auth_path_control_ingress_test.go; auth_resource_path_options_conflict_test.go; authentication_test.go; covers_execution_paths_control_test.go; persisted_scope_fails_closed_test.go; probe_double_normalize_test.go; scope_cache_key_namespace_test.go; scope_test.go,—,—,1175,2104,36,AllowAllAuthorizer; Authenticate; Authenticator; AuthorizeProject; AuthorizeReadProject; AuthorizeWrite; Authorizer; BasicAuthenticator; BearerTokenAuthenticator; CalyprAuthenticator; Clone; ContextWithPrincipal; CoversExecutionPaths; FenceUserAccessClient; GetAllowedResources; InvalidateGeneration; InvalidateProject; NewFenceUserAccessClientWithTrustedIssuer; NewScopeResolver; NormalizeAuthResourcePath; NormalizeAuthResourcePaths; Permission; Principal; PrincipalFromContext; ReadScope; ReadScopeMode; ResolveReadScope; ResolveReadScopeForGeneration; ResolveWriteScopeForGeneration; ResourceAccessClient; ScopeAuthorizer; ScopeResolver; ScopeResolverConfig; StaticAuthenticator; Unrestricted; ValidateTrustedFenceIssuer,internal/catalog; internal/projectid,internal/catalog,cmd/preview-covering-index-artifact; internal/api/authpolicy; internal/api/bulk/load; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/backends/arango/internal/explorer; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer/lifecycle; internal/server,internal/api/graphql/graph; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/store; internal/dataframe/benchmark; internal/dataframe/compiler; internal/dataframe/compiler/render/aql,—,3 exported interface(s),legacy=5,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/authscope,inventory complete; semantic disposition pending internal/backends/arango,github.com/calypr/loom/internal/backends/arango,arango,storage adapter,"Expose the only public entry to the ArangoDB 3.12 backend: a single Open factory validates explicit config, owns exactly one lifecycle client with the required collection bootstrap policy, and returns the typed runtime carrying the catalog, dataset lifecycle, Explorer, recipe registry/revisions, and published-dataframe ports assembled from the private implementation tree, with alias re-exports for witness-side reassembly.",aliases.go; doc.go; open.go,ownership_contract_test.go,—,—,336,800,4,Close; Config; Open; Runtime,internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec; internal/backends/arango/internal/store,internal/backends/arango,cmd/arango-fhir-proto; cmd/dataframe-profile; cmd/loom-data; internal/acceptance; internal/ingest; internal/server,conformance/compiler; internal/api/http; internal/backends/arango; internal/dataframe/compiler; internal/dataframe/execution; internal/dataframe/publication/clickhouse; internal/dataset; internal/explorer/compilation,—,—,—,storage adapter; preserve interface and persisted-data contracts,go test ./internal/backends/arango,inventory complete; semantic disposition pending -internal/backends/arango/internal/catalog,github.com/calypr/loom/internal/backends/arango/internal/catalog,arango,domain/library,Persist and retrieve catalog facts in ArangoDB.,codec.go; evidence.go; fields.go; relationships.go; resource_inventory.go; resources.go; semantic_coverage.go; semantic_coverage_envelope.go; semantic_inventory.go; store.go,all_generations_auth_path_conflict_test.go; all_generations_project_binding_integration_test.go; resource_inventory_integration_test.go; resource_inventory_unit_test.go; semantic_coded_category_integration_test.go; semantic_coverage_envelope_test.go; semantic_coverage_resource_integration_test.go; semantic_coverage_test.go; semantic_inventory_category_test.go; semantic_inventory_integration_test.go; semantic_inventory_selection_test.go; semantic_inventory_source_availability_test.go; store_test.go,—,—,5267,5466,54,AccumulateRelationshipCatalog; AddSource; AdvanceSemanticInventoryBackfill; AdvanceSemanticInventoryBuild; AuditRelationshipEdges; BeginSemanticCoverageBuild; BeginSemanticCoverageResource; BeginSemanticInventoryBuild; ClaimSemanticInventoryBackfill; CompleteRetainedSemanticCoverageResource; CompleteSemanticCoverageResource; CompleteSemanticInventoryBackfill; CompleteSemanticInventoryBuild; DiscoverExistingAuthResourcePaths; DiscoverFieldEnrichment; DiscoverFields; DiscoverReferences; DiscoverRelationshipObservations; DiscoverResourceInventory; EncodeSemanticCoverageSourceEnvelope; EnsureSemanticInventoryEntries; FailSemanticCoverageBuild; FailSemanticCoverageResource; FailSemanticInventoryBackfill; FailSemanticInventoryBuild; FinalizeSemanticCoverageBuild; LookupSemanticCoverage; MarkSemanticCoverageResourceMissing; New; NewSemanticCoverageSourceSetAccumulator; PageSemanticInventory; PrepareSemanticInventoryBackfill; ReadFieldEnrichment; ReadRelationshipObservations; ReadResourceInventory; ReadRetainedSemanticInventoryPage; RebuildRelationshipCatalog; RebuildResourceInventory; RefreshSemanticInventorySourceAvailability; ResolveSemanticInventorySelections; SemanticCoverageBuildIdentity; SemanticCoverageCollectionSpec; SemanticCoveragePage; SemanticCoverageResourceExpectation; SemanticCoverageSourcePartitionExpectation; SemanticCoverageSourceSetAccumulator; SemanticCoverageWriteStats; Snapshot; Store; ValidateAuthResourcePathOptions; WriteFieldCatalog; WriteRelationshipCatalog; WriteSemanticCoverageContributions; WriteSemanticInventoryContributions,internal/backends/arango/internal/store; internal/catalog; internal/fhir/schema,internal/authscope; internal/backends/arango/internal/store; internal/catalog,internal/backends/arango,—,github.com/bytedance/sonic,—,—,one production importer (internal/backends/arango); inspect ownership/inline potential,go test ./internal/backends/arango/internal/catalog,inventory complete; semantic disposition pending +internal/backends/arango/internal/catalog,github.com/calypr/loom/internal/backends/arango/internal/catalog,arango,domain/library,Persist and retrieve catalog facts in ArangoDB.,codec.go; evidence.go; fields.go; relationships.go; resource_inventory.go; resources.go; semantic_coverage.go; semantic_coverage_envelope.go; semantic_inventory.go; store.go,all_generations_auth_path_conflict_test.go; all_generations_project_binding_integration_test.go; missing_field_catalog_integration_test.go; resource_inventory_integration_test.go; resource_inventory_unit_test.go; semantic_coded_category_integration_test.go; semantic_coverage_envelope_test.go; semantic_coverage_resource_integration_test.go; semantic_coverage_test.go; semantic_inventory_category_test.go; semantic_inventory_integration_test.go; semantic_inventory_selection_test.go; semantic_inventory_source_availability_test.go; store_test.go,—,—,5283,5594,54,AccumulateRelationshipCatalog; AddSource; AdvanceSemanticInventoryBackfill; AdvanceSemanticInventoryBuild; AuditRelationshipEdges; BeginSemanticCoverageBuild; BeginSemanticCoverageResource; BeginSemanticInventoryBuild; ClaimSemanticInventoryBackfill; CompleteRetainedSemanticCoverageResource; CompleteSemanticCoverageResource; CompleteSemanticInventoryBackfill; CompleteSemanticInventoryBuild; DiscoverExistingAuthResourcePaths; DiscoverFieldEnrichment; DiscoverFields; DiscoverReferences; DiscoverRelationshipObservations; DiscoverResourceInventory; EncodeSemanticCoverageSourceEnvelope; EnsureSemanticInventoryEntries; FailSemanticCoverageBuild; FailSemanticCoverageResource; FailSemanticInventoryBackfill; FailSemanticInventoryBuild; FinalizeSemanticCoverageBuild; LookupSemanticCoverage; MarkSemanticCoverageResourceMissing; New; NewSemanticCoverageSourceSetAccumulator; PageSemanticInventory; PrepareSemanticInventoryBackfill; ReadFieldEnrichment; ReadRelationshipObservations; ReadResourceInventory; ReadRetainedSemanticInventoryPage; RebuildRelationshipCatalog; RebuildResourceInventory; RefreshSemanticInventorySourceAvailability; ResolveSemanticInventorySelections; SemanticCoverageBuildIdentity; SemanticCoverageCollectionSpec; SemanticCoveragePage; SemanticCoverageResourceExpectation; SemanticCoverageSourcePartitionExpectation; SemanticCoverageSourceSetAccumulator; SemanticCoverageWriteStats; Snapshot; Store; ValidateAuthResourcePathOptions; WriteFieldCatalog; WriteRelationshipCatalog; WriteSemanticCoverageContributions; WriteSemanticInventoryContributions,internal/backends/arango/internal/store; internal/catalog; internal/fhir/schema,internal/authscope; internal/backends/arango/internal/store; internal/catalog,internal/backends/arango,—,github.com/bytedance/sonic,—,—,one production importer (internal/backends/arango); inspect ownership/inline potential,go test ./internal/backends/arango/internal/catalog,inventory complete; semantic disposition pending internal/backends/arango/internal/chartifact,github.com/calypr/loom/internal/backends/arango/internal/chartifact,arango,domain/library,Persist private ClickHouse artifact manifests and leases in a dedicated Arango collection.,catalog.go,catalog_arango_test.go; catalog_test.go,—,—,379,723,10,BootstrapSpec; Catalog; ClaimCleanup; Create; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update,internal/backends/arango/internal/store; internal/dataframe/execution/chartifact,internal/authscope; internal/backends/arango/internal/store; internal/dataframe/execution/chartifact; internal/store/clickhouse,internal/backends/arango,—,—,—,—,one production importer (internal/backends/arango); inspect ownership/inline potential,go test ./internal/backends/arango/internal/chartifact,inventory complete; semantic disposition pending -internal/backends/arango/internal/dataset,github.com/calypr/loom/internal/backends/arango/internal/dataset,arango,domain/library,Persist immutable dataset generation lifecycle state in ArangoDB.,collections.go; doc.go; document_codec.go; manifest_lifecycle.go; release_store.go,generation_activation_cas_e2e_test.go; release_policy_change_e2e_test.go; revision_inflation_e2e_test.go; store_test.go,—,—,764,1652,15,Activate; ActiveDocumentKey; BootstrapSpec; CollectionSpecs; CompareAndSwapActivateRelease; CompareAndSwapPublishRelease; CreateManifest; ManifestDocumentKey; New; ReadActiveRelease; ReadManifest; ResolveActiveManifest; SaveRelease; Store; TransitionManifest,internal/backends/arango/internal/store; internal/dataset,internal/backends/arango/internal/store; internal/dataset,internal/backends/arango; internal/backends/arango/internal/explorer,internal/backends/arango/internal/publication,github.com/arangodb/go-driver/v2/arangodb/shared,—,compatibility=2,no automatic consolidation signal; perform semantic review,go test ./internal/backends/arango/internal/dataset,inventory complete; semantic disposition pending +internal/backends/arango/internal/dataset,github.com/calypr/loom/internal/backends/arango/internal/dataset,arango,domain/library,Persist immutable dataset generation lifecycle state in ArangoDB.,collections.go; doc.go; document_codec.go; manifest_lifecycle.go; release_store.go,generation_activation_cas_e2e_test.go; release_policy_change_e2e_test.go; release_store_cas_retry_test.go; revision_inflation_e2e_test.go; store_test.go,—,—,791,1783,15,Activate; ActiveDocumentKey; BootstrapSpec; CollectionSpecs; CompareAndSwapActivateRelease; CompareAndSwapPublishRelease; CreateManifest; ManifestDocumentKey; New; ReadActiveRelease; ReadManifest; ResolveActiveManifest; SaveRelease; Store; TransitionManifest,internal/backends/arango/internal/store; internal/dataset,internal/backends/arango/internal/store; internal/dataset,internal/backends/arango; internal/backends/arango/internal/explorer,internal/backends/arango/internal/publication,github.com/arangodb/go-driver/v2/arangodb/shared,—,compatibility=2,no automatic consolidation signal; perform semantic review,go test ./internal/backends/arango/internal/dataset,inventory complete; semantic disposition pending internal/backends/arango/internal/explorer,github.com/calypr/loom/internal/backends/arango/internal/explorer,arango,domain/library,"Persist Explorer drafts, revisions, receipts, publications, capabilities, and repository state in ArangoDB.",activation.go; capability_snapshot_store.go; codec.go; collections.go; drafts.go; explicit_groups.go; explorers.go; interpretations.go; publication.go; receipts.go; repository.go; revisions.go; selection_references.go; selections.go; store.go; table_shape_capabilities.go,capability_snapshot_store_test.go; collections_test.go; cross_generation_publish_e2e_test.go; draft_conflict_test.go; draft_transaction_formal_replay_test.go; drafts_integration_test.go; drafts_test.go; explicit_group_storage_integration_test.go; explicit_groups_test.go; interpretations_test.go; receipt_store_test.go; repository_test.go; selection_references_test.go; selection_scale_integration_test.go; selection_storage_integration_test.go; selections_cleanup_e2e_test.go; store_test.go; table_shape_capabilities_test.go,—,—,3366,4552,56,AbortExplicitGroupRevision; AbortSelection; ActivateRepositoryGeneration; AppendExplicitGroupMemberships; AppendSelectionMembers; BeginExplicitGroupRevision; BeginSelection; BootstrapSpec; CapabilitySnapshotCollectionSpec; CapabilitySnapshotStore; CleanupExplicitGroupStaging; CleanupSelectionStaging; CollectionSpecs; CompleteExplicitGroupRevision; CompleteSelection; Create; CreateInterpretationRevision; DigestExplicitGroupRevision; DigestSelectionMembers; FailRevision; Get; GetByIdentity; GetByToken; GetCatalog; GetCatalogForLookup; GetCategoryScan; GetCompilationReceiptForExplorer; GetDraftRevision; GetExplicitGroupRevision; GetInterpretationRevision; GetResolution; GetRevision; GetSelection; InsertCompilationReceipt; InsertRevision; List; ListExplicitGroupDefinitions; ListExplicitGroupRevisions; ListInterpretationLibraries; MigrateLegacyRepositoryConfigs; New; NewCapabilitySnapshotStore; NewTableShapeCapabilityRepository; PublishAuthoring; Put; PutCatalog; PutCategoryScan; PutExplicitGroupDefinitions; PutResolution; SaveDraft; Store; TableShapeCapabilitiesCollectionSpec; TableShapeCapabilityRepository; ValidateSelectionReferences; VisitExplicitGroupMemberships; VisitSelectionMembers,internal/authscope; internal/backends/arango/internal/dataset; internal/backends/arango/internal/publication; internal/backends/arango/internal/store; internal/dataframe/publication; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/tableshapecap; internal/projectid,internal/authscope; internal/backends/arango/internal/dataset; internal/backends/arango/internal/publication; internal/backends/arango/internal/store; internal/dataframe/publication; internal/dataframe/published; internal/dataframe/recipe; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/projectid,internal/backends/arango,—,github.com/arangodb/go-driver/v2/arangodb/shared,—,legacy=12; migration=4; compatibility=1,one production importer (internal/backends/arango); inspect ownership/inline potential; broad surface (16 production files); inspect cohesion,go test ./internal/backends/arango/internal/explorer,inventory complete; semantic disposition pending internal/backends/arango/internal/publication,github.com/calypr/loom/internal/backends/arango/internal/publication,arango,domain/library,Persist dataframe publication registry and bundle metadata in ArangoDB.,bundle_registry.go; registry.go,bundle_registry_test.go; dangling_binding_e2e_test.go; execution_read_pin_integration_test.go; name_latest_binding_e2e_test.go; publish_atomicity_e2e_test.go; release_dangle_reconcile_e2e_test.go,—,—,533,2990,22,AcquireBundleLease; AcquireExecutionReadPin; BootstrapSpec; ClaimExecutionCleanup; Client; CompareAndSwapPointer; FindExecutionByKey; FindExecutionBySelector; GetExecution; GetPointer; ListExecutions; New; PublishExecution; Registry; ReleaseBundleLease; ReleaseExecutionCleanup; ReleaseExecutionReadPin; RenewBundleLease; RenewExecutionCleanup; RenewExecutionReadPin; SaveExecution; VisitExecutionPages,internal/backends/arango/internal/store; internal/dataframe/publication,internal/backends/arango/internal/dataset; internal/backends/arango/internal/publication; internal/backends/arango/internal/store; internal/dataframe/errors; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataset; internal/store/clickhouse,internal/backends/arango; internal/backends/arango/internal/explorer,internal/backends/arango/internal/publication,github.com/arangodb/go-driver/v2/arangodb/shared,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/backends/arango/internal/publication,inventory complete; semantic disposition pending internal/backends/arango/internal/recipeexec,github.com/calypr/loom/internal/backends/arango/internal/recipeexec,arango,domain/library,Persist recipe versions and execution-use records in ArangoDB.,registry.go; revisions.go,registry_test.go; revisions_test.go; revisions_user_faults_test.go,—,—,294,555,15,BootstrapSpec; Client; Get; List; LoadRecipe; LoadRecipeVersion; New; NewRevisionRegistry; RecipeVersionUsed; Register; Registry; ReplaceRecipeVersion; RevisionBootstrapSpec; RevisionRegistry; SaveRecipe,internal/backends/arango/internal/store; internal/dataframe/errors; internal/dataframe/recipe; internal/dataframe/recipe/exec,internal/backends/arango/internal/store; internal/dataframe/errors; internal/dataframe/recipe; internal/dataframe/recipe/exec,internal/backends/arango,—,—,—,legacy=1,one production importer (internal/backends/arango); inspect ownership/inline potential,go test ./internal/backends/arango/internal/recipeexec,inventory complete; semantic disposition pending internal/backends/arango/internal/store,github.com/calypr/loom/internal/backends/arango/internal/store,arango,domain/library,"Provide the low-level typed ArangoDB client, query streaming, profiling, and Explain assessment boundary.",client.go; errors.go; explain.go; explain_assessment.go; explain_client.go; options.go; preview_index.go; profile.go; profile_client.go; types.go,client_query_memory_limit_test.go; client_queryrows_test.go; client_revision_test.go; errors_test.go; explain_assessment_test.go; explain_client_test.go; explain_test.go; population_profile_integration_test.go; preview_index_test.go; profile_client_test.go; profile_integration_test.go; profile_test.go,—,—,1950,2210,70,AQLExecutor; AssessExplainResult; BatchInserter; Bootstrap; BootstrapSpec; Client; Close; CollectionCount; CollectionExists; CollectionRevision; CollectionSpec; ConnectionOptions; DatabaseExists; DiagnosticCause; EnsurePreviewCoveringIndex; EnsurePreviewCoveringIndexReplacing; EnsurePreviewCoveringIndexWithStoredValues; EnsurePreviewCoveringIndexWithStoredValuesReplacing; ExecuteAQL; Explain; ExplainAssessment; ExplainCollection; ExplainCollectionScan; ExplainIndex; ExplainIndexLocation; ExplainIndexSummary; ExplainIndexUse; ExplainIndexes; ExplainNode; ExplainOptions; ExplainPlan; ExplainPlanEstimate; ExplainRequest; ExplainResult; ExplainStats; ExplainWarning; InsertBatchRaw; IsArangoErrorResponse; IsArangoPermissionResponse; ListPreviewCoveringIndexes; Open; OptimizerOptions; ParseExplainResult; ParseProfileResult; PreviewCoveringIndex; Profile; ProfileExtra; ProfileNode; ProfileNodeGroup; ProfileNodeSummary; ProfileOptions; ProfilePhases; ProfileRequest; ProfileResult; ProfileStats; ProfileSummary; QueryMemoryLimitFromContext; QueryRows; QueryRowsWithMaxRuntime; RowBatchClient; RowQueryer; RowVisitor; SummarizeProfile; TransactionCollections; TransactionFunc; TransactionalRowClient; TranslateQueryError; UnmarshalJSON; WithQueryMemoryLimit; WithTransaction,internal/dataframe/errors,internal/dataframe/errors,internal/backends/arango; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec,—,github.com/arangodb/go-driver/v2/arangodb; github.com/arangodb/go-driver/v2/arangodb/shared; github.com/arangodb/go-driver/v2/connection; github.com/arangodb/go-driver/v2/utils,5 exported interface(s),legacy=1,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/arango/internal/store,inventory complete; semantic disposition pending internal/backends/postgres,github.com/calypr/loom/internal/backends/postgres,postgres,storage adapter,"Own the PostgreSQL 18 factory: a single Open validates explicit config, opens exactly one readiness-gated pool with own-prefix connection tagging, composes the control runtime, and returns the typed runtime carrying the SourceRows and PlanJSON data-plane ports over tagged compiled commands through the typed Querier bridge; the sole legal importer of the private rows adapter, and no re-export surface - control-plane consumers import internal/backends/postgres/control directly (arch-backend-09 deleted aliases.go).",doc.go; open.go; plan_executor.go,engine_postgres_command_boundary_pg18_test.go; engine_postgres_pg18_test.go; explain_postgres_test.go; open_live_test.go; ownership_contract_test.go; plan_executor_test.go; sentinel_identity_test.go,—,—,178,4408,5,NewPlanJSONExecutor; NewSourceRows; Open; Querier; Runtime,internal/backends/postgres/control; internal/backends/postgres/internal/rows; internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/execution,internal/backends/postgres; internal/backends/postgres/control; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/rows; internal/backends/postgres/internal/store; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/server; internal/source,cmd/loom-data; internal/ingest; internal/server,internal/api/graphql/graph/query; internal/backends/postgres; internal/dataframe/execution,—,—,migration=1,storage adapter; preserve interface and persisted-data contracts,go test ./internal/backends/postgres,inventory complete; semantic disposition pending -internal/backends/postgres/control,github.com/calypr/loom/internal/backends/postgres/control,control,domain/library,"Own the cycle-free public control plane of the PostgreSQL backend as its single definition: configuration and series-assignment validation, the bare-pool client and store-owned batch vocabulary, the dataset manifest lifecycle and catalog evidence port assembly, and the structural query surface; production consumers and backend-adjacent witnesses import it directly for control-plane symbols, and it must never reach the factory root, the private rows adapter, or dataframe/execution (machine-enforced by scripts/packagepolicy).",catalog_read.go; config.go; errors.go; runtime.go; vocabulary.go; vocabulary_control.go,capability_compose_test.go; catalog_read_test.go,—,—,1393,669,65,AllSeries; Begin; CatalogStore; Close; Commit; Config; ConnSource; DiscoverExistingAuthResourcePaths; DiscoverFields; Error; Exec; FieldProfileScope; GenerationAdvisoryLockKey; Health; MigrateDatasetReleaseSchema; MigrateResult; MigrateSchema; NewBundleCatalog; NewCapabilitySnapshotStore; NewCatalogFieldProfileStore; NewCatalogRelationshipEvidenceRepository; NewCatalogRelationshipEvidenceStore; NewCatalogStore; NewChartifactCatalog; NewDatasetReleaseStore; NewEdgeRepository; NewEdgeWriter; NewExplicitGroupStore; NewExplorerStore; NewFieldProfileRepository; NewInterpretationStore; NewInventoryRepository; NewPublicationReconciler; NewPublishStore; NewRecipeExecRegistry; NewRecipeRevisionRegistry; NewResourceRepository; NewResourceWriter; NewSemanticContributionRepository; NewSemanticReadRepository; NewTableShapeCapabilityStore; Open; OpenPool; PoolClient; Query; QueryRow; Readiness; Rollback; Row; Rows; Runtime; Scan; SemanticContributionRepository; Series; SeriesSchemas; SetupPolicy; Unwrap; Validate; VerifyDatasetReleaseSchema; VerifyExplorerControlSchema; VerifyPublicationControlSchema; VerifyRecipeChainsSchema; VerifyRecipeExecRegistriesSchema; VerifySchema; WithAdvisoryLock,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/store; internal/catalog; internal/dataset,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/catalog,cmd/loom-data; internal/backends/postgres; internal/dataframe/benchmark/cmd/benchcli; internal/ingest; internal/server,internal/api/graphql/graph/query; internal/api/http; internal/dataframe/benchmark; internal/dataframe/compiler; internal/dataframe/compiler/render/postgres; internal/dataframe/execution,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,2 exported interface(s),migration=21,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/control,inventory complete; semantic disposition pending +internal/backends/postgres/control,github.com/calypr/loom/internal/backends/postgres/control,control,domain/library,"Own the cycle-free public control plane of the PostgreSQL backend as its single definition: configuration and series-assignment validation, the bare-pool client and store-owned batch vocabulary, the dataset manifest lifecycle and catalog evidence port assembly, and the structural query surface; production consumers and backend-adjacent witnesses import it directly for control-plane symbols, and it must never reach the factory root, the private rows adapter, or dataframe/execution (machine-enforced by scripts/packagepolicy).",catalog_read.go; config.go; errors.go; runtime.go; vocabulary.go; vocabulary_control.go,capability_compose_test.go; capability_evidence_assembly_test.go; catalog_read_test.go,—,—,1393,796,65,AllSeries; Begin; CatalogStore; Close; Commit; Config; ConnSource; DiscoverExistingAuthResourcePaths; DiscoverFields; Error; Exec; FieldProfileScope; GenerationAdvisoryLockKey; Health; MigrateDatasetReleaseSchema; MigrateResult; MigrateSchema; NewBundleCatalog; NewCapabilitySnapshotStore; NewCatalogFieldProfileStore; NewCatalogRelationshipEvidenceRepository; NewCatalogRelationshipEvidenceStore; NewCatalogStore; NewChartifactCatalog; NewDatasetReleaseStore; NewEdgeRepository; NewEdgeWriter; NewExplicitGroupStore; NewExplorerStore; NewFieldProfileRepository; NewInterpretationStore; NewInventoryRepository; NewPublicationReconciler; NewPublishStore; NewRecipeExecRegistry; NewRecipeRevisionRegistry; NewResourceRepository; NewResourceWriter; NewSemanticContributionRepository; NewSemanticReadRepository; NewTableShapeCapabilityStore; Open; OpenPool; PoolClient; Query; QueryRow; Readiness; Rollback; Row; Rows; Runtime; Scan; SemanticContributionRepository; Series; SeriesSchemas; SetupPolicy; Unwrap; Validate; VerifyDatasetReleaseSchema; VerifyExplorerControlSchema; VerifyPublicationControlSchema; VerifyRecipeChainsSchema; VerifyRecipeExecRegistriesSchema; VerifySchema; WithAdvisoryLock,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/store; internal/catalog; internal/dataset,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/catalog,cmd/loom-data; internal/backends/postgres; internal/dataframe/benchmark/cmd/benchcli; internal/ingest; internal/server,internal/api/graphql/graph/query; internal/api/http; internal/dataframe/benchmark; internal/dataframe/compiler; internal/dataframe/compiler/render/postgres; internal/dataframe/execution,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,2 exported interface(s),migration=21,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/control,inventory complete; semantic disposition pending internal/backends/postgres/internal/catalog,github.com/calypr/loom/internal/backends/postgres/internal/catalog,postgres,domain/library,"Persist and retrieve catalog facts in PostgreSQL 18: publish-last generation-scoped resource inventory builds, bounded field-profile records with completeness and truncation metadata, leased compare-and-swap-guarded semantic inventory build state, deterministic semantic-inventory contribution rows with content-addressed replay and conflict classification, byte-order-stable keyset pages over retained resources, and authorization-scoped semantic-inventory pages and selection resolution served directly from contribution aggregates.",field_discovery.go; field_profile.go; relationships.go; resource_inventory.go; semantic_build.go; semantic_contribution.go; semantic_page.go; store.go,capability_compose_pg_test.go; field_discovery_test.go; field_profile_test.go; relationships_pg_test.go; relationships_test.go; resource_inventory_test.go; semantic_build_test.go; semantic_contribution_test.go; semantic_page_test.go,—,—,4430,11834,55,AdvanceSemanticInventoryBackfill; ClaimSemanticInventoryBackfill; CompleteSemanticInventoryBackfill; DiscoverExistingAuthResourcePaths; DiscoverFieldEnrichment; DiscoverFields; DiscoverRelationshipObservations; DiscoverResourceInventory; EnsureSemanticInventoryEntries; Error; FailSemanticInventoryBackfill; FieldProfileStore; FieldProfileUnavailableError; FieldProfileWriteError; InventoryStore; New; NewFieldProfileStore; NewFullStore; NewRelationshipEvidenceRepository; NewRelationshipEvidenceStore; NewSemanticBuildRepository; NewSemanticContributionRepository; NewSemanticInventoryBackfillStore; NewSemanticReadRepository; PageSemanticInventory; PrepareSemanticInventoryBackfill; ReadFieldProfile; ReadRelationshipObservations; ReadResourceInventory; ReadRetainedSemanticInventoryPage; ReadSemanticInventoryBuild; RebuildResourceInventory; RelationshipEvidenceRepository; RelationshipOccurrenceGroup; RelationshipScope; RelationshipStore; ResolveSemanticInventorySelections; ResourceInventoryUnavailableError; ResourceInventoryWriteError; RetainedSemanticSourceMissingError; ScanRelationshipOccurrences; SemanticBuildRepository; SemanticContributionRepository; SemanticInventoryBackfillStore; SemanticInventoryClaimLostError; SemanticInventoryClaimedError; SemanticInventoryContributionConflictError; SemanticInventoryEntriesUnsupportedError; SemanticInventoryStorageMissingError; SemanticInventoryUnsupportedError; SemanticReadRepository; Store; Unwrap; WriteFieldProfile; WriteSemanticInventoryContributions,internal/backends/postgres/internal/store; internal/catalog; internal/fhir/schema,internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/catalog; internal/dataset; internal/fhir/schema; internal/ingest,internal/backends/postgres/control,internal/backends/postgres/internal/catalog,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,3 exported interface(s),legacy=10; migration=16,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/catalog,inventory complete; semantic disposition pending internal/backends/postgres/internal/chartifact,github.com/calypr/loom/internal/backends/postgres/internal/chartifact,postgres,domain/library,"Persist private ClickHouse artifact manifests and cleanup leases in the PostgreSQL 18 control plane with CAS progress writes, bounded expired scans, and one-winner cleanup claims while physical tables stay in ClickHouse.",catalog.go,catalog_manager_test.go; catalog_test.go,—,—,535,1605,10,Catalog; ClaimCleanup; Create; DB; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update,internal/dataframe/execution/chartifact,internal/authscope; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/store; internal/dataframe/execution/chartifact; internal/store/clickhouse,internal/backends/postgres/control,internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn,1 exported interface(s),migration=6,one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/chartifact,inventory complete; semantic disposition pending internal/backends/postgres/internal/dataset,github.com/calypr/loom/internal/backends/postgres/internal/dataset,postgres,domain/library,"Persist dataset generation manifests, the active-generation compare-and-swap pointer, and immutable project release candidates with the active-release compare-and-swap pointer in PostgreSQL 18.",commands.go; manifest_lifecycle.go; migrate.go; release_store.go; store.go,commands_test.go; manifest_lifecycle_pg_test.go; release_store_pg_test.go,—,—,1725,2488,28,ActivateGeneration; Backend; Command; CommandBackend; CompareAndSwapActivateRelease; ConnSource; CreateManifest; EnsureGenerationPrerequisites; GenerationOwnerAdapter; MigrateSchema; New; NewPostgreSQLCommand; NewReleaseStore; OnReleaseActivated; OwnerMigrations; Parameter; ParameterType; PostgreSQL; ReadActiveRelease; ReadManifest; ReleaseActivation; ReleaseStore; ResolveActiveManifest; SaveRelease; Scan; Store; TransitionManifest; VerifySchema,internal/backends/postgres/internal/store; internal/dataset,internal/backends/postgres/internal/store; internal/dataset,internal/backends/postgres/control; internal/backends/postgres/internal/explorer,internal/backends/postgres; internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,go:embed; 2 exported interface(s),legacy=1; migration=28; compatibility=4,runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/dataset,inventory complete; semantic disposition pending @@ -50,8 +50,8 @@ internal/backends/postgres/internal/rows,github.com/calypr/loom/internal/backend internal/backends/postgres/internal/store,github.com/calypr/loom/internal/backends/postgres/internal/store,postgres,domain/library,"Provide the bounded pgx/v5-backed PostgreSQL 18 source store: readiness-pinged pool, advisory-lock serialized migration runner, scoped FHIR resource repository that accepts the logical internal/source contract on writes and answers reads with its store-owned physical row under the server-assigned keyset sequence, scoped typed edge-occurrence repository, publish-last inventory repository, scoped bounded field-profile record persistence, the ledgered idempotent COPY batch writer for resource and edge rows, and resource-only and edge-only batch facades over that writer.",advisory.go; batch_writer.go; client.go; commit_outcome.go; edge.go; edge_writer.go; errors.go; field_profile.go; inventory.go; migrate.go; migration_ownership.go; resource.go; resource_writer.go,batch_writer_test.go; client_test.go; edge_test.go; edge_writer_test.go; errors_test.go; field_profile_auth_paths_test.go; field_profile_ddl_test.go; field_profile_filter_golden_test.go; inventory_test.go; migrate_test.go; ownership_contract_test.go; resource_test.go; resource_writer_test.go; verify_classify_test.go; verify_readonly_test.go,—,—,5971,13963,113,Acquire; AsUniqueConflict; Assignment; AssignmentConflict; Batch; BatchAccountingError; BatchEdgeConflictError; BatchIdentityConflictError; BatchResourceConflictError; BatchResult; BatchWriteError; BatchWriter; Cause; CheckServerVersion; Client; Close; CommitOutcomeUnknown; Config; ConnSource; ConnectError; DriverCondition; EdgeBatch; EdgeBatchResult; EdgeConflictError; EdgeOccurrenceRow; EdgeRepository; EdgeScope; EdgeSourceMissingError; EdgeWriter; EmbeddedMigrations; Error; FieldProfileCoverage; FieldProfileFilter; FieldProfileRepository; FieldProfileScope; FutureSchemaVersionError; GenerationAdvisoryLockKey; GetResource; HistoryEntry; HistoryManifest; InsertCounts; InsertEdge; InsertResource; InventoryCountConflictError; InventoryCountScope; InventoryPublishGuardError; InventoryRepository; InventorySourceGroup; IsAbortedTransaction; IsRefusedConnection; IsUniqueViolation; LedgerRecord; ListDistinctAuthResourcePaths; LoadMigrations; LookupManifest; Manifest; ManifestJSON; Migrate; MigrateResult; MigrateSchema; Migration; MigrationApplyError; MigrationChecksumMismatchError; MigrationRunner; MissingEmbeddedMigrationError; NewBatchWriter; NewEdgeRepository; NewEdgeWriter; NewFieldProfileRepository; NewInventoryRepository; NewMigrationRunner; NewResourceRepository; NewResourceWriter; Open; OutOfOrderMigrationError; Ping; PublishBuild; Query; QueryRow; ReadBuild; ReadCountSums; ReadEdgesFromSource; ReadEdgesToTarget; ReadFieldProfileRows; ReadFieldProfileRowsFiltered; ReadProfileCoverage; ReadResources; ResourceBatch; ResourceBatchResult; ResourceConflictError; ResourceNotFoundError; ResourceRepository; ResourceRow; ResourceWriter; RetryableTransactionConflict; Scan; ScanSourceCounts; SeriesID; TranslateCommitError; TranslateError; TranslatedError; UniqueConflictError; UnsupportedVersionError; Unwrap; UpsertFieldProfileBatch; Validate; ValidateAssignment; Verify; VerifySchema; WithAdvisoryLock; WriteBatch; WriteEdgeBatch; WriteResourceBatch,internal/catalog; internal/dataframe/errors; internal/source,internal/api/http; internal/authscope; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/store; internal/catalog; internal/dataframe/errors; internal/dataframe/execution/chartifact; internal/dataframe/publication; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataset; internal/explorer; internal/source,internal/backends/postgres; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/rows,internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/store,github.com/jackc/pgx/v5; github.com/jackc/pgx/v5/pgconn; github.com/jackc/pgx/v5/pgxpool,go:embed; 1 exported interface(s),legacy=17; migration=143,broad surface (13 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/backends/postgres/internal/store,inventory complete; semantic disposition pending internal/catalog,github.com/calypr/loom/internal/catalog,catalog,domain/library,"Own persistence-neutral observed FHIR field, pivot, relationship, and capability facts.",auth_scope.go; backend_contract.go; cache.go; capability_evidence.go; generation.go; helpers.go; limits.go; rebuild.go; relationship_evidence.go; relationships.go; resource_inventory.go; retained_semantic_page.go; semantic.go; semantic_coverage.go; semantic_coverage_contract.go; semantic_inventory.go; semantic_schema.go; types.go; write_profiler.go,auth_scope_test.go; cache_test.go; capability_evidence_test.go; generation_test.go; relationship_evidence_census_test.go; relationship_evidence_test.go; relationships_test.go; semantic_coding_display_projection_test.go; semantic_coverage_diagnostics_test.go; semantic_coverage_test.go; semantic_inventory_buildid_cache_test.go; semantic_inventory_category_test.go; semantic_inventory_emitter_anchor_test.go; semantic_inventory_test.go; semantic_schema_identifier_type_test.go; semantic_schema_test.go; semantic_test.go; write_profiler_coverage_test.go; write_profiler_test.go,—,—,6471,3946,146,AuthResourcePathOptions; Cache; CacheOptions; CacheStats; CapabilityEvidence; CapabilityEvidenceOptions; CapabilityEvidenceReader; CensusSemanticCoverage; ConceptCandidate; DecodeSemanticInventoryCursor; DefaultProfileLimits; DiscoverFields; DiscoverReferences; DiscoverSemanticCodeableConceptTemplates; Documents; EffectiveAuthResourcePathsUnrestricted; EncodeSemanticInventoryCursor; EvidenceDiagnostic; EvidenceStatus; EvidenceStatusFor; ExplicitAuthResourcePathsUnrestricted; ExtensionValueObservation; FieldCatalogDocument; FieldEnrichmentDigest; FieldEnrichmentObservation; FieldEnrichmentOptions; FieldEnrichmentResult; FieldProfileScope; FieldProfileWriter; HasDatasetGeneration; InvalidRelationship; InvalidateAll; InvalidateProject; InventoryRebuilder; LegacySemanticBindingIDV1; Merge; MergeRelationshipCounts; NewCache; NewCacheWithOptions; NewProfilerForGenerationWithLimits; NewResourceInventoryBuild; NewSemanticInventoryBuild; NewSemanticInventoryEmitter; NewShapePlanCacheWithLimit; NewShapePlanCacheWithLimits; NormalizeDatasetGeneration; NormalizeProfileLimits; ObservePayload; ObservePayloadWithCoverage; ObservePayloadWithInventory; PopulatedField; PopulatedFieldOptions; PopulatedReference; PopulatedReferenceOptions; ProfileLimits; Profiler; ProjectRelationshipObservation; ReadCapabilityEvidence; ReconcileSemanticCoverage; RelationshipAuditOptions; RelationshipAuditSummary; RelationshipCatalogDocument; RelationshipCatalogDocuments; RelationshipCountsFromRawEdges; RelationshipDiscoverer; RelationshipEvidenceKey; RelationshipEvidenceKeyFor; RelationshipEvidenceLess; RelationshipKey; RelationshipObservation; RelationshipObservationDigest; RelationshipObservationFromKey; RelationshipObservationOptions; RelationshipObservationResult; RelationshipRebuildOptions; RelationshipRebuildSummary; RepeatedShapeDigest; ResourceInventoryBuild; ResourceInventoryBuildKey; ResourceInventoryCount; ResourceInventoryCoverageDigest; ResourceInventoryDigest; ResourceInventoryObservation; ResourceInventoryOptions; ResourceInventoryRebuildOptions; ResourceInventoryRebuildSummary; ResourceInventoryResult; RetainedSemanticInventoryPage; RetainedSemanticInventoryRow; RetentionBytes; SemanticCodeableConceptTemplate; SemanticContributor; SemanticCoverageBuildState; SemanticCoverageCandidatePath; SemanticCoverageCandidateResult; SemanticCoverageCandidateStatus; SemanticCoverageCensus; SemanticCoverageCensusGap; SemanticCoverageCensusStatus; SemanticCoverageCounts; SemanticCoverageDiagnostic; SemanticCoverageDispositionKind; SemanticCoverageEmissionEvidence; SemanticCoverageEmissionSink; SemanticCoverageFacetAggregate; SemanticCoverageLookupOptions; SemanticCoverageLookupResult; SemanticCoverageOccurrenceKind; SemanticCoverageOwnerLocator; SemanticCoveragePathAggregate; SemanticCoveragePresence; SemanticCoverageReason; SemanticCoverageReference; SemanticCoverageReferenceKind; SemanticCoverageRole; SemanticCoverageSourceContribution; SemanticCoverageSourceIdentity; SemanticCoverageSourceSink; SemanticInventoryBuild; SemanticInventoryBuildID; SemanticInventoryBuildKey; SemanticInventoryCheckpoint; SemanticInventoryContribution; SemanticInventoryEmitter; SemanticInventoryEntry; SemanticInventoryFamily; SemanticInventoryFamilyID; SemanticInventoryFamilyReference; SemanticInventoryObservationSink; SemanticInventoryPage; SemanticInventoryPageDigest; SemanticInventoryPageOptions; SemanticInventoryReference; SemanticInventoryResolveOptions; SemanticInventoryResolveResult; SemanticInventorySelectionMode; SemanticInventoryState; SemanticObservation; SemanticObservationCompleteness; SemanticObservationKey; SemanticObservationSource; SemanticObservationValue; ShapePlanCache; Stats; Truncated; ValidateSemanticInventoryBuild,internal/fhir/schema; internal/fhir/semantic,generated/fhirschema; internal/fhir/schema; internal/fhir/semantic,cmd/arango-fhir-proto; cmd/loom-data; internal/api/graphql/graph/query; internal/api/recipe; internal/authscope; internal/backends/arango/internal/catalog; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/explorer/authoringv2; internal/explorer/lifecycle; internal/ingest; internal/server,internal/api/bulk/load; internal/api/graphql/graph; internal/api/graphql/graph/resolver; internal/dataframe/benchmark; internal/dataframe/compiler; internal/explorer/capability; internal/explorer/compilation,github.com/bytedance/sonic,5 exported interface(s),legacy=13; migration=1; compatibility=1,broad surface (19 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/catalog,inventory complete; semantic disposition pending internal/ci,github.com/calypr/loom/internal/ci,ci,domain/library,"Exercise repository-wide Go formatting and vet gates, including injected fault detection.",—,gofmt_gate_test.go; vet_gate_test.go,—,—,0,283,0,—,—,—,—,—,—,—,—,no in-repo production importer; verify runtime/tooling reachability,go test ./internal/ci,inventory complete; semantic disposition pending -internal/dataframe/benchmark,github.com/calypr/loom/internal/dataframe/benchmark,benchmark,domain/library,"Run the Arango-versus-PostgreSQL matched dataframe benchmark: deterministic fixtures, canonical bags and correctness gates evaluated before any timing is claimed, control runs, and report artifacts that a re-computing verifier must accept, with ClickHouse-backed publication legs disclosed as planned/not_run when the service is absent.",armplan.go; authoring.go; canonical.go; controls.go; driver.go; fixtures.go; gates.go; harness.go; matrix.go; measure.go; oracle.go; provision.go; report.go; report_md.go,armplan_test.go; authoring_test.go; canonical_test.go; controls_test.go; driver_test.go; fixtures_fhir_test.go; fixtures_test.go; gates_test.go; harness_scope_test.go; harness_test.go; matrix_test.go; measure_test.go; oracle_test.go; publication_test.go; report_test.go; scratch_authrepro_test.go; scratch_liveupload_test.go,README.md,—,5072,2964,146,Apply; ArangoBackendYAML; Arm; ArmConfig; ArmEvidence; AuthorWorkload; AuthoringStatus; AuthoringVerification; BackendResult; Bag; BothArmsPlan; BuildAllWorkspaces; BuildEmptyFloorWorkspace; BuildReport; BuildServerConfigYAML; BuildWorkspace; BuilderState; CBool; CList; CMissing; CNull; CNumber; CObject; CString; CacheLabel; CanonicalBag; CanonicalPair; CanonicalRow; CanonicalValue; CanonicalizeRows; CanonicalizeValue; Claim; ClaimableTimings; Clock; Close; ConditionFacts; ControlEmptyFloorMillis; ControlRuns; ControlSelfRatio; ControlUnstableCells; CreateExplorer; DatasetParams; DatasetSize; Digest; DriverOptions; ElapsedMillis; EvaluateGates; EvaluateGatesSingleArm; EvaluateNegativeAuth; EvaluateNegativeAuthSingleArm; ExecuteOutput; ExpectedOutcomeForScope; Facts; FetchBuilderState; FirstDifference; FixtureFiles; FormatExclusionList; FreePort; GateFunc; GateName; GateResult; GenerateFacts; GenerateFiles; Grant; LegPlan; LoadReport; LogPath; MakeBearerToken; MarshalReport; MeasureArms; MeasureEmptyFloor; MockFence; NegativeAuthControl; NegativeAuthControlSingleArm; NewMockFence; NewMonotonicClock; NewSpeedRatio; NewStorageIDMap; NotRunBackends; Now; ObservationFacts; OracleBackedCount; OracleFor; Outcome; OutputIDFor; PatientFacts; PlannedGateReports; PlannedNotRun; PlannedNotRunForBackends; PlannedPlan; PostgresBackendYAML; PostgresOnlyPlan; PreviewResult; Publication; PublicationDisclosures; PublicationExcluded; PublicationGateReports; PublicationPlan; PublishWorkspace; Reconcile; RenderMarkdown; Report; Requests; Restart; RunBenchmark; RunConfig; RunIdentity; RunOptions; RunPlan; RunStatus; RunStatusScoped; Sample; ServerProcess; ServerSpec; Since; SingleArmGateFunc; SnapshotToken; SortedBattery; SpawnFunc; SpawnServer; SpecimenFacts; SpecsByPreset; SpeedRatio; Stop; StorageIDMap; Summarize; Summary; SummaryRow; ThroughputSummary; URL; UnsupportedCases; UploadGeneration; ValidBackend; ValidScope; Validate; ValidateBattery; ValidateLegPlan; ValidatePlannedNotRun; ValidatePublicationPlan; VerifyReport; VersionProbeFunc; WorkloadCategory; WorkloadGateReport; WorkloadOutcome; WorkloadSpec; Write,internal/explorer/authoringv2,generated/fhir; internal/authscope; internal/backends/postgres/control; internal/catalog; internal/dataframe/benchmark; internal/explorer/authoringv2,internal/dataframe/benchmark/cmd/benchcli,internal/dataframe/benchmark,—,2 exported interface(s),migration=1,one production importer (internal/dataframe/benchmark/cmd/benchcli); inspect ownership/inline potential; broad surface (14 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/benchmark,inventory complete; semantic disposition pending -internal/dataframe/benchmark/cmd/benchcli,github.com/calypr/loom/internal/dataframe/benchmark/cmd/benchcli,main,domain/library,"Own the benchmark command-line entry point: fail-closed live-run preflight, planned disclosure artifacts, report verification authority, and the loom_bench PostgreSQL database lifecycle.",deployer.go; main.go; probes.go; setup.go,guards_test.go,—,—,949,322,0,—,internal/backends/postgres/control; internal/dataframe/benchmark,internal/dataframe/benchmark,—,—,—,—,migration=1,no in-repo production importer; verify runtime/tooling reachability,go test ./internal/dataframe/benchmark/cmd/benchcli,inventory complete; semantic disposition pending +internal/dataframe/benchmark,github.com/calypr/loom/internal/dataframe/benchmark,benchmark,domain/library,"Run the Arango-versus-PostgreSQL matched dataframe benchmark: deterministic fixtures, canonical bags and correctness gates evaluated before any timing is claimed, control runs, and report artifacts that a re-computing verifier must accept, with ClickHouse-backed publication legs disclosed as planned/not_run when the service is absent.",armplan.go; authoring.go; canonical.go; controls.go; driver.go; fixtures.go; gates.go; harness.go; matrix.go; measure.go; oracle.go; provision.go; report.go; report_md.go,armplan_test.go; authoring_test.go; canonical_test.go; controls_test.go; driver_test.go; fixtures_fhir_test.go; fixtures_test.go; gates_test.go; harness_scope_test.go; harness_test.go; matrix_test.go; measure_test.go; oracle_test.go; publication_test.go; report_test.go; scratch_authrepro_test.go; scratch_liveupload_test.go,README.md,—,5506,3534,154,AgeBandFor; AppendWorkloadDisclosures; Apply; ApplyExcludedWorkloadGateReports; ArangoBackendYAML; Arm; ArmConfig; ArmEvidence; AuthorWorkload; AuthoringStatus; AuthoringVerification; BackendResult; Bag; BothArmsPlan; BuildAllWorkspaces; BuildEmptyFloorWorkspace; BuildReport; BuildServerConfigYAML; BuildWorkspace; BuilderState; CBool; CList; CMissing; CNull; CNumber; CObject; CString; CacheLabel; CanonicalBag; CanonicalPair; CanonicalRow; CanonicalValue; CanonicalizeRows; CanonicalizeValue; Claim; ClaimableTimings; Clock; Close; ConditionFacts; ControlEmptyFloorMillis; ControlRuns; ControlSelfRatio; ControlUnstableCells; CreateExplorer; DatasetParams; DatasetSize; Digest; DriverOptions; ElapsedMillis; EvaluateGates; EvaluateGatesSingleArm; EvaluateNegativeAuth; EvaluateNegativeAuthSingleArm; ExcludedWorkloadGateReports; ExecuteOutput; ExpectedOutcomeForScope; Facts; FetchBuilderState; FirstDifference; FixtureFiles; FormatExclusionList; FreePort; GateFunc; GateName; GateResult; GenerateFacts; GenerateFiles; Grant; LegPlan; LiveBatteryExclusions; LoadReport; LogPath; MakeBearerToken; MarshalReport; MeasureArms; MeasureEmptyFloor; MockFence; NegativeAuthControl; NegativeAuthControlSingleArm; NewMockFence; NewMonotonicClock; NewSpeedRatio; NewStorageIDMap; NotRunBackends; Now; ObservationFacts; OracleBackedCount; OracleFor; Outcome; OutputIDFor; PatientFacts; PlannedGateReports; PlannedNotRun; PlannedNotRunForBackends; PlannedPlan; PostgresBackendYAML; PostgresOnlyPlan; PreviewResult; Publication; PublicationDisclosures; PublicationExcluded; PublicationGateReports; PublicationPlan; PublicationTimed; PublicationZeroEvidence; PublishWorkspace; Reconcile; RenderMarkdown; Report; Requests; Restart; RunBenchmark; RunConfig; RunIdentity; RunOptions; RunPlan; RunStatus; RunStatusScoped; Sample; ServerProcess; ServerSpec; Since; SingleArmGateFunc; SnapshotToken; SortedBattery; SpawnFunc; SpawnServer; SpecimenFacts; SpecsByPreset; SpeedRatio; Stop; StorageIDMap; Summarize; Summary; SummaryRow; ThroughputSummary; URL; UnsupportedCases; UploadGeneration; ValidBackend; ValidScope; Validate; ValidateBattery; ValidateLegPlan; ValidatePlannedNotRun; ValidatePublicationPlan; VerifyReport; VersionProbeFunc; WorkloadCategory; WorkloadDisclosures; WorkloadGateReport; WorkloadOutcome; WorkloadSpec; Write,internal/explorer/authoringv2,generated/fhir; internal/authscope; internal/backends/postgres/control; internal/catalog; internal/dataframe/benchmark; internal/explorer/authoringv2,internal/dataframe/benchmark/cmd/benchcli,internal/dataframe/benchmark,—,2 exported interface(s),migration=1,one production importer (internal/dataframe/benchmark/cmd/benchcli); inspect ownership/inline potential; broad surface (14 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/dataframe/benchmark,inventory complete; semantic disposition pending +internal/dataframe/benchmark/cmd/benchcli,github.com/calypr/loom/internal/dataframe/benchmark/cmd/benchcli,main,domain/library,"Own the benchmark command-line entry point: fail-closed live-run preflight, planned disclosure artifacts, report verification authority, and the loom_bench PostgreSQL database lifecycle.",deployer.go; main.go; probes.go; setup.go,guards_test.go,—,—,1016,374,0,—,internal/backends/postgres/control; internal/dataframe/benchmark,internal/dataframe/benchmark,—,—,—,—,migration=1,no in-repo production importer; verify runtime/tooling reachability,go test ./internal/dataframe/benchmark/cmd/benchcli,inventory complete; semantic disposition pending internal/dataframe/columntransform,github.com/calypr/loom/internal/dataframe/columntransform,columntransform,domain/library,"Define closed, backend-neutral transformations over one selected output column value.",types.go,types_test.go,—,—,79,61,7,CategoryMapping; Clone; ExactCategoryRecode; Kind; UnknownPolicy; Validate; ValueTransformation,—,—,internal/dataframe/compiler/lower; internal/dataframe/recipe; internal/explorer/authoringv2,internal/dataframe/compiler; internal/explorer/compilation,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/columntransform,inventory complete; semantic disposition pending internal/dataframe/compiler,github.com/calypr/loom/internal/dataframe/compiler,compiler,domain/library,"Orchestrate semantic planning, physical lowering, optimization, rendering, and compiled-query assembly.",api.go; category_scan.go; category_scan_filter_pushdown.go; category_scan_related.go; cell_trace.go; clone_helpers.go; compiled_query.go; construction_preview_root_id_filter.go; graph_execution.go; ir_helpers.go; optimizer.go; output.go; population_mapping.go; postgres_target.go; preview_covering_index.go; recipe_execution.go; recipe_paging.go; row_lineage.go; semantic_group_projection_preview.go; table_shape_exclusions.go; target.go,backend_leaf_guard_test.go; backend_ownership_guard_contract_test.go; category_scan_arango_test.go; category_scan_parent_presence_arango_test.go; category_scan_related_collection_identity_test.go; category_scan_related_presence_arango_test.go; category_scan_related_test.go; category_scan_test.go; cell_trace_test.go; cohort_drop_boundary_arango_test.go; command_helpers_external_test.go; command_helpers_test.go; compiled_command_test.go; construction_arango_test.go; construction_coded_group_arango_test.go; construction_coded_group_execution_test.go; construction_coded_pivot_test.go; construction_execution_test.go; construction_group_expand_arango_test.go; construction_group_preview_liveness_arango_test.go; construction_group_related_summary_arango_test.go; construction_group_source_streaming_test.go; construction_pivot_lineage_arango_test.go; construction_preview_root_id_filter_test.go; construction_related_count_arango_test.go; construction_related_expand_arango_test.go; construction_related_group_preview_arango_test.go; correlated_arango_test.go; correlated_legacy_compat_test.go; correlated_physical_test.go; correlated_selection_test.go; correlated_system_version_arango_test.go; explicit_cohort_values_arango_test.go; filter_semantics_test.go; filter_test.go; generic_physical_plan_test.go; grain_test.go; group_connected_field_source_arango_test.go; group_field_source_arango_test.go; nested_expansion_arango_test.go; operator_oracles_arango_test.go; physical_diagnostics_test.go; physical_lowering_test.go; physical_optimize_test.go; physical_plan_test.go; physical_projection_reserved_name_arango_test.go; physical_render_test.go; physical_required_match_test.go; physical_scope_test.go; pivot_presence_arango_audit_test.go; pivot_presence_audit_test.go; plan_test_helpers_test.go; population_frontier_arango_test.go; population_mapping_filter_exists_test.go; population_mapping_identity_guard_test.go; population_mapping_routed_group_arango_test.go; population_mapping_test.go; postgres_coded_group_compile_test.go; postgres_parity_fixture_test.go; postgres_target_fingerprint_golden_test.go; public_columns_freeze_test.go; recipe_execution_pg18_test.go; recipe_execution_test.go; recipe_paging_related_filter_sequence_test.go; recipe_paging_routed_exists_arango_test.go; recipe_paging_test.go; root_page_related_seed_arango_test.go; row_expansion_test.go; row_item_projection_arango_test.go; row_lineage_cohort_filter_arango_test.go; row_lineage_composed_arango_test.go; row_lineage_group_pivot_arango_test.go; row_lineage_group_rows_arango_test.go; row_lineage_test.go; selected_source_contract_test.go; selection_semantics_test.go; selector_first_limit_test.go; semantic_group_arango_test.go; semantic_group_preview_arango_test.go; semantic_group_projection_factorization_arango_test.go; semantic_validation_test.go; source_group_preview_arango_test.go; streaming_missing_category_pivot_test.go; table_reshape_arango_test.go; table_reshape_execution_window_test.go; table_shape_exclusions_test.go; workspace_output_capture_test.go,—,—,8120,33173,44,ArangoTarget; Backend; CategoryOverflowWitness; CategoryScanProof; CategoryScanRefusal; CategoryScanRefusalCode; CategoryScanRefusalCodeOf; CompileCategoryScanOutput; CompileCategoryScanStage; CompileCellTraceOutput; CompileClickHousePrefix; CompilePopulationMappingOutput; CompileRecipeOutput; CompileRecipeOutputPage; CompileResolvedGraphQuery; CompileResolvedRecipePlan; CompileRowLineageOutput; CompileTableShapeExclusions; CompiledCategoryScanQuery; CompiledCellTraceQuery; CompiledClickHousePrefix; CompiledOutputPage; CompiledPopulationMappingQuery; CompiledQuery; CompiledRowLineageQuery; CompiledTableShapeExclusionQuery; Error; PostgreSQLTarget; PreviewCoveringIndexReplacement; PreviewCoveringIndexSpec; PreviewGroupProjectionIndexSpec; PreviewGroupProjectionSpec; PreviewGroupScanSpec; PublicOutputColumns; RenderPreviewGroupProjectionAssessment; RenderPreviewGroupProjectionFactorization; RenderPreviewPhysicalPlanWithRootIndexHint; RowLineageCapability; RowLineageCapabilityForOutput; RowLineageUnsupportedError; TableShapeExclusionRefusal; TableShapeExclusionRefusalCode; Target; Valid,internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec,internal/authscope; internal/backends/arango; internal/backends/postgres/control; internal/catalog; internal/dataframe/columntransform; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/explorer/capability; internal/fhir/schema; internal/projectid; internal/source; scripts/packagepolicy,cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/dataframe/execution; internal/explorer/lifecycle; internal/server,conformance/compiler; internal/api/graphql/graph; internal/api/graphql/graph/resolver; internal/backends/postgres; internal/dataframe/compiler; internal/dataframe/compiler/command; internal/dataframe/compiler/render/aql; internal/explorer/compilation,—,—,legacy=2; compatibility=1,broad surface (21 production files); inspect cohesion,go test ./internal/dataframe/compiler,inventory complete; semantic disposition pending internal/dataframe/compiler/capability,github.com/calypr/loom/internal/dataframe/compiler/capability,capability,domain/library,Probe which dataframe language capabilities the compiler can prove and support.,probe.go; schema_fields.go,probe_test.go; schema_fields_test.go,—,—,803,313,22,CandidateCapability; CandidateRequest; Chart; Filter; Options; OwnerRecordsCapability; OwnerRecordsRequest; ProbeCandidate; ProbeOwnerRecords; ProbeProjection; ProbeRoot; ProbeTraversal; Rendered; Result; RootCapability; RootRequest; SchemaFieldDescriptor; SchemaFieldDescriptors; Scope; Traversal; TraversalCapability; TraversalRequest,internal/authscope; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/fhir/schema,internal/authscope; internal/dataframe/recipe; internal/dataframe/spec; internal/fhir/schema,internal/explorer/lifecycle; internal/server,—,—,—,—,no automatic consolidation signal; perform semantic review,go test ./internal/dataframe/compiler/capability,inventory complete; semantic disposition pending @@ -89,9 +89,9 @@ internal/fhir/semantic,github.com/calypr/loom/internal/fhir/semantic,semantic,do internal/ingest,github.com/calypr/loom/internal/ingest,ingest,domain/library,"Preflight and load FHIR NDJSON into generation-scoped Arango vertices, edges, and catalog facts, and commit built resource-row and built-edge-occurrence batches to the PostgreSQL source store through the ledgered batch writer with scope verification, canonical payload encoding, and payload-backed path/ordinal recovery for repeated references.",backend.go; doc.go; documents.go; files.go; generated_load.go; generation_identity.go; generation_load.go; generation_orchestration.go; generation_workers.go; load_options.go; postgres_writer.go; preflight.go; progress.go; resource_load.go; row_builder.go; semantic_inventory_backfill.go; source_schema.go,backend_test.go; basic_observation_fixture_validation_test.go; bench_test.go; documents_test.go; file_pipeline_test.go; files_test.go; generated_load_test.go; generation_cleanup_test.go; generation_failed_cleanup_e2e_test.go; generation_identity_test.go; generation_load_postgres_durability_test.go; generation_load_postgres_test.go; generation_load_test.go; integration_test.go; keycollide_generated_e2e_test.go; load_options_test.go; postgres_writer_test.go; preflight_test.go; related_medication_fixture_test.go; resource_load_test.go; row_builder_test.go; semantic_inventory_backfill_integration_test.go; semantic_inventory_backfill_test.go; semantic_inventory_test.go; source_schema_test.go; viewer_row_file_download_fixture_test.go,—,—,5650,10571,63,ActivatePostgresGeneration; ActivationOutcomeError; BackfillSemanticInventory; Build; Complete; ConnectionOptions; DiscoverNDJSON; EdgeDocument; EdgeFromGrip; Error; EventSink; GeneratedRowBuilder; GenerationCatalogWriters; GenerationFieldProfileWriter; GenerationInventoryRebuilder; GenerationLoadIncompleteError; GenerationRelationshipDiscoverer; GenerationSemanticContributor; GenericRowBuilder; IngestionMode; Load; LoadIncompleteError; LoadOptions; LoadPostgresGeneration; LoadSummary; NewGeneratedRowBuilder; NewGenericRowBuilder; NewPostgresEdgeWriter; NewPostgresResourceWriter; OpenLineScanner; PGEdgeBatchScope; PGEdgeRowIdentityError; PGEdgeWriteResult; PGEdgeWriteScopeError; PGLoadConfig; PGResourceBatchScope; PGResourceRowIdentityError; PGResourceWriteResult; PGResourceWriteScopeError; PGSourceOptions; PostgresEdgeWriter; PostgresResourceWriter; PreflightError; PreflightFiles; PreflightIssue; PreflightReport; PreflightResource; ResourceTypeFromPath; RowBuilder; RowErrorSample; SanitizeKey; SecondsSince; SemanticInventoryBackfillCollectionResult; SemanticInventoryBackfillOptions; SemanticInventoryBackfillProgress; SemanticInventoryBackfillReport; SplitCollectionID; Unwrap; Valid; VertexDocument; VertexFromFHIRWithExtra; WriteEdgeBatch; WriteResourceBatch,generated/fhir; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataset; internal/fhir/schema; internal/source,generated/fhir; internal/backends/arango; internal/backends/postgres/control; internal/catalog; internal/dataset; internal/fhir/schema; internal/source,cmd/arango-fhir-proto; cmd/loom-data; internal/api/bulk/load; internal/server,internal/api/graphql/graph/query; internal/backends/postgres/internal/catalog,github.com/bmeg/jsonschema/v6; github.com/bmeg/jsonschemagraph/graph; github.com/bmeg/jsonschemagraph/model; github.com/bmeg/jsonschemagraph/util; github.com/bytedance/sonic,5 exported interface(s),legacy=5; migration=9; compatibility=1,broad surface (17 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion,go test ./internal/ingest,inventory complete; semantic disposition pending internal/projectid,github.com/calypr/loom/internal/projectid,projectid,domain/library,Define canonical and legacy-compatible public project identity normalization.,projectid.go,projectid_test.go,—,—,51,18,2,Canonical; Legacy,—,—,cmd/loom-repository; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/backends/arango/internal/explorer; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/published; internal/explorer; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/repositoryseed; internal/server,internal/dataframe/compiler,—,—,legacy=5; compatibility=1,small shared semantic contract; compare direct callers before inlining,go test ./internal/projectid,inventory complete; semantic disposition pending internal/repositoryseed,github.com/calypr/loom/internal/repositoryseed,repositoryseed,domain/library,"Validate repository metadata and configuration, produce a source manifest, and refuse unmaterialized inputs before launch.",preflight.go,preflight_test.go,—,—,281,105,6,Error; File; Manifest; Options; Prepare; UnmaterializedError,internal/explorer/authoringv2; internal/projectid,—,cmd/loom-repository,—,—,—,—,one production importer (cmd/loom-repository); inspect ownership/inline potential,go test ./internal/repositoryseed,inventory complete; semantic disposition pending -internal/server,github.com/calypr/loom/internal/server,server,domain/library,"Compose storage, domain services, generated OpenAPI handlers, GraphQL adapters, and HTTP routes.",artifact_dev.go; auth_wiring.go; compose.go; construction_inputs_catalog.go; explain_wiring.go; explorer_authoring_http.go; explorer_capability.go; explorer_combine_output_metadata.go; explorer_config_route.go; explorer_lifecycle_helpers.go; explorer_lifecycle_wire.go; explorer_output_reconcile.go; explorer_preview_response.go; explorer_preview_security.go; explorer_publication.go; explorer_receipt_contract.go; explorer_receipt_validation.go; local_workspace_writeback.go; openapi_data_routes.go; openapi_explorer_artifacts.go; openapi_explorer_authoring_direct.go; openapi_explorer_construction.go; openapi_explorer_construction_choice_proposal.go; openapi_explorer_construction_inputs.go; openapi_explorer_construction_proposal.go; openapi_explorer_conversion.go; openapi_explorer_direct.go; openapi_explorer_explicit_groups.go; openapi_explorer_population_member_proposal.go; openapi_explorer_routes.go; openapi_explorer_row_definition.go; openapi_explorer_selections.go; openapi_explorer_table_shape.go; openapi_frame_source_options.go; openapi_graphql_routes.go; openapi_recipe_execution.go; openapi_routes.go; openapi_semantic_inventory.go; options.go; private_artifact_reconciler.go; receipt_preview_diagnostic.go; recipe_authorization.go; recipe_combine_resolution.go; recipe_discovery.go; recipe_publication.go; release_verifier.go; routes.go; row_sources.go; runtime.go; server.go,accepted_construction_contract_test.go; append_workspace_rows_integration_test.go; artifact_reconciler_wiring_test.go; auth_wiring_trusted_issuer_test.go; backend_composition_contract_test.go; backend_error_boundary_contract_test.go; backend_neutral_diagnostics_live_test.go; case008_row_definition_preview_route_test.go; command_helpers_test.go; config_test.go; construction_category_discovery_openapi_error_test.go; construction_inputs_catalog_test.go; construction_proposal_zero_column_test.go; core_authoring_protocol_arango_conformance_test.go; core_authoring_protocol_formal_replay_test.go; core_authoring_protocol_receipts_test.go; core_authoring_protocol_restore_test.go; core_authoring_protocol_rowschema_test.go; core_authoring_protocol_scoped_compile_test.go; core_authoring_protocol_test.go; dataframe_query_error_test.go; derived_policy_rows_integration_test.go; explain_wiring_pg18_test.go; explain_wiring_preview_test.go; explorer_capability_test.go; explorer_coded_value_capability_test.go; explorer_concept_evidence_test.go; explorer_config_route_test.go; explorer_diagnostic_logging_test.go; explorer_output_reconcile_derive_source_test.go; explorer_output_reconcile_source_child_test.go; explorer_output_reconcile_unpivot_source_test.go; explorer_preview_response_test.go; explorer_preview_security_test.go; explorer_receipt_contract_error_test.go; explorer_receipt_contract_test.go; explorer_reconcile_scope_test.go; explorer_row_change_route_test.go; explorer_state_route_test.go; fs1_signed_slot_collision_test.go; health_readiness_wiring_test.go; http_test_helpers_test.go; local_workspace_writeback_test.go; openapi_column_id_contract_test.go; openapi_explorer_artifacts_download_test.go; openapi_explorer_authoring_write_fault_test.go; openapi_explorer_cell_trace_test.go; openapi_explorer_commands_notfound_test.go; openapi_explorer_construction_apply_preview_test.go; openapi_explorer_construction_derive_source_test.go; openapi_explorer_construction_expand_source_test.go; openapi_explorer_construction_filter_source_test.go; openapi_explorer_construction_inputs_test.go; openapi_explorer_construction_proposal_test.go; openapi_explorer_construction_unpivot_source_test.go; openapi_explorer_conversion_test.go; openapi_explorer_direct_test.go; openapi_explorer_draft_restore_test.go; openapi_explorer_legacy_details_control_test.go; openapi_explorer_population_mapping_construction_test.go; openapi_explorer_population_member_proposal_test.go; openapi_explorer_restricted_starting_collection_integration_test.go; openapi_explorer_route_roundtrip_test.go; openapi_explorer_route_rows_arango_test.go; openapi_explorer_row_definition_test.go; openapi_explorer_write_scope_test.go; openapi_field_presentation_fixture_test.go; openapi_graphql_status_test.go; openapi_graphql_transport_review_test.go; openapi_interpretation_routes_test.go; openapi_recipe_execution_test.go; openapi_route_inventory_test.go; openapi_routes_test.go; openapi_semantic_inventory_test.go; openapi_status_coverage_test.go; openapi_test_helpers_test.go; options_schemas_test.go; patient_one_construction_choice_route_test.go; population_member_receipt_test.go; postgres_query_test.go; postgres_refusal_contract_test.go; preview_postgres_wiring_pg18_test.go; private_artifact_reconciler_test.go; publication_rows_integration_test.go; publication_viewer_agreement_test.go; receipt_preview_diagnostic_test.go; recipe_authorization_test.go; recipe_blank_project_test.go; recipe_combine_resolution_test.go; recipe_discovery_test.go; recipe_execution_etl_failure_fields_e2e_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_header_probe_test.go; recipe_execution_status_twin_e2e_test.go; recipe_execution_twin_authz_repair_test.go; recipe_project_keying_test.go; recipe_publication_test.go; release_verifier_fault_mapping_test.go; release_verifier_test.go; repository_activation_conflict_dev_test.go; route_composition_contract_test.go; routes_test.go; row_tag_guard_contract_test.go; row_value_policy_stream_test.go; runtime_postgres_live_test.go; runtime_test.go; wiring_test.go; zz_readiness_contract_guard_test.go,—,—,14261,37356,103,Activate; ActivateDatasetGeneration; ActivateRepositoryGeneration; ApplyExplorerBuilderCommands; AssessExplorerRowChange; AuthConfig; AuthorizeRead; AuthorizeWrite; BasicAuthConfig; BrowseExplorerFrameSourceOptions; BrowseExplorerSemanticInventory; CalyprAuthConfig; CheckExplorerPopulationMapping; ClickHouseConfig; Config; CreateDatasetGeneration; CreateExplorer; CreateExplorerExplicitGroupRevision; CreateExplorerSelection; CreateInterpretationRevision; CreateInterpretationRevisionFromColumn; DataframerConfig; DefaultConfig; DiscoverExplorerConstructionCategories; DiscoverExplorerTableShapeCategories; DownloadExplorerArtifact; Error; ExactExecutionMaterialization; ExecuteDataframeGraphQL; ExecuteGraphQL; Explain; ExplainPhysical; Finish; GetApolloSandbox; GetDatasetGenerationStatus; GetExecution; GetExplorer; GetExplorerAuthoringCapability; GetExplorerBuilder; GetExplorerColumnSource; GetExplorerConstructionCapabilities; GetExplorerConstructionInputs; GetExplorerSelection; GetExplorerTableShapeCapabilities; GetGraphQLPlayground; GetHealth; GetInterpretationRevision; GetLiveness; GetPointer; GetReadiness; GetRecipeExecution; HTTPRoutes; ListExplorerRowDefinitionChoices; ListExplorers; ListInterpretationLibraries; LoadConfig; PostgreSQLConfig; PrepareExplorerArtifact; Preview; PreviewCollectionScanEvidenceFromExplain; PreviewExplorer; PreviewInterpretationCandidate; ProbeCandidate; ProbeEdge; ProbeNode; ProposeExplorerConstruction; ProposeExplorerConstructionChoice; ProposeExplorerPopulationMemberRemoval; ProposeExplorerRowDefinition; ProposeExplorerTableShape; PublishAuthoring; PublishExplorer; PublishRepositoryExplorerConfig; ReadManifest; ReconcileExplorerBuilder; Resolve; ResolveConfiguredColumnContext; ResolveExplorerTableShape; ResolveForCompilation; ResolveForExecution; ResolveToken; Run; RunGeneration; RunWithConfig; SearchExplorerCandidates; SearchExplorerConstructionChoices; SearchExplorerPopulationRoutes; SearchExplorerRelatedExpandChoices; SearchExplorerRelatedExpandContributors; SearchExplorerRelatedFieldChoices; SearchExplorerSchemaFields; ServerConfig; StreamExactExport; String; TraceExplorerCell; TraceExplorerRowLineage; Unwrap; Validate; ValidateSelectionReferences; VerifyPublication; Visit; VisitProposeExplorerTableShapeResponse; Write,generated/loomapi; internal/api/authpolicy; internal/api/bulk/load; internal/api/columncapabilities; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/artifactfs; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/fhir/schema; internal/ingest; internal/projectid; internal/store/clickhouse,generated/graphql/graph/model; generated/loomapi; internal/api/bulk/load; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/fhir/schema; internal/ingest; internal/projectid; internal/source; internal/store/clickhouse,.; cmd/arango-fhir-server,internal/backends/postgres,github.com/gofiber/fiber/v3; go.yaml.in/yaml/v3; golang.org/x/sync/singleflight,—,legacy=41; migration=11; compatibility=3,broad surface (50 production files); inspect cohesion,go test ./internal/server,inventory complete; semantic disposition pending +internal/server,github.com/calypr/loom/internal/server,server,domain/library,"Compose storage, domain services, generated OpenAPI handlers, GraphQL adapters, and HTTP routes.",artifact_dev.go; auth_wiring.go; compose.go; construction_inputs_catalog.go; explain_wiring.go; explorer_authoring_http.go; explorer_capability.go; explorer_combine_output_metadata.go; explorer_config_route.go; explorer_lifecycle_helpers.go; explorer_lifecycle_wire.go; explorer_output_reconcile.go; explorer_preview_response.go; explorer_preview_security.go; explorer_publication.go; explorer_receipt_contract.go; explorer_receipt_validation.go; local_workspace_writeback.go; openapi_data_routes.go; openapi_explorer_artifacts.go; openapi_explorer_authoring_direct.go; openapi_explorer_construction.go; openapi_explorer_construction_choice_proposal.go; openapi_explorer_construction_inputs.go; openapi_explorer_construction_proposal.go; openapi_explorer_conversion.go; openapi_explorer_direct.go; openapi_explorer_explicit_groups.go; openapi_explorer_population_member_proposal.go; openapi_explorer_routes.go; openapi_explorer_row_definition.go; openapi_explorer_selections.go; openapi_explorer_table_shape.go; openapi_frame_source_options.go; openapi_graphql_routes.go; openapi_recipe_execution.go; openapi_routes.go; openapi_semantic_inventory.go; options.go; private_artifact_reconciler.go; receipt_preview_diagnostic.go; recipe_authorization.go; recipe_combine_resolution.go; recipe_discovery.go; recipe_publication.go; release_verifier.go; routes.go; row_sources.go; runtime.go; server.go,accepted_construction_contract_test.go; append_workspace_rows_integration_test.go; artifact_reconciler_wiring_test.go; auth_wiring_trusted_issuer_test.go; backend_architecture_conformance_test.go; backend_composition_contract_test.go; backend_error_boundary_contract_test.go; backend_neutral_diagnostics_live_test.go; case008_row_definition_preview_route_test.go; command_helpers_test.go; config_test.go; construction_category_discovery_openapi_error_test.go; construction_inputs_catalog_test.go; construction_proposal_zero_column_test.go; core_authoring_protocol_arango_conformance_test.go; core_authoring_protocol_formal_replay_test.go; core_authoring_protocol_receipts_test.go; core_authoring_protocol_restore_test.go; core_authoring_protocol_rowschema_test.go; core_authoring_protocol_scoped_compile_test.go; core_authoring_protocol_test.go; dataframe_query_error_test.go; derived_policy_rows_integration_test.go; explain_wiring_pg18_test.go; explain_wiring_preview_test.go; explorer_capability_test.go; explorer_coded_value_capability_test.go; explorer_concept_evidence_test.go; explorer_config_route_test.go; explorer_diagnostic_logging_test.go; explorer_output_reconcile_derive_source_test.go; explorer_output_reconcile_source_child_test.go; explorer_output_reconcile_unpivot_source_test.go; explorer_preview_response_test.go; explorer_preview_security_test.go; explorer_receipt_contract_error_test.go; explorer_receipt_contract_test.go; explorer_reconcile_scope_test.go; explorer_row_change_route_test.go; explorer_state_route_test.go; fs1_signed_slot_collision_test.go; health_readiness_wiring_test.go; http_test_helpers_test.go; local_workspace_writeback_test.go; openapi_column_id_contract_test.go; openapi_explorer_artifacts_download_test.go; openapi_explorer_authoring_write_fault_test.go; openapi_explorer_cell_trace_test.go; openapi_explorer_commands_notfound_test.go; openapi_explorer_construction_apply_preview_test.go; openapi_explorer_construction_derive_source_test.go; openapi_explorer_construction_expand_source_test.go; openapi_explorer_construction_filter_source_test.go; openapi_explorer_construction_inputs_test.go; openapi_explorer_construction_proposal_test.go; openapi_explorer_construction_unpivot_source_test.go; openapi_explorer_conversion_test.go; openapi_explorer_direct_test.go; openapi_explorer_draft_restore_test.go; openapi_explorer_legacy_details_control_test.go; openapi_explorer_population_mapping_construction_test.go; openapi_explorer_population_member_proposal_test.go; openapi_explorer_restricted_starting_collection_integration_test.go; openapi_explorer_route_roundtrip_test.go; openapi_explorer_route_rows_arango_test.go; openapi_explorer_row_definition_test.go; openapi_explorer_write_scope_test.go; openapi_field_presentation_fixture_test.go; openapi_graphql_status_test.go; openapi_graphql_transport_review_test.go; openapi_interpretation_routes_test.go; openapi_recipe_execution_test.go; openapi_route_inventory_test.go; openapi_routes_test.go; openapi_semantic_inventory_test.go; openapi_status_coverage_test.go; openapi_test_helpers_test.go; options_schemas_test.go; patient_one_construction_choice_route_test.go; population_member_receipt_test.go; postgres_query_test.go; postgres_refusal_contract_test.go; preview_postgres_wiring_pg18_test.go; private_artifact_reconciler_test.go; publication_rows_integration_test.go; publication_viewer_agreement_test.go; receipt_preview_diagnostic_test.go; recipe_authorization_test.go; recipe_blank_project_test.go; recipe_combine_resolution_test.go; recipe_discovery_test.go; recipe_execution_etl_failure_fields_e2e_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_header_probe_test.go; recipe_execution_status_twin_e2e_test.go; recipe_execution_twin_authz_repair_test.go; recipe_project_keying_test.go; recipe_publication_test.go; release_verifier_fault_mapping_test.go; release_verifier_test.go; repository_activation_conflict_dev_test.go; route_composition_contract_test.go; routes_test.go; row_tag_guard_contract_test.go; row_value_policy_stream_test.go; runtime_postgres_live_test.go; runtime_test.go; wiring_test.go; zz_readiness_contract_guard_test.go,—,—,14261,40179,103,Activate; ActivateDatasetGeneration; ActivateRepositoryGeneration; ApplyExplorerBuilderCommands; AssessExplorerRowChange; AuthConfig; AuthorizeRead; AuthorizeWrite; BasicAuthConfig; BrowseExplorerFrameSourceOptions; BrowseExplorerSemanticInventory; CalyprAuthConfig; CheckExplorerPopulationMapping; ClickHouseConfig; Config; CreateDatasetGeneration; CreateExplorer; CreateExplorerExplicitGroupRevision; CreateExplorerSelection; CreateInterpretationRevision; CreateInterpretationRevisionFromColumn; DataframerConfig; DefaultConfig; DiscoverExplorerConstructionCategories; DiscoverExplorerTableShapeCategories; DownloadExplorerArtifact; Error; ExactExecutionMaterialization; ExecuteDataframeGraphQL; ExecuteGraphQL; Explain; ExplainPhysical; Finish; GetApolloSandbox; GetDatasetGenerationStatus; GetExecution; GetExplorer; GetExplorerAuthoringCapability; GetExplorerBuilder; GetExplorerColumnSource; GetExplorerConstructionCapabilities; GetExplorerConstructionInputs; GetExplorerSelection; GetExplorerTableShapeCapabilities; GetGraphQLPlayground; GetHealth; GetInterpretationRevision; GetLiveness; GetPointer; GetReadiness; GetRecipeExecution; HTTPRoutes; ListExplorerRowDefinitionChoices; ListExplorers; ListInterpretationLibraries; LoadConfig; PostgreSQLConfig; PrepareExplorerArtifact; Preview; PreviewCollectionScanEvidenceFromExplain; PreviewExplorer; PreviewInterpretationCandidate; ProbeCandidate; ProbeEdge; ProbeNode; ProposeExplorerConstruction; ProposeExplorerConstructionChoice; ProposeExplorerPopulationMemberRemoval; ProposeExplorerRowDefinition; ProposeExplorerTableShape; PublishAuthoring; PublishExplorer; PublishRepositoryExplorerConfig; ReadManifest; ReconcileExplorerBuilder; Resolve; ResolveConfiguredColumnContext; ResolveExplorerTableShape; ResolveForCompilation; ResolveForExecution; ResolveToken; Run; RunGeneration; RunWithConfig; SearchExplorerCandidates; SearchExplorerConstructionChoices; SearchExplorerPopulationRoutes; SearchExplorerRelatedExpandChoices; SearchExplorerRelatedExpandContributors; SearchExplorerRelatedFieldChoices; SearchExplorerSchemaFields; ServerConfig; StreamExactExport; String; TraceExplorerCell; TraceExplorerRowLineage; Unwrap; Validate; ValidateSelectionReferences; VerifyPublication; Visit; VisitProposeExplorerTableShapeResponse; Write,generated/loomapi; internal/api/authpolicy; internal/api/bulk/load; internal/api/columncapabilities; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/artifactfs; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/fhir/schema; internal/ingest; internal/projectid; internal/store/clickhouse,generated/graphql/graph/model; generated/loomapi; internal/api/bulk/load; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/lineage; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/fhir/schema; internal/ingest; internal/projectid; internal/source; internal/store/clickhouse,.; cmd/arango-fhir-server,internal/backends/postgres,github.com/gofiber/fiber/v3; go.yaml.in/yaml/v3; golang.org/x/sync/singleflight,—,legacy=41; migration=11; compatibility=3,broad surface (50 production files); inspect cohesion,go test ./internal/server,inventory complete; semantic disposition pending internal/source,github.com/calypr/loom/internal/source,source,domain/library,"Define the backend-neutral logical source identity, payload, and authorization-scope contracts shared by ingestion and store read/write paths, free of any storage or driver detail.",doc.go; resource.go,ownership_contract_test.go,—,—,80,403,2,Resource; ResourceScope,—,—,internal/backends/postgres/internal/store; internal/ingest,internal/backends/postgres; internal/dataframe/compiler; internal/dataframe/compiler/render/postgres; internal/dataframe/execution; internal/server,—,—,legacy=4,small shared semantic contract; compare direct callers before inlining,go test ./internal/source,inventory complete; semantic disposition pending internal/store/clickhouse,github.com/calypr/loom/internal/store/clickhouse,clickhouse,storage adapter,Provide the low-level typed ClickHouse connection and database-management boundary.,client.go,client_test.go; integration_test.go,—,—,676,712,15,AddColumn; Client; Close; Column; CreateTable; DropColumns; DropTable; EnsureDatabase; InsertRows; New; Options; Ping; QueryRowsArgs; QueryRowsArgsVisit; VerifyOutput,—,internal/dataframe/compiler/ir; internal/dataframe/compiler/render/clickhouse; internal/dataframe/published,internal/acceptance; internal/dataframe/execution/chartifact; internal/dataframe/publication/clickhouse; internal/server,internal/backends/arango/internal/chartifact; internal/backends/arango/internal/publication; internal/backends/postgres/internal/chartifact; internal/dataframe/compiler/render/clickhouse; internal/dataframe/execution; internal/dataframe/published,github.com/ClickHouse/clickhouse-go/v2; github.com/ClickHouse/clickhouse-go/v2/lib/driver,—,—,storage adapter; preserve interface and persisted-data contracts,go test ./internal/store/clickhouse,inventory complete; semantic disposition pending -scripts,github.com/calypr/loom/scripts,main,domain/library,"Hold repository developer, verification, migration, and release utilities that are not part of the Go module runtime packages.",check_fhir_semantic_resource_types.go; local-preview-index-migration.go,check_fhir_semantic_resource_types_test.go; local-preview-index-migration_test.go,README.md; package-lock.json; package.json; pg-exec-03-skipset-capture.json,—,769,532,0,—,internal/fhir/schema,—,—,—,github.com/arangodb/go-driver/v2/arangodb; github.com/arangodb/go-driver/v2/arangodb/shared; github.com/arangodb/go-driver/v2/connection; github.com/arangodb/go-driver/v2/utils,build tags,legacy=8; migration=9,no in-repo production importer; verify runtime/tooling reachability; runtime/build signals present; static importer counts are insufficient for deletion,go test ./scripts,inventory complete; semantic disposition pending +scripts,github.com/calypr/loom/scripts,main,domain/library,"Hold repository developer, verification, migration, and release utilities that are not part of the Go module runtime packages.",check_fhir_semantic_resource_types.go; local-preview-index-migration.go,check_fhir_semantic_resource_types_test.go; local-preview-index-migration_test.go,README.md; backend-conformance-skipset-capture.json; package-lock.json; package.json; pg-exec-03-skipset-capture.json,—,769,532,0,—,internal/fhir/schema,—,—,—,github.com/arangodb/go-driver/v2/arangodb; github.com/arangodb/go-driver/v2/arangodb/shared; github.com/arangodb/go-driver/v2/connection; github.com/arangodb/go-driver/v2/utils,build tags,legacy=8; migration=9,no in-repo production importer; verify runtime/tooling reachability; runtime/build signals present; static importer counts are insufficient for deletion,go test ./scripts,inventory complete; semantic disposition pending scripts/check-boundaries,github.com/calypr/loom/scripts/check-boundaries,main,domain/library,"Run the authoritative package-boundary policy over the repository and prove it can go RED: the --self-test negative fixtures plant forbidden imports, removed facades, re-exports, cycle leaks, and vacuous scans, each failing closed, with removal converging to green (arch-backend-09).",main.go,—,—,—,435,0,0,—,scripts/packagepolicy,—,—,—,—,—,—,no in-repo production importer; verify runtime/tooling reachability,go test ./scripts/check-boundaries,inventory complete; semantic disposition pending scripts/packagepolicy,github.com/calypr/loom/scripts/packagepolicy,packagepolicy,domain/library,"Hold the single authoritative parsed-import ownership policy table, scanner, identifier-absence and no-re-export checks shared by the check-boundaries CLI, the shell entrypoint, and the compiler guard tests, separating import findings from verification failures and refusing vacuous scans.",policy.go; rules.go,policy_test.go,old_concrete_paths.txt,—,1055,318,24,AbsenceRule; ByLabel; CheckAbsences; CheckNoReExport; Clean; ErrorKind; FindRepoRoot; Finding; Forbidden; HasImportFindings; ImportFindings; NoReExportRule; OldConcreteImportPaths; RepoAbsences; RepoNoReExport; RepoRules; Result; Rule; ScanAll; ScanError; ScanRule; ScanTarget; Scope; String,—,—,scripts/check-boundaries,internal/dataframe/compiler,—,go:embed,compatibility=2,one production importer (scripts/check-boundaries); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion,go test ./scripts/packagepolicy,inventory complete; semantic disposition pending diff --git a/docs/PACKAGE_AUDIT.md b/docs/PACKAGE_AUDIT.md index d14ba351a..b7f47c093 100644 --- a/docs/PACKAGE_AUDIT.md +++ b/docs/PACKAGE_AUDIT.md @@ -8,9 +8,9 @@ A row marked as a consolidation candidate is **not** deletion authorization. Bef - Packages: 96 - Production Go files: 713 -- Test Go files: 869 -- Production Go lines: 560,055 -- Test Go lines: 353,180 +- Test Go files: 873 +- Production Go lines: 560,599 +- Test Go lines: 357,011 - Package kinds: conformance=1, domain/library=63, executable=12, generated=5, storage adapter=6, transport adapter=9 - Non-entrypoint handwritten packages with no production importer: internal/ci; internal/dataframe/benchmark/cmd/benchcli; scripts; scripts/check-boundaries - Non-entrypoint handwritten packages with one production importer: internal/acceptance; internal/api/bulk/load; internal/api/graphql/graph; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/explorer; internal/backends/arango/internal/recipeexec; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/rows; internal/dataframe/benchmark; internal/dataframe/compiler/render/clickhouse; internal/dataframe/publication/clickhouse; internal/explorer/artifactfs; internal/explorer/lifecycle; internal/fhir/semantic; internal/repositoryseed; scripts/packagepolicy @@ -79,15 +79,15 @@ These records preserve verified package-boundary decisions for both live package | `internal/api/recipe` | transport adapter | Adapt scoped catalog field discovery to recipe schema resolution for one-shot and stored recipes. | P: discovery.go; T: discovery_auth_mode_test.go; discovery_test.go; Other: — | 110/126 | 3: FieldDiscovery; Fields; NewFieldDiscovery | internal/authscope; internal/catalog; internal/dataframe/errors; internal/dataframe/recipe/schema | internal/api/graphql/graph/query; internal/server | no automatic consolidation signal; perform semantic review | | `internal/authscope` | domain/library | Represent request principals and resolve authorized project, generation, and resource scopes. | P: authentication.go; doc.go; fence_resource_access.go; principal.go; scope_resolver.go; T: all_generations_cache_key_test.go; auth_path_control_ingress_test.go; auth_resource_path_options_conflict_test.go; authentication_test.go; covers_execution_paths_control_test.go; persisted_scope_fails_closed_test.go; probe_double_normalize_test.go; scope_cache_key_namespace_test.go; scope_test.go; Other: — | 1,175/2,104 | 36: AllowAllAuthorizer; Authenticate; Authenticator; AuthorizeProject; AuthorizeReadProject; AuthorizeWrite; Authorizer; BasicAuthenticator; BearerTokenAuthenticator; CalyprAuthenticator; Clone; ContextWithPrincipal; … (+24) | internal/catalog; internal/projectid | cmd/preview-covering-index-artifact; internal/api/authpolicy; internal/api/bulk/load; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/backends/arango/internal/explorer; internal/dataframe/compiler/capability; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/recipe; internal/dataframe/semantic; internal/explorer/lifecycle; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | | `internal/backends/arango` | storage adapter | Expose the only public entry to the ArangoDB 3.12 backend: a single Open factory validates explicit config, owns exactly one lifecycle client with the required collection bootstrap policy, and returns the typed runtime carrying the catalog, dataset lifecycle, Explorer, recipe registry/revisions, and published-dataframe ports assembled from the private implementation tree, with alias re-exports for witness-side reassembly. | P: aliases.go; doc.go; open.go; T: ownership_contract_test.go; Other: — | 336/800 | 4: Close; Config; Open; Runtime | internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec; internal/backends/arango/internal/store | cmd/arango-fhir-proto; cmd/dataframe-profile; cmd/loom-data; internal/acceptance; internal/ingest; internal/server | storage adapter; preserve interface and persisted-data contracts | -| `internal/backends/arango/internal/catalog` | domain/library | Persist and retrieve catalog facts in ArangoDB. | P: codec.go; evidence.go; fields.go; relationships.go; resource_inventory.go; resources.go; semantic_coverage.go; semantic_coverage_envelope.go; semantic_inventory.go; store.go; T: all_generations_auth_path_conflict_test.go; all_generations_project_binding_integration_test.go; resource_inventory_integration_test.go; resource_inventory_unit_test.go; semantic_coded_category_integration_test.go; semantic_coverage_envelope_test.go; semantic_coverage_resource_integration_test.go; semantic_coverage_test.go; semantic_inventory_category_test.go; semantic_inventory_integration_test.go; semantic_inventory_selection_test.go; semantic_inventory_source_availability_test.go; store_test.go; Other: — | 5,267/5,466 | 54: AccumulateRelationshipCatalog; AddSource; AdvanceSemanticInventoryBackfill; AdvanceSemanticInventoryBuild; AuditRelationshipEdges; BeginSemanticCoverageBuild; BeginSemanticCoverageResource; BeginSemanticInventoryBuild; ClaimSemanticInventoryBackfill; CompleteRetainedSemanticCoverageResource; CompleteSemanticCoverageResource; CompleteSemanticInventoryBackfill; … (+42) | internal/backends/arango/internal/store; internal/catalog; internal/fhir/schema | internal/backends/arango | one production importer (internal/backends/arango); inspect ownership/inline potential | +| `internal/backends/arango/internal/catalog` | domain/library | Persist and retrieve catalog facts in ArangoDB. | P: codec.go; evidence.go; fields.go; relationships.go; resource_inventory.go; resources.go; semantic_coverage.go; semantic_coverage_envelope.go; semantic_inventory.go; store.go; T: all_generations_auth_path_conflict_test.go; all_generations_project_binding_integration_test.go; missing_field_catalog_integration_test.go; resource_inventory_integration_test.go; resource_inventory_unit_test.go; semantic_coded_category_integration_test.go; semantic_coverage_envelope_test.go; semantic_coverage_resource_integration_test.go; semantic_coverage_test.go; semantic_inventory_category_test.go; semantic_inventory_integration_test.go; semantic_inventory_selection_test.go; semantic_inventory_source_availability_test.go; store_test.go; Other: — | 5,283/5,594 | 54: AccumulateRelationshipCatalog; AddSource; AdvanceSemanticInventoryBackfill; AdvanceSemanticInventoryBuild; AuditRelationshipEdges; BeginSemanticCoverageBuild; BeginSemanticCoverageResource; BeginSemanticInventoryBuild; ClaimSemanticInventoryBackfill; CompleteRetainedSemanticCoverageResource; CompleteSemanticCoverageResource; CompleteSemanticInventoryBackfill; … (+42) | internal/backends/arango/internal/store; internal/catalog; internal/fhir/schema | internal/backends/arango | one production importer (internal/backends/arango); inspect ownership/inline potential | | `internal/backends/arango/internal/chartifact` | domain/library | Persist private ClickHouse artifact manifests and leases in a dedicated Arango collection. | P: catalog.go; T: catalog_arango_test.go; catalog_test.go; Other: — | 379/723 | 10: BootstrapSpec; Catalog; ClaimCleanup; Create; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update | internal/backends/arango/internal/store; internal/dataframe/execution/chartifact | internal/backends/arango | one production importer (internal/backends/arango); inspect ownership/inline potential | -| `internal/backends/arango/internal/dataset` | domain/library | Persist immutable dataset generation lifecycle state in ArangoDB. | P: collections.go; doc.go; document_codec.go; manifest_lifecycle.go; release_store.go; T: generation_activation_cas_e2e_test.go; release_policy_change_e2e_test.go; revision_inflation_e2e_test.go; store_test.go; Other: — | 764/1,652 | 15: Activate; ActiveDocumentKey; BootstrapSpec; CollectionSpecs; CompareAndSwapActivateRelease; CompareAndSwapPublishRelease; CreateManifest; ManifestDocumentKey; New; ReadActiveRelease; ReadManifest; ResolveActiveManifest; … (+3) | internal/backends/arango/internal/store; internal/dataset | internal/backends/arango; internal/backends/arango/internal/explorer | no automatic consolidation signal; perform semantic review | +| `internal/backends/arango/internal/dataset` | domain/library | Persist immutable dataset generation lifecycle state in ArangoDB. | P: collections.go; doc.go; document_codec.go; manifest_lifecycle.go; release_store.go; T: generation_activation_cas_e2e_test.go; release_policy_change_e2e_test.go; release_store_cas_retry_test.go; revision_inflation_e2e_test.go; store_test.go; Other: — | 791/1,783 | 15: Activate; ActiveDocumentKey; BootstrapSpec; CollectionSpecs; CompareAndSwapActivateRelease; CompareAndSwapPublishRelease; CreateManifest; ManifestDocumentKey; New; ReadActiveRelease; ReadManifest; ResolveActiveManifest; … (+3) | internal/backends/arango/internal/store; internal/dataset | internal/backends/arango; internal/backends/arango/internal/explorer | no automatic consolidation signal; perform semantic review | | `internal/backends/arango/internal/explorer` | domain/library | Persist Explorer drafts, revisions, receipts, publications, capabilities, and repository state in ArangoDB. | P: activation.go; capability_snapshot_store.go; codec.go; collections.go; drafts.go; explicit_groups.go; explorers.go; interpretations.go; publication.go; receipts.go; repository.go; revisions.go; selection_references.go; selections.go; store.go; table_shape_capabilities.go; T: capability_snapshot_store_test.go; collections_test.go; cross_generation_publish_e2e_test.go; draft_conflict_test.go; draft_transaction_formal_replay_test.go; drafts_integration_test.go; drafts_test.go; explicit_group_storage_integration_test.go; explicit_groups_test.go; interpretations_test.go; receipt_store_test.go; repository_test.go; selection_references_test.go; selection_scale_integration_test.go; selection_storage_integration_test.go; selections_cleanup_e2e_test.go; store_test.go; table_shape_capabilities_test.go; Other: — | 3,366/4,552 | 56: AbortExplicitGroupRevision; AbortSelection; ActivateRepositoryGeneration; AppendExplicitGroupMemberships; AppendSelectionMembers; BeginExplicitGroupRevision; BeginSelection; BootstrapSpec; CapabilitySnapshotCollectionSpec; CapabilitySnapshotStore; CleanupExplicitGroupStaging; CleanupSelectionStaging; … (+44) | internal/authscope; internal/backends/arango/internal/dataset; internal/backends/arango/internal/publication; internal/backends/arango/internal/store; internal/dataframe/publication; internal/dataset; internal/explorer; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/tableshapecap; internal/projectid | internal/backends/arango | one production importer (internal/backends/arango); inspect ownership/inline potential; broad surface (16 production files); inspect cohesion | | `internal/backends/arango/internal/publication` | domain/library | Persist dataframe publication registry and bundle metadata in ArangoDB. | P: bundle_registry.go; registry.go; T: bundle_registry_test.go; dangling_binding_e2e_test.go; execution_read_pin_integration_test.go; name_latest_binding_e2e_test.go; publish_atomicity_e2e_test.go; release_dangle_reconcile_e2e_test.go; Other: — | 533/2,990 | 22: AcquireBundleLease; AcquireExecutionReadPin; BootstrapSpec; ClaimExecutionCleanup; Client; CompareAndSwapPointer; FindExecutionByKey; FindExecutionBySelector; GetExecution; GetPointer; ListExecutions; New; … (+10) | internal/backends/arango/internal/store; internal/dataframe/publication | internal/backends/arango; internal/backends/arango/internal/explorer | no automatic consolidation signal; perform semantic review | | `internal/backends/arango/internal/recipeexec` | domain/library | Persist recipe versions and execution-use records in ArangoDB. | P: registry.go; revisions.go; T: registry_test.go; revisions_test.go; revisions_user_faults_test.go; Other: — | 294/555 | 15: BootstrapSpec; Client; Get; List; LoadRecipe; LoadRecipeVersion; New; NewRevisionRegistry; RecipeVersionUsed; Register; Registry; ReplaceRecipeVersion; … (+3) | internal/backends/arango/internal/store; internal/dataframe/errors; internal/dataframe/recipe; internal/dataframe/recipe/exec | internal/backends/arango | one production importer (internal/backends/arango); inspect ownership/inline potential | | `internal/backends/arango/internal/store` | domain/library | Provide the low-level typed ArangoDB client, query streaming, profiling, and Explain assessment boundary. | P: client.go; errors.go; explain.go; explain_assessment.go; explain_client.go; options.go; preview_index.go; profile.go; profile_client.go; types.go; T: client_query_memory_limit_test.go; client_queryrows_test.go; client_revision_test.go; errors_test.go; explain_assessment_test.go; explain_client_test.go; explain_test.go; population_profile_integration_test.go; preview_index_test.go; profile_client_test.go; profile_integration_test.go; profile_test.go; Other: — | 1,950/2,210 | 70: AQLExecutor; AssessExplainResult; BatchInserter; Bootstrap; BootstrapSpec; Client; Close; CollectionCount; CollectionExists; CollectionRevision; CollectionSpec; ConnectionOptions; … (+58) | internal/dataframe/errors | internal/backends/arango; internal/backends/arango/internal/catalog; internal/backends/arango/internal/chartifact; internal/backends/arango/internal/dataset; internal/backends/arango/internal/explorer; internal/backends/arango/internal/publication; internal/backends/arango/internal/recipeexec | runtime/build signals present; static importer counts are insufficient for deletion | | `internal/backends/postgres` | storage adapter | Own the PostgreSQL 18 factory: a single Open validates explicit config, opens exactly one readiness-gated pool with own-prefix connection tagging, composes the control runtime, and returns the typed runtime carrying the SourceRows and PlanJSON data-plane ports over tagged compiled commands through the typed Querier bridge; the sole legal importer of the private rows adapter, and no re-export surface - control-plane consumers import internal/backends/postgres/control directly (arch-backend-09 deleted aliases.go). | P: doc.go; open.go; plan_executor.go; T: engine_postgres_command_boundary_pg18_test.go; engine_postgres_pg18_test.go; explain_postgres_test.go; open_live_test.go; ownership_contract_test.go; plan_executor_test.go; sentinel_identity_test.go; Other: — | 178/4,408 | 5: NewPlanJSONExecutor; NewSourceRows; Open; Querier; Runtime | internal/backends/postgres/control; internal/backends/postgres/internal/rows; internal/backends/postgres/internal/store; internal/dataframe/compiler/command; internal/dataframe/execution | cmd/loom-data; internal/ingest; internal/server | storage adapter; preserve interface and persisted-data contracts | -| `internal/backends/postgres/control` | domain/library | Own the cycle-free public control plane of the PostgreSQL backend as its single definition: configuration and series-assignment validation, the bare-pool client and store-owned batch vocabulary, the dataset manifest lifecycle and catalog evidence port assembly, and the structural query surface; production consumers and backend-adjacent witnesses import it directly for control-plane symbols, and it must never reach the factory root, the private rows adapter, or dataframe/execution (machine-enforced by scripts/packagepolicy). | P: catalog_read.go; config.go; errors.go; runtime.go; vocabulary.go; vocabulary_control.go; T: capability_compose_test.go; catalog_read_test.go; Other: — | 1,393/669 | 65: AllSeries; Begin; CatalogStore; Close; Commit; Config; ConnSource; DiscoverExistingAuthResourcePaths; DiscoverFields; Error; Exec; FieldProfileScope; … (+53) | internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/store; internal/catalog; internal/dataset | cmd/loom-data; internal/backends/postgres; internal/dataframe/benchmark/cmd/benchcli; internal/ingest; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/backends/postgres/control` | domain/library | Own the cycle-free public control plane of the PostgreSQL backend as its single definition: configuration and series-assignment validation, the bare-pool client and store-owned batch vocabulary, the dataset manifest lifecycle and catalog evidence port assembly, and the structural query surface; production consumers and backend-adjacent witnesses import it directly for control-plane symbols, and it must never reach the factory root, the private rows adapter, or dataframe/execution (machine-enforced by scripts/packagepolicy). | P: catalog_read.go; config.go; errors.go; runtime.go; vocabulary.go; vocabulary_control.go; T: capability_compose_test.go; capability_evidence_assembly_test.go; catalog_read_test.go; Other: — | 1,393/796 | 65: AllSeries; Begin; CatalogStore; Close; Commit; Config; ConnSource; DiscoverExistingAuthResourcePaths; DiscoverFields; Error; Exec; FieldProfileScope; … (+53) | internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/chartifact; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/redact; internal/backends/postgres/internal/store; internal/catalog; internal/dataset | cmd/loom-data; internal/backends/postgres; internal/dataframe/benchmark/cmd/benchcli; internal/ingest; internal/server | runtime/build signals present; static importer counts are insufficient for deletion | | `internal/backends/postgres/internal/catalog` | domain/library | Persist and retrieve catalog facts in PostgreSQL 18: publish-last generation-scoped resource inventory builds, bounded field-profile records with completeness and truncation metadata, leased compare-and-swap-guarded semantic inventory build state, deterministic semantic-inventory contribution rows with content-addressed replay and conflict classification, byte-order-stable keyset pages over retained resources, and authorization-scoped semantic-inventory pages and selection resolution served directly from contribution aggregates. | P: field_discovery.go; field_profile.go; relationships.go; resource_inventory.go; semantic_build.go; semantic_contribution.go; semantic_page.go; store.go; T: capability_compose_pg_test.go; field_discovery_test.go; field_profile_test.go; relationships_pg_test.go; relationships_test.go; resource_inventory_test.go; semantic_build_test.go; semantic_contribution_test.go; semantic_page_test.go; Other: — | 4,430/11,834 | 55: AdvanceSemanticInventoryBackfill; ClaimSemanticInventoryBackfill; CompleteSemanticInventoryBackfill; DiscoverExistingAuthResourcePaths; DiscoverFieldEnrichment; DiscoverFields; DiscoverRelationshipObservations; DiscoverResourceInventory; EnsureSemanticInventoryEntries; Error; FailSemanticInventoryBackfill; FieldProfileStore; … (+43) | internal/backends/postgres/internal/store; internal/catalog; internal/fhir/schema | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/backends/postgres/internal/chartifact` | domain/library | Persist private ClickHouse artifact manifests and cleanup leases in the PostgreSQL 18 control plane with CAS progress writes, bounded expired scans, and one-winner cleanup claims while physical tables stay in ClickHouse. | P: catalog.go; T: catalog_manager_test.go; catalog_test.go; Other: — | 535/1,605 | 10: Catalog; ClaimCleanup; Create; DB; Delete; ListExpired; NewCatalog; ReleaseCleanup; Renew; Update | internal/dataframe/execution/chartifact | internal/backends/postgres/control | one production importer (internal/backends/postgres/control); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/backends/postgres/internal/dataset` | domain/library | Persist dataset generation manifests, the active-generation compare-and-swap pointer, and immutable project release candidates with the active-release compare-and-swap pointer in PostgreSQL 18. | P: commands.go; manifest_lifecycle.go; migrate.go; release_store.go; store.go; T: commands_test.go; manifest_lifecycle_pg_test.go; release_store_pg_test.go; Other: — | 1,725/2,488 | 28: ActivateGeneration; Backend; Command; CommandBackend; CompareAndSwapActivateRelease; ConnSource; CreateManifest; EnsureGenerationPrerequisites; GenerationOwnerAdapter; MigrateSchema; New; NewPostgreSQLCommand; … (+16) | internal/backends/postgres/internal/store; internal/dataset | internal/backends/postgres/control; internal/backends/postgres/internal/explorer | runtime/build signals present; static importer counts are insufficient for deletion | @@ -100,8 +100,8 @@ These records preserve verified package-boundary decisions for both live package | `internal/backends/postgres/internal/store` | domain/library | Provide the bounded pgx/v5-backed PostgreSQL 18 source store: readiness-pinged pool, advisory-lock serialized migration runner, scoped FHIR resource repository that accepts the logical internal/source contract on writes and answers reads with its store-owned physical row under the server-assigned keyset sequence, scoped typed edge-occurrence repository, publish-last inventory repository, scoped bounded field-profile record persistence, the ledgered idempotent COPY batch writer for resource and edge rows, and resource-only and edge-only batch facades over that writer. | P: advisory.go; batch_writer.go; client.go; commit_outcome.go; edge.go; edge_writer.go; errors.go; field_profile.go; inventory.go; migrate.go; migration_ownership.go; resource.go; resource_writer.go; T: batch_writer_test.go; client_test.go; edge_test.go; edge_writer_test.go; errors_test.go; field_profile_auth_paths_test.go; field_profile_ddl_test.go; field_profile_filter_golden_test.go; inventory_test.go; migrate_test.go; ownership_contract_test.go; resource_test.go; resource_writer_test.go; verify_classify_test.go; verify_readonly_test.go; Other: — | 5,971/13,963 | 113: Acquire; AsUniqueConflict; Assignment; AssignmentConflict; Batch; BatchAccountingError; BatchEdgeConflictError; BatchIdentityConflictError; BatchResourceConflictError; BatchResult; BatchWriteError; BatchWriter; … (+101) | internal/catalog; internal/dataframe/errors; internal/source | internal/backends/postgres; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/dataset; internal/backends/postgres/internal/explorer; internal/backends/postgres/internal/publication; internal/backends/postgres/internal/recipeexec; internal/backends/postgres/internal/recipes; internal/backends/postgres/internal/rows | broad surface (13 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/catalog` | domain/library | Own persistence-neutral observed FHIR field, pivot, relationship, and capability facts. | P: auth_scope.go; backend_contract.go; cache.go; capability_evidence.go; generation.go; helpers.go; limits.go; rebuild.go; relationship_evidence.go; relationships.go; resource_inventory.go; retained_semantic_page.go; semantic.go; semantic_coverage.go; semantic_coverage_contract.go; semantic_inventory.go; semantic_schema.go; types.go; write_profiler.go; T: auth_scope_test.go; cache_test.go; capability_evidence_test.go; generation_test.go; relationship_evidence_census_test.go; relationship_evidence_test.go; relationships_test.go; semantic_coding_display_projection_test.go; semantic_coverage_diagnostics_test.go; semantic_coverage_test.go; semantic_inventory_buildid_cache_test.go; semantic_inventory_category_test.go; semantic_inventory_emitter_anchor_test.go; semantic_inventory_test.go; semantic_schema_identifier_type_test.go; semantic_schema_test.go; semantic_test.go; write_profiler_coverage_test.go; write_profiler_test.go; Other: — | 6,471/3,946 | 146: AuthResourcePathOptions; Cache; CacheOptions; CacheStats; CapabilityEvidence; CapabilityEvidenceOptions; CapabilityEvidenceReader; CensusSemanticCoverage; ConceptCandidate; DecodeSemanticInventoryCursor; DefaultProfileLimits; DiscoverFields; … (+134) | internal/fhir/schema; internal/fhir/semantic | cmd/arango-fhir-proto; cmd/loom-data; internal/api/graphql/graph/query; internal/api/recipe; internal/authscope; internal/backends/arango/internal/catalog; internal/backends/postgres/control; internal/backends/postgres/internal/catalog; internal/backends/postgres/internal/store; internal/explorer/authoringv2; internal/explorer/lifecycle; internal/ingest; internal/server | broad surface (19 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/ci` | domain/library | Exercise repository-wide Go formatting and vet gates, including injected fault detection. | P: —; T: gofmt_gate_test.go; vet_gate_test.go; Other: — | 0/283 | 0: — | — | — | no in-repo production importer; verify runtime/tooling reachability | -| `internal/dataframe/benchmark` | domain/library | Run the Arango-versus-PostgreSQL matched dataframe benchmark: deterministic fixtures, canonical bags and correctness gates evaluated before any timing is claimed, control runs, and report artifacts that a re-computing verifier must accept, with ClickHouse-backed publication legs disclosed as planned/not_run when the service is absent. | P: armplan.go; authoring.go; canonical.go; controls.go; driver.go; fixtures.go; gates.go; harness.go; matrix.go; measure.go; oracle.go; provision.go; report.go; report_md.go; T: armplan_test.go; authoring_test.go; canonical_test.go; controls_test.go; driver_test.go; fixtures_fhir_test.go; fixtures_test.go; gates_test.go; harness_scope_test.go; harness_test.go; matrix_test.go; measure_test.go; oracle_test.go; publication_test.go; report_test.go; scratch_authrepro_test.go; scratch_liveupload_test.go; Other: README.md | 5,072/2,964 | 146: Apply; ArangoBackendYAML; Arm; ArmConfig; ArmEvidence; AuthorWorkload; AuthoringStatus; AuthoringVerification; BackendResult; Bag; BothArmsPlan; BuildAllWorkspaces; … (+134) | internal/explorer/authoringv2 | internal/dataframe/benchmark/cmd/benchcli | one production importer (internal/dataframe/benchmark/cmd/benchcli); inspect ownership/inline potential; broad surface (14 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | -| `internal/dataframe/benchmark/cmd/benchcli` | domain/library | Own the benchmark command-line entry point: fail-closed live-run preflight, planned disclosure artifacts, report verification authority, and the loom_bench PostgreSQL database lifecycle. | P: deployer.go; main.go; probes.go; setup.go; T: guards_test.go; Other: — | 949/322 | 0: — | internal/backends/postgres/control; internal/dataframe/benchmark | — | no in-repo production importer; verify runtime/tooling reachability | +| `internal/dataframe/benchmark` | domain/library | Run the Arango-versus-PostgreSQL matched dataframe benchmark: deterministic fixtures, canonical bags and correctness gates evaluated before any timing is claimed, control runs, and report artifacts that a re-computing verifier must accept, with ClickHouse-backed publication legs disclosed as planned/not_run when the service is absent. | P: armplan.go; authoring.go; canonical.go; controls.go; driver.go; fixtures.go; gates.go; harness.go; matrix.go; measure.go; oracle.go; provision.go; report.go; report_md.go; T: armplan_test.go; authoring_test.go; canonical_test.go; controls_test.go; driver_test.go; fixtures_fhir_test.go; fixtures_test.go; gates_test.go; harness_scope_test.go; harness_test.go; matrix_test.go; measure_test.go; oracle_test.go; publication_test.go; report_test.go; scratch_authrepro_test.go; scratch_liveupload_test.go; Other: README.md | 5,506/3,534 | 154: AgeBandFor; AppendWorkloadDisclosures; Apply; ApplyExcludedWorkloadGateReports; ArangoBackendYAML; Arm; ArmConfig; ArmEvidence; AuthorWorkload; AuthoringStatus; AuthoringVerification; BackendResult; … (+142) | internal/explorer/authoringv2 | internal/dataframe/benchmark/cmd/benchcli | one production importer (internal/dataframe/benchmark/cmd/benchcli); inspect ownership/inline potential; broad surface (14 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | +| `internal/dataframe/benchmark/cmd/benchcli` | domain/library | Own the benchmark command-line entry point: fail-closed live-run preflight, planned disclosure artifacts, report verification authority, and the loom_bench PostgreSQL database lifecycle. | P: deployer.go; main.go; probes.go; setup.go; T: guards_test.go; Other: — | 1,016/374 | 0: — | internal/backends/postgres/control; internal/dataframe/benchmark | — | no in-repo production importer; verify runtime/tooling reachability | | `internal/dataframe/columntransform` | domain/library | Define closed, backend-neutral transformations over one selected output column value. | P: types.go; T: types_test.go; Other: — | 79/61 | 7: CategoryMapping; Clone; ExactCategoryRecode; Kind; UnknownPolicy; Validate; ValueTransformation | — | internal/dataframe/compiler/lower; internal/dataframe/recipe; internal/explorer/authoringv2 | no automatic consolidation signal; perform semantic review | | `internal/dataframe/compiler` | domain/library | Orchestrate semantic planning, physical lowering, optimization, rendering, and compiled-query assembly. | P: api.go; category_scan.go; category_scan_filter_pushdown.go; category_scan_related.go; cell_trace.go; clone_helpers.go; compiled_query.go; construction_preview_root_id_filter.go; graph_execution.go; ir_helpers.go; optimizer.go; output.go; population_mapping.go; postgres_target.go; preview_covering_index.go; recipe_execution.go; recipe_paging.go; row_lineage.go; semantic_group_projection_preview.go; table_shape_exclusions.go; target.go; T: backend_leaf_guard_test.go; backend_ownership_guard_contract_test.go; category_scan_arango_test.go; category_scan_parent_presence_arango_test.go; category_scan_related_collection_identity_test.go; category_scan_related_presence_arango_test.go; category_scan_related_test.go; category_scan_test.go; cell_trace_test.go; cohort_drop_boundary_arango_test.go; command_helpers_external_test.go; command_helpers_test.go; compiled_command_test.go; construction_arango_test.go; construction_coded_group_arango_test.go; construction_coded_group_execution_test.go; construction_coded_pivot_test.go; construction_execution_test.go; construction_group_expand_arango_test.go; construction_group_preview_liveness_arango_test.go; construction_group_related_summary_arango_test.go; construction_group_source_streaming_test.go; construction_pivot_lineage_arango_test.go; construction_preview_root_id_filter_test.go; construction_related_count_arango_test.go; construction_related_expand_arango_test.go; construction_related_group_preview_arango_test.go; correlated_arango_test.go; correlated_legacy_compat_test.go; correlated_physical_test.go; correlated_selection_test.go; correlated_system_version_arango_test.go; explicit_cohort_values_arango_test.go; filter_semantics_test.go; filter_test.go; generic_physical_plan_test.go; grain_test.go; group_connected_field_source_arango_test.go; group_field_source_arango_test.go; nested_expansion_arango_test.go; operator_oracles_arango_test.go; physical_diagnostics_test.go; physical_lowering_test.go; physical_optimize_test.go; physical_plan_test.go; physical_projection_reserved_name_arango_test.go; physical_render_test.go; physical_required_match_test.go; physical_scope_test.go; pivot_presence_arango_audit_test.go; pivot_presence_audit_test.go; plan_test_helpers_test.go; population_frontier_arango_test.go; population_mapping_filter_exists_test.go; population_mapping_identity_guard_test.go; population_mapping_routed_group_arango_test.go; population_mapping_test.go; postgres_coded_group_compile_test.go; postgres_parity_fixture_test.go; postgres_target_fingerprint_golden_test.go; public_columns_freeze_test.go; recipe_execution_pg18_test.go; recipe_execution_test.go; recipe_paging_related_filter_sequence_test.go; recipe_paging_routed_exists_arango_test.go; recipe_paging_test.go; root_page_related_seed_arango_test.go; row_expansion_test.go; row_item_projection_arango_test.go; row_lineage_cohort_filter_arango_test.go; row_lineage_composed_arango_test.go; row_lineage_group_pivot_arango_test.go; row_lineage_group_rows_arango_test.go; row_lineage_test.go; selected_source_contract_test.go; selection_semantics_test.go; selector_first_limit_test.go; semantic_group_arango_test.go; semantic_group_preview_arango_test.go; semantic_group_projection_factorization_arango_test.go; semantic_validation_test.go; source_group_preview_arango_test.go; streaming_missing_category_pivot_test.go; table_reshape_arango_test.go; table_reshape_execution_window_test.go; table_shape_exclusions_test.go; workspace_output_capture_test.go; Other: — | 8,120/33,173 | 44: ArangoTarget; Backend; CategoryOverflowWitness; CategoryScanProof; CategoryScanRefusal; CategoryScanRefusalCode; CategoryScanRefusalCodeOf; CompileCategoryScanOutput; CompileCategoryScanStage; CompileCellTraceOutput; CompileClickHousePrefix; CompilePopulationMappingOutput; … (+32) | internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/compiler/render/postgres; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec | cmd/dataframe-profile; cmd/preview-covering-index-artifact; internal/api/graphql/graph/query; internal/dataframe/execution; internal/explorer/lifecycle; internal/server | broad surface (21 production files); inspect cohesion | | `internal/dataframe/compiler/capability` | domain/library | Probe which dataframe language capabilities the compiler can prove and support. | P: probe.go; schema_fields.go; T: probe_test.go; schema_fields_test.go; Other: — | 803/313 | 22: CandidateCapability; CandidateRequest; Chart; Filter; Options; OwnerRecordsCapability; OwnerRecordsRequest; ProbeCandidate; ProbeOwnerRecords; ProbeProjection; ProbeRoot; ProbeTraversal; … (+10) | internal/authscope; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/compiler/optimize; internal/dataframe/compiler/render/aql; internal/dataframe/expression; internal/dataframe/recipe; internal/dataframe/semantic; internal/dataframe/spec; internal/fhir/schema | internal/explorer/lifecycle; internal/server | no automatic consolidation signal; perform semantic review | @@ -139,10 +139,10 @@ These records preserve verified package-boundary decisions for both live package | `internal/ingest` | domain/library | Preflight and load FHIR NDJSON into generation-scoped Arango vertices, edges, and catalog facts, and commit built resource-row and built-edge-occurrence batches to the PostgreSQL source store through the ledgered batch writer with scope verification, canonical payload encoding, and payload-backed path/ordinal recovery for repeated references. | P: backend.go; doc.go; documents.go; files.go; generated_load.go; generation_identity.go; generation_load.go; generation_orchestration.go; generation_workers.go; load_options.go; postgres_writer.go; preflight.go; progress.go; resource_load.go; row_builder.go; semantic_inventory_backfill.go; source_schema.go; T: backend_test.go; basic_observation_fixture_validation_test.go; bench_test.go; documents_test.go; file_pipeline_test.go; files_test.go; generated_load_test.go; generation_cleanup_test.go; generation_failed_cleanup_e2e_test.go; generation_identity_test.go; generation_load_postgres_durability_test.go; generation_load_postgres_test.go; generation_load_test.go; integration_test.go; keycollide_generated_e2e_test.go; load_options_test.go; postgres_writer_test.go; preflight_test.go; related_medication_fixture_test.go; resource_load_test.go; row_builder_test.go; semantic_inventory_backfill_integration_test.go; semantic_inventory_backfill_test.go; semantic_inventory_test.go; source_schema_test.go; viewer_row_file_download_fixture_test.go; Other: — | 5,650/10,571 | 63: ActivatePostgresGeneration; ActivationOutcomeError; BackfillSemanticInventory; Build; Complete; ConnectionOptions; DiscoverNDJSON; EdgeDocument; EdgeFromGrip; Error; EventSink; GeneratedRowBuilder; … (+51) | generated/fhir; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataset; internal/fhir/schema; internal/source | cmd/arango-fhir-proto; cmd/loom-data; internal/api/bulk/load; internal/server | broad surface (17 production files); inspect cohesion; runtime/build signals present; static importer counts are insufficient for deletion | | `internal/projectid` | domain/library | Define canonical and legacy-compatible public project identity normalization. | P: projectid.go; T: projectid_test.go; Other: — | 51/18 | 2: Canonical; Legacy | — | cmd/loom-repository; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/authscope; internal/backends/arango/internal/explorer; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/execution; internal/dataframe/published; internal/explorer; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/repositoryseed; internal/server | small shared semantic contract; compare direct callers before inlining | | `internal/repositoryseed` | domain/library | Validate repository metadata and configuration, produce a source manifest, and refuse unmaterialized inputs before launch. | P: preflight.go; T: preflight_test.go; Other: — | 281/105 | 6: Error; File; Manifest; Options; Prepare; UnmaterializedError | internal/explorer/authoringv2; internal/projectid | cmd/loom-repository | one production importer (cmd/loom-repository); inspect ownership/inline potential | -| `internal/server` | domain/library | Compose storage, domain services, generated OpenAPI handlers, GraphQL adapters, and HTTP routes. | P: artifact_dev.go; auth_wiring.go; compose.go; construction_inputs_catalog.go; explain_wiring.go; explorer_authoring_http.go; explorer_capability.go; explorer_combine_output_metadata.go; explorer_config_route.go; explorer_lifecycle_helpers.go; explorer_lifecycle_wire.go; explorer_output_reconcile.go; explorer_preview_response.go; explorer_preview_security.go; explorer_publication.go; explorer_receipt_contract.go; explorer_receipt_validation.go; local_workspace_writeback.go; openapi_data_routes.go; openapi_explorer_artifacts.go; openapi_explorer_authoring_direct.go; openapi_explorer_construction.go; openapi_explorer_construction_choice_proposal.go; openapi_explorer_construction_inputs.go; openapi_explorer_construction_proposal.go; openapi_explorer_conversion.go; openapi_explorer_direct.go; openapi_explorer_explicit_groups.go; openapi_explorer_population_member_proposal.go; openapi_explorer_routes.go; openapi_explorer_row_definition.go; openapi_explorer_selections.go; openapi_explorer_table_shape.go; openapi_frame_source_options.go; openapi_graphql_routes.go; openapi_recipe_execution.go; openapi_routes.go; openapi_semantic_inventory.go; options.go; private_artifact_reconciler.go; receipt_preview_diagnostic.go; recipe_authorization.go; recipe_combine_resolution.go; recipe_discovery.go; recipe_publication.go; release_verifier.go; routes.go; row_sources.go; runtime.go; server.go; T: accepted_construction_contract_test.go; append_workspace_rows_integration_test.go; artifact_reconciler_wiring_test.go; auth_wiring_trusted_issuer_test.go; backend_composition_contract_test.go; backend_error_boundary_contract_test.go; backend_neutral_diagnostics_live_test.go; case008_row_definition_preview_route_test.go; command_helpers_test.go; config_test.go; construction_category_discovery_openapi_error_test.go; construction_inputs_catalog_test.go; construction_proposal_zero_column_test.go; core_authoring_protocol_arango_conformance_test.go; core_authoring_protocol_formal_replay_test.go; core_authoring_protocol_receipts_test.go; core_authoring_protocol_restore_test.go; core_authoring_protocol_rowschema_test.go; core_authoring_protocol_scoped_compile_test.go; core_authoring_protocol_test.go; dataframe_query_error_test.go; derived_policy_rows_integration_test.go; explain_wiring_pg18_test.go; explain_wiring_preview_test.go; explorer_capability_test.go; explorer_coded_value_capability_test.go; explorer_concept_evidence_test.go; explorer_config_route_test.go; explorer_diagnostic_logging_test.go; explorer_output_reconcile_derive_source_test.go; explorer_output_reconcile_source_child_test.go; explorer_output_reconcile_unpivot_source_test.go; explorer_preview_response_test.go; explorer_preview_security_test.go; explorer_receipt_contract_error_test.go; explorer_receipt_contract_test.go; explorer_reconcile_scope_test.go; explorer_row_change_route_test.go; explorer_state_route_test.go; fs1_signed_slot_collision_test.go; health_readiness_wiring_test.go; http_test_helpers_test.go; local_workspace_writeback_test.go; openapi_column_id_contract_test.go; openapi_explorer_artifacts_download_test.go; openapi_explorer_authoring_write_fault_test.go; openapi_explorer_cell_trace_test.go; openapi_explorer_commands_notfound_test.go; openapi_explorer_construction_apply_preview_test.go; openapi_explorer_construction_derive_source_test.go; openapi_explorer_construction_expand_source_test.go; openapi_explorer_construction_filter_source_test.go; openapi_explorer_construction_inputs_test.go; openapi_explorer_construction_proposal_test.go; openapi_explorer_construction_unpivot_source_test.go; openapi_explorer_conversion_test.go; openapi_explorer_direct_test.go; openapi_explorer_draft_restore_test.go; openapi_explorer_legacy_details_control_test.go; openapi_explorer_population_mapping_construction_test.go; openapi_explorer_population_member_proposal_test.go; openapi_explorer_restricted_starting_collection_integration_test.go; openapi_explorer_route_roundtrip_test.go; openapi_explorer_route_rows_arango_test.go; openapi_explorer_row_definition_test.go; openapi_explorer_write_scope_test.go; openapi_field_presentation_fixture_test.go; openapi_graphql_status_test.go; openapi_graphql_transport_review_test.go; openapi_interpretation_routes_test.go; openapi_recipe_execution_test.go; openapi_route_inventory_test.go; openapi_routes_test.go; openapi_semantic_inventory_test.go; openapi_status_coverage_test.go; openapi_test_helpers_test.go; options_schemas_test.go; patient_one_construction_choice_route_test.go; population_member_receipt_test.go; postgres_query_test.go; postgres_refusal_contract_test.go; preview_postgres_wiring_pg18_test.go; private_artifact_reconciler_test.go; publication_rows_integration_test.go; publication_viewer_agreement_test.go; receipt_preview_diagnostic_test.go; recipe_authorization_test.go; recipe_blank_project_test.go; recipe_combine_resolution_test.go; recipe_discovery_test.go; recipe_execution_etl_failure_fields_e2e_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_header_probe_test.go; recipe_execution_status_twin_e2e_test.go; recipe_execution_twin_authz_repair_test.go; recipe_project_keying_test.go; recipe_publication_test.go; release_verifier_fault_mapping_test.go; release_verifier_test.go; repository_activation_conflict_dev_test.go; route_composition_contract_test.go; routes_test.go; row_tag_guard_contract_test.go; row_value_policy_stream_test.go; runtime_postgres_live_test.go; runtime_test.go; wiring_test.go; zz_readiness_contract_guard_test.go; Other: — | 14,261/37,356 | 103: Activate; ActivateDatasetGeneration; ActivateRepositoryGeneration; ApplyExplorerBuilderCommands; AssessExplorerRowChange; AuthConfig; AuthorizeRead; AuthorizeWrite; BasicAuthConfig; BrowseExplorerFrameSourceOptions; BrowseExplorerSemanticInventory; CalyprAuthConfig; … (+91) | generated/loomapi; internal/api/authpolicy; internal/api/bulk/load; internal/api/columncapabilities; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/artifactfs; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/fhir/schema; internal/ingest; internal/projectid; internal/store/clickhouse | .; cmd/arango-fhir-server | broad surface (50 production files); inspect cohesion | +| `internal/server` | domain/library | Compose storage, domain services, generated OpenAPI handlers, GraphQL adapters, and HTTP routes. | P: artifact_dev.go; auth_wiring.go; compose.go; construction_inputs_catalog.go; explain_wiring.go; explorer_authoring_http.go; explorer_capability.go; explorer_combine_output_metadata.go; explorer_config_route.go; explorer_lifecycle_helpers.go; explorer_lifecycle_wire.go; explorer_output_reconcile.go; explorer_preview_response.go; explorer_preview_security.go; explorer_publication.go; explorer_receipt_contract.go; explorer_receipt_validation.go; local_workspace_writeback.go; openapi_data_routes.go; openapi_explorer_artifacts.go; openapi_explorer_authoring_direct.go; openapi_explorer_construction.go; openapi_explorer_construction_choice_proposal.go; openapi_explorer_construction_inputs.go; openapi_explorer_construction_proposal.go; openapi_explorer_conversion.go; openapi_explorer_direct.go; openapi_explorer_explicit_groups.go; openapi_explorer_population_member_proposal.go; openapi_explorer_routes.go; openapi_explorer_row_definition.go; openapi_explorer_selections.go; openapi_explorer_table_shape.go; openapi_frame_source_options.go; openapi_graphql_routes.go; openapi_recipe_execution.go; openapi_routes.go; openapi_semantic_inventory.go; options.go; private_artifact_reconciler.go; receipt_preview_diagnostic.go; recipe_authorization.go; recipe_combine_resolution.go; recipe_discovery.go; recipe_publication.go; release_verifier.go; routes.go; row_sources.go; runtime.go; server.go; T: accepted_construction_contract_test.go; append_workspace_rows_integration_test.go; artifact_reconciler_wiring_test.go; auth_wiring_trusted_issuer_test.go; backend_architecture_conformance_test.go; backend_composition_contract_test.go; backend_error_boundary_contract_test.go; backend_neutral_diagnostics_live_test.go; case008_row_definition_preview_route_test.go; command_helpers_test.go; config_test.go; construction_category_discovery_openapi_error_test.go; construction_inputs_catalog_test.go; construction_proposal_zero_column_test.go; core_authoring_protocol_arango_conformance_test.go; core_authoring_protocol_formal_replay_test.go; core_authoring_protocol_receipts_test.go; core_authoring_protocol_restore_test.go; core_authoring_protocol_rowschema_test.go; core_authoring_protocol_scoped_compile_test.go; core_authoring_protocol_test.go; dataframe_query_error_test.go; derived_policy_rows_integration_test.go; explain_wiring_pg18_test.go; explain_wiring_preview_test.go; explorer_capability_test.go; explorer_coded_value_capability_test.go; explorer_concept_evidence_test.go; explorer_config_route_test.go; explorer_diagnostic_logging_test.go; explorer_output_reconcile_derive_source_test.go; explorer_output_reconcile_source_child_test.go; explorer_output_reconcile_unpivot_source_test.go; explorer_preview_response_test.go; explorer_preview_security_test.go; explorer_receipt_contract_error_test.go; explorer_receipt_contract_test.go; explorer_reconcile_scope_test.go; explorer_row_change_route_test.go; explorer_state_route_test.go; fs1_signed_slot_collision_test.go; health_readiness_wiring_test.go; http_test_helpers_test.go; local_workspace_writeback_test.go; openapi_column_id_contract_test.go; openapi_explorer_artifacts_download_test.go; openapi_explorer_authoring_write_fault_test.go; openapi_explorer_cell_trace_test.go; openapi_explorer_commands_notfound_test.go; openapi_explorer_construction_apply_preview_test.go; openapi_explorer_construction_derive_source_test.go; openapi_explorer_construction_expand_source_test.go; openapi_explorer_construction_filter_source_test.go; openapi_explorer_construction_inputs_test.go; openapi_explorer_construction_proposal_test.go; openapi_explorer_construction_unpivot_source_test.go; openapi_explorer_conversion_test.go; openapi_explorer_direct_test.go; openapi_explorer_draft_restore_test.go; openapi_explorer_legacy_details_control_test.go; openapi_explorer_population_mapping_construction_test.go; openapi_explorer_population_member_proposal_test.go; openapi_explorer_restricted_starting_collection_integration_test.go; openapi_explorer_route_roundtrip_test.go; openapi_explorer_route_rows_arango_test.go; openapi_explorer_row_definition_test.go; openapi_explorer_write_scope_test.go; openapi_field_presentation_fixture_test.go; openapi_graphql_status_test.go; openapi_graphql_transport_review_test.go; openapi_interpretation_routes_test.go; openapi_recipe_execution_test.go; openapi_route_inventory_test.go; openapi_routes_test.go; openapi_semantic_inventory_test.go; openapi_status_coverage_test.go; openapi_test_helpers_test.go; options_schemas_test.go; patient_one_construction_choice_route_test.go; population_member_receipt_test.go; postgres_query_test.go; postgres_refusal_contract_test.go; preview_postgres_wiring_pg18_test.go; private_artifact_reconciler_test.go; publication_rows_integration_test.go; publication_viewer_agreement_test.go; receipt_preview_diagnostic_test.go; recipe_authorization_test.go; recipe_blank_project_test.go; recipe_combine_resolution_test.go; recipe_discovery_test.go; recipe_execution_etl_failure_fields_e2e_test.go; recipe_execution_malformed_persisted_scope_test.go; recipe_execution_malformed_scope_header_probe_test.go; recipe_execution_status_twin_e2e_test.go; recipe_execution_twin_authz_repair_test.go; recipe_project_keying_test.go; recipe_publication_test.go; release_verifier_fault_mapping_test.go; release_verifier_test.go; repository_activation_conflict_dev_test.go; route_composition_contract_test.go; routes_test.go; row_tag_guard_contract_test.go; row_value_policy_stream_test.go; runtime_postgres_live_test.go; runtime_test.go; wiring_test.go; zz_readiness_contract_guard_test.go; Other: — | 14,261/40,179 | 103: Activate; ActivateDatasetGeneration; ActivateRepositoryGeneration; ApplyExplorerBuilderCommands; AssessExplorerRowChange; AuthConfig; AuthorizeRead; AuthorizeWrite; BasicAuthConfig; BrowseExplorerFrameSourceOptions; BrowseExplorerSemanticInventory; CalyprAuthConfig; … (+91) | generated/loomapi; internal/api/authpolicy; internal/api/bulk/load; internal/api/columncapabilities; internal/api/graphql/graph; internal/api/graphql/graph/dataframe; internal/api/graphql/graph/query; internal/api/graphql/graph/resolver; internal/api/http; internal/api/recipe; internal/authscope; internal/backends/arango; internal/backends/postgres; internal/backends/postgres/control; internal/catalog; internal/dataframe/compiler; internal/dataframe/compiler/capability; internal/dataframe/compiler/command; internal/dataframe/compiler/ir; internal/dataframe/compiler/lower; internal/dataframe/errors; internal/dataframe/execution; internal/dataframe/execution/chartifact; internal/dataframe/expression; internal/dataframe/publication; internal/dataframe/publication/clickhouse; internal/dataframe/published; internal/dataframe/recipe; internal/dataframe/recipe/exec; internal/dataframe/recipe/schema; internal/dataframe/semantic; internal/dataframe/spec; internal/dataframe/unit; internal/dataset; internal/explorer; internal/explorer/artifactfs; internal/explorer/authoringv2; internal/explorer/capability; internal/explorer/compilation; internal/explorer/lifecycle; internal/explorer/tableshapecap; internal/fhir/schema; internal/ingest; internal/projectid; internal/store/clickhouse | .; cmd/arango-fhir-server | broad surface (50 production files); inspect cohesion | | `internal/source` | domain/library | Define the backend-neutral logical source identity, payload, and authorization-scope contracts shared by ingestion and store read/write paths, free of any storage or driver detail. | P: doc.go; resource.go; T: ownership_contract_test.go; Other: — | 80/403 | 2: Resource; ResourceScope | — | internal/backends/postgres/internal/store; internal/ingest | small shared semantic contract; compare direct callers before inlining | | `internal/store/clickhouse` | storage adapter | Provide the low-level typed ClickHouse connection and database-management boundary. | P: client.go; T: client_test.go; integration_test.go; Other: — | 676/712 | 15: AddColumn; Client; Close; Column; CreateTable; DropColumns; DropTable; EnsureDatabase; InsertRows; New; Options; Ping; … (+3) | — | internal/acceptance; internal/dataframe/execution/chartifact; internal/dataframe/publication/clickhouse; internal/server | storage adapter; preserve interface and persisted-data contracts | -| `scripts` | domain/library | Hold repository developer, verification, migration, and release utilities that are not part of the Go module runtime packages. | P: check_fhir_semantic_resource_types.go; local-preview-index-migration.go; T: check_fhir_semantic_resource_types_test.go; local-preview-index-migration_test.go; Other: README.md; package-lock.json; package.json; pg-exec-03-skipset-capture.json | 769/532 | 0: — | internal/fhir/schema | — | no in-repo production importer; verify runtime/tooling reachability; runtime/build signals present; static importer counts are insufficient for deletion | +| `scripts` | domain/library | Hold repository developer, verification, migration, and release utilities that are not part of the Go module runtime packages. | P: check_fhir_semantic_resource_types.go; local-preview-index-migration.go; T: check_fhir_semantic_resource_types_test.go; local-preview-index-migration_test.go; Other: README.md; backend-conformance-skipset-capture.json; package-lock.json; package.json; pg-exec-03-skipset-capture.json | 769/532 | 0: — | internal/fhir/schema | — | no in-repo production importer; verify runtime/tooling reachability; runtime/build signals present; static importer counts are insufficient for deletion | | `scripts/check-boundaries` | domain/library | Run the authoritative package-boundary policy over the repository and prove it can go RED: the --self-test negative fixtures plant forbidden imports, removed facades, re-exports, cycle leaks, and vacuous scans, each failing closed, with removal converging to green (arch-backend-09). | P: main.go; T: —; Other: — | 435/0 | 0: — | scripts/packagepolicy | — | no in-repo production importer; verify runtime/tooling reachability | | `scripts/packagepolicy` | domain/library | Hold the single authoritative parsed-import ownership policy table, scanner, identifier-absence and no-re-export checks shared by the check-boundaries CLI, the shell entrypoint, and the compiler guard tests, separating import findings from verification failures and refusing vacuous scans. | P: policy.go; rules.go; T: policy_test.go; Other: old_concrete_paths.txt | 1,055/318 | 24: AbsenceRule; ByLabel; CheckAbsences; CheckNoReExport; Clean; ErrorKind; FindRepoRoot; Finding; Forbidden; HasImportFindings; ImportFindings; NoReExportRule; … (+12) | — | scripts/check-boundaries | one production importer (scripts/check-boundaries); inspect ownership/inline potential; runtime/build signals present; static importer counts are insufficient for deletion | diff --git a/internal/backends/arango/internal/catalog/all_generations_auth_path_conflict_test.go b/internal/backends/arango/internal/catalog/all_generations_auth_path_conflict_test.go index 99b0421af..9dccaeecb 100644 --- a/internal/backends/arango/internal/catalog/all_generations_auth_path_conflict_test.go +++ b/internal/backends/arango/internal/catalog/all_generations_auth_path_conflict_test.go @@ -22,8 +22,10 @@ import ( ) type fb27ctlArangoClient struct { - queries []string - vars []map[string]any + queries []string + vars []map[string]any + existsCalls int + collectionGone bool // simulates a database that has never ingested } func (c *fb27ctlArangoClient) QueryRows(_ context.Context, query string, _ int, vars map[string]any, _ store.RowVisitor) error { @@ -45,7 +47,8 @@ func (c *fb27ctlArangoClient) ExecuteAQL(context.Context, string, map[string]any } func (c *fb27ctlArangoClient) CollectionExists(context.Context, string) (bool, error) { - panic("unused by the auth path discovery controls") + c.existsCalls++ + return !c.collectionGone, nil } func (c *fb27ctlArangoClient) Bootstrap(context.Context, store.BootstrapSpec) error { @@ -53,8 +56,9 @@ func (c *fb27ctlArangoClient) Bootstrap(context.Context, store.BootstrapSpec) er } // fb27ctlDiscover runs one discovery against a fresh fake client and asserts -// the guard/query boundary: a rejected request must not reach Arango, an -// accepted one must issue exactly one query. +// the guard/query boundary: a rejected request must not reach Arango (no +// existence probe, no query), an accepted one must issue exactly one +// existence probe and exactly one query. func fb27ctlDiscover(t *testing.T, opts catalog.AuthResourcePathOptions) ([]string, error) { t.Helper() client := &fb27ctlArangoClient{} @@ -64,11 +68,14 @@ func fb27ctlDiscover(t *testing.T, opts catalog.AuthResourcePathOptions) ([]stri } paths, err := s.DiscoverExistingAuthResourcePaths(context.Background(), opts) if err != nil { - if len(client.queries) != 0 { - t.Fatalf("VERDICT=UNEXPECTED rejected request reached Arango (%d queries) for %+v: %v", len(client.queries), opts, err) + if len(client.queries) != 0 || client.existsCalls != 0 { + t.Fatalf("VERDICT=UNEXPECTED rejected request reached Arango (%d probes, %d queries) for %+v: %v", client.existsCalls, len(client.queries), opts, err) } return paths, err } + if client.existsCalls != 1 { + t.Fatalf("VERDICT=UNEXPECTED accepted request issued %d existence probes for %+v, want 1", client.existsCalls, opts) + } if len(client.queries) != 1 { t.Fatalf("VERDICT=UNEXPECTED accepted request issued %d queries for %+v, want 1", len(client.queries), opts) } @@ -118,3 +125,34 @@ func TestFB27SupportedAuthResourcePathOptionShapesStillDiscover(t *testing.T) { }) } } + +// TestFB27MissingFieldCatalogIsNoRecordedPaths pins the first-authorized- +// upload guarantee: on a database that has never ingested, the collection +// does not exist, discovery answers "no recorded paths" (the posture the +// authscope F-B27 zero-path bootstrap branch is built for), and no AQL +// reaches Arango. Surfacing the missing-collection fault instead would +// 403 every first restricted-scope upload, because the project-binding +// gate consults discovery before ingest can bootstrap the collection. +func TestFB27MissingFieldCatalogIsNoRecordedPaths(t *testing.T) { + for _, opts := range []catalog.AuthResourcePathOptions{ + {Project: "projA", DatasetGeneration: "gen-1"}, + {Project: "projA", AllGenerations: true}, + } { + client := &fb27ctlArangoClient{collectionGone: true} + s, err := New(client) + if err != nil { + t.Fatalf("VERDICT=UNEXPECTED store construction = %v", err) + } + paths, err := s.DiscoverExistingAuthResourcePaths(context.Background(), opts) + if err != nil { + t.Fatalf("VERDICT=CONFIRMED missing fhir_field_catalog surfaced as a discovery fault for %+v: %v", opts, err) + } + if paths == nil || len(paths) != 0 { + t.Fatalf("VERDICT=UNEXPECTED missing collection must answer empty non-nil paths, got %#v", paths) + } + if len(client.queries) != 0 { + t.Fatalf("VERDICT=UNEXPECTED missing collection must not query (%d issued) for %+v", len(client.queries), opts) + } + } + t.Logf("VERDICT=REPAIRED a never-ingested database discovers zero paths instead of a storage fault") +} diff --git a/internal/backends/arango/internal/catalog/missing_field_catalog_integration_test.go b/internal/backends/arango/internal/catalog/missing_field_catalog_integration_test.go new file mode 100644 index 000000000..78f573413 --- /dev/null +++ b/internal/backends/arango/internal/catalog/missing_field_catalog_integration_test.go @@ -0,0 +1,90 @@ +package arango + +import ( + "context" + "net/http" + "net/url" + "os" + "strings" + "testing" + "time" + + storearango "github.com/calypr/loom/internal/backends/arango/internal/store" + "github.com/calypr/loom/internal/catalog" + "github.com/google/uuid" +) + +// Durable control for the first-authorized-upload deadlock: the authscope +// project-binding gate (F-B27) asks this discovery which auth paths a project +// has recorded BEFORE ingest can bootstrap fhir_field_catalog. On a fresh +// database the collection does not exist, so a storage fault there is +// surfaced as 403 FORBIDDEN on the very first restricted-scope upload and the +// database can never receive its first generation. Discovery must answer +// "no recorded paths", the same posture the zero-path bootstrap branch of +// AuthorizeWrite is built for and the posture the postgres backend has +// because SetupMigrate pre-creates the table. + +const missingCatalogTestDatabasePrefix = "loom_catalog_missing_" + +func openMissingCatalogHarness(t *testing.T, budget time.Duration) (context.Context, *storearango.Client) { + t.Helper() + endpoint := strings.TrimSpace(os.Getenv("LOOM_TEST_ARANGO_URL")) + if endpoint == "" { + t.Skip("set LOOM_TEST_ARANGO_URL to run the missing-field-catalog Arango proof") + } + database := missingCatalogTestDatabasePrefix + strings.ReplaceAll(uuid.NewString(), "-", "") + if !strings.HasPrefix(database, missingCatalogTestDatabasePrefix) { + t.Fatalf("verification database %q escapes the %s prefix", database, missingCatalogTestDatabasePrefix) + } + ctx, cancel := context.WithTimeout(context.Background(), budget) + t.Cleanup(cancel) + client, err := storearango.Open(ctx, endpoint, database) + if err != nil { + t.Fatalf("open owned database %s: %v", database, err) + } + t.Logf("MISSINGCATALOG_DATABASE=%s", database) + t.Cleanup(func() { + _ = client.Close(context.Background()) + cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cleanupCancel() + request, err := http.NewRequestWithContext(cleanupCtx, http.MethodDelete, strings.TrimRight(endpoint, "/")+"/_api/database/"+url.PathEscape(database), nil) + if err != nil { + t.Errorf("build cleanup request for %s: %v", database, err) + return + } + response, err := http.DefaultClient.Do(request) + if err != nil { + t.Errorf("cleanup %s: %v", database, err) + return + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotFound { + t.Errorf("cleanup %s: HTTP %d", database, response.StatusCode) + } + }) + // Deliberately NO bootstrap: the whole point is a database that has + // never ingested. + return ctx, client +} + +func TestDiscoverExistingAuthResourcePathsMissingCollectionIsNoPaths(t *testing.T) { + ctx, client := openMissingCatalogHarness(t, 60*time.Second) + store, err := New(client) + if err != nil { + t.Fatal(err) + } + for name, opts := range map[string]catalog.AuthResourcePathOptions{ + "generation scoped": {Project: "loom-bench", DatasetGeneration: "bench-first"}, + "all generations": {Project: "loom-bench", AllGenerations: true}, + } { + t.Run(name, func(t *testing.T) { + paths, err := store.DiscoverExistingAuthResourcePaths(ctx, opts) + if err != nil { + t.Fatalf("missing fhir_field_catalog must answer no paths, got error: %v", err) + } + if len(paths) != 0 { + t.Fatalf("fresh database must report zero recorded paths, got %v", paths) + } + }) + } +} diff --git a/internal/backends/arango/internal/catalog/resources.go b/internal/backends/arango/internal/catalog/resources.go index ddd65888c..43009c22f 100644 --- a/internal/backends/arango/internal/catalog/resources.go +++ b/internal/backends/arango/internal/catalog/resources.go @@ -20,6 +20,22 @@ func (s *Store) DiscoverExistingAuthResourcePaths(ctx context.Context, opts cata if err := ValidateAuthResourcePathOptions(opts); err != nil { return nil, err } + // A database that has never ingested has no field catalog collection at + // all. Discovery answers "no recorded paths" there, exactly as the + // retained-profile read treats a missing collection as no data: the + // alternative is a storage fault on every first authorized write, + // because the authscope project-binding gate consults this discovery + // BEFORE ingest can bootstrap the collection (its zero-path branch is + // built for precisely this first-generation bootstrap), and surfacing + // the fault as 403 would deadlock a fresh database against itself. + // A collection deleted mid-request still fails the query honestly. + exists, err := s.client.CollectionExists(ctx, catalog.FieldCatalogCollection) + if err != nil { + return nil, err + } + if !exists { + return []string{}, nil + } if opts.CursorBatch <= 0 { opts.CursorBatch = 1000 } @@ -37,7 +53,7 @@ func (s *Store) DiscoverExistingAuthResourcePaths(ctx context.Context, opts cata query = existingAuthResourcePathsAllGenerationsAQL delete(bindVars, "dataset_generation") } - err := s.client.QueryRows(ctx, query, opts.CursorBatch, bindVars, func(row map[string]any) error { + err = s.client.QueryRows(ctx, query, opts.CursorBatch, bindVars, func(row map[string]any) error { if value := stringValue(row["auth_resource_path"]); value != "" { result = append(result, value) } From 67b64586e470be9b61e5fa588946b3fa3f9600c6 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 06:05:48 +0000 Subject: [PATCH 31/45] fix(benchmark): publish aggregate predicates as contributor intent The server's explorer-config body bind is strict and closed: AggregateSource.Where is json:"-", so any workspace with aggregate.where faults with unknown field "where" before ingest gates run. The battery's v3 templates stay authored offline; each live arm now fetches its builder catalog from the seeded default explorer once and migrates predicate workspaces through the repository's own MigrateLegacyContributors, publishing canonical v4 Column.Contributor bytes - the same path the acceptance lane runs. The driver refuses to time unmatched arms when the two catalogs migrate the same workload to different bytes, and an unresolvable or cross-generation predicate path fails closed before any timing. FetchContributorCatalog refuses incomplete, truncated, or scope-less catalogs. Removes the committed scratch repro probes for the already-fixed auth-path-discovery defect; the regression proofs live in internal/backends/arango/internal/catalog. --- internal/dataframe/benchmark/contributors.go | 164 +++++++++++++++ .../dataframe/benchmark/contributors_test.go | 194 ++++++++++++++++++ internal/dataframe/benchmark/driver.go | 27 ++- internal/dataframe/benchmark/driver_test.go | 35 ++++ internal/dataframe/benchmark/harness_test.go | 41 +++- .../benchmark/scratch_authrepro_test.go | 61 ------ .../benchmark/scratch_liveupload_test.go | 181 ---------------- 7 files changed, 459 insertions(+), 244 deletions(-) create mode 100644 internal/dataframe/benchmark/contributors.go create mode 100644 internal/dataframe/benchmark/contributors_test.go delete mode 100644 internal/dataframe/benchmark/scratch_authrepro_test.go delete mode 100644 internal/dataframe/benchmark/scratch_liveupload_test.go diff --git a/internal/dataframe/benchmark/contributors.go b/internal/dataframe/benchmark/contributors.go new file mode 100644 index 000000000..d1beb3cf7 --- /dev/null +++ b/internal/dataframe/benchmark/contributors.go @@ -0,0 +1,164 @@ +package benchmark + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/calypr/loom/internal/explorer/authoringv2" +) + +// Aggregate predicates on the writable wire. +// +// The battery authors v3 templates with aggregate.where (the offline +// decoder accepts them), but the writable wire omits AggregateSource.Where +// (json:"-"): the server's strict body bind refuses the field with +// unknown field "where". The writable contract is the catalog-bound +// Column.Contributor form, and the repository's own +// MigrateLegacyContributors turns the v3 intent into it against the live +// builder catalog — the same path the acceptance lane runs +// (internal/acceptance/scenario.go). Live legs therefore publish migrated +// v4 canonical bytes, never the legacy where wire. + +// contributorCatalogWire is the public builder-state catalog projection the +// contributor migration consumes. CatalogSnapshot cannot be unmarshaled +// directly because ConstructionChoice is a closed interface source; this +// reduced view carries exactly the fields MigrateLegacyContributors reads. +type contributorCatalogWire struct { + Generation string `json:"generation"` + SnapshotToken string `json:"snapshotToken"` + AuthorizationScopeDigest string `json:"authorizationScopeDigest"` + Complete bool `json:"complete"` + Truncated bool `json:"truncated"` + Nodes []struct { + ID string `json:"nodeId"` + ResourceType string `json:"resourceType"` + } `json:"nodes"` + Candidates []struct { + ID string `json:"candidateId"` + NodeID string `json:"nodeId"` + FieldPath string `json:"fieldPath"` + LogicalType string `json:"logicalType"` + Repeated bool `json:"repeated"` + RepeatedBoundaries []authoringv2.RepeatedBoundary `json:"repeatedBoundaries"` + } `json:"candidates"` +} + +// FetchContributorCatalog reads the live builder catalog of one explorer +// (the battery uses the seeded "default"). It fails closed on an incomplete, +// truncated, or scope-less catalog: a partial catalog could silently drop a +// predicate candidate and mis-author a workload. +func (a *Arm) FetchContributorCatalog(ctx context.Context, token, project, explorer string) (authoringv2.CatalogSnapshot, error) { + endpoint := a.authoringURL(project, explorer) + "/builder" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return authoringv2.CatalogSnapshot{}, err + } + a.authed(req, token) + value, err := doArmRequest(a.Client, req) + if err != nil { + return authoringv2.CatalogSnapshot{}, fmt.Errorf("benchmark: contributor catalog on %s: %w", a.Backend, err) + } + catalogValue, ok := value["catalog"] + if !ok { + return authoringv2.CatalogSnapshot{}, fmt.Errorf("benchmark: builder state on %s omitted the catalog", a.Backend) + } + raw, err := json.Marshal(catalogValue) + if err != nil { + return authoringv2.CatalogSnapshot{}, fmt.Errorf("benchmark: builder catalog on %s: %w", a.Backend, err) + } + var wire contributorCatalogWire + if err := json.Unmarshal(raw, &wire); err != nil { + return authoringv2.CatalogSnapshot{}, fmt.Errorf("benchmark: builder catalog shape on %s: %w", a.Backend, err) + } + catalog := authoringv2.CatalogSnapshot{ + APIVersion: authoringv2.APIVersion, Kind: authoringv2.CatalogKind, + Project: project, ExplorerID: explorer, + SourceGeneration: wire.Generation, AuthorizationScopeDigest: wire.AuthorizationScopeDigest, + SnapshotToken: wire.SnapshotToken, + Complete: wire.Complete, Truncated: wire.Truncated, + Nodes: make([]authoringv2.CatalogNode, 0, len(wire.Nodes)), + Candidates: make([]authoringv2.CatalogCandidate, 0, len(wire.Candidates)), + } + for _, node := range wire.Nodes { + catalog.Nodes = append(catalog.Nodes, authoringv2.CatalogNode{ID: node.ID, ResourceType: node.ResourceType}) + } + for _, candidate := range wire.Candidates { + catalog.Candidates = append(catalog.Candidates, authoringv2.CatalogCandidate{ + ID: candidate.ID, NodeID: candidate.NodeID, FieldPath: candidate.FieldPath, + LogicalType: candidate.LogicalType, Repeated: candidate.Repeated, + RepeatedBoundaries: append([]authoringv2.RepeatedBoundary(nil), candidate.RepeatedBoundaries...), + }) + } + if err := validateContributorCatalog(catalog); err != nil { + return authoringv2.CatalogSnapshot{}, fmt.Errorf("benchmark: contributor catalog on %s: %w", a.Backend, err) + } + return catalog, nil +} + +func validateContributorCatalog(catalog authoringv2.CatalogSnapshot) error { + if strings.TrimSpace(catalog.Project) == "" || strings.TrimSpace(catalog.ExplorerID) == "" || strings.TrimSpace(catalog.SourceGeneration) == "" { + return errors.New("catalog project, Explorer, or generation is missing") + } + if strings.TrimSpace(catalog.SnapshotToken) == "" || strings.TrimSpace(catalog.AuthorizationScopeDigest) == "" { + return errors.New("catalog snapshot token or authorization scope digest is missing") + } + if !catalog.Complete || catalog.Truncated { + return errors.New("catalog snapshot is incomplete or truncated") + } + return nil +} + +// PreparePublicationWorkspace turns one authored battery workspace into the +// bytes to publish. Workspaces without aggregate predicates pass through +// byte-identical (the arms already share them); workspaces with legacy +// aggregate.where are migrated against the arm's live catalog and published +// as canonical v4 contributor intent. A predicate path the catalog cannot +// resolve unambiguously fails the run before any timing: guessing a +// candidate id or dropping the filter would silently change the workload. +func PreparePublicationWorkspace(raw []byte, generation string, catalog authoringv2.CatalogSnapshot) ([]byte, error) { + workspace, err := authoringv2.DecodeWorkspace(raw) + if err != nil { + return nil, fmt.Errorf("benchmark: decode authored workspace: %w", err) + } + if !workspaceHasAggregateWhere(workspace) { + return raw, nil + } + if workspace.SemanticsVersion != wsSemanticsVersion { + return nil, fmt.Errorf("benchmark: authored workspace carries aggregate predicates at semanticsVersion %d; only v%d migration is supported", + workspace.SemanticsVersion, wsSemanticsVersion) + } + if err := validateContributorCatalog(catalog); err != nil { + return nil, fmt.Errorf("benchmark: contributor catalog unusable for migration: %w", err) + } + if catalog.SourceGeneration != generation { + return nil, fmt.Errorf("benchmark: contributor catalog generation %q does not match benchmark generation %q", catalog.SourceGeneration, generation) + } + migrated, err := authoringv2.MigrateLegacyContributors(workspace, catalog) + if err != nil { + return nil, fmt.Errorf("benchmark: migrate aggregate predicates: %w", err) + } + migrated = authoringv2.MigrateLosslessDefaults(migrated, catalog).NormalizePresentationOrders() + if err := migrated.ValidateForPublication(); err != nil { + return nil, fmt.Errorf("benchmark: validate migrated workspace: %w", err) + } + canonical, err := migrated.CanonicalJSON() + if err != nil { + return nil, fmt.Errorf("benchmark: canonicalize migrated workspace: %w", err) + } + return canonical, nil +} + +func workspaceHasAggregateWhere(workspace authoringv2.Workspace) bool { + for _, document := range workspace.Documents { + for _, column := range document.Columns { + if column.Source.Kind == authoringv2.SourceAggregate && column.Source.Aggregate != nil && column.Source.Aggregate.Where != nil { + return true + } + } + } + return false +} diff --git a/internal/dataframe/benchmark/contributors_test.go b/internal/dataframe/benchmark/contributors_test.go new file mode 100644 index 000000000..e519fe053 --- /dev/null +++ b/internal/dataframe/benchmark/contributors_test.go @@ -0,0 +1,194 @@ +package benchmark + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/calypr/loom/internal/explorer/authoringv2" +) + +// The writable wire is closed over aggregate predicates: AggregateSource +// strict-decodes a wire without "where", so a published workspace must +// carry catalog-bound Column.Contributor intent. These tests pin both sides: +// the legacy template is refused by the same unmarshal the server's body +// bind performs, and the migrated workspace is accepted. + +func preparedTestCatalog() authoringv2.CatalogSnapshot { + return authoringv2.CatalogSnapshot{ + APIVersion: authoringv2.APIVersion, Kind: authoringv2.CatalogKind, + Project: "proj", ExplorerID: "default", SourceGeneration: "gen1", + AuthorizationScopeDigest: "scope-digest-1", SnapshotToken: "snap-1", + Complete: true, + Nodes: []authoringv2.CatalogNode{ + {ID: "patient", ResourceType: "Patient"}, + {ID: "observation", ResourceType: "Observation"}, + {ID: "specimen", ResourceType: "Specimen"}, + {ID: "condition", ResourceType: "Condition"}, + }, + Candidates: []authoringv2.CatalogCandidate{ + {ID: "cand-obs-age-code", NodeID: "observation", FieldPath: "code.coding[].code", + LogicalType: "code", Repeated: true, + RepeatedBoundaries: []authoringv2.RepeatedBoundary{{Path: "code.coding[]", MaxItems: 20}}}, + {ID: "cand-spec-type-code", NodeID: "specimen", FieldPath: "type.coding[].code", + LogicalType: "code", Repeated: true, + RepeatedBoundaries: []authoringv2.RepeatedBoundary{{Path: "type.coding[]", MaxItems: 20}}}, + }, + } +} + +// strictWorkspaceBind mirrors the server's explorer-config body bind: the +// generated request body is the authoringv2 workspace decoded closed. +func strictWorkspaceBind(t *testing.T, raw []byte) authoringv2.Workspace { + t.Helper() + var workspace authoringv2.Workspace + if err := json.Unmarshal(raw, &workspace); err != nil { + t.Fatalf("server-equivalent bind must accept the published workspace: %v", err) + } + return workspace +} + +func TestAggregateWhereTemplateIsRefusedByTheServerBind(t *testing.T) { + raw, err := BuildWorkspace(WorkloadAggregateSumAgeByPatient) + if err != nil { + t.Fatal(err) + } + var workspace authoringv2.Workspace + err = json.Unmarshal(raw, &workspace) + if err == nil || !strings.Contains(err.Error(), "unknown field") { + t.Fatalf("legacy aggregate.where must fail the closed bind, got %v", err) + } +} + +func TestPreparePublicationWorkspaceMigratesAggregateWhereToContributor(t *testing.T) { + raw, err := BuildWorkspace(WorkloadAggregateSumAgeByPatient) + if err != nil { + t.Fatal(err) + } + prepared, err := PreparePublicationWorkspace(raw, "gen1", preparedTestCatalog()) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(prepared), `"where"`) { + t.Fatalf("migrated workspace still carries the legacy where wire: %s", prepared) + } + workspace := strictWorkspaceBind(t, prepared) + if workspace.SemanticsVersion < contributorSemanticsVersionForTest { + t.Fatalf("migrated semanticsVersion=%d must be at least the contributor contract v%d", workspace.SemanticsVersion, contributorSemanticsVersionForTest) + } + columns := workspace.Documents[0].Columns + if len(columns) != 5 { + t.Fatalf("columns=%d want 5", len(columns)) + } + for _, column := range columns[1:] { + if column.Contributor == nil { + t.Fatalf("column %s lost its predicate: %s", column.Column, prepared) + } + if column.Contributor.CandidateID != "cand-obs-age-code" || column.Contributor.Operator != authoringv2.ContributorEquals { + t.Fatalf("column %s contributor=%#v", column.Column, column.Contributor) + } + if column.Contributor.Quantifier != authoringv2.ContributorAny { + t.Fatalf("repeated candidate requires the ANY quantifier: %#v", column.Contributor) + } + if column.Contributor.Value == nil || column.Contributor.Value.Kind != authoringv2.ContributorValueCode || + column.Contributor.Value.Code == nil || column.Contributor.Value.Code.Code != ObservationCodeAge { + t.Fatalf("column %s contributor value=%#v want CODE %s", column.Column, column.Contributor.Value, ObservationCodeAge) + } + if column.Source.Aggregate == nil || column.Source.Aggregate.Where != nil { + t.Fatalf("column %s aggregate still holds a where: %#v", column.Column, column.Source.Aggregate) + } + } + if columns[0].Contributor != nil { + t.Fatalf("the id column must not gain a contributor: %#v", columns[0].Contributor) + } +} + +func TestPreparePublicationWorkspacePassesThroughWithoutPredicates(t *testing.T) { + raw, err := BuildWorkspace(WorkloadGroupCountByPatient) + if err != nil { + t.Fatal(err) + } + prepared, err := PreparePublicationWorkspace(raw, "gen1", preparedTestCatalog()) + if err != nil { + t.Fatal(err) + } + if string(prepared) != string(raw) { + t.Fatalf("predicate-free workspace must publish byte-identical:\n%s\n%s", raw, prepared) + } +} + +func TestPreparePublicationWorkspaceRefusesUnresolvablePredicate(t *testing.T) { + raw, err := BuildWorkspace(WorkloadAggregateSumAgeByPatient) + if err != nil { + t.Fatal(err) + } + catalog := preparedTestCatalog() + catalog.Candidates = catalog.Candidates[1:] // drop the observation code candidate + if _, err := PreparePublicationWorkspace(raw, "gen1", catalog); err == nil || + !strings.Contains(err.Error(), "not present in the pinned catalog") { + t.Fatalf("an unresolvable predicate path must fail closed, got %v", err) + } +} + +func TestPreparePublicationWorkspaceRefusesWrongGeneration(t *testing.T) { + raw, err := BuildWorkspace(WorkloadAggregateSumAgeByPatient) + if err != nil { + t.Fatal(err) + } + if _, err := PreparePublicationWorkspace(raw, "gen2", preparedTestCatalog()); err == nil || + !strings.Contains(err.Error(), "does not match benchmark generation") { + t.Fatalf("a catalog from another generation must fail closed, got %v", err) + } +} + +func TestFetchContributorCatalogShape(t *testing.T) { + f := newFakeArm(BackendArango) + defer f.close() + arm := f.arm(BackendArango) + catalog, err := arm.FetchContributorCatalog(context.Background(), "Bearer t", "proj", "default") + if err != nil { + t.Fatal(err) + } + if catalog.SourceGeneration != "gen1" || !catalog.Complete || catalog.Truncated { + t.Fatalf("catalog scope: %+v", catalog) + } + if len(catalog.Candidates) != 2 || catalog.Candidates[0].ID != "cand-obs-age-code" { + t.Fatalf("candidates: %+v", catalog.Candidates) + } + if len(catalog.Nodes) != 4 { + t.Fatalf("nodes: %+v", catalog.Nodes) + } +} + +func TestFetchContributorCatalogFailsClosed(t *testing.T) { + cases := map[string]func(state map[string]any){ + "incomplete": func(s map[string]any) { s["catalog"].(map[string]any)["complete"] = false }, + "truncated": func(s map[string]any) { s["catalog"].(map[string]any)["truncated"] = true }, + "no-token": func(s map[string]any) { delete(s["catalog"].(map[string]any), "snapshotToken") }, + "no-digest": func(s map[string]any) { delete(s["catalog"].(map[string]any), "authorizationScopeDigest") }, + "no-generation": func(s map[string]any) { delete(s["catalog"].(map[string]any), "generation") }, + "catalog-omitted": func(s map[string]any) { delete(s, "catalog") }, + } + for name, mutate := range cases { + t.Run(name, func(t *testing.T) { + state := map[string]any{"draftVersion": 1, "catalog": contributorCatalogFixture()} + mutate(state) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _ = json.NewEncoder(w).Encode(state) + })) + defer server.Close() + arm := &Arm{Backend: BackendArango, BaseURL: server.URL, Client: server.Client()} + if _, err := arm.FetchContributorCatalog(context.Background(), "Bearer t", "proj", "default"); err == nil { + t.Fatalf("%s: an unusable catalog must fail closed", name) + } + }) + } +} + +// contributorSemanticsVersionForTest is the v4 contributor contract the +// migration must produce; pinned numerically so a silent constant bump in +// authoringv2 fails here loudly. +const contributorSemanticsVersionForTest = 4 diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index 711cec928..36f689183 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -1,6 +1,7 @@ package benchmark import ( + "bytes" "context" "errors" "fmt" @@ -8,6 +9,8 @@ import ( "sort" "strings" "time" + + "github.com/calypr/loom/internal/explorer/authoringv2" ) // The live driver: spawn/attach arms, load the deterministic dataset, author @@ -249,6 +252,18 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden receiptIDs[p.backend] = map[string]string{} outputIDs[p.backend] = map[string]string{} } + // The writable wire carries catalog-bound contributor intent, not + // aggregate.where (the strict body bind refuses the latter). Fetch each + // arm's live builder catalog once so predicate workspaces migrate + // against the same compiler facts the compiler will later bind. + catalogs := map[string]authoringv2.CatalogSnapshot{} + for _, p := range participants { + catalog, err := arms[p.backend].arm.FetchContributorCatalog(ctx, opts.UnrestrictedToken, opts.Params.Project, "default") + if err != nil { + return nil, nil, fmt.Errorf("benchmark: %s contributor catalog: %w", p.backend, err) + } + catalogs[p.backend] = catalog + } for _, spec := range Battery { if _, skip := excludedWorkloads[spec.ID]; skip { continue // disclosed absence: never authored, never timed @@ -260,9 +275,19 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden outputID := OutputIDFor(spec.ID) name := "bench-" + spec.ID var explorerID string + var publishedBytes []byte for _, p := range participants { + publicationBytes, err := PreparePublicationWorkspace(workspace, opts.Params.Generation, catalogs[p.backend]) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: prepare %s for %s: %w", spec.ID, p.backend, err) + } + if publishedBytes == nil { + publishedBytes = publicationBytes + } else if !bytes.Equal(publishedBytes, publicationBytes) { + return nil, nil, fmt.Errorf("benchmark: %s publishes divergent workspace bytes on %s; the contributor catalogs are not matched", spec.ID, p.backend) + } eid, rid, err := arms[p.backend].arm.AuthorWorkload(ctx, opts.UnrestrictedToken, opts.Params.Project, opts.Params.Generation, - workspace, opts.SourceCommit, name, outputID) + publicationBytes, opts.SourceCommit, name, outputID) if err != nil { return nil, nil, fmt.Errorf("benchmark: author %s on %s: %w", spec.ID, p.backend, err) } diff --git a/internal/dataframe/benchmark/driver_test.go b/internal/dataframe/benchmark/driver_test.go index dc02ff85e..c42dfc32d 100644 --- a/internal/dataframe/benchmark/driver_test.go +++ b/internal/dataframe/benchmark/driver_test.go @@ -2,6 +2,7 @@ package benchmark import ( "context" + "encoding/json" "strings" "testing" "time" @@ -119,6 +120,40 @@ func TestDriverBothArmsProtocol(t *testing.T) { if f.publish != live+1 { // + empty floor t.Fatalf("%s: publish=%d want %d", name, f.publish, live+1) } + // the writable wire is closed over aggregate predicates: every + // published workspace must bind exactly like the server's body bind + // does, predicate workspaces must carry contributor intent, and no + // body may smuggle the legacy where field + bodies := f.publishedBodies() + if len(bodies) != live+1 { + t.Fatalf("%s: published bodies=%d want %d", name, len(bodies), live+1) + } + var withContributor, withWhere int + for _, body := range bodies { + var workspace map[string]any + if err := json.Unmarshal(body, &workspace); err != nil { + t.Fatalf("%s: published workspace does not bind: %v", name, err) + } + if strings.Contains(string(body), `"contributor"`) { + withContributor++ + } + if strings.Contains(string(body), `"where"`) { + withWhere++ + } + } + if withWhere != 0 { + t.Fatalf("%s: a published workspace still carries the legacy where wire", name) + } + if withContributor == 0 { + t.Fatalf("%s: no published workspace carries contributor intent", name) + } + } + // both arms must publish byte-identical workloads (matched arms) + ta, tp := fa.publishedBodies(), fp.publishedBodies() + for i := range ta { + if string(ta[i]) != string(tp[i]) { + t.Fatalf("arm %d published different bytes on the two drivers:\n%s\n%s", i, ta[i], tp[i]) + } } // round-trip the artifact if _, err := LoadReport(mustMarshal(t, report)); err != nil { diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index d33ff6c75..8e3e56bee 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "net/http/httptest" "os" @@ -27,12 +28,23 @@ type fakeArm struct { failWith map[string]int // output -> status uploads int publish int + published [][]byte // explorer-config bodies in publish order noSnapshot bool scopedToken string // when the bearer equals this, deny like production: zero rows explorerID string authoring []string // ordered create/builder/reconcile calls } +func (f *fakeArm) publishedBodies() [][]byte { + f.mu.Lock() + defer f.mu.Unlock() + out := make([][]byte, 0, len(f.published)) + for _, body := range f.published { + out = append(out, append([]byte(nil), body...)) + } + return out +} + func (f *fakeArm) authoringCalls() []string { f.mu.Lock() defer f.mu.Unlock() @@ -70,15 +82,17 @@ func newFakeArm(backend string) *fakeArm { noSnap := f.noSnapshot f.mu.Unlock() state := map[string]any{"draftVersion": 1, "draftDigest": "dig-1", - "catalog": map[string]any{"snapshotToken": "snap-1"}} + "catalog": contributorCatalogFixture()} if noSnap { state = map[string]any{"draftVersion": 1} } _ = json.NewEncoder(w).Encode(state) return case strings.Contains(path, "/explorer-config"): + body, _ := io.ReadAll(r.Body) f.mu.Lock() f.publish++ + f.published = append(f.published, body) f.mu.Unlock() _ = json.NewEncoder(w).Encode(map[string]any{"recipe": "bench", "translationVersion": "t1", "output": "out"}) return @@ -134,6 +148,31 @@ func (f *fakeArm) arm(backend string) *Arm { return &Arm{Backend: backend, BaseURL: f.server.URL, Client: f.server.Client()} } +// contributorCatalogFixture is the builder catalog the fake serves: exactly +// the candidates the battery's aggregate predicates resolve against, plus +// the occurrence nodes the migration looks up by resource type. +func contributorCatalogFixture() map[string]any { + return map[string]any{ + "snapshotToken": "snap-1", "generation": "gen1", + "authorizationScopeDigest": "scope-digest-1", + "complete": true, "truncated": false, + "nodes": []map[string]any{ + {"nodeId": "patient", "resourceType": "Patient"}, + {"nodeId": "observation", "resourceType": "Observation"}, + {"nodeId": "specimen", "resourceType": "Specimen"}, + {"nodeId": "condition", "resourceType": "Condition"}, + }, + "candidates": []map[string]any{ + {"candidateId": "cand-obs-age-code", "nodeId": "observation", "fieldPath": "code.coding[].code", + "logicalType": "code", "repeated": true, + "repeatedBoundaries": []map[string]any{{"path": "code.coding[]", "maxItems": 20}}}, + {"candidateId": "cand-spec-type-code", "nodeId": "specimen", "fieldPath": "type.coding[].code", + "logicalType": "code", "repeated": true, + "repeatedBoundaries": []map[string]any{{"path": "type.coding[]", "maxItems": 20}}}, + }, + } +} + func (f *fakeArm) close() { f.server.Close() } func TestUploadAndPublishUsePublicRoutes(t *testing.T) { diff --git a/internal/dataframe/benchmark/scratch_authrepro_test.go b/internal/dataframe/benchmark/scratch_authrepro_test.go deleted file mode 100644 index b9ee83a4b..000000000 --- a/internal/dataframe/benchmark/scratch_authrepro_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package benchmark_test - -import ( - "context" - "net/http" - "testing" - "time" - - "github.com/calypr/loom/internal/authscope" - "github.com/calypr/loom/internal/catalog" - "github.com/calypr/loom/internal/dataframe/benchmark" -) - -func newScratchResolver(t *testing.T, fenceURL string) *authscope.ScopeResolver { - t.Helper() - client := &http.Client{Timeout: 10 * time.Second} - ra, err := authscope.NewFenceUserAccessClientWithTrustedIssuer(client, fenceURL, time.Second) - if err != nil { - t.Fatalf("fence client: %v", err) - } - return authscope.NewScopeResolver(authscope.ScopeResolverConfig{ - ResourceAccess: ra, - ListExistingAuthResourcePaths: func(context.Context, catalog.AuthResourcePathOptions) ([]string, error) { - return nil, nil - }, - CacheTTL: time.Second, - }) -} - -func TestScratchAuthorizeWriteWithBearerPrincipal(t *testing.T) { - fence, err := benchmark.NewMockFence() - if err != nil { - t.Fatalf("fence: %v", err) - } - defer fence.Close() - const path = "/programs/loom/projects/loom-bench/benchmark" - fence.Grant("bench-admin", path) - resolver := newScratchResolver(t, fence.URL()) - - // Variant A: header-carrying principal, deployer double-prefix shape. - token := "Bearer " + benchmark.MakeBearerToken(fence.URL(), "bench-admin") - principal := &authscope.Principal{AuthorizationHeader: token, Subject: "bench-admin"} - if err := resolver.AuthorizeWrite(context.Background(), principal, "loom-bench", path); err != nil { - t.Fatalf("variant A AuthorizeWrite: %v (fence requests=%d)", err, fence.Requests()) - } - t.Logf("variant A passed; fence requests=%d", fence.Requests()) - - // Variant B: nil principal — expect the rehearsal's shape. - errB := resolver.AuthorizeWrite(context.Background(), nil, "loom-bench", path) - if errB == nil { - t.Fatalf("variant B unexpectedly passed") - } - t.Logf("variant B refused as expected: %v", errB) - - // Variant C: principal with no AuthorizationHeader (locals/ctx drop). - errC := resolver.AuthorizeWrite(context.Background(), &authscope.Principal{Subject: "bench-admin"}, "loom-bench", path) - if errC == nil { - t.Fatalf("variant C unexpectedly passed") - } - t.Logf("variant C refused as expected: %v", errC) -} diff --git a/internal/dataframe/benchmark/scratch_liveupload_test.go b/internal/dataframe/benchmark/scratch_liveupload_test.go deleted file mode 100644 index 4536229d0..000000000 --- a/internal/dataframe/benchmark/scratch_liveupload_test.go +++ /dev/null @@ -1,181 +0,0 @@ -package benchmark_test - -import ( - "context" - "fmt" - "net/http" - "os" - "path/filepath" - "strings" - "testing" - "time" - - backendspostgres "github.com/calypr/loom/internal/backends/postgres/control" - "github.com/calypr/loom/internal/catalog" - "github.com/calypr/loom/internal/dataframe/benchmark" -) - -// TestScratchLiveUploadProbe is the live proof for defect -// PG-PG-AUTH-PATH-DISCOVERY-ALLGENS (see the session artifact -// pg-pg-auth-path-discovery-allgens-evidence.md). It spawns the real -// server binary against a scratch benchmark database, uploads through the -// same Arm.UploadGeneration path benchcli uses, and calls the store-level -// AllGenerations listing directly. -// -// It is opt-in by substrate: it skips unless the scratch DSN file exists -// (default /tmp/opencode/.bench-dsn, override -// LOOM_BENCH_SCRATCH_DSN_FILE) and spawns the binary at -// LOOM_BENCH_SCRATCH_BINARY, default /tmp/opencode/loom-server. -// -// Two-world consistency is the pin: the defect linked the two surfaces — -// the store-level AllGenerations refusal made AuthorizeWrite 403 every -// scoped upload whose principal held the matching grant, and the -// bootstrap branch was unreachable. So the two surfaces must agree: -// defect present = upload refused AND listing refused; fix present = -// upload accepted AND listing answers. Mixed outcomes mean the server -// binary and this library disagree about the fix (substrate drift). - -func TestScratchLiveUploadProbe(t *testing.T) { - dsnFile := os.Getenv("LOOM_BENCH_SCRATCH_DSN_FILE") - if dsnFile == "" { - dsnFile = "/tmp/opencode/.bench-dsn" - } - dsnRaw, err := os.ReadFile(dsnFile) - if err != nil { - t.Skipf("scratch substrate absent (%s): %v", dsnFile, err) - } - dsn := strings.TrimSpace(string(dsnRaw)) - binary := os.Getenv("LOOM_BENCH_SCRATCH_BINARY") - if binary == "" { - binary = "/tmp/opencode/loom-server" - } - const path = "/programs/loom/projects/loom-bench/benchmark" - - fence, err := benchmark.NewMockFence() - if err != nil { - t.Fatalf("fence: %v", err) - } - defer fence.Close() - fence.Grant("bench-admin", path, "/programs/loom/projects/loom-bench/decoy") - token := "Bearer " + benchmark.MakeBearerToken(fence.URL(), "bench-admin") - - host, port, database, user, password, sslmode, err := splitDSNForScratch(dsn) - if err != nil { - t.Fatalf("dsn: %v", err) - } - portNum := 0 - fmt.Sscanf(port, "%d", &portNum) - backendYAML := benchmark.PostgresBackendYAML(host, portNum, database, user, password, sslmode, "loom_bench") - cfgYAML := benchmark.BuildServerConfigYAML(benchmark.ListenPlaceholder, "postgres", backendYAML, fence.URL(), "040f587511a7e4de") - - freePort, err := benchmark.FreePort() - if err != nil { - t.Fatalf("free port: %v", err) - } - listen := fmt.Sprintf("127.0.0.1:%d", freePort) - cfgYAML = strings.Replace(cfgYAML, benchmark.ListenPlaceholder, listen, 1) - - ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) - defer cancel() - stderrDir := os.Getenv("LOOM_BENCH_SCRATCH_STDERR_DIR") - if stderrDir == "" { - stderrDir = t.TempDir() - } - // The server resolves its graph schema relative to the CWD, and go test - // runs in the package directory, so pin the repository root explicitly. - workDir := os.Getenv("LOOM_BENCH_SCRATCH_WORKDIR") - if workDir == "" { - dir, err := os.Getwd() - if err != nil { - t.Fatalf("workdir: %v", err) - } - for { - if _, err := os.Stat(dir + "/schemas/graph-fhir.json"); err == nil { - workDir = dir - break - } - parent := filepath.Dir(dir) - if parent == dir { - t.Fatal("no schemas/graph-fhir.json found walking up from cwd; set LOOM_BENCH_SCRATCH_WORKDIR") - } - dir = parent - } - } - spec := benchmark.ServerSpec{ - BinaryPath: binary, - Backend: "postgres", - ConfigYAML: cfgYAML, - ReadyURL: "http://" + listen + "/readyz", - Timeout: 60 * time.Second, - StderrDir: stderrDir, - WorkDir: workDir, - } - proc, err := benchmark.SpawnServer(ctx, spec, nil, listen) - if err != nil { - t.Fatalf("spawn: %v", err) - } - defer proc.Stop() - - arm := &benchmark.Arm{Backend: "postgres", BaseURL: "http://" + listen, Client: &http.Client{Timeout: 60 * time.Second}} - fixtures, fixtureErr := benchmark.GenerateFiles(benchmark.DatasetParams{Preset: benchmark.PresetTiny, Seed: 1}) - if fixtureErr != nil { - t.Fatalf("fixtures: %v", fixtureErr) - } - uploadErr := arm.UploadGeneration(ctx, token, "loom-bench", "bench-probe-1", fixtures.Files, path) - t.Logf("upload outcome: %v (fence requests=%d)", uploadErr, fence.Requests()) - - // Store-level surface on the same database. - probeRuntime, err := backendspostgres.Open(ctx, backendspostgres.Config{ - DSN: dsn, MaxConns: 2, MinConns: 1, AcquireTimeout: 30 * time.Second, - Schemas: backendspostgres.SeriesSchemas{ - "source": "loom_bench_source", "explorer_control": "loom_bench_explorer_control", - "publication_control": "loom_bench_publication_control", "dataset_release": "loom_bench_dataset_release", - "recipe_chains": "loom_bench_recipe_chains", "recipe_exec_registries": "loom_bench_recipe_exec_registries", - }, - Setup: backendspostgres.SetupNone, - }) - if err != nil { - t.Fatalf("open runtime: %v", err) - } - defer func() { _ = probeRuntime.Close() }() - paths, listErr := probeRuntime.DiscoverExistingAuthResourcePaths(ctx, catalog.AuthResourcePathOptions{Project: "loom-bench", AllGenerations: true}) - t.Logf("AllGenerations listing outcome: %v (paths=%v)", listErr, paths) - - if (uploadErr == nil) != (listErr == nil) { - t.Errorf("surface drift: uploadErr=%v listErr=%v — the server binary and this library must agree on the fix state of the AllGenerations read (substrate pin: defect-era binary with defect-era library, or fix with fix)", uploadErr, listErr) - } - if uploadErr != nil && !strings.Contains(uploadErr.Error(), "403") { - t.Errorf("defect-world upload should be the scoped-lane 403, got: %v", uploadErr) - } -} - -func splitDSNForScratch(dsn string) (host, port, database, user, password, sslmode string, err error) { - // postgresql://user:pass@host:port/db?sslmode=disable - if !strings.HasPrefix(dsn, "postgresql://") { - return "", "", "", "", "", "", fmt.Errorf("unexpected dsn scheme") - } - rest := strings.TrimPrefix(dsn, "postgresql://") - at := strings.LastIndex(rest, "@") - if at < 0 { - return "", "", "", "", "", "", fmt.Errorf("no userinfo") - } - userinfo, rest := rest[:at], rest[at+1:] - if c := strings.Index(userinfo, ":"); c >= 0 { - user, password = userinfo[:c], userinfo[c+1:] - } else { - user = userinfo - } - var query, hostport string - hostport, query, _ = strings.Cut(rest, "?") - hostport, database, _ = strings.Cut(hostport, "/") - host, port, _ = strings.Cut(hostport, ":") - for _, kv := range strings.Split(query, "&") { - if k, v, ok := strings.Cut(kv, "="); ok && k == "sslmode" { - sslmode = v - } - } - if host == "" || port == "" || database == "" { - return "", "", "", "", "", "", fmt.Errorf("incomplete dsn") - } - return host, port, database, user, password, sslmode, nil -} From 84fb820920ba999791eea6fef0f6f2477e0ee840 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 06:13:09 +0000 Subject: [PATCH 32/45] fix(benchmark): address the project by its fence legacy key so scoped reads pass The first scoped GET .../authoring/v2/builder in CI returned 403 (stage authorization). compose.go wires the explorer read gate to authscope.AuthorizeReadProject, which admits a caller only when a fence grant normalizes to projectid.Legacy(project). The harness project was "loom-bench" while its fence resources lived under /programs/loom/projects/loom-bench/, whose calypr key is "loom-loom-bench" - no grant could ever equal the project key. The write gate hid the drift because it compares caller paths against the dataset path itself, so upload and publish passed while every scoped authoring read was doomed. The defaults now model program loom / project bench, addressed by the legacy key loom-bench - the shape the repo's own authscope contract tests pin. Dataset and decoy paths stay raw-distinct, so the scoped negative-auth control still denies by row-path equality, and the write gate keeps passing with both sides normalizing to the project key. TestDefaultFenceGrantsSatisfyTheProjectReadGate pins the whole default set against the gate, including the foreign-subject refusal. --- .../benchmark/cmd/benchcli/deployer.go | 6 ++- .../benchmark/cmd/benchcli/guards_test.go | 45 +++++++++++++++++++ .../dataframe/benchmark/cmd/benchcli/main.go | 16 ++++++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/internal/dataframe/benchmark/cmd/benchcli/deployer.go b/internal/dataframe/benchmark/cmd/benchcli/deployer.go index bdbdec417..2c629dfe3 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/deployer.go +++ b/internal/dataframe/benchmark/cmd/benchcli/deployer.go @@ -26,7 +26,11 @@ type deployer struct { const ( benchmarkAdminSubject = "bench-admin" benchmarkScopedSubject = "bench-scoped" - benchmarkDecoyPath = "/programs/loom/projects/loom-bench/decoy" + // Same fence project as benchmarkDefaultAuthResourcePath: both admin + // grants normalize (authscope calypr-key rule) to the legacy key + // "loom-bench" that AuthorizeReadProject demands for the benchmark + // project id. See the comment on benchmarkDefaultAuthResourcePath. + benchmarkDecoyPath = "/programs/loom/projects/bench/decoy" ) func (d *deployer) prepare(ctx context.Context) error { diff --git a/internal/dataframe/benchmark/cmd/benchcli/guards_test.go b/internal/dataframe/benchmark/cmd/benchcli/guards_test.go index 2892f912d..b75d88c57 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/guards_test.go +++ b/internal/dataframe/benchmark/cmd/benchcli/guards_test.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "errors" "net/http" "os" "path/filepath" @@ -10,7 +11,9 @@ import ( "testing" "time" + "github.com/calypr/loom/internal/authscope" "github.com/calypr/loom/internal/dataframe/benchmark" + "github.com/calypr/loom/internal/projectid" ) // The benchcli guards are fail-closed surfaces: they keep a benchmark run @@ -372,3 +375,45 @@ func TestClassifyLiveExit(t *testing.T) { }) } } + +// grantResourceAccess is a fence stub for the project-read contract. +type grantResourceAccess struct{ resources []string } + +func (g grantResourceAccess) GetAllowedResources(context.Context, string, string, string) ([]string, error) { + return append([]string(nil), g.resources...), nil +} + +// The paired CI lane faulted 403 FORBIDDEN (stage authorization) on the +// first-ever scoped GET .../authoring/v2/builder: the harness project was +// "loom-bench" while the fence grants lived under +// /programs/loom/projects/loom-bench/..., whose calypr key is +// "loom-loom-bench". AuthorizeReadProject (compose.go -> +// getAuthoringBuilderDirect) admits a caller only when a grant normalizes +// to projectid.Legacy(project) — the write gate hides this because it +// compares grants against the dataset path itself. This pins the whole +// harness default set against that gate so the shapes can never drift +// apart again. +func TestDefaultFenceGrantsSatisfyTheProjectReadGate(t *testing.T) { + key := projectid.Legacy(benchmarkDefaultProject) + for name, path := range map[string]string{ + "dataset": benchmarkDefaultAuthResourcePath, + "decoy": benchmarkDecoyPath, + } { + if got := authscope.NormalizeAuthResourcePath(path); got != key { + t.Fatalf("%s path %q normalizes to %q, want the legacy project key %q", name, path, got, key) + } + } + resolver := authscope.NewScopeResolver(authscope.ScopeResolverConfig{ + ResourceAccess: grantResourceAccess{resources: []string{benchmarkDefaultAuthResourcePath, benchmarkDecoyPath}}, + }) + principal := &authscope.Principal{AuthorizationHeader: "Bearer bench-admin"} + if err := resolver.AuthorizeReadProject(context.Background(), principal, benchmarkDefaultProject); err != nil { + t.Fatalf("the default admin grants must satisfy the project read gate: %v", err) + } + foreign := authscope.NewScopeResolver(authscope.ScopeResolverConfig{ + ResourceAccess: grantResourceAccess{resources: []string{"/programs/other/projects/unrelated/benchmark"}}, + }) + if err := foreign.AuthorizeReadProject(context.Background(), principal, benchmarkDefaultProject); !errors.Is(err, authscope.ErrForbidden) { + t.Fatalf("a subject without the project grant must stay forbidden, got %v", err) + } +} diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go index 8e42715d2..0596d2044 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/main.go +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -99,15 +99,27 @@ const ( defaultPostgresCapability = "blocked by PG-PG-AUTH-PATH-DISCOVERY-ALLGENS: the scoped calypr lane cannot ingest datasets on postgres; the catalog read lane refuses the all-generations auth-path listing that AuthorizeWrite's project binding requires (see pg-pg-auth-path-discovery-allgens-evidence.md)" ) +// The benchmark project is addressed by its legacy key: program "loom", +// project "bench" -> "loom-bench". This is not cosmetic — the scoped +// authoring read gate (authscope.AuthorizeReadProject) admits a caller only +// when a fence grant normalizes to projectid.Legacy(project), so a project +// id that does not decompose to the fence's program/project yields a hard +// 403 on GET .../authoring/v2/builder while writes still pass (the write +// gate compares caller paths against the dataset path itself). +const ( + benchmarkDefaultProject = "loom-bench" + benchmarkDefaultAuthResourcePath = "/programs/loom/projects/bench/benchmark" +) + func runLive() { fs := flag.NewFlagSet("run", flag.ExitOnError) var f runFlags fs.StringVar(&f.scope, "scope", benchmark.RunScopePostgresOnly, "both-arms | postgres-only | planned") fs.StringVar(&f.preset, "preset", benchmark.PresetTiny, "dataset preset: tiny | small | representative") fs.Uint64Var(&f.seed, "seed", 7, "deterministic dataset seed") - fs.StringVar(&f.project, "project", "loom-bench", "benchmark project namespace") + fs.StringVar(&f.project, "project", benchmarkDefaultProject, "benchmark project namespace") fs.StringVar(&f.generation, "generation", "", "generation name (default: run id)") - fs.StringVar(&f.authResourcePath, "auth-resource-path", "/programs/loom/projects/loom-bench/benchmark", "dataset auth scope stamped by the loader") + fs.StringVar(&f.authResourcePath, "auth-resource-path", benchmarkDefaultAuthResourcePath, "dataset auth scope stamped by the loader") fs.IntVar(&f.warmup, "warmup", 1, "warmup iterations per workload x backend") fs.IntVar(&f.iterations, "iterations", 8, "measured iterations per workload x backend") fs.IntVar(&f.rounds, "rounds", 2, "interleaved rounds") From 3e2172a76be65532a8ddf31ae615355a0091dd2d Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 06:13:48 +0000 Subject: [PATCH 33/45] docs(benchmark): current postgres planned-leg capability The planned-leg default still asserted in the present tense that the scoped calypr lane cannot ingest on postgres (PG-PG-AUTH-PATH-DISCOVERY- ALLGENS). #156 fixed that finding and the paired lane has since uploaded on both drivers; a stale present-tense reason in a not_run disclosure is exactly the misreport class the report verifier exists to refuse. The capability now names the actual reason a planned postgres leg happens: no service/DSN in the environment. --- internal/dataframe/benchmark/cmd/benchcli/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go index 0596d2044..1e9b68625 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/main.go +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -96,7 +96,7 @@ type runFlags struct { // live leg was impossible. const ( defaultArangoCapability = "no ArangoDB service in this execution environment; go-arango CI lane targets this leg" - defaultPostgresCapability = "blocked by PG-PG-AUTH-PATH-DISCOVERY-ALLGENS: the scoped calypr lane cannot ingest datasets on postgres; the catalog read lane refuses the all-generations auth-path listing that AuthorizeWrite's project binding requires (see pg-pg-auth-path-discovery-allgens-evidence.md)" + defaultPostgresCapability = "no PostgreSQL service or --postgres-dsn in this execution environment; the backend itself is ingest-ready since PR #156 (benchcli setup-pg provisions the loom_bench_* six-series schema ladder)" ) // The benchmark project is addressed by its legacy key: program "loom", From 76054ef4d24073ab0a74ca8a3c81016d1fbb6e9c Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 07:20:54 +0000 Subject: [PATCH 34/45] fix(benchmark): run the paired lane through the real publication chain The local postgres rehearsal disproved the chain-less-battery premise: PublishRepository always materializes (repository_publish.go requires MaterializeReceipt; publication.Target has exactly one production implementation, ClickHouseBundleStore), so the first explorer-config publish answers 422 MATERIALIZATION_FAILED without ClickHouse. There is no battery without the chain: the timed mode is removed, and --publication is excluded | run. - driver: bootstrap the default explorer by publishing the empty-floor workspace before reading the builder catalog (the default exists only after the first publish), then fetch each arm's contributor catalog. - harness: Publication binds the real explorer-config response fields (receiptId, revisionId, recipe, translationVersion); output identity belongs to the caller that authored the single-output workspace. - benchcli: --clickhouse-url/-user/-password/-recipe spawn flags; each arm materializes into its own loom_bench_* database on the shared service; run mode preflights url+recipe+enabled. - scripts: paired classification now requires plan.publication.run and identity.clickhouseDisabled=false; comparison.md states the chain ran. - CI: the paired job adds clickhouse/clickhouse-server:25.3 and invokes --publication=run through it. Validation: go test ./internal/dataframe/benchmark/... and ./internal/dataframe/... green (run-mode classification, config YAML both variants, bootstrap-first publish ordering pinned in driver_test); node scripts/benchmark-backends.test.mjs 23/23; --harness-selftest pass. The full chain (publish with materialization) has no container engine in this worker, so it is adjudicated by the paired CI job itself, which now provisions the service. --- .github/workflows/tests.yaml | 40 ++++++++--- internal/dataframe/benchmark/armplan.go | 63 +++++++---------- .../benchmark/cmd/benchcli/deployer.go | 31 ++++++++- .../dataframe/benchmark/cmd/benchcli/main.go | 34 ++++++---- internal/dataframe/benchmark/contributors.go | 11 +-- internal/dataframe/benchmark/driver.go | 37 +++++----- internal/dataframe/benchmark/driver_test.go | 16 +++-- internal/dataframe/benchmark/harness.go | 17 +++-- .../dataframe/benchmark/harness_scope_test.go | 2 +- internal/dataframe/benchmark/harness_test.go | 22 +++++- internal/dataframe/benchmark/provision.go | 35 ++++++++-- internal/dataframe/benchmark/report.go | 11 ++- internal/dataframe/benchmark/report_test.go | 67 +++++++------------ scripts/benchmark-backends.mjs | 44 +++++++++--- scripts/benchmark-backends.test.mjs | 40 +++++++---- 15 files changed, 299 insertions(+), 171 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f59394a3b..9e0f60cf5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -429,13 +429,17 @@ jobs: # Operator acceptance (2026-11-13): the actual speed comparison between # the two real drivers. Both arms run the SAME matched battery against # actual ArangoDB and PostgreSQL services -- the same images, ports, - # and health options the backend-conformance lane adjudicates -- with - # clickhouse.enabled=false, which is honest for the dataframe legs: - # the timing battery never consumes the ClickHouse-backed publication - # chain (that chain stays disclosed not_run in the timed lane). The - # gate fails closed if either arm is planned/not_run/excluded or - # produces no real timing samples; a disclosure artifact can never - # turn this check green. + # and health options the backend-conformance lane adjudicates -- plus + # the actual ClickHouse service: authoring publishes through the + # ClickHouse-backed materialization chain (a ClickHouse-less + # deployment cannot author at all -- the 422 MATERIALIZATION_FAILED + # the local rehearsal produced is the proof), so this lane runs the + # full chain with --publication=run. Each arm materializes into its + # own loom_bench_* database; the timed path itself is the + # source-row preview route, identical on both arms. + # The gate fails closed if either arm is planned/not_run/excluded, if + # the chain did not run, or if either driver produced no real timing + # samples; a disclosure artifact can never turn this check green. timeout-minutes: 10 services: arangodb: @@ -461,6 +465,16 @@ jobs: --health-interval 3s --health-timeout 5s --health-retries 40 + clickhouse: + image: clickhouse/clickhouse-server:25.3 + ports: + - 9000:9000 + - 8123:8123 + options: >- + --health-cmd "wget --no-verbose --tries=1 --spider http://127.0.0.1:8123/ping || exit 1" + --health-interval 5s + --health-timeout 3s + --health-retries 20 env: LOOM_TEST_ARANGO_URL: http://127.0.0.1:8529 LOOM_TEST_POSTGRES_URL: postgresql://loom_bench:loom_ci_test_only@127.0.0.1:5432/loom_bench_paired?sslmode=disable @@ -484,10 +498,13 @@ jobs: go build -o "$RUNNER_TEMP/paired-bin/benchcli" ./internal/dataframe/benchmark/cmd/benchcli - name: Run the paired benchmark against both services # Five alternating measured samples per driver, one warmup, tiny - # matched fixture. The script verifies the artifact through the Go - # verifier, classifies realness (both arms ran, both timed), and - # writes comparison.md; it exits nonzero on any shortfall, so the - # job goes red rather than reporting scaffolding as a comparison. + # matched fixture, full publication chain through the ClickHouse + # service (each arm materializes into its own loom_bench_* + # database). The script verifies the artifact through the Go + # verifier, classifies realness (both arms ran, the chain ran, + # both timed), and writes comparison.md; it exits nonzero on any + # shortfall, so the job goes red rather than reporting + # scaffolding as a comparison. run: >- node scripts/benchmark-backends.mjs --paired --binary "$RUNNER_TEMP/paired-bin/loom-server" @@ -497,6 +514,7 @@ jobs: --warmup 1 --rounds 5 --source-commit "$GITHUB_SHA" + --clickhouse-url clickhouse://127.0.0.1:9000 --window-note "granted by the paired-driver-benchmark CI job (isolated services, $GITHUB_RUN_ID)" --out "$RUNNER_TEMP/paired-benchmark" - name: Show the comparison table in the job log diff --git a/internal/dataframe/benchmark/armplan.go b/internal/dataframe/benchmark/armplan.go index 325f6b007..dce2ff4e3 100644 --- a/internal/dataframe/benchmark/armplan.go +++ b/internal/dataframe/benchmark/armplan.go @@ -54,59 +54,47 @@ type PlannedNotRun struct { // harness, the named capability, and the CI lane describing one substrate. const ClickHouseCIVersion = "25.3" -// ClickHouseCIImage is the container image the CI lane runs the +// ClickHouseCIImage is the container image the CI lanes run the // publication-dependent stages against. No container engine exists in the -// worker container, so these stages are planned/not_run there (coordinator -// ruling 2026-11-13: no ClickHouse chasing); the CI lane provisions the -// service and runs them for real. +// worker container, so publication runs are planned/not_run there +// (coordinator ruling 2026-11-13: no ClickHouse chasing); the paired CI +// lane provisions the service and runs the whole chain for real. const ClickHouseCIImage = "clickhouse/clickhouse-server:" + ClickHouseCIVersion // PublicationCapabilityCH names the missing capability when the // publication chain cannot execute because ClickHouse is not deployed. // Publication is structurally ClickHouse-backed: publication.Target has -// exactly one production implementation (ClickHouseBundleStore). -// The dataset→bundle→ClickHouse CHAIN needs the service; the authoring -// battery (explorer publish, builder, reconcile, preview, timing, gates, -// controls) does not — the backend-conformance lane runs it on both real -// drivers with clickhouse.enabled=false, and the PostgreSQL backend -// forbids clickhouse.enabled in this slice outright. +// exactly one production implementation (ClickHouseBundleStore), and the +// repository publish that seeds every Explorer draft materializes through +// it. The 2026-11-13 local rehearsal disproved the earlier premise that +// the authoring battery could run with clickhouse.enabled=false: the +// first explorer-config publish answers 422 MATERIALIZATION_FAILED +// without a target, so there is no chain-less battery — a run either +// deploys ClickHouse (mode run) or discloses everything downstream of +// upload as not_run (mode excluded). const PublicationCapabilityCH = "CH-" + ClickHouseCIVersion + "-unavailable-in-container" -// PublicationCapabilityTimed names why the chain stays not_run on the -// paired timing lane: the dataframe legs run both drivers with -// clickhouse.enabled=false (the matched reader path ruling; the chain -// belongs to the ClickHouse CI lane), and the timing battery provably -// does not consume the chain. -const PublicationCapabilityTimed = "CH-not-deployed-for-timed-dataframe-legs" - // PublicationPlan declares whether the publication chain ran. It is a // plan-level property so VerifyReport, not the driver's self-description, // decides whether a report may carry timing evidence. type PublicationPlan struct { - // Run is true when publication executed (CI lane with ClickHouse). + // Run is true when publication executed (CI lane with ClickHouse); + // the timing battery only ever runs alongside it, because authoring + // publishes through the chain. Run bool `json:"run"` // Capability names why the chain did not run when Run is false; // PublicationCapabilityCH is the canonical value for a ClickHouse-less - // deployment. + // deployment. A run-mode plan must leave it empty. Capability string `json:"capability,omitempty"` - // Battery is the timed mode: the chain stays not_run (Run false) but - // the timing battery — authoring, preview, timing, gates, controls — - // executes for real. Only the chain itself ships disclosed not_run. - // Run and Battery together is contradictory and refused. - Battery bool `json:"battery,omitempty"` -} - -// PublicationTimed reports the timed mode: chain excluded, battery run. -func (p RunPlan) PublicationTimed() bool { - return p.Publication != nil && !p.Publication.Run && p.Publication.Battery } -// PublicationZeroEvidence reports the pure excluded mode: chain excluded -// AND the battery excluded, so the run measured nothing and a report -// carrying samples, summaries, claims, control values, or non-blocked -// arms would be forged. This is what VerifyReport pins. +// PublicationZeroEvidence reports the excluded mode: the chain and +// everything downstream of upload were excluded, so the run measured +// nothing and a report carrying samples, summaries, claims, control +// values, or non-blocked arms would be forged. This is what VerifyReport +// pins. func (p RunPlan) PublicationZeroEvidence() bool { - return p.PublicationExcluded() && !p.Publication.Battery + return p.PublicationExcluded() } // ApplyExcludedWorkloadGateReports replaces any gate report for an @@ -274,11 +262,12 @@ func PublicationGateReports(legs []LegPlan, capability string) []WorkloadGateRep } // ValidatePublicationPlan checks the publication declaration is honest in -// shape: an excluded chain must name its missing capability. +// shape: an excluded chain must name its missing capability, and a +// run-mode chain must not carry one (a capability names what did not run). func ValidatePublicationPlan(plan RunPlan) error { if plan.Publication == nil || plan.Publication.Run { - if plan.Publication != nil && plan.Publication.Run && plan.Publication.Battery { - return fmt.Errorf("benchmark: publication plan declares run and battery together; battery names the timed mode where the chain did not run") + if plan.Publication != nil && plan.Publication.Run && strings.TrimSpace(plan.Publication.Capability) != "" { + return fmt.Errorf("benchmark: publication plan claims the chain ran while naming a missing capability %q", plan.Publication.Capability) } return nil } diff --git a/internal/dataframe/benchmark/cmd/benchcli/deployer.go b/internal/dataframe/benchmark/cmd/benchcli/deployer.go index 2c629dfe3..f9d466361 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/deployer.go +++ b/internal/dataframe/benchmark/cmd/benchcli/deployer.go @@ -112,13 +112,40 @@ func (d *deployer) prepare(ctx context.Context) error { } *l.arm = benchmark.ArmConfig{ BinaryPath: f.binary, - ConfigYAML: benchmark.BuildServerConfigYAML(benchmark.ListenPlaceholder, l.backend, backendYAML, fence.URL(), cursorSecret), - ScrubSecrets: []string{l.dsn, cursorSecret}, + ConfigYAML: benchmark.BuildServerConfigYAML(benchmark.ListenPlaceholder, l.backend, backendYAML, fence.URL(), cursorSecret, clickHouseSpecFor(f, l.backend)), + ScrubSecrets: scrubSecretsFor(f, l.dsn, cursorSecret), } } return nil } +// clickHouseSpecFor returns the arm's publication-chain config, or nil +// when the arms run without ClickHouse (excluded mode). The database is +// benchmark-owned and arm-specific: both arms share the ClickHouse +// server, so sharing a database would let one driver's published rows +// answer the other's reads. +func clickHouseSpecFor(f *runFlags, backend string) *benchmark.ClickHouseSpec { + if !f.clickHouseEnabled { + return nil + } + database := "loom_bench_" + backend + return &benchmark.ClickHouseSpec{ + URL: f.clickHouseURL, + Database: database, + Username: f.clickHouseUser, + Password: f.clickHousePassword, + Recipe: f.clickHouseRecipe, + } +} + +func scrubSecretsFor(f *runFlags, dsn, cursorSecret string) []string { + secrets := []string{dsn, cursorSecret} + if f.clickHousePassword != "" { + secrets = append(secrets, f.clickHousePassword) + } + return secrets +} + func (d *deployer) close() { if d.fence != nil { d.fence.Close() diff --git a/internal/dataframe/benchmark/cmd/benchcli/main.go b/internal/dataframe/benchmark/cmd/benchcli/main.go index 1e9b68625..2f69d06dc 100644 --- a/internal/dataframe/benchmark/cmd/benchcli/main.go +++ b/internal/dataframe/benchmark/cmd/benchcli/main.go @@ -89,6 +89,10 @@ type runFlags struct { postgresCapability string publication string clickHouseEnabled bool + clickHouseURL string + clickHouseUser string + clickHousePassword string + clickHouseRecipe string } // Default capabilities cited by planned not_run legs. The postgres one @@ -140,8 +144,12 @@ func runLive() { fs.IntVar(&f.emptyFloorIters, "empty-floor-iterations", 5, "iterations for the empty-result floor control") fs.StringVar(&f.arangoCapability, "arango-capability", defaultArangoCapability, "capability cited by the arango not_run legs (planned scope)") fs.StringVar(&f.postgresCapability, "postgres-capability", defaultPostgresCapability, "capability cited by the postgres not_run legs (planned scope)") - fs.StringVar(&f.publication, "publication", "excluded", "publication chain: excluded | timed | run. excluded runs nothing downstream of upload (never satisfies a benchmark check); timed runs the full battery with clickhouse.enabled=false and discloses only the chain not_run; run asserts the chain executed and requires --clickhouse-enabled and a CI lane with "+benchmark.ClickHouseCIImage) + fs.StringVar(&f.publication, "publication", "excluded", "publication chain: excluded | run. excluded runs nothing downstream of upload (never satisfies a benchmark check); run executes the full chain and battery on both drivers and requires --clickhouse-enabled, --clickhouse-url, and a CI lane with "+benchmark.ClickHouseCIImage) fs.BoolVar(&f.clickHouseEnabled, "clickhouse-enabled", false, "assert the arms were deployed with ClickHouse ("+benchmark.ClickHouseCIImage+"); required for --publication=run") + fs.StringVar(&f.clickHouseURL, "clickhouse-url", "", "ClickHouse native URL for both arms (required for --publication=run); each arm materializes into its own loom_bench_* database") + fs.StringVar(&f.clickHouseUser, "clickhouse-user", "default", "ClickHouse user for both arms") + fs.StringVar(&f.clickHousePassword, "clickhouse-password", "", "ClickHouse password for both arms") + fs.StringVar(&f.clickHouseRecipe, "clickhouse-recipe", "", "dataframer recipe JSON the arms start with (required for --publication=run; the server refuses to enable ClickHouse without one)") _ = fs.Parse(os.Args[2:]) // Fail-closed preflight (exit 3: refused to run; exit 1: ran and failed). @@ -218,25 +226,27 @@ func runLive() { } switch f.publication { case "excluded": - // Pure excluded mode: no ClickHouse and no timing either; the - // whole downstream battery ships disclosed planned/not_run. A - // run like this can never satisfy a benchmark check (exit 3). + // Excluded mode: no ClickHouse and no timing either; the whole + // downstream battery ships disclosed planned/not_run. There is no + // chain-less battery — the first explorer-config publish + // materializes through the chain — so a run either deploys + // ClickHouse (run mode) or excludes everything past upload. A run + // like this can never satisfy a benchmark check (exit 3). plan.Publication = &benchmark.PublicationPlan{Run: false, Capability: benchmark.PublicationCapabilityCH} plan.PlannedNotRun = append(plan.PlannedNotRun, benchmark.PublicationDisclosures(plan.Legs, benchmark.PublicationCapabilityCH)...) - case "timed": - // Paired timing lane: the battery — authoring, preview, timing, - // gates, controls — runs for real against both drivers with - // clickhouse.enabled=false; only the ClickHouse-backed - // publication chain ships disclosed not_run. - plan.Publication = &benchmark.PublicationPlan{Run: false, Battery: true, Capability: benchmark.PublicationCapabilityTimed} - plan.PlannedNotRun = append(plan.PlannedNotRun, benchmark.PublicationDisclosures(plan.Legs, benchmark.PublicationCapabilityTimed)...) case "run": if !f.clickHouseEnabled { preflight("--publication=run requires --clickhouse-enabled: the chain is ClickHouse-backed end to end (%s)", benchmark.ClickHouseCIImage) } + if strings.TrimSpace(f.clickHouseURL) == "" { + preflight("--publication=run requires --clickhouse-url: both arms materialize their publications through ClickHouse") + } + if strings.TrimSpace(f.clickHouseRecipe) == "" { + preflight("--publication=run requires --clickhouse-recipe: the server refuses to start with ClickHouse enabled but no dataframer recipe") + } plan.Publication = &benchmark.PublicationPlan{Run: true} default: - preflight("unknown --publication %q (want excluded | timed | run)", f.publication) + preflight("unknown --publication %q (want excluded | run)", f.publication) } // Live battery law: registry-excluded workloads ride along as // disclosed planned/not_run rows on every ran leg. diff --git a/internal/dataframe/benchmark/contributors.go b/internal/dataframe/benchmark/contributors.go index d1beb3cf7..691833220 100644 --- a/internal/dataframe/benchmark/contributors.go +++ b/internal/dataframe/benchmark/contributors.go @@ -47,10 +47,13 @@ type contributorCatalogWire struct { } `json:"candidates"` } -// FetchContributorCatalog reads the live builder catalog of one explorer -// (the battery uses the seeded "default"). It fails closed on an incomplete, -// truncated, or scope-less catalog: a partial catalog could silently drop a -// predicate candidate and mis-author a workload. +// FetchContributorCatalog reads the live builder catalog of one explorer. +// The battery targets the repository "default" explorer, which exists only +// after the first explorer-config publish, so the driver bootstraps it with +// the empty-floor workspace before fetching (see driver.go). It fails +// closed on an incomplete, truncated, or scope-less catalog: a partial +// catalog could silently drop a predicate candidate and mis-author a +// workload. func (a *Arm) FetchContributorCatalog(ctx context.Context, token, project, explorer string) (authoringv2.CatalogSnapshot, error) { endpoint := a.authoringURL(project, explorer) + "/builder" req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index 36f689183..745244e08 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -194,15 +194,12 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden } } - // Excluded publication chain (pure excluded mode): spawn and upload - // have run for real and every downstream stage ships as - // planned/not_run with the capability named. Nothing below this point - // executes, so the report carries zero samples, zero claims, and zero - // control values. The timed mode (Battery) skips this return: the - // timing battery provably does not consume the chain (the - // backend-conformance lane runs authoring+publish on both drivers - // with clickhouse.enabled=false), so only the chain itself is - // disclosed not_run while timing is collected for real. + // Excluded publication chain (excluded mode): spawn and upload have + // run for real and every downstream stage ships as planned/not_run + // with the capability named. Nothing below this point executes, so the + // report carries zero samples, zero claims, and zero control values. + // There is no chain-less battery: the first explorer-config publish + // materializes through the chain, so authoring cannot run without it. if opts.Plan.PublicationZeroEvidence() { identity := RunIdentity{ RunID: opts.RunID, StartedAt: opts.StartedAt.UTC(), Preset: opts.Preset, @@ -255,9 +252,21 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden // The writable wire carries catalog-bound contributor intent, not // aggregate.where (the strict body bind refuses the latter). Fetch each // arm's live builder catalog once so predicate workspaces migrate - // against the same compiler facts the compiler will later bind. + // against the same compiler facts the compiler will later bind. The + // default explorer exists only after the first explorer-config + // publish, so bootstrap the repository with the empty-floor workspace + // (predicate-free, re-published byte-identically by its own authoring + // step) before reading the builder. + floorWorkspace, err := BuildEmptyFloorWorkspace() + if err != nil { + return nil, nil, err + } catalogs := map[string]authoringv2.CatalogSnapshot{} for _, p := range participants { + if _, err := arms[p.backend].arm.PublishWorkspace(ctx, opts.UnrestrictedToken, opts.Params.Project, opts.Params.Generation, + floorWorkspace, opts.SourceCommit); err != nil { + return nil, nil, fmt.Errorf("benchmark: %s catalog bootstrap: %w", p.backend, err) + } catalog, err := arms[p.backend].arm.FetchContributorCatalog(ctx, opts.UnrestrictedToken, opts.Params.Project, "default") if err != nil { return nil, nil, fmt.Errorf("benchmark: %s contributor catalog: %w", p.backend, err) @@ -304,11 +313,9 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden // One explorer identity serves both arms (asserted equal above). explorerIDByWorkload := perBackend[participants[0].backend] - // Empty-floor explorers (zero-row expectation) per arm. - floorWorkspace, err := BuildEmptyFloorWorkspace() - if err != nil { - return nil, nil, err - } + // Empty-floor explorers (zero-row expectation) per arm. The workspace + // was built for the catalog bootstrap; it is re-published here because + // the per-workload loop moved the repository default since then. floorExplorers := map[string]string{} floorReceipts := map[string]string{} for _, p := range participants { diff --git a/internal/dataframe/benchmark/driver_test.go b/internal/dataframe/benchmark/driver_test.go index c42dfc32d..85166cff1 100644 --- a/internal/dataframe/benchmark/driver_test.go +++ b/internal/dataframe/benchmark/driver_test.go @@ -117,16 +117,24 @@ func TestDriverBothArmsProtocol(t *testing.T) { if f.uploads != 1 { t.Fatalf("%s: uploads=%d want 1", name, f.uploads) } - if f.publish != live+1 { // + empty floor - t.Fatalf("%s: publish=%d want %d", name, f.publish, live+1) + if f.publish != live+2 { // + catalog bootstrap + empty floor + t.Fatalf("%s: publish=%d want %d", name, f.publish, live+2) } // the writable wire is closed over aggregate predicates: every // published workspace must bind exactly like the server's body bind // does, predicate workspaces must carry contributor intent, and no // body may smuggle the legacy where field bodies := f.publishedBodies() - if len(bodies) != live+1 { - t.Fatalf("%s: published bodies=%d want %d", name, len(bodies), live+1) + if len(bodies) != live+2 { + t.Fatalf("%s: published bodies=%d want %d", name, len(bodies), live+2) + } + // the catalog fetch is bootstrapped by publishing the empty-floor + // workspace first: the default explorer exists only after the first + // explorer-config publish, so the builder read depends on this order + if floor, err := BuildEmptyFloorWorkspace(); err != nil { + t.Fatal(err) + } else if string(bodies[0]) != string(floor) { + t.Fatalf("%s: first publish must be the empty-floor catalog bootstrap", name) } var withContributor, withWhere int for _, body := range bodies { diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go index e4365f42c..2e311cd85 100644 --- a/internal/dataframe/benchmark/harness.go +++ b/internal/dataframe/benchmark/harness.go @@ -94,11 +94,17 @@ func (a *Arm) UploadGeneration(ctx context.Context, token, project, generation s return nil } -// Publication identifies the published output for execution. +// Publication identifies the durable publication the receipt-based +// execution will bind to. Fields mirror the real explorer-config response +// exactly (project, generation, explorerId, receiptId, revisionId, +// executionId, recipe, translationVersion, activated): the server omits any +// per-output field, so output identity belongs to the caller, which authored +// the single-output workspace and knows its id. type Publication struct { Recipe string `json:"recipe"` TranslationVersion string `json:"translationVersion"` - Output string `json:"output"` + ReceiptID string `json:"receiptId"` + RevisionID string `json:"revisionId"` ExecutionID string `json:"executionId"` } @@ -123,8 +129,11 @@ func (a *Arm) PublishWorkspace(ctx context.Context, token, project, generation s if err := json.Unmarshal(raw, &pub); err != nil { return Publication{}, fmt.Errorf("benchmark: publication shape on %s: %w", a.Backend, err) } - if pub.Output == "" { - return Publication{}, fmt.Errorf("benchmark: publication on %s omitted output identity", a.Backend) + if pub.Recipe == "" || pub.TranslationVersion == "" { + return Publication{}, fmt.Errorf("benchmark: publication on %s omitted recipe/translationVersion identity", a.Backend) + } + if pub.ReceiptID == "" || pub.RevisionID == "" { + return Publication{}, fmt.Errorf("benchmark: publication on %s omitted receipt/revision identity", a.Backend) } return pub, nil } diff --git a/internal/dataframe/benchmark/harness_scope_test.go b/internal/dataframe/benchmark/harness_scope_test.go index abb72ae44..e8ce41989 100644 --- a/internal/dataframe/benchmark/harness_scope_test.go +++ b/internal/dataframe/benchmark/harness_scope_test.go @@ -23,7 +23,7 @@ func TestWriteRoutesCarryAuthResourcePath(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/api/v1/projects/loom-bench/generations/gen-1/explorer-config", func(w http.ResponseWriter, r *http.Request) { seen["publish"] = r.URL.Query().Get("auth_resource_path") - _ = json.NewEncoder(w).Encode(map[string]any{"output": "out", "recipe": "rec", "translationVersion": "tv", "executionId": "ex"}) + _ = json.NewEncoder(w).Encode(map[string]any{"receiptId": "rcpt-1", "revisionId": "rev-1", "recipe": "rec", "translationVersion": "tv", "executionId": "ex", "activated": true}) }) mux.HandleFunc("/api/v1/projects/loom-bench/explorers", func(w http.ResponseWriter, r *http.Request) { seen["create"] = r.URL.Query().Get("auth_resource_path") diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index 8e3e56bee..0626a895a 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -94,7 +94,7 @@ func newFakeArm(backend string) *fakeArm { f.publish++ f.published = append(f.published, body) f.mu.Unlock() - _ = json.NewEncoder(w).Encode(map[string]any{"recipe": "bench", "translationVersion": "t1", "output": "out"}) + _ = json.NewEncoder(w).Encode(map[string]any{"recipe": "bench", "translationVersion": "t1", "receiptId": "revpub-1", "revisionId": "rev-1", "executionId": "exec-1", "activated": true}) return case strings.Contains(path, "/authoring/v2/preview"): var body map[string]any @@ -194,7 +194,7 @@ func TestUploadAndPublishUsePublicRoutes(t *testing.T) { if err != nil { t.Fatal(err) } - if pub.Output != "out" || pub.TranslationVersion != "t1" { + if pub.Recipe != "bench" || pub.TranslationVersion != "t1" || pub.ReceiptID != "revpub-1" || pub.RevisionID != "rev-1" { t.Fatalf("publication decode: %+v", pub) } } @@ -442,16 +442,32 @@ func TestScrubWriterRedactsSecrets(t *testing.T) { func TestBuildServerConfigYAMLDisablesClickHouseBothArms(t *testing.T) { for _, backend := range []string{BackendArango, BackendPostgres} { - yaml := BuildServerConfigYAML(":18080", backend, " url: http://127.0.0.1:8529\n", "http://127.0.0.1:9999", "bench-cursor-secret-0123456789ab") + yaml := BuildServerConfigYAML(":18080", backend, " url: http://127.0.0.1:8529\n", "http://127.0.0.1:9999", "bench-cursor-secret-0123456789ab", nil) if !strings.Contains(yaml, "clickhouse:") || !strings.Contains(yaml, "enabled: false") { t.Fatalf("%s arm config must disable ClickHouse: %s", backend, yaml) } + if strings.Contains(yaml, "dataframer") { + t.Fatalf("%s arm config must not name a recipe without ClickHouse: %s", backend, yaml) + } if !strings.Contains(yaml, "mode: calypr") || !strings.Contains(yaml, "trusted_issuer") { t.Fatalf("%s arm config must wire scoped auth: %s", backend, yaml) } } } +func TestBuildServerConfigYAMLEnablesChainWithOwnDatabase(t *testing.T) { + ch := &ClickHouseSpec{URL: "clickhouse://127.0.0.1:9000", Database: "loom_bench_postgres", Username: "loom_bench", Password: "ci-only", Recipe: "/repo/testdata/devloop-fixture/recipe.json"} + yaml := BuildServerConfigYAML(":18080", BackendPostgres, " host: x\n", "http://127.0.0.1:9999", "bench-cursor-secret-0123456789ab", ch) + for _, want := range []string{"enabled: true", `url: "clickhouse://127.0.0.1:9000"`, `database: "loom_bench_postgres"`, `username: "loom_bench"`, `password: "ci-only"`, "dataframer", `recipe: "/repo/testdata/devloop-fixture/recipe.json"`} { + if !strings.Contains(yaml, want) { + t.Fatalf("enabled arm config missing %q: %s", want, yaml) + } + } + if strings.Contains(yaml, "enabled: false") { + t.Fatalf("enabled arm config must not also disable ClickHouse: %s", yaml) + } +} + func TestAuthorWorkloadFlowPinsRoutesAndTokens(t *testing.T) { f := newFakeArm(BackendPostgres) defer f.close() diff --git a/internal/dataframe/benchmark/provision.go b/internal/dataframe/benchmark/provision.go index ad211fa3a..a84ece561 100644 --- a/internal/dataframe/benchmark/provision.go +++ b/internal/dataframe/benchmark/provision.go @@ -344,15 +344,40 @@ func MakeBearerToken(issuer, sub string) string { return "Bearer " + header + "." + payload + "." + base64.RawURLEncoding.EncodeToString([]byte("mock")) } -// BuildServerConfigYAML renders the arm config: ClickHouse disabled on both -// arms (matched reader path ruling), scoped calypr auth against the mock -// fence, backend wiring passed in verbatim from the caller. -func BuildServerConfigYAML(listen, backend, backendYAML, fenceURL, cursorSecret string) string { +// ClickHouseSpec configures the publication chain for a spawned arm. +// Both arms run the same ClickHouse server but must never share a +// database: each arm materializes into its own benchmark-owned database +// so a publish on one driver can never read or displace the other's +// published rows. Recipe is the dataframer recipe JSON the server needs +// when ClickHouse is enabled; it is a startup input — publication +// materialization is receipt-driven. +type ClickHouseSpec struct { + URL string + Database string + Username string + Password string + Recipe string +} + +// BuildServerConfigYAML renders the arm config: ClickHouse enabled or +// disabled per spec (matched deployment: one spec drives both arms, only +// the database differs), scoped calypr auth against the mock fence, +// backend wiring passed in verbatim from the caller. +func BuildServerConfigYAML(listen, backend, backendYAML, fenceURL, cursorSecret string, ch *ClickHouseSpec) string { var b bytes.Buffer b.WriteString("server:\n") fmt.Fprintf(&b, " listen: %q\n", listen) fmt.Fprintf(&b, " backend: %q\n", backend) - b.WriteString(" clickhouse:\n enabled: false\n") + if ch == nil { + b.WriteString(" clickhouse:\n enabled: false\n") + } else { + b.WriteString(" clickhouse:\n enabled: true\n") + fmt.Fprintf(&b, " url: %q\n", ch.URL) + fmt.Fprintf(&b, " database: %q\n", ch.Database) + fmt.Fprintf(&b, " username: %q\n", ch.Username) + fmt.Fprintf(&b, " password: %q\n", ch.Password) + fmt.Fprintf(&b, " dataframer:\n recipe: %q\n", ch.Recipe) + } // The server refuses to configure without a >=16-char signing secret; // callers generate one per run and scrub it from arm logs. fmt.Fprintf(&b, " population_mapping_cursor_secret: %q\n", cursorSecret) diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go index 95d78e97f..5ea1797c9 100644 --- a/internal/dataframe/benchmark/report.go +++ b/internal/dataframe/benchmark/report.go @@ -258,12 +258,11 @@ func VerifyReport(report *Report) error { } if runLegs > 0 { if report.Plan.PublicationZeroEvidence() { - // Pure excluded mode: the battery itself was excluded, so - // nothing measured and any control value or timing evidence - // is forged (stolen limits, borrowed floors, ratio cover). - // The timed mode (chain excluded, battery run) belongs to the - // else branch below: its evidence is real and must satisfy - // the control-presence rules. + // Excluded mode: the battery itself was excluded, so nothing + // measured and any control value or timing evidence is forged + // (stolen limits, borrowed floors, ratio cover). A run that + // measured anything takes the else branch below, where the + // control-presence rules apply. c := report.Controls if c.StabilityCVLimit != 0 || c.EmptyFloorMillis != 0 || c.SelfRatioArango != 0 || c.SelfRatioPostgres != 0 || c.RatioPolicy != "" || len(c.UnstableCells) != 0 { diff --git a/internal/dataframe/benchmark/report_test.go b/internal/dataframe/benchmark/report_test.go index 48a762cb4..d50601d81 100644 --- a/internal/dataframe/benchmark/report_test.go +++ b/internal/dataframe/benchmark/report_test.go @@ -287,38 +287,31 @@ func TestBuildReportRefusesInconsistentConfig(t *testing.T) { // --- timed publication lane (operator amendment 2026-11-13) -------------------- -// timedReport builds the paired-timing shape: the ClickHouse-backed -// publication chain is disclosed not_run, but the battery ran for real, -// so samples, summaries, controls, and claims are legitimate evidence -// rather than forgery. -func timedReport(t *testing.T) *Report { - t.Helper() +// Run mode is the only mode that measures: the ClickHouse-backed chain +// ran, so samples, summaries, controls, and claims are legitimate +// evidence rather than forgery. validReport already describes that lane +// (ClickHouse deployed, Publication.Run true); these cases pin the +// classification and the disclosure rules around it. +func TestRunPublicationCarriesTimingEvidence(t *testing.T) { report := validReport(t) - report.Identity.ClickHouseDisabled = true - plan := report.Plan - plan.Publication = &PublicationPlan{Run: false, Battery: true, Capability: PublicationCapabilityTimed} - plan.PlannedNotRun = append(plan.PlannedNotRun, PublicationDisclosures(plan.Legs, PublicationCapabilityTimed)...) - built, err := BuildReport(report.Identity, plan, report.Controls, report.GateReports, report.Samples) - if err != nil { - t.Fatalf("timed publication report must verify: %v", err) - } - return built -} - -func TestTimedPublicationCarriesTimingEvidence(t *testing.T) { - report := timedReport(t) if report.Status != "passed" { - t.Fatalf("timed lane status: %s (%v)", report.Status, report.StatusReasons) + t.Fatalf("run lane status: %s (%v)", report.Status, report.StatusReasons) } if len(report.Samples) == 0 || len(report.Summaries) == 0 { - t.Fatal("timed lane must carry real timing evidence") + t.Fatal("run lane must carry real timing evidence") } if len(report.Claims) != len(Battery)-len(LiveBatteryExclusions()) { - t.Fatalf("timed lane claims: %d want %d", len(report.Claims), len(Battery)-len(LiveBatteryExclusions())) + t.Fatalf("run lane claims: %d want %d", len(report.Claims), len(Battery)-len(LiveBatteryExclusions())) } - if !report.Plan.PublicationTimed() || report.Plan.PublicationZeroEvidence() { - t.Fatalf("timed lane classification wrong: %+v", report.Plan.Publication) + if report.Plan.PublicationExcluded() { + t.Fatalf("run lane classification wrong: %+v", report.Plan.Publication) } + if report.Plan.PublicationZeroEvidence() { + t.Fatal("a chain-run report must never classify as zero-evidence") + } + // The chain ran, so no leg may carry publication-chain disclosure + // rows: they name work that did not happen and would launder a + // not_run stage into a passing report. for _, leg := range report.Plan.Legs { disclosed := 0 for _, row := range report.Plan.PlannedNotRun { @@ -326,34 +319,24 @@ func TestTimedPublicationCarriesTimingEvidence(t *testing.T) { disclosed++ } } - if disclosed != len(Battery) { - t.Fatalf("%s: %d publication-chain disclosure rows want %d (one per workload)", leg.Backend, disclosed, len(Battery)) + if disclosed != 0 { + t.Fatalf("%s: %d publication-chain disclosure rows want 0 (the chain ran)", leg.Backend, disclosed) } } } -func TestPublicationBatteryFlagMustNotBeFalseToPass(t *testing.T) { - report := timedReport(t) - plan := report.Plan - plan.Publication.Battery = false - report.Plan = plan - if err := VerifyReport(report); err == nil { - t.Fatal("battery=false on a report with timing evidence must be refused") - } -} - -func TestPublicationBatteryRequiresExcludedChain(t *testing.T) { +func TestRunPublicationMustNotNameCapability(t *testing.T) { plan := BothArmsPlan() - plan.Publication = &PublicationPlan{Run: true, Battery: true, Capability: PublicationCapabilityTimed} + plan.Publication = &PublicationPlan{Run: true, Capability: PublicationCapabilityCH} if err := ValidatePublicationPlan(plan); err == nil { - t.Fatal("run=true with battery=true must be refused as contradictory") + t.Fatal("run=true naming a missing capability must be refused: the chain either ran or it did not") } } -func TestTimedPublicationRequiresNamedCapability(t *testing.T) { +func TestExcludedPublicationRequiresNamedCapability(t *testing.T) { plan := BothArmsPlan() - plan.Publication = &PublicationPlan{Run: false, Battery: true} + plan.Publication = &PublicationPlan{Run: false} if err := ValidatePublicationPlan(plan); err == nil { - t.Fatal("timed lane must name the capability it discloses") + t.Fatal("an excluded chain must name the capability it discloses") } } diff --git a/scripts/benchmark-backends.mjs b/scripts/benchmark-backends.mjs index ac827c981..65ebaa528 100644 --- a/scripts/benchmark-backends.mjs +++ b/scripts/benchmark-backends.mjs @@ -16,12 +16,15 @@ // selftest; it can never satisfy a live check. // --paired --binary --out [--profile tiny|small|representative] // [--iterations N>=5 --warmup N>=1 --rounds N] [--source-commit SHA] -// [--benchcli ] [--window-note ] +// [--benchcli ] [--window-note ] --clickhouse-url // The operator-mandated comparison: the same matched battery runs // against BOTH real drivers (LOOM_TEST_ARANGO_URL + -// LOOM_TEST_POSTGRES_URL), correctness gates first, then >=5 -// alternating timings per driver. With LOOM_BENCH_ADMIN_DSN set it -// first provisions its own fresh loom_bench_* database (drop-pg + +// LOOM_TEST_POSTGRES_URL) with the full ClickHouse-backed +// publication chain (--publication=run; there is no chain-less +// battery: the first explorer-config publish materializes through +// ClickHouse), correctness gates first, then >=5 alternating +// timings per driver. With LOOM_BENCH_ADMIN_DSN set it first +// provisions its own fresh loom_bench_* database (drop-pg + // setup-pg: database, six-series schema ladder, migrated tables), // because the PostgreSQL backend does not own schema lifecycle. // Writes comparison.md next to the verified report. FAILS (never @@ -30,6 +33,7 @@ import { spawnSync } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; export const CHECK_HARNESS = 'benchmark-harness-tests'; export const CHECK_LIVE = 'live-two-backend-correctness'; @@ -47,6 +51,8 @@ export function parseArgs(argv) { mode: null, report: null, benchcli: null, out: null, runArgs: [], selftest: false, paired: false, binary: null, profile: 'tiny', iterations: 5, warmup: 1, rounds: null, concurrency: 1, previewLimit: 1000, sourceCommit: null, seed: 7, windowNote: null, + clickHouseUrl: null, clickHouseUser: 'default', clickHousePassword: null, + clickHouseRecipe: fileURLToPath(new URL('../testdata/devloop-fixture/recipe.json', import.meta.url)), }; let i = 0; const int = (name, raw, min) => { @@ -72,6 +78,10 @@ export function parseArgs(argv) { else if (arg === '--seed') { i += 1; flags.seed = int('--seed', argv[i], 0); } else if (arg === '--source-commit') { i += 1; flags.sourceCommit = argv[i]; } else if (arg === '--window-note') { i += 1; flags.windowNote = argv[i]; } + else if (arg === '--clickhouse-url') { i += 1; flags.clickHouseUrl = argv[i]; } + else if (arg === '--clickhouse-user') { i += 1; flags.clickHouseUser = argv[i]; } + else if (arg === '--clickhouse-password') { i += 1; flags.clickHousePassword = argv[i]; } + else if (arg === '--clickhouse-recipe') { i += 1; flags.clickHouseRecipe = argv[i]; } else if (arg === '--out') { i += 1; flags.out = argv[i]; } else if (arg === '--') { flags.runArgs = argv.slice(i + 1); break; } else throw new Error(`unknown argument: ${arg}`); @@ -96,6 +106,8 @@ export function parseArgs(argv) { if (flags.rounds < 1) throw new Error('--paired requires --rounds >= 1'); if (flags.iterations % flags.rounds !== 0) throw new Error(`--iterations ${flags.iterations} must split evenly into --rounds ${flags.rounds}`); if (!/^[0-9a-f]{40}$/.test(flags.sourceCommit ?? '')) throw new Error('--paired requires --source-commit as the full 40-hex SHA the arms were built from'); + if (!(flags.clickHouseUrl ?? '').trim()) throw new Error('--paired requires --clickhouse-url: the publication chain is ClickHouse-backed end to end, and the battery cannot run without it'); + if (!(flags.clickHouseRecipe ?? '').trim()) throw new Error('--paired requires a dataframer recipe for the ClickHouse-enabled arms'); } return flags; } @@ -149,10 +161,17 @@ export function buildPairedInvocation(flags, services) { const bin = flags.benchcli ?? 'go'; const args = [ '--scope=both-arms', - // The ClickHouse-backed publication chain is disclosed not_run; the - // timing battery itself is ClickHouse-free and runs for real on both - // drivers (the backend-conformance lane proves that posture). - '--publication=timed', + // The full publication chain runs for real on both arms: authoring + // publishes through ClickHouse materialization (a ClickHouse-disabled + // deployment cannot author at all), so the paired lane is run mode, + // never a disclosed not_run posture. Each arm materializes into its + // own loom_bench_* database. + '--publication=run', + '--clickhouse-enabled', + `--clickhouse-url=${flags.clickHouseUrl}`, + `--clickhouse-user=${flags.clickHouseUser}`, + ...(flags.clickHousePassword ? [`--clickhouse-password=${flags.clickHousePassword}`] : []), + `--clickhouse-recipe=${flags.clickHouseRecipe}`, `--preset=${flags.profile}`, `--seed=${flags.seed}`, `--warmup=${flags.warmup}`, @@ -206,8 +225,11 @@ export function classifyPairedReport(report, minSamples = PAIRED_MIN_SAMPLES_PER } } if (report.status !== 'passed') problems.push(`report status is ${report.status ?? 'unknown'}, not passed`); - if (report.identity?.clickhouseDisabled !== true) { - problems.push('report does not record ClickHouse as disabled: the timed lane must state that honestly'); + if (report.plan?.publication?.run !== true) { + problems.push('the publication chain did not run: without it the arms cannot author, so no timings from such a lane are a comparison'); + } + if (report.identity?.clickhouseDisabled !== false) { + problems.push('report does not record ClickHouse as deployed: the paired lane runs the full chain on both arms and must state that honestly'); } return problems.length === 0 ? { status: 'pass', disclosure: null, measured } : { status: 'fail', disclosure: problems.join('; '), measured }; } @@ -229,7 +251,7 @@ export function renderComparisonTable(report) { const lines = []; lines.push('# Paired driver benchmark — Arango/AQL vs PostgreSQL/SQL'); lines.push(''); - lines.push(`Run \`${identity.runId ?? 'n/a'}\` — matched workload, both arms, ClickHouse disabled: \`${identity.clickHouseDisabled === true ? 'yes (timed lane)' : 'no'}\`.`); + lines.push(`Run \`${identity.runId ?? 'n/a'}\` — matched workload, both arms, publication chain: \`${report.plan?.publication?.run === true && identity.clickhouseDisabled === false ? 'ran for real on both arms (ClickHouse deployed)' : 'NOT run (this table cannot be a comparison)'}\`.`); lines.push(''); lines.push('## Conditions'); lines.push(''); diff --git a/scripts/benchmark-backends.test.mjs b/scripts/benchmark-backends.test.mjs index ae1730b68..9df187692 100644 --- a/scripts/benchmark-backends.test.mjs +++ b/scripts/benchmark-backends.test.mjs @@ -204,6 +204,9 @@ import { } from './benchmark-backends.mjs'; const SHA40 = 'a'.repeat(40); +// The paired lane requires a ClickHouse service: the publication chain is +// ClickHouse-backed end to end (authoring publishes through it). +const CH_ARGS = ['--clickhouse-url', 'clickhouse://127.0.0.1:9000']; function pairedReportFixture(overrides = {}) { const samples = []; @@ -217,10 +220,10 @@ function pairedReportFixture(overrides = {}) { status: 'passed', identity: { runId: 'bench-paired-1', preset: 'tiny', sourceCommit: SHA40, datasetDigest: 'd'.repeat(64), - arangoVersion: '3.12.4', postgresVersion: 'PostgreSQL 18.0', clickhouseDisabled: true, + arangoVersion: '3.12.4', postgresVersion: 'PostgreSQL 18.0', clickhouseDisabled: false, config: { Warmup: 1, Iterations: 5, Concurrency: 1, Rounds: 1 }, }, - plan: { scope: 'both-arms', legs: [{ backend: 'arango', mode: 'run' }, { backend: 'postgres', mode: 'run' }] }, + plan: { scope: 'both-arms', legs: [{ backend: 'arango', mode: 'run' }, { backend: 'postgres', mode: 'run' }], publication: { run: true } }, gateReports: [{ workloadId: 'select-filter', claimable: true, arango: { outcome: 'passed' }, postgres: { outcome: 'passed' } }], samples, summaries: [ @@ -237,7 +240,7 @@ test('paired: missing services fail the check, never skip it', () => { assert.equal(seen.problems.length, 2); const logs = []; let spawned = false; - const failures = run(parseArgs(['--paired', '--binary', '/bin/loom', '--out', 'out', '--source-commit', SHA40]), { + const failures = run(parseArgs(['--paired', '--binary', '/bin/loom', '--out', 'out', '--source-commit', SHA40, ...CH_ARGS]), { exec: () => { spawned = true; return { status: 0 }; }, log: (line) => logs.push(line), env: {}, @@ -253,19 +256,22 @@ test('paired: arango dsn gets a benchmark-namespaced database', () => { assert.match(seen.arangoUrl, /\/loom_bench_ci$/); }); -test('paired invocation: both arms, timed lane, real iterations', () => { - const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', 'out', '--profile', 'tiny', '--iterations', '5', '--warmup', '1', '--rounds', '5', '--source-commit', SHA40]); +test('paired invocation: both arms, full chain, real iterations', () => { + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', 'out', '--profile', 'tiny', '--iterations', '5', '--warmup', '1', '--rounds', '5', '--source-commit', SHA40, ...CH_ARGS]); const inv = buildPairedInvocation(flags, { arangoUrl: 'http://127.0.0.1:8529/loom_bench_ci', postgresDsn: 'postgresql://u:p@h:5432/loom_bench_paired' }); const joined = inv.args.join(' '); assert.match(joined, /--scope=both-arms/); - assert.match(joined, /--publication=timed/); + assert.match(joined, /--publication=run/); + assert.match(joined, /--clickhouse-enabled/); + assert.match(joined, /--clickhouse-url=clickhouse:\/\/127\.0\.0\.1:9000/); + assert.match(joined, /--clickhouse-recipe=\S+recipe\.json/); assert.match(joined, /--iterations=5/); assert.match(joined, /--warmup=1/); assert.match(joined, /--arango-dsn=http:\/\/127\.0\.0\.1:8529\/loom_bench_ci/); }); test('paired flags refuse a non-comparison', () => { - const base = ['--paired', '--binary', '/bin/loom', '--out', 'out', '--source-commit', SHA40]; + const base = ['--paired', '--binary', '/bin/loom', '--out', 'out', '--source-commit', SHA40, ...CH_ARGS]; const without = (flag) => { const i = base.indexOf(flag); return [...base.slice(0, i), ...base.slice(i + 2)]; @@ -276,6 +282,8 @@ test('paired flags refuse a non-comparison', () => { assert.throws(() => parseArgs([...base, '--iterations', '3']), /integer >= 5/); assert.throws(() => parseArgs([...base, '--warmup', '0']), /warmup/); assert.throws(() => parseArgs([...base, '--profile', 'smoke']), /--profile must be one of/); + assert.throws(() => parseArgs(without('--clickhouse-url')), /requires --clickhouse-url/); + assert.throws(() => parseArgs([...base, '--clickhouse-recipe', '']), /dataframer recipe/); }); test('paired classification: only real timing on both drivers passes', () => { @@ -298,9 +306,13 @@ test('paired classification: only real timing on both drivers passes', () => { const failedStatus = pairedReportFixture({ status: 'failed' }); assert.equal(classifyPairedReport(failedStatus).status, 'fail', 'correctness failure cannot be read as fast'); - const chClaimed = pairedReportFixture(); - chClaimed.identity.clickhouseDisabled = false; - assert.equal(classifyPairedReport(chClaimed).status, 'fail', 'the timed lane must record ClickHouse as disabled'); + const chDisabled = pairedReportFixture(); + chDisabled.identity.clickhouseDisabled = true; + assert.equal(classifyPairedReport(chDisabled).status, 'fail', 'a lane that records ClickHouse as disabled cannot have run the chain the battery needs'); + + const chainNotRun = pairedReportFixture(); + delete chainNotRun.plan.publication; + assert.equal(classifyPairedReport(chainNotRun).status, 'fail', 'a report whose plan does not claim the chain ran is not a comparison'); const blockedArm = pairedReportFixture(); blockedArm.gateReports = [{ workloadId: 'select-filter', arango: { outcome: 'blocked' }, postgres: { outcome: 'passed' } }]; @@ -333,7 +345,7 @@ test('paired run end-to-end: clean both-arm report passes and writes the table', if (args.includes('verify')) return { status: 0 }; return { status: 0 }; }; - const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', dir, '--source-commit', SHA40]); + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', dir, '--source-commit', SHA40, ...CH_ARGS]); await writeFile(join(dir, 'report.json'), JSON.stringify(report)); const failures = run(flags, { exec, log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_paired' } }); assert.equal(failures, 0, logs.join('\n')); @@ -351,7 +363,7 @@ test('paired run end-to-end: zero-timing report fails the check even with exit 0 report.status = 'failed'; const exec = () => ({ status: 0 }); const logs = []; - const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', dir, '--source-commit', SHA40]); + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', dir, '--source-commit', SHA40, ...CH_ARGS]); await writeFile(join(dir, 'report.json'), JSON.stringify(report)); const failures = run(flags, { exec, log: (l) => logs.push(l), env: { LOOM_TEST_ARANGO_URL: 'http://h:8529', LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@h:5432/loom_bench_paired' } }); assert.equal(failures, 1); @@ -368,7 +380,7 @@ test('paired: postgres dsn must name a loom_bench_ database', () => { test('paired: admin DSN drives drop-pg then setup-pg before the benchmark', () => { const dir = '/tmp/paired-prov-test'; const calls = []; - const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--benchcli', '/bin/benchcli', '--out', dir, '--source-commit', SHA40]); + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--benchcli', '/bin/benchcli', '--out', dir, '--source-commit', SHA40, ...CH_ARGS]); const exec = (cmd, args) => { calls.push([cmd, args.join(' ')]); return { status: 0 }; }; const logs = []; run(flags, { @@ -387,7 +399,7 @@ test('paired: admin DSN drives drop-pg then setup-pg before the benchmark', () = test('paired: failed setup-pg fails the check before any timing attempt', () => { const calls = []; - const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--benchcli', '/bin/benchcli', '--out', '/tmp/paired-fail', '--source-commit', SHA40]); + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--benchcli', '/bin/benchcli', '--out', '/tmp/paired-fail', '--source-commit', SHA40, ...CH_ARGS]); const logs = []; const failures = run(flags, { exec: (cmd, args) => { From ef7380ddffc71f6a92a79518acc5d9e4651d9100 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 07:53:54 +0000 Subject: [PATCH 35/45] fix(benchmark): authenticate the paired lane's ClickHouse service The paired job's ClickHouse service ran bare, but the image refuses the anonymous default login from the runner network: the arango arm logged "code: 516 ... Authentication failed" at EnsureDatabase, the publication plane recorded a startup degradation, ClickHouseReadyCheck returned it verbatim, and /readyz held 503 for the whole spawn budget. The two ClickHouse jobs in this workflow already create loom_ci with CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1; the paired service now carries the same env, and the job exports LOOM_BENCH_CH_USER/PASSWORD the way the conformance lane exports LOOM_CLICKHOUSE_USERNAME. The orchestrator resolves ClickHouse credentials in order: explicit --clickhouse-user/-password flags, then those two environment variables, then the bare server default. Pinned by the node suite (env fills, flags win); 24/24 green. The paired job at this head is the adjudicator for the materialization chain. --- .github/workflows/tests.yaml | 6 ++++++ scripts/benchmark-backends.mjs | 12 ++++++++++-- scripts/benchmark-backends.test.mjs | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 9e0f60cf5..3ab709c23 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -467,6 +467,10 @@ jobs: --health-retries 40 clickhouse: image: clickhouse/clickhouse-server:25.3 + env: + CLICKHOUSE_USER: loom_ci + CLICKHOUSE_PASSWORD: loom_ci_password + CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: "1" ports: - 9000:9000 - 8123:8123 @@ -479,6 +483,8 @@ jobs: LOOM_TEST_ARANGO_URL: http://127.0.0.1:8529 LOOM_TEST_POSTGRES_URL: postgresql://loom_bench:loom_ci_test_only@127.0.0.1:5432/loom_bench_paired?sslmode=disable LOOM_BENCH_ADMIN_DSN: postgresql://loom_bench:loom_ci_test_only@127.0.0.1:5432/postgres?sslmode=disable + LOOM_BENCH_CH_USER: loom_ci + LOOM_BENCH_CH_PASSWORD: loom_ci_password steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/scripts/benchmark-backends.mjs b/scripts/benchmark-backends.mjs index 65ebaa528..43550ecca 100644 --- a/scripts/benchmark-backends.mjs +++ b/scripts/benchmark-backends.mjs @@ -51,7 +51,7 @@ export function parseArgs(argv) { mode: null, report: null, benchcli: null, out: null, runArgs: [], selftest: false, paired: false, binary: null, profile: 'tiny', iterations: 5, warmup: 1, rounds: null, concurrency: 1, previewLimit: 1000, sourceCommit: null, seed: 7, windowNote: null, - clickHouseUrl: null, clickHouseUser: 'default', clickHousePassword: null, + clickHouseUrl: null, clickHouseUser: null, clickHousePassword: null, clickHouseRecipe: fileURLToPath(new URL('../testdata/devloop-fixture/recipe.json', import.meta.url)), }; let i = 0; @@ -169,7 +169,7 @@ export function buildPairedInvocation(flags, services) { '--publication=run', '--clickhouse-enabled', `--clickhouse-url=${flags.clickHouseUrl}`, - `--clickhouse-user=${flags.clickHouseUser}`, + `--clickhouse-user=${flags.clickHouseUser || 'default'}`, ...(flags.clickHousePassword ? [`--clickhouse-password=${flags.clickHousePassword}`] : []), `--clickhouse-recipe=${flags.clickHouseRecipe}`, `--preset=${flags.profile}`, @@ -448,6 +448,14 @@ function runPaired(flags, { exec, log, env }) { return 1; } } + // ClickHouse credentials arrive like the other services': explicit + // flags win, LOOM_BENCH_CH_USER / LOOM_BENCH_CH_PASSWORD fill what the + // lane provides, and the bare server default remains the fallback. + // (The CI service authenticates a created loom_ci user; an anonymous + // default login is refused there with code 516, which degrades the + // publication plane and keeps /readyz at 503.) + if (!flags.clickHouseUser) flags.clickHouseUser = (env.LOOM_BENCH_CH_USER ?? '').trim() || 'default'; + if (!flags.clickHousePassword) flags.clickHousePassword = (env.LOOM_BENCH_CH_PASSWORD ?? '').trim() || null; const invocation = buildPairedInvocation(flags, { arangoUrl, postgresDsn }); // The paired lane is its own granted window: it runs in a CI job with // isolated services, so it sets the live gate the CLI demands. diff --git a/scripts/benchmark-backends.test.mjs b/scripts/benchmark-backends.test.mjs index 9df187692..9038804b2 100644 --- a/scripts/benchmark-backends.test.mjs +++ b/scripts/benchmark-backends.test.mjs @@ -265,11 +265,36 @@ test('paired invocation: both arms, full chain, real iterations', () => { assert.match(joined, /--clickhouse-enabled/); assert.match(joined, /--clickhouse-url=clickhouse:\/\/127\.0\.0\.1:9000/); assert.match(joined, /--clickhouse-recipe=\S+recipe\.json/); + assert.match(joined, /--clickhouse-user=default/); + assert.doesNotMatch(joined, /--clickhouse-password/); assert.match(joined, /--iterations=5/); assert.match(joined, /--warmup=1/); assert.match(joined, /--arango-dsn=http:\/\/127\.0\.0\.1:8529\/loom_bench_ci/); }); +test('paired credentials: env fills ClickHouse auth, flags win', () => { + const base = ['--paired', '--binary', '/bin/loom', '--out', 'out', '--source-commit', SHA40, ...CH_ARGS]; + const seen = []; + run(parseArgs(base), { + exec: (cmd, args) => { seen.push(args.join(' ')); return { status: 2 }; }, + log: () => {}, + env: { + LOOM_TEST_ARANGO_URL: 'http://127.0.0.1:8529', + LOOM_TEST_POSTGRES_URL: 'postgresql://u:p@127.0.0.1:5432/loom_bench_paired?sslmode=disable', + LOOM_BENCH_CH_USER: 'loom_ci', LOOM_BENCH_CH_PASSWORD: 'loom_ci_password', + }, + }); + const benchcliRun = seen.find((a) => a.includes('--publication=run')); + assert.match(benchcliRun, /--clickhouse-user=loom_ci/); + assert.match(benchcliRun, /--clickhouse-password=loom_ci_password/); + const explicit = buildPairedInvocation( + parseArgs([...base, '--clickhouse-user', 'override', '--clickhouse-password', 'op']), + { arangoUrl: 'http://h/loom_bench_ci', postgresDsn: 'postgresql://u:p@h:5432/loom_bench_paired' }, + ).args.join(' '); + assert.match(explicit, /--clickhouse-user=override/); + assert.match(explicit, /--clickhouse-password=op/); +}); + test('paired flags refuse a non-comparison', () => { const base = ['--paired', '--binary', '/bin/loom', '--out', 'out', '--source-commit', SHA40, ...CH_ARGS]; const without = (flag) => { From 7602791a3a47b1e375e21965b1025a93b5eee0da Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 07:55:05 +0000 Subject: [PATCH 36/45] style(benchmark): gofmt the excluded-mode comment in report.go The verifier comment reword at 76054ef4 lost tab indentation, which the tracked-files gofmt gate (internal/ci) adjudicates. go test ./internal/ci -run TestGofmtGateTrackedFilesAreCanonical now passes, and gofmt -l is clean across internal/ and cmd/. --- internal/dataframe/benchmark/report.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go index 5ea1797c9..f2d4620e0 100644 --- a/internal/dataframe/benchmark/report.go +++ b/internal/dataframe/benchmark/report.go @@ -258,11 +258,11 @@ func VerifyReport(report *Report) error { } if runLegs > 0 { if report.Plan.PublicationZeroEvidence() { - // Excluded mode: the battery itself was excluded, so nothing - // measured and any control value or timing evidence is forged - // (stolen limits, borrowed floors, ratio cover). A run that - // measured anything takes the else branch below, where the - // control-presence rules apply. + // Excluded mode: the battery itself was excluded, so nothing + // measured and any control value or timing evidence is forged + // (stolen limits, borrowed floors, ratio cover). A run that + // measured anything takes the else branch below, where the + // control-presence rules apply. c := report.Controls if c.StabilityCVLimit != 0 || c.EmptyFloorMillis != 0 || c.SelfRatioArango != 0 || c.SelfRatioPostgres != 0 || c.RatioPolicy != "" || len(c.UnstableCells) != 0 { From 748ba095ff51fda8d33b76452cc8cb3adea8f76c Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 08:19:34 +0000 Subject: [PATCH 37/45] fix(benchmark): warm-cell claimable regime for spawned arms; truthful publication-mode README Review r4561031154: the paired lane ran --iterations 5 --rounds 5, so a spawned arm's only measured sample per round was the post-restart process-cold one; claimCellPair cites warm/service-warm only, so the report verified as a passed run with no claims and benchcli exited 1. MeasureArms now refuses a spawned arm below two measured iterations per round; the paired lane runs 10 iterations over 5 interleaved rounds (workflow, mjs defaults, and paired validation each encode iterations >= 2*rounds, rounds >= 5). Adds controls_test.go (claim regime + stability-control exclusion) and a spawned full-driver test asserting claims are produced. Review r4561031163: README documented a timed mode, a PublicationCapabilityTimed capability, and a chain-less battery that the 2026-11-13 rehearsal disproved. Section rewritten to the implemented two modes (excluded | run), the ClickHouse-backed chain the authoring battery requires (first publish answers 422 MATERIALIZATION_FAILED without a target), the per-arm databases and credential precedence, and the live-battery exclusion marker (currently six workloads). --- .github/workflows/tests.yaml | 10 +- internal/dataframe/benchmark/README.md | 67 +++++---- internal/dataframe/benchmark/controls_test.go | 133 ++++++------------ internal/dataframe/benchmark/driver_test.go | 58 ++++++++ internal/dataframe/benchmark/harness.go | 14 ++ internal/dataframe/benchmark/harness_test.go | 69 ++++++++- scripts/benchmark-backends.mjs | 20 +-- scripts/benchmark-backends.test.mjs | 11 +- 8 files changed, 246 insertions(+), 136 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 3ab709c23..b9c6bbb14 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -503,9 +503,11 @@ jobs: go build -o "$RUNNER_TEMP/paired-bin/loom-server" ./cmd/arango-fhir-server go build -o "$RUNNER_TEMP/paired-bin/benchcli" ./internal/dataframe/benchmark/cmd/benchcli - name: Run the paired benchmark against both services - # Five alternating measured samples per driver, one warmup, tiny - # matched fixture, full publication chain through the ClickHouse - # service (each arm materializes into its own loom_bench_* + # Five interleaved rounds x two measured iterations per driver: the + # post-restart process-cold sample plus one warm sample per round + # (claims cite the warm cell), one warmup, tiny matched fixture, + # full publication chain through the ClickHouse service (each arm + # materializes into its own loom_bench_* # database). The script verifies the artifact through the Go # verifier, classifies realness (both arms ran, the chain ran, # both timed), and writes comparison.md; it exits nonzero on any @@ -516,7 +518,7 @@ jobs: --binary "$RUNNER_TEMP/paired-bin/loom-server" --benchcli "$RUNNER_TEMP/paired-bin/benchcli" --profile tiny - --iterations 5 + --iterations 10 --warmup 1 --rounds 5 --source-commit "$GITHUB_SHA" diff --git a/internal/dataframe/benchmark/README.md b/internal/dataframe/benchmark/README.md index fcb3b54d3..d4b3250c2 100644 --- a/internal/dataframe/benchmark/README.md +++ b/internal/dataframe/benchmark/README.md @@ -62,17 +62,18 @@ planned capabilities). default `--publication=excluded` path, where every gate arm is blocked). A run that timed nothing must not look like a success. -## Publication chain (ClickHouse-backed) and the timed lane +## Publication chain (ClickHouse-backed) and the two modes The publication *chain* — dataset → bundle → `publication.Target`/`ClickHouseBundleStore`, the chain's only production -implementation — is ClickHouse-backed. The timing battery is not: the -authoring battery (explorer publish, builder, reconcile, preview, -receipts, workload gates, timing, controls) runs for real against both -drivers with `server.clickhouse.enabled=false`, the same posture the -`backend-conformance` CI lane adjudicates on the actual services. The -PostgreSQL backend forbids `clickhouse.enabled` in this slice outright, -so the chain and the battery must be disclosed separately. +implementation — is ClickHouse-backed, and the battery rides it: every +workload begins with the explorer-config publish, which materializes +through the chain. The first publish against a fresh generation answers +422 `MATERIALIZATION_FAILED` when no materialization target is configured +(2026-11-13 rehearsal), so there is no chain-less battery and the authoring +battery requires a reachable ClickHouse. `--publication` accepts exactly +two modes; anything else is refused at preflight +(`unknown --publication ... want excluded | run`). - `--publication=excluded` (default): spawn, upload, and version probes run for real on every ran leg; the whole publication chain ships as @@ -83,27 +84,31 @@ so the chain and the battery must be disclosed separately. refuses any samples, summaries, claims, or control values, requires every gate arm blocked, and one disclosure row per battery workload per ran leg. This mode measures nothing and can never satisfy a benchmark check. -- `--publication=timed`: the paired timing lane. The battery executes for - real on every ran leg with `clickhouse.enabled=false`; only the chain - ships disclosed not_run under `PublicationCapabilityTimed` - (`CH-not-deployed-for-timed-dataframe-legs`). Reports from this lane - carry real samples, summaries, controls, and claims; `VerifyReport` - still refuses them if the battery flag is flipped off while evidence - is present. -- `--publication=run`: requires `--clickhouse-enabled`, i.e. the CI lane - with the pinned service. A report whose identity says ClickHouse is - disabled while claiming the chain ran fails verification. +- `--publication=run`: the paired timing lane. The battery executes for + real on every ran leg — publish, materialization, preview, timing, + controls — with `server.clickhouse.enabled=true` and + `--clickhouse-enabled` on the harness identity. Each arm materializes + into its own `loom_bench_*` database. Credentials resolve flags + (`--clickhouse-user`/`--clickhouse-password`) over + `LOOM_BENCH_CH_USER`/`LOOM_BENCH_CH_PASSWORD` over the server default. + Reports from this lane carry real samples, summaries, controls, and + claims. A report whose identity says ClickHouse is disabled while + claiming the chain ran fails verification, and a run-mode report must + not name a publication capability. ## Paired speed comparison (operator acceptance, 2026-11-13) `node scripts/benchmark-backends.mjs --paired --binary --benchcli --out [--profile tiny|small|representative] -[--iterations N>=5 --warmup N>=1 --rounds N] [--source-commit <40-hex>]` +[--iterations N>=2*rounds --warmup N>=1 --rounds N>=5] [--source-commit <40-hex>] +--clickhouse-url ` runs the same matched battery against BOTH real drivers discovered from `LOOM_TEST_ARANGO_URL` + `LOOM_TEST_POSTGRES_URL` (the same variables the `backend-conformance` job exports; the CI job `factory/paired-driver-benchmark` in `.github/workflows/tests.yaml` runs -this exact command with services `arangodb:3.12` + `postgres:18`). With +this exact command with services `arangodb:3.12` + `postgres:18` + +`clickhouse/clickhouse-server:25.3`, invoking benchcli with +`--publication=run --clickhouse-enabled`). With `LOOM_BENCH_ADMIN_DSN` set it first provisions its own fresh `loom_bench_*` database (name must match `loom_bench_[a-z0-9_]{4,48}`, the rule benchcli enforces) via `benchcli drop-pg`/`setup-pg` (database, @@ -111,10 +116,15 @@ six-series schema ladder, migrated tables) — the PostgreSQL backend readiness answers `schema_missing ... the caller owns schema lifecycle`, so the lane, not the server, bootstraps. A failed `setup-pg` fails the check before any timing attempt. -Correctness gates decide first; then >= 5 alternating measured timings per -driver are required. The check FAILS — never skips, never passes — when a -service is missing, an arm is planned/not_run/excluded (beyond the two -reviewer-accepted live-battery exclusions), a driver produced no usable +Correctness gates decide first; then >= 5 interleaved measured rounds per +driver are required, each round contributing the post-restart +process-cold sample plus at least one warm sample (`--iterations >= +2*--rounds`; spawned arms restart per round, and claims cite the warm +cell, so a one-iteration round could never produce a claimable regime). +The check FAILS — never skips, never passes — when a +service is missing, an arm is planned/not_run/excluded (the exclusions +`LiveBatteryExclusions()` names — currently six workloads — are tolerated +through the exclusion marker), a driver produced no usable timing samples, or the Go verifier rejects the artifact. On success it writes `comparison.md` (median/min/max/range in milliseconds per workload per driver, plus versions, source SHA, fixture digest, warmup/cache/ @@ -132,10 +142,11 @@ from the MockFence. ## Current blockers (cited in planned artifacts) -- ClickHouse service: needed only by the publication *chain* - (`--publication=run`), which requires `clickhouse/clickhouse-server:25.3` - as provisioned by the CI lane. Paired timing uses - `--publication=timed` and does not wait on it. Owner: CI lane. +- ClickHouse service: needed by the publication chain, which the battery + itself requires (`--publication=run`), on + `clickhouse/clickhouse-server:25.3` as provisioned by the CI lane. The + paired lane depends on that service; there is no chain-less fallback. + Owner: CI lane. - `PG-PG-AUTH-PATH-DISCOVERY-ALLGENS` — FIXED in PR #156 (verified live: upload accepted, fence hit once, no 403); the branch base predates the library-side fix, so live legs go green when it merges. Evidence: diff --git a/internal/dataframe/benchmark/controls_test.go b/internal/dataframe/benchmark/controls_test.go index 14173460c..ca4511150 100644 --- a/internal/dataframe/benchmark/controls_test.go +++ b/internal/dataframe/benchmark/controls_test.go @@ -1,109 +1,56 @@ package benchmark -import ( - "context" - "strings" - "testing" - "time" -) +import "testing" -func timedSamples(backend, workload string, base float64, n int) []Sample { - var out []Sample - for i := 0; i < n; i++ { - s := sample(backend, workload, OutcomePassed, base+float64(i%2)*0.2, false) - s.Round = i / 2 - s.Iteration = i % 2 - out = append(out, s) - } - return out -} +// Measurement-integrity controls (review r4561031154): claims cite only a +// claimable regime (warm, or service-warm for attached arms) and only from +// cells the stability control accepted. -func TestControlUnstableCells(t *testing.T) { - stable := timedSamples(BackendPostgres, WorkloadFilterActiveGender, 10, 8) - if got := ControlUnstableCells(stable, StabilityCVLimit); len(got) != 0 { - t.Fatalf("tight samples must be stable, got %v", got) - } - // high-variance cell - var wild []Sample - for i := 0; i < 8; i++ { - ms := 5.0 - if i%2 == 0 { - ms = 500.0 - } - s := sample(BackendArango, WorkloadGroupCountByPatient, OutcomePassed, ms, false) - s.Round, s.Iteration = i/2, i%2 - wild = append(wild, s) - } - got := ControlUnstableCells(append(append([]Sample{}, stable...), wild...), StabilityCVLimit) - if len(got) != 1 || got[0] != WorkloadGroupCountByPatient+"/"+BackendArango { - t.Fatalf("expected only the wild cell unstable, got %v", got) - } - // too few samples: unstable, never silently trusted - thin := timedSamples(BackendArango, WorkloadDistinctFamily, 10, 2) - got = ControlUnstableCells(thin, StabilityCVLimit) - if len(got) != 1 { - t.Fatalf("2-sample cell must be reported unstable, got %v", got) - } - // warmup and failures are excluded from the timing set - filtered := append(timedSamples(BackendPostgres, WorkloadDistinctFamily, 10, 8), - sample(BackendPostgres, WorkloadDistinctFamily, OutcomePassed, 9000, true), - sample(BackendPostgres, WorkloadDistinctFamily, OutcomeTimeout, 9000, false)) - if got := ControlUnstableCells(filtered, StabilityCVLimit); len(got) != 0 { - t.Fatalf("warmup/failed samples must not poison stability, got %v", got) +func warmRows(arangoMedian, postgresMedian float64) []SummaryRow { + return []SummaryRow{ + {WorkloadID: "w", Backend: BackendArango, Cache: CacheWarm, Summary: Summary{Count: 5, MedianMillis: arangoMedian}}, + {WorkloadID: "w", Backend: BackendPostgres, Cache: CacheWarm, Summary: Summary{Count: 5, MedianMillis: postgresMedian}}, } } -func TestControlSelfRatio(t *testing.T) { - samples := append( - timedSamples(BackendPostgres, WorkloadFilterActiveGender, 10, 8), - timedSamples(BackendPostgres, WorkloadGroupCountByPatient, 20, 8)..., - ) - value, ok := ControlSelfRatio(samples, BackendPostgres) - if !ok || value < 1 || value > 1.05 { - t.Fatalf("self-ratio on stable halves must sit at 1±0.05, got %v ok=%v", value, ok) +func TestClaimsRequireClaimableRegime(t *testing.T) { + gates := []WorkloadGateReport{{WorkloadID: "w", Claimable: true}} + coldOnly := []SummaryRow{ + {WorkloadID: "w", Backend: BackendArango, Cache: CacheProcessCold, Summary: Summary{Count: 5, MedianMillis: 20}}, + {WorkloadID: "w", Backend: BackendPostgres, Cache: CacheProcessCold, Summary: Summary{Count: 5, MedianMillis: 10}}, } - // first half fast, second half slow: worst deviation must show up - var skewed []Sample - for i := 0; i < 8; i++ { - ms := 10.0 - if i >= 4 { - ms = 30.0 - } - s := sample(BackendArango, WorkloadFilterActiveGender, OutcomePassed, ms, false) - s.Round, s.Iteration = i/2, i%2 - skewed = append(skewed, s) + controls := ControlRuns{StabilityCVLimit: StabilityCVLimit} + if claims := buildClaims(gates, coldOnly, controls); len(claims) != 0 { + t.Fatalf("process-cold-only cells must never back claims, got %v", claims) } - value, ok = ControlSelfRatio(skewed, BackendArango) - if !ok || value < 2.5 { - t.Fatalf("skewed halves must yield a large self-ratio, got %v ok=%v", value, ok) + // One arm warm, the peer only process-cold: no pair, no claim. + split := []SummaryRow{warmRows(20, 10)[0], coldOnly[1]} + if claims := buildClaims(gates, split, controls); len(claims) != 0 { + t.Fatalf("a claim needs the claimable regime on both arms, got %v", claims) } - if _, ok := ControlSelfRatio(timedSamples(BackendArango, WorkloadFilterActiveGender, 10, 2), BackendArango); ok { - t.Fatal("2-sample cell cannot establish a self-ratio") + claims := buildClaims(gates, warmRows(20, 10), controls) + if len(claims) != 1 { + t.Fatalf("warm pair with passing gates must claim, got %v", claims) } - if _, ok := ControlSelfRatio(nil, BackendArango); ok { - t.Fatal("no cells must not report a self-ratio") + if claims[0].Ratio.ArangoOverPostgres != 2 || claims[0].Arango.MedianMillis != 20 || claims[0].Postgres.MedianMillis != 10 { + t.Fatalf("claim cites the wrong cells: %+v", claims[0]) } } -func TestControlEmptyFloor(t *testing.T) { - f := newFakeArm(BackendPostgres) - defer f.close() - arm := f.arm(BackendPostgres) - f.rows["empty-out"] = []map[string]any{} // a real server returns [], not null - median, err := arm.MeasureEmptyFloor(context.Background(), NewMonotonicClock(), "Bearer t", "proj", "bench", "receipt", "empty-out", 5) - if err != nil { - t.Fatal(err) - } - if median <= 0 { - t.Fatalf("floor median must be positive, got %v", median) - } - if _, err := arm.MeasureEmptyFloor(context.Background(), NewMonotonicClock(), "Bearer t", "proj", "bench", "receipt", "empty-out", 2); err == nil { - t.Fatal("fewer than 3 floor iterations must be refused") - } - // a floor request that returns rows is a protocol error - f.rows["empty-out"] = []map[string]any{{"patient_id": "P1"}} - if _, err := arm.MeasureEmptyFloor(context.Background(), NewMonotonicClock(), "Bearer t", "proj", "bench", "receipt", "empty-out", 5); err == nil || !strings.Contains(err.Error(), "not empty") { - t.Fatalf("non-empty floor request must fail closed, got %v", err) +func TestStabilityControlExcludesUnstableCellClaims(t *testing.T) { + gates := []WorkloadGateReport{{WorkloadID: "w", Claimable: true}} + rows := warmRows(20, 10) + claims := buildClaims(gates, rows, ControlRuns{StabilityCVLimit: StabilityCVLimit, UnstableCells: []string{"w/" + BackendPostgres}}) + if len(claims) != 0 { + t.Fatalf("an unstable cell excludes its workload's claims, got %v", claims) + } + // An unstable cell elsewhere does not touch this workload. + claims = buildClaims(gates, rows, ControlRuns{StabilityCVLimit: StabilityCVLimit, UnstableCells: []string{"other/" + BackendPostgres}}) + if len(claims) != 1 { + t.Fatalf("unrelated unstable cells must not suppress this claim, got %v", claims) + } + // Non-claimable gates never claim even with perfect cells. + if claims := buildClaims([]WorkloadGateReport{{WorkloadID: "w", Claimable: false}}, rows, ControlRuns{StabilityCVLimit: StabilityCVLimit}); len(claims) != 0 { + t.Fatalf("ratio-only-both-passed policy must hold, got %v", claims) } - _ = time.Now } diff --git a/internal/dataframe/benchmark/driver_test.go b/internal/dataframe/benchmark/driver_test.go index 85166cff1..e83887202 100644 --- a/internal/dataframe/benchmark/driver_test.go +++ b/internal/dataframe/benchmark/driver_test.go @@ -3,6 +3,9 @@ package benchmark import ( "context" "encoding/json" + "fmt" + "os" + "path/filepath" "strings" "testing" "time" @@ -244,3 +247,58 @@ func TestDriverRequiresRealSourceCommit(t *testing.T) { t.Fatal("short source commit must be refused") } } + +// TestDriverSpawnedArmsProduceClaims pins the operator lane end to end +// (review r4561031154): spawned arms restart every round, so the schedule +// must yield a warm cell per workload per arm and the report must carry +// claims. The dummy binary sleeps; the fake serves the arm routes and +// /readyz on the address the driver chose. +func TestDriverSpawnedArmsProduceClaims(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + dir := t.TempDir() + script := filepath.Join(dir, "dummy-arm.sh") + if err := os.WriteFile(script, []byte("#!/bin/sh\nexec sleep 300\n"), 0o755); err != nil { + t.Fatal(err) + } + opts := driverOptions(fa, fp, RunScopeBothArms) + opts.Config = RunConfig{Warmup: 1, Iterations: 10, Concurrency: 1, Rounds: 5} + opts.StderrDir = dir + opts.Arango = ArmConfig{BinaryPath: script, ConfigYAML: "listen: unused"} + opts.Postgres = ArmConfig{BinaryPath: script, ConfigYAML: "listen: unused"} + fakes := map[string]*fakeArm{BackendArango: fa, BackendPostgres: fp} + opts.Spawn = func(ctx context.Context, spec ServerSpec, secrets []string, listenAddr string) (*ServerProcess, error) { + f, ok := fakes[spec.Backend] + if !ok { + return nil, fmt.Errorf("no fake arm for backend %q", spec.Backend) + } + if err := f.serveOn(listenAddr); err != nil { + return nil, err + } + return SpawnServer(ctx, spec, secrets, listenAddr) + } + report, evidence, err := RunBenchmark(context.Background(), opts) + if err != nil { + t.Fatal(err) + } + if report.Status != "passed" { + t.Fatalf("spawned run must pass: %s %v", report.Status, report.StatusReasons) + } + if len(report.Claims) == 0 { + t.Fatal("spawned lane must produce claims from warm cells") + } + for _, claim := range report.Claims { + if claim.Arango.Count < 1 || claim.Postgres.Count < 1 { + t.Fatalf("claim cites empty cells: %+v", claim) + } + } + if len(evidence) != 2 { + t.Fatalf("evidence: %+v", evidence) + } + for _, e := range evidence { + if !e.Spawned || e.LogPath == "" { + t.Fatalf("both arms must be spawned with logs: %+v", e) + } + } +} diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go index 2e311cd85..068d2ae97 100644 --- a/internal/dataframe/benchmark/harness.go +++ b/internal/dataframe/benchmark/harness.go @@ -271,6 +271,20 @@ func MeasureArms(ctx context.Context, clock Clock, workloadID string, arango, po var samples []Sample finals := map[string]PreviewResult{} itersPerRound := opts.Config.Iterations / opts.Config.Rounds + // A spawned arm restarts before every round, so with one measured + // iteration per round the only measured sample is the post-restart + // process-cold one and the arm has no warm cell to claim (review + // r4561031154: such a run passes gates yet verifies as "passed run + // with no claims"). Fail before measuring rather than emit a report + // the verifier must reject. + if coldRestart != nil && itersPerRound < 2 { + for _, backend := range []string{BackendArango, BackendPostgres} { + if arm := arms[backend]; arm != nil && arm.Proc != nil { + return nil, nil, fmt.Errorf("benchmark: spawned arm %s restarts each round; one measured iteration per round yields only process-cold samples, so no warm cell can ever carry a claim: require Iterations >= 2*Rounds (got Iterations=%d, Rounds=%d)", + backend, opts.Config.Iterations, opts.Config.Rounds) + } + } + } runPhase := func(warmup bool, rounds, iters int) { iteration := 0 for round := 1; round <= rounds; round++ { diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index 0626a895a..09b7c025e 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "net" "net/http" "net/http/httptest" "os" @@ -33,6 +34,8 @@ type fakeArm struct { scopedToken string // when the bearer equals this, deny like production: zero rows explorerID string authoring []string // ordered create/builder/reconcile calls + handler http.Handler + extra []*http.Server // serveOn listeners, closed with the fake } func (f *fakeArm) publishedBodies() [][]byte { @@ -140,10 +143,28 @@ func newFakeArm(backend string) *fakeArm { f.mu.Unlock() _ = json.NewEncoder(w).Encode(map[string]any{"summary": map[string]any{"resources": map[string]any{"Patient": 12}}}) }) + mux.HandleFunc("/readyz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) f.server = httptest.NewServer(mux) + f.handler = mux return f } +// serveOn binds the fake's handler on an extra address, so a spawned arm's +// BaseURL (chosen by the driver from a free port) reaches the same routes +// the attached fake serves. +func (f *fakeArm) serveOn(listenAddr string) error { + ln, err := net.Listen("tcp", listenAddr) + if err != nil { + return err + } + srv := &http.Server{Handler: f.handler} + f.mu.Lock() + f.extra = append(f.extra, srv) + f.mu.Unlock() + go func() { _ = srv.Serve(ln) }() + return nil +} + func (f *fakeArm) arm(backend string) *Arm { return &Arm{Backend: backend, BaseURL: f.server.URL, Client: f.server.Client()} } @@ -173,7 +194,14 @@ func contributorCatalogFixture() map[string]any { } } -func (f *fakeArm) close() { f.server.Close() } +func (f *fakeArm) close() { + f.server.Close() + f.mu.Lock() + defer f.mu.Unlock() + for _, srv := range f.extra { + _ = srv.Close() + } +} func TestUploadAndPublishUsePublicRoutes(t *testing.T) { f := newFakeArm(BackendArango) @@ -648,3 +676,42 @@ func TestMeasureArmsFailedColdRestartIsLabeled(t *testing.T) { t.Fatalf("cold-restart failures=%d want 2 (one per round)", failures) } } + +func TestMeasureArmsRefusesSpawnedArmWithoutWarmIterationPerRound(t *testing.T) { + ready := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer ready.Close() + cold := func(ctx context.Context, arm *Arm) error { + if arm == nil || arm.Proc == nil { + return nil + } + return arm.Proc.Restart(ctx) + } + clock := &scriptedClock{base: time.Unix(1700000000, 0)} + opts := RunOptions{Project: "p", Generation: "g", Explorer: "w", UnrestrictedToken: "Bearer x", PreviewLimit: 100, + Config: RunConfig{Warmup: 1, Iterations: 2, Concurrency: 1, Rounds: 2}} // itersPerRound == 1 + rcpt := map[string]string{BackendArango: "r-a", BackendPostgres: "r-p"} + outs := map[string]string{BackendArango: "w", BackendPostgres: "w"} + + // A spawned arm with one measured iteration per round yields only the + // post-restart process-cold sample: no warm cell can ever back a claim + // (review r4561031154). Refuse before measuring. + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fa.rows["w"] = []map[string]any{{"patient_id": "p"}} + fp.rows["w"] = []map[string]any{{"patient_id": "p"}} + arango := fa.arm(BackendArango) + arango.Proc = spawnSleeperProcess(t, ready.URL, BackendArango) + if _, _, err := MeasureArms(context.Background(), clock, "w", arango, fp.arm(BackendPostgres), opts, rcpt, outs, cold); err == nil || + !strings.Contains(err.Error(), "Iterations >= 2*Rounds") { + t.Fatalf("spawned arm with one measured iteration per round must be refused, got %v", err) + } + + // The same schedule is legal for attached arms: they never restart, so + // every measured sample is service-warm and claimable. + if _, _, err := MeasureArms(context.Background(), clock, "w", fa.arm(BackendArango), fp.arm(BackendPostgres), opts, rcpt, outs, cold); err != nil { + t.Fatalf("attached arms keep the one-iteration-per-round schedule legal: %v", err) + } +} diff --git a/scripts/benchmark-backends.mjs b/scripts/benchmark-backends.mjs index 43550ecca..14229c2f0 100644 --- a/scripts/benchmark-backends.mjs +++ b/scripts/benchmark-backends.mjs @@ -15,7 +15,7 @@ // Node-only sanity pass over this script's own logic. Labelled // selftest; it can never satisfy a live check. // --paired --binary --out [--profile tiny|small|representative] -// [--iterations N>=5 --warmup N>=1 --rounds N] [--source-commit SHA] +// [--iterations N>=2*rounds --warmup N>=1 --rounds N>=5] [--source-commit SHA] // [--benchcli ] [--window-note ] --clickhouse-url // The operator-mandated comparison: the same matched battery runs // against BOTH real drivers (LOOM_TEST_ARANGO_URL + @@ -49,7 +49,7 @@ export const PAIRED_PROFILES = ['tiny', 'small', 'representative']; export function parseArgs(argv) { const flags = { mode: null, report: null, benchcli: null, out: null, runArgs: [], selftest: false, - paired: false, binary: null, profile: 'tiny', iterations: 5, warmup: 1, rounds: null, + paired: false, binary: null, profile: 'tiny', iterations: 10, warmup: 1, rounds: null, concurrency: 1, previewLimit: 1000, sourceCommit: null, seed: 7, windowNote: null, clickHouseUrl: null, clickHouseUser: null, clickHousePassword: null, clickHouseRecipe: fileURLToPath(new URL('../testdata/devloop-fixture/recipe.json', import.meta.url)), @@ -93,18 +93,22 @@ export function parseArgs(argv) { if (flags.mode === 'verify' && !flags.report) throw new Error('--verify-only requires --report'); if (flags.mode === 'run' && !flags.out) throw new Error('--run requires --out'); if (flags.paired) { - // Default: fully alternating — one measured iteration per round, so - // every driver's sample sits at the same position in the schedule. - if (flags.rounds === null) flags.rounds = flags.iterations; + // Spawned arms restart before each round, so a round yields one + // post-restart process-cold sample plus its warm remainder; the claimed + // cell is the warm one (review r4561031154). Default: five interleaved + // rounds, two measured iterations each. + if (flags.rounds === null) flags.rounds = PAIRED_MIN_SAMPLES_PER_BACKEND; if (!flags.out) throw new Error('--paired requires --out'); if (!flags.binary) throw new Error('--paired requires --binary (the server binary the arms are spawned from)'); if (!PAIRED_PROFILES.includes(flags.profile)) throw new Error(`--profile must be one of ${PAIRED_PROFILES.join(', ')}, got ${flags.profile}`); - if (flags.iterations < PAIRED_MIN_SAMPLES_PER_BACKEND) { - throw new Error(`--paired requires --iterations >= ${PAIRED_MIN_SAMPLES_PER_BACKEND}: fewer alternating runs per driver is not a comparison`); + if (flags.rounds < PAIRED_MIN_SAMPLES_PER_BACKEND) { + throw new Error(`--paired requires --rounds >= ${PAIRED_MIN_SAMPLES_PER_BACKEND}: fewer interleaved rounds per driver is not an alternating comparison`); } if (flags.warmup < 1) throw new Error('--paired requires --warmup >= 1 so cached timings are meaningful'); - if (flags.rounds < 1) throw new Error('--paired requires --rounds >= 1'); if (flags.iterations % flags.rounds !== 0) throw new Error(`--iterations ${flags.iterations} must split evenly into --rounds ${flags.rounds}`); + if (flags.iterations < 2 * flags.rounds) { + throw new Error(`--paired requires --iterations >= 2*--rounds: a spawned arm restarts each round, so each round needs the post-restart cold sample plus at least one warm sample (got --iterations ${flags.iterations} --rounds ${flags.rounds})`); + } if (!/^[0-9a-f]{40}$/.test(flags.sourceCommit ?? '')) throw new Error('--paired requires --source-commit as the full 40-hex SHA the arms were built from'); if (!(flags.clickHouseUrl ?? '').trim()) throw new Error('--paired requires --clickhouse-url: the publication chain is ClickHouse-backed end to end, and the battery cannot run without it'); if (!(flags.clickHouseRecipe ?? '').trim()) throw new Error('--paired requires a dataframer recipe for the ClickHouse-enabled arms'); diff --git a/scripts/benchmark-backends.test.mjs b/scripts/benchmark-backends.test.mjs index 9038804b2..4ddaa5d4f 100644 --- a/scripts/benchmark-backends.test.mjs +++ b/scripts/benchmark-backends.test.mjs @@ -257,7 +257,7 @@ test('paired: arango dsn gets a benchmark-namespaced database', () => { }); test('paired invocation: both arms, full chain, real iterations', () => { - const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', 'out', '--profile', 'tiny', '--iterations', '5', '--warmup', '1', '--rounds', '5', '--source-commit', SHA40, ...CH_ARGS]); + const flags = parseArgs(['--paired', '--binary', '/bin/loom', '--out', 'out', '--profile', 'tiny', '--iterations', '10', '--warmup', '1', '--rounds', '5', '--source-commit', SHA40, ...CH_ARGS]); const inv = buildPairedInvocation(flags, { arangoUrl: 'http://127.0.0.1:8529/loom_bench_ci', postgresDsn: 'postgresql://u:p@h:5432/loom_bench_paired' }); const joined = inv.args.join(' '); assert.match(joined, /--scope=both-arms/); @@ -267,8 +267,9 @@ test('paired invocation: both arms, full chain, real iterations', () => { assert.match(joined, /--clickhouse-recipe=\S+recipe\.json/); assert.match(joined, /--clickhouse-user=default/); assert.doesNotMatch(joined, /--clickhouse-password/); - assert.match(joined, /--iterations=5/); + assert.match(joined, /--iterations=10/); assert.match(joined, /--warmup=1/); + assert.match(joined, /--rounds=5/); assert.match(joined, /--arango-dsn=http:\/\/127\.0\.0\.1:8529\/loom_bench_ci/); }); @@ -305,6 +306,12 @@ test('paired flags refuse a non-comparison', () => { assert.throws(() => parseArgs(without('--binary')), /requires --binary/); assert.throws(() => parseArgs(without('--source-commit')), /source-commit/); assert.throws(() => parseArgs([...base, '--iterations', '3']), /integer >= 5/); + assert.throws(() => parseArgs([...base, '--rounds', '2']), /--rounds >= 5/); + assert.throws(() => parseArgs([...base, '--iterations', '5', '--rounds', '5']), /iterations >= 2\*--rounds/); + assert.throws(() => parseArgs([...base, '--iterations', '9', '--rounds', '5']), /split evenly/); + const defaulted = parseArgs(base); + assert.equal(defaulted.iterations, 10); + assert.equal(defaulted.rounds, 5); assert.throws(() => parseArgs([...base, '--warmup', '0']), /warmup/); assert.throws(() => parseArgs([...base, '--profile', 'smoke']), /--profile must be one of/); assert.throws(() => parseArgs(without('--clickhouse-url')), /requires --clickhouse-url/); From 4d2263d754fbbb9df54adbe9e39d311151a8e431 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 08:43:34 +0000 Subject: [PATCH 38/45] feat(benchmark): authoring publish verification, activation fail-closed, matched-state proof Ruling-B route-independent conditions, ahead of the seeding-path decision: - PublishReceipt (POST authoring/v2/publish) verifies its answer: receipt identity round-trips, revision identity exists, and every output is READY with a materialization identity (condition ii: materialization evidence consumed, never decorative). - doArmRequest wraps activation-style refusals (typed activation codes, activate-ExplorerConfigV2, retained-revision prose) in ErrActivationRefusal centrally, so no route can tolerate, retry, or special-case one (condition iii); detection is content-based, verified not to mislabel generic 5xx. - SetupProofEvidence + MatchedSetupProof give the pre-timing matched-state proof: content-derived draft digest equality, exact output-set match, materialization-count equality, and agreeing setup-preview rows across arms (condition i). Revision ids are recorded, not compared. Wiring into AuthorWorkload waits on the coordinator's seeding-path selection (command-replay vs store-backed seed route); these primitives survive either path. Fakes serve the two new routes. Suites green. --- .../dataframe/benchmark/authoringpublish.go | 216 ++++++++++++++++++ .../benchmark/authoringpublish_test.go | 151 ++++++++++++ internal/dataframe/benchmark/harness.go | 6 +- internal/dataframe/benchmark/harness_test.go | 54 +++++ 4 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 internal/dataframe/benchmark/authoringpublish.go create mode 100644 internal/dataframe/benchmark/authoringpublish_test.go diff --git a/internal/dataframe/benchmark/authoringpublish.go b/internal/dataframe/benchmark/authoringpublish.go new file mode 100644 index 000000000..d73688967 --- /dev/null +++ b/internal/dataframe/benchmark/authoringpublish.go @@ -0,0 +1,216 @@ +package benchmark + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "sort" + "strings" +) + +// Authoring-plane publication primitives shared by every setup route (the +// ruling-B conditions). They speak only public routes: the authoring +// publish, the active-state readback, and the pre-timing matched-state +// proof. No arm may reach the repository activation posture through them, +// and activation-style refusals are fatal wherever they appear (condition +// iii): the benchmark never tolerates, retries, or special-cases one. + +// ErrActivationRefusal marks an activation-style refusal answered by the +// server (typed 409/503 stances whose message is that a revision was +// retained, or that activation itself failed). The verifier contract is +// fail-closed: such a refusal aborts the run with this error wrapped, so a +// green report can never sit on a tolerated activation gap. +var ErrActivationRefusal = errors.New("benchmark: activation-style refusal (fail-closed, never tolerated)") + +// activationRefusalMarkers are the server's own typed codes and words for +// activation-stage refusals; matching is on codes and the retained-revision +// phrase, not on status codes (which overlap with retryable transport +// faults elsewhere). Codes are matched bare so JSON formatting drift cannot +// hide a refusal from the fail-closed posture. +var activationRefusalMarkers = []string{ + "GENERATION_ACTIVATION_FAILED", + "PUBLICATION_ACTIVATION_CONFLICT", + "RELEASE_ACTIVATION_FAILED", + "MATERIALIZATION_ACTIVATION_FAILED", + "activate ExplorerConfigV2 failed", + "prior Explorer revision was retained", +} + +func activationRefusalIn(body string) bool { + for _, marker := range activationRefusalMarkers { + if strings.Contains(body, marker) { + return true + } + } + return false +} + +// PublicationOutput is one materialized output as the authoring publish +// route answers it. +type PublicationOutput struct { + OutputID string `json:"outputId"` + State string `json:"state"` + MaterializationID string `json:"materializationId"` +} + +// AuthoringPublication is the verified response of the authoring publish +// route: identity fields must all be present, and every output must be +// READY with a materialization identity (condition ii: materialization +// evidence is consumed, never decorative). +type AuthoringPublication struct { + ReceiptID string `json:"receiptId"` + RevisionID string `json:"revisionId"` + State string `json:"state"` + Outputs []PublicationOutput `json:"outputs"` + Diagnostics []any `json:"diagnostics"` +} + +// PublishReceipt publishes a reconciled receipt through the authoring +// route (POST explorers/{id}/authoring/v2/publish) on this arm and +// verifies the answer: the receipt identity must round-trip, the revision +// identity must exist, and every output must carry a READY materialization +// identity. Any shortfall is an error before the caller can use the +// receipt for anything. +func (a *Arm) PublishReceipt(ctx context.Context, token, project, explorer, receiptID string) (AuthoringPublication, error) { + endpoint := a.scoped(a.authoringURL(project, explorer) + "/publish") + raw, err := json.Marshal(map[string]any{"receiptId": receiptID}) + if err != nil { + return AuthoringPublication{}, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(raw)) + if err != nil { + return AuthoringPublication{}, err + } + req.Header.Set("Content-Type", "application/json") + a.authed(req, token) + value, err := doArmRequest(a.Client, req) + if err != nil { + return AuthoringPublication{}, fmt.Errorf("benchmark: authoring publish on %s: %w", a.Backend, err) + } + encoded, _ := json.Marshal(value) + var pub AuthoringPublication + if err := json.Unmarshal(encoded, &pub); err != nil { + return AuthoringPublication{}, fmt.Errorf("benchmark: authoring publish shape on %s: %w", a.Backend, err) + } + if pub.ReceiptID != receiptID { + return AuthoringPublication{}, fmt.Errorf("benchmark: authoring publish on %s answered receipt %q for reconcile %q", a.Backend, pub.ReceiptID, receiptID) + } + if strings.TrimSpace(pub.RevisionID) == "" { + return AuthoringPublication{}, fmt.Errorf("benchmark: authoring publish on %s omitted the revision identity", a.Backend) + } + if strings.TrimSpace(pub.State) == "" { + return AuthoringPublication{}, fmt.Errorf("benchmark: authoring publish on %s omitted the revision state", a.Backend) + } + if len(pub.Outputs) == 0 { + return AuthoringPublication{}, fmt.Errorf("benchmark: authoring publish on %s answered no materialized outputs", a.Backend) + } + for _, out := range pub.Outputs { + if strings.TrimSpace(out.OutputID) == "" || strings.TrimSpace(out.MaterializationID) == "" { + return AuthoringPublication{}, fmt.Errorf("benchmark: authoring publish on %s answered output %q without output/materialization identity", a.Backend, out.OutputID) + } + if out.State != "READY" { + return AuthoringPublication{}, fmt.Errorf("benchmark: authoring publish on %s left output %s in state %q, want READY", a.Backend, out.OutputID, out.State) + } + } + return pub, nil +} + +// ExplorerActiveState is the matched-state readback: the stable identity +// fields the explorer route answers for a published interactive explorer. +// Timestamps are excluded on purpose; digests are content-derived, so +// cross-arm equality is the setup-equivalence proof. +type ExplorerActiveState struct { + DraftDigest string `json:"draftDigest"` + ActiveRevisionID string `json:"activeRevisionId"` +} + +// FetchExplorerActiveState reads the explorer's active runtime projection +// for the matched-state proof. +func (a *Arm) FetchExplorerActiveState(ctx context.Context, token, project, explorerID string) (ExplorerActiveState, error) { + endpoint := a.scoped(a.BaseURL + "/api/v1/projects/" + url.PathEscape(project) + "/explorers/" + url.PathEscape(explorerID)) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return ExplorerActiveState{}, err + } + a.authed(req, token) + value, err := doArmRequest(a.Client, req) + if err != nil { + return ExplorerActiveState{}, fmt.Errorf("benchmark: explorer readback on %s: %w", a.Backend, err) + } + raw, _ := json.Marshal(value) + var state ExplorerActiveState + if err := json.Unmarshal(raw, &state); err != nil { + return ExplorerActiveState{}, fmt.Errorf("benchmark: explorer readback shape on %s: %w", a.Backend, err) + } + return state, nil +} + +// SetupProof is everything the pre-timing matched-state proof compares for +// one workload on one arm. +type SetupProof struct { + DraftDigest string + RevisionID string + OutputIDs []string + Materializations int + PreviewRows int +} + +// SetupProofEvidence publishes the receipt, reads back the active state, +// and runs one untimed setup preview on this arm. Publish failures +// (including activation-style refusals) abort here; nothing downstream of +// a divergent setup may be timed. +func (a *Arm) SetupProofEvidence(ctx context.Context, token, project, explorerID, receiptID, outputID string) (SetupProof, error) { + pub, err := a.PublishReceipt(ctx, token, project, explorerID, receiptID) + if err != nil { + return SetupProof{}, err + } + state, err := a.FetchExplorerActiveState(ctx, token, project, explorerID) + if err != nil { + return SetupProof{}, err + } + if strings.TrimSpace(state.DraftDigest) == "" { + return SetupProof{}, fmt.Errorf("benchmark: explorer readback on %s omitted the draft digest", a.Backend) + } + preview, err := a.ExecuteOutput(ctx, token, project, explorerID, receiptID, outputID, 1) + if err != nil { + return SetupProof{}, fmt.Errorf("benchmark: setup preview on %s: %w", a.Backend, err) + } + proof := SetupProof{DraftDigest: state.DraftDigest, RevisionID: state.ActiveRevisionID, PreviewRows: preview.RowCount, Materializations: len(pub.Outputs)} + for _, out := range pub.Outputs { + proof.OutputIDs = append(proof.OutputIDs, out.OutputID) + } + sort.Strings(proof.OutputIDs) + return proof, nil +} + +// MatchedSetupProof compares the two arms' pre-timing evidence +// (condition i). The draft digest is the content-equivalence proof; the +// output set must match exactly and every arm must carry a materialization +// identity per output; the setup preview rows must agree. Revision ids are +// recorded by the caller but not compared (server-internal identity +// composition may differ across backends). +func MatchedSetupProof(workloadID string, arango, postgres SetupProof) error { + if arango.DraftDigest == "" || postgres.DraftDigest == "" { + return fmt.Errorf("benchmark: %s matched-state proof missing draft digest", workloadID) + } + if arango.DraftDigest != postgres.DraftDigest { + return fmt.Errorf("benchmark: %s matched-state proof diverged: draft digest %s != %s", workloadID, arango.DraftDigest, postgres.DraftDigest) + } + if len(arango.OutputIDs) != len(postgres.OutputIDs) || arango.Materializations != len(arango.OutputIDs) || postgres.Materializations != len(postgres.OutputIDs) { + return fmt.Errorf("benchmark: %s matched-state proof output/materialization counts differ: arango %d/%d, postgres %d/%d", + workloadID, len(arango.OutputIDs), arango.Materializations, len(postgres.OutputIDs), postgres.Materializations) + } + for i := range arango.OutputIDs { + if arango.OutputIDs[i] != postgres.OutputIDs[i] { + return fmt.Errorf("benchmark: %s matched-state proof output set diverged at %d: %s != %s", workloadID, i, arango.OutputIDs[i], postgres.OutputIDs[i]) + } + } + if arango.PreviewRows != postgres.PreviewRows { + return fmt.Errorf("benchmark: %s matched-state proof setup preview rows differ: arango %d, postgres %d", workloadID, arango.PreviewRows, postgres.PreviewRows) + } + return nil +} diff --git a/internal/dataframe/benchmark/authoringpublish_test.go b/internal/dataframe/benchmark/authoringpublish_test.go new file mode 100644 index 000000000..dd8730c3b --- /dev/null +++ b/internal/dataframe/benchmark/authoringpublish_test.go @@ -0,0 +1,151 @@ +package benchmark + +import ( + "context" + "errors" + "strings" + "testing" +) + +// Ruling-B conditions: the authoring publish must consume its materialization +// evidence (ii), activation-style refusals are fatal everywhere (iii), and +// the two arms must prove matched state before any timing (i). + +func TestPublishReceiptVerifiesMaterializationIdentity(t *testing.T) { + f := newFakeArm(BackendArango) + defer f.close() + arm := f.arm(BackendArango) + pub, err := arm.PublishReceipt(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-out") + if err != nil { + t.Fatal(err) + } + if pub.ReceiptID != "rcpt-out" || pub.RevisionID == "" || len(pub.Outputs) != 1 || pub.Outputs[0].MaterializationID == "" { + t.Fatalf("verified publication lost identity: %+v", pub) + } + calls := f.calls + found := false + for _, c := range calls { + if c == BackendArango+":publish:rcpt-out" { + found = true + } + } + if !found { + t.Fatalf("publish route not called: %v", calls) + } + + // A READY-less output is a hard failure: materialization evidence must + // be consumed, never accepted decoratively. + f.mu.Lock() + f.publishOutputs = []PublicationOutput{{OutputID: "out", State: "PENDING", MaterializationID: "mat-1"}} + f.mu.Unlock() + if _, err := arm.PublishReceipt(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-out"); err == nil || !strings.Contains(err.Error(), "PENDING") { + t.Fatalf("non-READY output must fail: %v", err) + } + f.mu.Lock() + f.publishOutputs = []PublicationOutput{{OutputID: "out", State: "READY"}} + f.mu.Unlock() + if _, err := arm.PublishReceipt(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-out"); err == nil || !strings.Contains(err.Error(), "materialization identity") { + t.Fatalf("output without materialization id must fail: %v", err) + } + f.mu.Lock() + f.publishOutputs = nil + f.publishEchoBad = true + f.mu.Unlock() + if _, err := arm.PublishReceipt(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-out"); err == nil || !strings.Contains(err.Error(), "for reconcile") { + t.Fatalf("receipt identity mismatch must fail: %v", err) + } + f.mu.Lock() + f.publishEchoBad = false + f.publishNoOutputs = true + f.mu.Unlock() + if _, err := arm.PublishReceipt(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-out"); err == nil || !strings.Contains(err.Error(), "no materialized outputs") { + t.Fatalf("zero outputs must fail: %v", err) + } +} + +func TestActivationRefusalIsFatalOnEveryRoute(t *testing.T) { + activationBody := `{"error":{"code":"GENERATION_ACTIVATION_FAILED","message":"activate ExplorerConfigV2 failed; the prior Explorer revision was retained"}}` + f := newFakeArm(BackendPostgres) + defer f.close() + f.mu.Lock() + f.activationBody = activationBody + f.mu.Unlock() + arm := f.arm(BackendPostgres) + + _, err := arm.PublishReceipt(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-1") + if !errors.Is(err, ErrActivationRefusal) { + t.Fatalf("authoring publish must fail closed on an activation-style refusal: %v", err) + } + // the repository workspace route wraps it too: no lane can tolerate one + workspace := []byte(`{"kind":"explorer-builder"}`) + if _, err := arm.PublishWorkspace(context.Background(), "Bearer x", "proj", "gen-1", workspace, "a"); !errors.Is(err, ErrActivationRefusal) { + t.Fatalf("repository publish must fail closed on the same refusal: %v", err) + } + // a plain scripted failure (no activation marker) must NOT be labeled as + // an activation refusal: the detection is content-based, not status-based + f.mu.Lock() + f.activationBody = "" + f.failWith["anyout"] = 503 + f.mu.Unlock() + if _, err := arm.ExecuteOutput(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-1", "anyout", 10); errors.Is(err, ErrActivationRefusal) { + t.Fatal("generic 503 must not be mislabeled as an activation refusal") + } +} + +func TestMatchedSetupProofCatchesDivergence(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fa.rows["out"] = []map[string]any{{"patient_id": "p1"}} + fp.rows["out"] = []map[string]any{{"patient_id": "p1"}} + + proof := func(f *fakeArm, backend string) SetupProof { + t.Helper() + p, err := f.arm(backend).SetupProofEvidence(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-out", "out") + if err != nil { + t.Fatal(err) + } + return p + } + pa, pp := proof(fa, BackendArango), proof(fp, BackendPostgres) + if err := MatchedSetupProof("w", pa, pp); err != nil { + t.Fatalf("matched arms must prove: %v", err) + } + if pa.PreviewRows != 1 || len(pa.OutputIDs) != 1 || pa.DraftDigest == "" { + t.Fatalf("proof lost content: %+v", pa) + } + + // draft digest divergence (the content-equivalence proof) + bad := pp + bad.DraftDigest = "sha256:other" + if err := MatchedSetupProof("w", pa, bad); err == nil || !strings.Contains(err.Error(), "draft digest") { + t.Fatalf("digest divergence must fail: %v", err) + } + // row divergence before timing + bad = pp + bad.PreviewRows = 2 + if err := MatchedSetupProof("w", pa, bad); err == nil || !strings.Contains(err.Error(), "preview rows") { + t.Fatalf("row divergence must fail: %v", err) + } + // output-set divergence + bad = pp + bad.OutputIDs = []string{"out-other"} + bad.Materializations = 1 + if err := MatchedSetupProof("w", pa, bad); err == nil || !strings.Contains(err.Error(), "output set") { + t.Fatalf("output set divergence must fail: %v", err) + } + // materialization count must match the output set (decorative evidence + // cannot pass) + bad = pp + bad.Materializations = 0 + if err := MatchedSetupProof("w", pa, bad); err == nil || !strings.Contains(err.Error(), "output/materialization counts") { + t.Fatalf("decorative materialization count must fail: %v", err) + } + // a real cross-arm readback divergence surfaces through SetupProofEvidence + fp.mu.Lock() + fp.activeDigest = "" + fp.mu.Unlock() + if _, err := fp.arm(BackendPostgres).SetupProofEvidence(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-out", "out"); err != nil { + t.Fatalf("default digest script must answer: %v", err) + } +} diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go index 068d2ae97..0cb5658c1 100644 --- a/internal/dataframe/benchmark/harness.go +++ b/internal/dataframe/benchmark/harness.go @@ -223,7 +223,11 @@ func doArmRequest(client *http.Client, req *http.Request) (map[string]any, error return nil, err } if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return nil, fmt.Errorf("route %s returned %d: %s", req.URL.Path, resp.StatusCode, truncateToken(string(data))) + body := truncateToken(string(data)) + if activationRefusalIn(string(data)) { + return nil, fmt.Errorf("%w: route %s returned %d: %s", ErrActivationRefusal, req.URL.Path, resp.StatusCode, body) + } + return nil, fmt.Errorf("route %s returned %d: %s", req.URL.Path, resp.StatusCode, body) } var value map[string]any dec := json.NewDecoder(bytes.NewReader(data)) diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index 09b7c025e..d19780a12 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -36,6 +36,14 @@ type fakeArm struct { authoring []string // ordered create/builder/reconcile calls handler http.Handler extra []*http.Server // serveOn listeners, closed with the fake + // authoring publish + active-state readback scripts + publishOutputs []PublicationOutput + publishState string + publishEchoBad bool + publishNoOutputs bool // answers with an empty outputs array + activeDigest string + activeRevision string + activationBody string // when set, publish/explorer-config answer 503 with this body } func (f *fakeArm) publishedBodies() [][]byte { @@ -92,6 +100,13 @@ func newFakeArm(backend string) *fakeArm { _ = json.NewEncoder(w).Encode(state) return case strings.Contains(path, "/explorer-config"): + f.mu.Lock() + activation := f.activationBody + f.mu.Unlock() + if activation != "" { + http.Error(w, activation, http.StatusServiceUnavailable) + return + } body, _ := io.ReadAll(r.Body) f.mu.Lock() f.publish++ @@ -99,6 +114,45 @@ func newFakeArm(backend string) *fakeArm { f.mu.Unlock() _ = json.NewEncoder(w).Encode(map[string]any{"recipe": "bench", "translationVersion": "t1", "receiptId": "revpub-1", "revisionId": "rev-1", "executionId": "exec-1", "activated": true}) return + case strings.HasSuffix(path, "/authoring/v2/publish") && r.Method == http.MethodPost: + f.mu.Lock() + activation := f.activationBody + f.mu.Unlock() + if activation != "" { + http.Error(w, activation, http.StatusServiceUnavailable) + return + } + var body map[string]any + _ = json.NewDecoder(r.Body).Decode(&body) + receiptID, _ := body["receiptId"].(string) + f.mu.Lock() + f.calls = append(f.calls, fmt.Sprintf("%s:publish:%s", backend, receiptID)) + outs, state, echo, noOuts := f.publishOutputs, f.publishState, f.publishEchoBad, f.publishNoOutputs + f.mu.Unlock() + if state == "" { + state = "ACTIVE" + } + answer := receiptID + if echo { + answer = "receipt-other" + } + if len(outs) == 0 && !noOuts { + outs = []PublicationOutput{{OutputID: "out", State: "READY", MaterializationID: "mat-" + receiptID}} + } + _ = json.NewEncoder(w).Encode(map[string]any{"receiptId": answer, "revisionId": "authoring-test", "state": state, "outputs": outs}) + return + case r.Method == http.MethodGet && strings.Contains(path, "/explorers/") && !strings.Contains(path[strings.LastIndex(path, "/explorers/")+len("/explorers/"):], "/"): + f.mu.Lock() + digest, rev := f.activeDigest, f.activeRevision + f.mu.Unlock() + if digest == "" { + digest = "sha256:active-default" + } + if rev == "" { + rev = "authoring-active-default" + } + _ = json.NewEncoder(w).Encode(map[string]any{"draftDigest": digest, "activeRevisionId": rev}) + return case strings.Contains(path, "/authoring/v2/preview"): var body map[string]any _ = json.NewDecoder(r.Body).Decode(&body) From 6499633094359f71cdd40d81174735185f67d164 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 08:45:21 +0000 Subject: [PATCH 39/45] refactor(benchmark): key activation-refusal detection on codes and adapter posture, not prose Coordinator ruling (2026-11-13, Option A sequence): the fail-closed detector keys on the typed activation codes plus the structured CodeBackendUnavailable + stage=adapter posture (postgreSQLRuntime Unbiased shape in runtime.go), never on message prose. Prose-only bodies with no typed code are ordinary failures; the paired 503 shape is still detected via GENERATION_ACTIVATION_FAILED, and the remaining named refusal (ValidateSelectionReferences) is detected via the adapter posture. Tests pin all three cases. --- .../dataframe/benchmark/authoringpublish.go | 33 ++++++++++++++----- .../benchmark/authoringpublish_test.go | 24 ++++++++++++-- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/internal/dataframe/benchmark/authoringpublish.go b/internal/dataframe/benchmark/authoringpublish.go index d73688967..c0303804d 100644 --- a/internal/dataframe/benchmark/authoringpublish.go +++ b/internal/dataframe/benchmark/authoringpublish.go @@ -26,18 +26,30 @@ import ( // green report can never sit on a tolerated activation gap. var ErrActivationRefusal = errors.New("benchmark: activation-style refusal (fail-closed, never tolerated)") -// activationRefusalMarkers are the server's own typed codes and words for -// activation-stage refusals; matching is on codes and the retained-revision -// phrase, not on status codes (which overlap with retryable transport -// faults elsewhere). Codes are matched bare so JSON formatting drift cannot -// hide a refusal from the fail-closed posture. +// activationRefusalMarkers are the server's own typed codes for +// activation-stage refusals; matching is on codes and the structured +// adapter posture, never on message prose (coordinator ruling 2026-11-13). +// Codes are matched bare so JSON formatting drift cannot hide a refusal +// from the fail-closed posture. PUBLICATION_ACTIVATION_CONFLICT and +// RELEASE_ACTIVATION_FAILED are deterministic contract failures: losing +// them means the harness setup violated a contract, so they are fatal, not +// retried. var activationRefusalMarkers = []string{ "GENERATION_ACTIVATION_FAILED", "PUBLICATION_ACTIVATION_CONFLICT", "RELEASE_ACTIVATION_FAILED", "MATERIALIZATION_ACTIVATION_FAILED", - "activate ExplorerConfigV2 failed", - "prior Explorer revision was retained", +} + +// adapterRefusalMarkers together identify the typed named refusal the +// PostgreSQL runtime answers for capabilities with no adapter (postgreSQL +// RuntimeUnavailable: CodeBackendUnavailable + details stage=adapter). +// Both must appear: the code alone could in principle ride an unrelated +// envelope, and the stage detail alone is not an identity. +var adapterRefusalMarkers = []string{ + "BACKEND_UNAVAILABLE", + `"stage"`, + `"adapter"`, } func activationRefusalIn(body string) bool { @@ -46,7 +58,12 @@ func activationRefusalIn(body string) bool { return true } } - return false + for _, marker := range adapterRefusalMarkers { + if !strings.Contains(body, marker) { + return false + } + } + return true } // PublicationOutput is one materialized output as the authoring publish diff --git a/internal/dataframe/benchmark/authoringpublish_test.go b/internal/dataframe/benchmark/authoringpublish_test.go index dd8730c3b..205c55d9b 100644 --- a/internal/dataframe/benchmark/authoringpublish_test.go +++ b/internal/dataframe/benchmark/authoringpublish_test.go @@ -81,8 +81,28 @@ func TestActivationRefusalIsFatalOnEveryRoute(t *testing.T) { if _, err := arm.PublishWorkspace(context.Background(), "Bearer x", "proj", "gen-1", workspace, "a"); !errors.Is(err, ErrActivationRefusal) { t.Fatalf("repository publish must fail closed on the same refusal: %v", err) } - // a plain scripted failure (no activation marker) must NOT be labeled as - // an activation refusal: the detection is content-based, not status-based + + // the typed adapter posture (CodeBackendUnavailable + stage=adapter + // details) is the structural key (coordinator ruling: not prose) + f.mu.Lock() + f.activationBody = `{"error":{"code":"BACKEND_UNAVAILABLE","message":"Explicit-selection reference validation has no PostgreSQL adapter yet","retryable":false,"details":{"backend":"postgresql","stage":"adapter"}}}` + f.mu.Unlock() + if _, err := arm.PublishReceipt(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-1"); !errors.Is(err, ErrActivationRefusal) { + t.Fatalf("adapter-posture refusal must fail closed: %v", err) + } + + // message prose alone is no longer a key: a body carrying the old + // retained-revision wording without any typed code is an ordinary + // failure, not an activation refusal (ruling: key on codes/structure) + f.mu.Lock() + f.activationBody = `{"error":{"code":"SCRIPTED_NOTHING","message":"the prior Explorer revision was retained"}}` + f.mu.Unlock() + if _, err := arm.PublishReceipt(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-1"); errors.Is(err, ErrActivationRefusal) { + t.Fatal("prose without a typed code must not be labeled an activation refusal") + } + + // a plain scripted failure (no markers at all) must NOT be labeled as + // an activation refusal either f.mu.Lock() f.activationBody = "" f.failWith["anyout"] = 503 From 7fe53cef4ec04c046a3fcdb889e6ebf5818afb32 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 09:27:36 +0000 Subject: [PATCH 40/45] fix(benchmark): wire setup proof and authoring publish into the live driver setup path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review r4561644037: PublishReceipt, FetchExplorerActiveState, SetupProofEvidence, and MatchedSetupProof were referenced only by their own tests — the driver authored, reconciled, and went straight to the authorization gate and MeasureArms, so ruling conditions (i) and (ii) were enforced only against a fake. RunBenchmark now, per live workload: after AuthorWorkload on every arm, each arm publishes its reconciled receipt through the authoring route (untimed setup; every output must answer READY with a materialization identity) and reads back its active state; timing starts only after MatchedSetupProof passes — equal content-derived draft digest, equal output set with a materialization identity each, equal setup-preview rows. Divergence aborts with no report and no samples. Activation-style refusals on the new route abort fail-closed through the central doArmRequest keying. The repository explorer-config spine is unchanged (Option A); the authoring publish is added setup verification, same route and payloads on both arms. Single-arm lanes keep per-arm verification; cross-arm equality applies when both arms run. Tests: both-arms protocol now asserts one authoring publish per live workload per arm; a digest-divergence test proves the abort happens before timing (only the two first-workload proof previews may exist). README documents the pre-timing proof and the fail-closed refusal posture. gofmt/node/benchmark suites green; internal/ci green except the pre-existing shallow-checkout vet probe (CI-depth unaffected). --- internal/dataframe/benchmark/README.md | 10 ++- .../dataframe/benchmark/authoringpublish.go | 16 ++-- internal/dataframe/benchmark/driver.go | 21 +++++ internal/dataframe/benchmark/driver_test.go | 83 +++++++++++++++++++ internal/dataframe/benchmark/harness_test.go | 8 ++ 5 files changed, 131 insertions(+), 7 deletions(-) diff --git a/internal/dataframe/benchmark/README.md b/internal/dataframe/benchmark/README.md index d4b3250c2..830bed531 100644 --- a/internal/dataframe/benchmark/README.md +++ b/internal/dataframe/benchmark/README.md @@ -115,7 +115,15 @@ the rule benchcli enforces) via `benchcli drop-pg`/`setup-pg` (database, six-series schema ladder, migrated tables) — the PostgreSQL backend readiness answers `schema_missing ... the caller owns schema lifecycle`, so the lane, not the server, bootstraps. A failed `setup-pg` fails the -check before any timing attempt. +check before any timing attempt. Before any timing starts, each arm +publishes its reconciled receipt through the authoring route (untimed +setup; every output must answer READY with a materialization identity) and +the arms must prove matched state — equal content-derived draft digest, +equal output set, equal setup-preview rows — or the run aborts with no +report. Activation-style refusals (`GENERATION_ACTIVATION_FAILED`, the +typed `BACKEND_UNAVAILABLE` + `stage=adapter` posture, and siblings) are +fatal centrally in `doArmRequest` and are never tolerated, retried, or +special-cased on any route. Correctness gates decide first; then >= 5 interleaved measured rounds per driver are required, each round contributing the post-restart process-cold sample plus at least one warm sample (`--iterations >= diff --git a/internal/dataframe/benchmark/authoringpublish.go b/internal/dataframe/benchmark/authoringpublish.go index c0303804d..8fb9ff5ee 100644 --- a/internal/dataframe/benchmark/authoringpublish.go +++ b/internal/dataframe/benchmark/authoringpublish.go @@ -12,12 +12,16 @@ import ( "strings" ) -// Authoring-plane publication primitives shared by every setup route (the -// ruling-B conditions). They speak only public routes: the authoring -// publish, the active-state readback, and the pre-timing matched-state -// proof. No arm may reach the repository activation posture through them, -// and activation-style refusals are fatal wherever they appear (condition -// iii): the benchmark never tolerates, retries, or special-cases one. +// Authoring-plane publication primitives for the ruling setup conditions, +// wired into the live setup path by RunBenchmark (review r4561644037): per +// workload and per arm it calls SetupProofEvidence after authoring and +// gates all timing behind MatchedSetupProof, so the authoring publish, the +// materialization-identity verification, the active-state readback, and the +// pre-timing matched-state proof execute on every timed lane, not only in +// unit tests. They speak only public routes. No arm may reach the +// repository activation posture through them, and activation-style refusals +// are fatal wherever they appear: the benchmark never tolerates, retries, +// or special-cases one. // ErrActivationRefusal marks an activation-style refusal answered by the // server (typed 409/503 stances whose message is that a revision was diff --git a/internal/dataframe/benchmark/driver.go b/internal/dataframe/benchmark/driver.go index 745244e08..e9f377e28 100644 --- a/internal/dataframe/benchmark/driver.go +++ b/internal/dataframe/benchmark/driver.go @@ -309,6 +309,27 @@ func RunBenchmark(ctx context.Context, opts DriverOptions) (*Report, []ArmEviden receiptIDs[p.backend][spec.ID] = rid outputIDs[p.backend][spec.ID] = outputID } + // Pre-timing setup proof (ruling conditions i+ii, wired per review + // r4561644037): every arm publishes its reconciled receipt through + // the authoring route with the materialization identity verified + // (untimed setup; activation-style refusals abort fail-closed), and + // no timing may start until the arms prove matched state: equal + // content-derived draft digest, equal output set with a + // materialization identity each, and equal setup-preview rows. + proofs := map[string]SetupProof{} + for _, p := range participants { + proof, err := arms[p.backend].arm.SetupProofEvidence(ctx, opts.UnrestrictedToken, opts.Params.Project, + explorerID, receiptIDs[p.backend][spec.ID], outputID) + if err != nil { + return nil, nil, fmt.Errorf("benchmark: %s setup proof on %s: %w", spec.ID, p.backend, err) + } + proofs[p.backend] = proof + } + if len(participants) == 2 { + if err := MatchedSetupProof(spec.ID, proofs[BackendArango], proofs[BackendPostgres]); err != nil { + return nil, nil, err + } + } } // One explorer identity serves both arms (asserted equal above). explorerIDByWorkload := perBackend[participants[0].backend] diff --git a/internal/dataframe/benchmark/driver_test.go b/internal/dataframe/benchmark/driver_test.go index e83887202..88785bb47 100644 --- a/internal/dataframe/benchmark/driver_test.go +++ b/internal/dataframe/benchmark/driver_test.go @@ -302,3 +302,86 @@ func TestDriverSpawnedArmsProduceClaims(t *testing.T) { } } } + +// TestDriverRunsSetupProofOnBothArms pins the wiring (review r4561644037): +// the live setup path must publish every reconciled receipt through the +// authoring route on both arms and prove matched state before timing. +func TestDriverRunsSetupProofOnBothArms(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + report, _, err := RunBenchmark(context.Background(), driverOptions(fa, fp, RunScopeBothArms)) + if err != nil { + t.Fatal(err) + } + if report.Status != "passed" { + t.Fatalf("matched lane must pass: %s %v", report.Status, report.StatusReasons) + } + live := 0 + for _, spec := range Battery { + if _, excluded := LiveBatteryExclusions()[spec.ID]; !excluded { + live++ + } + } + for name, f := range map[string]*fakeArm{"arango": fa, "postgres": fp} { + publishes := 0 + for _, c := range f.routeCalls() { + if strings.Contains(c, ":publish:") { + publishes++ + } + } + if publishes != live { + t.Fatalf("%s: authoring publishes=%d want one per live workload (%d)", name, publishes, live) + } + } +} + +func firstLiveWorkload(t *testing.T) string { + t.Helper() + ex := LiveBatteryExclusions() + for _, spec := range Battery { + if _, bad := ex[spec.ID]; !bad { + return spec.ID + } + } + t.Fatal("no live workload") + return "" +} + +// TestDriverAbortsBeforeTimingOnDivergentSetupProof: a cross-arm draft +// digest divergence must abort the run with no report and no samples; +// timing may never start on a divergent setup. +func TestDriverAbortsBeforeTimingOnDivergentSetupProof(t *testing.T) { + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fa.mu.Lock() + fa.activeDigest = "sha256:arango-only" + fa.mu.Unlock() + report, _, err := RunBenchmark(context.Background(), driverOptions(fa, fp, RunScopeBothArms)) + if err == nil || !strings.Contains(err.Error(), "matched-state proof diverged") { + t.Fatalf("digest divergence must abort before timing: %v", err) + } + if report != nil { + t.Fatal("no report may exist when the matched-state proof fails") + } + // the only preview calls allowed are the two proof previews themselves + // (one per arm, first live workload); any measured-phase, gate, or + // later-workload preview would mean timing started on a divergent setup + previews := 0 + want := ":preview:" + OutputIDFor(firstLiveWorkload(t)) + for _, f := range []*fakeArm{fa, fp} { + for _, c := range f.routeCalls() { + if !strings.Contains(c, ":preview:") { + continue + } + previews++ + if !strings.HasSuffix(c, want) { + t.Fatalf("preview route ran beyond the first workload's setup proof: %s", c) + } + } + } + if previews != 2 { + t.Fatalf("proof previews=%d want 2 (one per arm, first workload), got no proof or timing ran", previews) + } +} diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index d19780a12..a8c3175d6 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -62,6 +62,14 @@ func (f *fakeArm) authoringCalls() []string { return append([]string(nil), f.authoring...) } +// routeCalls exposes the ordered "backend:route:key" call log, so driver +// tests can assert which routes a lane actually exercised. +func (f *fakeArm) routeCalls() []string { + f.mu.Lock() + defer f.mu.Unlock() + return append([]string(nil), f.calls...) +} + func newFakeArm(backend string) *fakeArm { f := &fakeArm{rows: map[string][]map[string]any{}, columns: []string{"patient_id", "gender"}, failWith: map[string]int{}} mux := http.NewServeMux() From 2f455ef3b42fc90c75c0e2088ddf3bcaddbb7723 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 10:24:20 +0000 Subject: [PATCH 41/45] fix(benchmark): verifier recomputes control values; stability CV judged on steady regime Review r4561644049: VerifyReport accepted SelfRatioArango/ SelfRatioPostgres, UnstableCells, and StabilityCVLimit on faith -- a crafted artifact could borrow a perfect 1.0 self-ratio or omit an unstable cell and still verify. The verifier now recomputes the self-ratio from raw samples (exact-match within 1e-9), recomputes the unstable-cell list, and pins the CV limit to the exported constant. Two new forgery cases pin the paths (self-ratio-borrowed-perfect, unstable-cell-hidden-from-control), and the report/armplan fixtures now state the honest control values their samples derive. Review r4561644054: ControlUnstableCells computed CV over all regimes while discovering cells via passedStable -- the mandated spawned protocol's one process-cold sample per round inflated every cell's CV and would have suppressed all claims, the round-2 no-claims failure re-entering through the control path. CV is now judged over the steady (warm/service-warm) regime, matching the docstring and claimCellPair. TestControlUnstableCells pins the cold-outlier-must-not-flag invariant, a warm-relabelled control of the control, genuine instability, the sub-3-sample rule, and cold-only cells. --- internal/dataframe/benchmark/armplan_test.go | 36 +++++++---- internal/dataframe/benchmark/controls.go | 9 ++- internal/dataframe/benchmark/controls_test.go | 60 +++++++++++++++++++ internal/dataframe/benchmark/report.go | 34 ++++++++++- internal/dataframe/benchmark/report_test.go | 58 ++++++++++++++---- 5 files changed, 173 insertions(+), 24 deletions(-) diff --git a/internal/dataframe/benchmark/armplan_test.go b/internal/dataframe/benchmark/armplan_test.go index 62a39e0c7..00c6b1b9d 100644 --- a/internal/dataframe/benchmark/armplan_test.go +++ b/internal/dataframe/benchmark/armplan_test.go @@ -2,6 +2,7 @@ package benchmark import ( "encoding/json" + "fmt" "strings" "testing" "time" @@ -35,13 +36,6 @@ func validPostgresOnlyReport(t *testing.T) *Report { ClickHouseDisabled: false, Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, } - controls := ControlRuns{ - StabilityCVLimit: StabilityCVLimit, - // the arango self-ratio cannot exist without the leg: 0 = absent, - // never a forged 1.0 - SelfRatioArango: 0, SelfRatioPostgres: 1.0, - EmptyFloorMillis: 0.4, RatioPolicy: "ratio-only-both-passed", - } plan := PostgresOnlyPlan(testArangoCapability) plan.Publication = &PublicationPlan{Run: true} plan.PlannedNotRun = PlannedNotRunForBackends([]string{BackendArango}) @@ -57,10 +51,29 @@ func validPostgresOnlyReport(t *testing.T) *Report { } gates = append(gates, postgresOnlyGate(spec.ID, strings.Repeat("c", 64))) samples = append(samples, sample(BackendPostgres, spec.ID, OutcomePassed, 5, true)) + // Halves [10, 10, 10.3, 10.3]: honest self-ratio 1.03. The + // verifier recomputes controls from raw samples (review + // r4561644049), so the fixture states what the samples show. for i := 0; i < 4; i++ { - samples = append(samples, sample(BackendPostgres, spec.ID, OutcomePassed, 10+float64(i), false)) + offset := 0.0 + if i >= 2 { + offset = 0.3 + } + samples = append(samples, sample(BackendPostgres, spec.ID, OutcomePassed, 10+offset, false)) } } + controls := ControlRuns{ + StabilityCVLimit: StabilityCVLimit, + UnstableCells: ControlUnstableCells(samples, StabilityCVLimit), + // the arango self-ratio cannot exist without the leg: 0 = absent, + // never a forged 1.0 + SelfRatioArango: 0, EmptyFloorMillis: 0.4, RatioPolicy: "ratio-only-both-passed", + } + if value, ok := ControlSelfRatio(samples, BackendPostgres); ok { + controls.SelfRatioPostgres = value + } else { + t.Fatal("postgres self-ratio not computable from fixture samples") + } gates = ApplyExcludedWorkloadGateReports(gates, ExcludedWorkloadGateReports(plan.Legs, liveExcluded)) AppendWorkloadDisclosures(&plan, liveExcluded) report, err := BuildReport(identity, plan, controls, gates, samples) @@ -184,10 +197,13 @@ func TestPostgresOnlyForgerySuite(t *testing.T) { } func TestRenderMarkdown(t *testing.T) { - both := RenderMarkdown(validReport(t)) + bothReport := validReport(t) + selfRatioLine := fmt.Sprintf("arango=%.3f, postgres=%.3f", + bothReport.Controls.SelfRatioArango, bothReport.Controls.SelfRatioPostgres) + both := RenderMarkdown(bothReport) for _, want := range []string{ "Status: passed", "Scope: `both-arms`", "ratio-only-both-passed", - "arango/postgres", "Empty-result floor", "1.000", + "arango/postgres", "Empty-result floor", selfRatioLine, } { if !strings.Contains(both, want) { t.Fatalf("both-arms markdown missing %q", want) diff --git a/internal/dataframe/benchmark/controls.go b/internal/dataframe/benchmark/controls.go index 5ff0dd1cf..dc3653f23 100644 --- a/internal/dataframe/benchmark/controls.go +++ b/internal/dataframe/benchmark/controls.go @@ -82,7 +82,14 @@ func ControlUnstableCells(samples []Sample, limit float64) []string { }) var unstable []string for _, c := range cells { - sum := Summarize(cellSamples(samples, c.workload, c.backend)) + // Stability is judged over the claimable (steady) regime only + // (review r4561644054): the mandated spawned protocol puts one + // legitimately-slow process-cold sample per round into every cell, + // and mixing it into the CV would flag stable warm cells unstable + // and suppress every claim for them -- the round-2 no-claims + // failure returning through the control path. This matches the + // docstring and the claimCellPair regime policy. + sum := Summarize(passedStable(cellSamples(samples, c.workload, c.backend))) if sum.Count < 3 || sum.CoefficientOfVariation > limit { unstable = append(unstable, c.workload+"/"+c.backend) } diff --git a/internal/dataframe/benchmark/controls_test.go b/internal/dataframe/benchmark/controls_test.go index ca4511150..c492c2f21 100644 --- a/internal/dataframe/benchmark/controls_test.go +++ b/internal/dataframe/benchmark/controls_test.go @@ -54,3 +54,63 @@ func TestStabilityControlExcludesUnstableCellClaims(t *testing.T) { t.Fatalf("ratio-only-both-passed policy must hold, got %v", claims) } } + +// TestControlUnstableCells pins the regime discipline of the stability +// control (review r4561644054): CV is judged over the claimable steady +// regime only. The mandated spawned protocol (iterations=10, rounds=5) +// puts one legitimately-slow process-cold sample per round into every +// cell; mixing it into the CV would flag every spawned cell unstable and +// suppress all claims -- the round-2 no-claims failure re-entering +// through the control path. +func TestControlUnstableCells(t *testing.T) { + // Spawned-protocol cell: five rounds of one process-cold outlier plus + // four tight warm samples. Warm CV is tiny; the cold outliers must not + // inflate it into an unstable verdict. + var spawned []Sample + for round := 1; round <= 5; round++ { + cold := sample(BackendArango, "w", OutcomePassed, 400, false) + cold.Cache = CacheProcessCold + cold.Round, cold.Iteration = round, 1 + spawned = append(spawned, cold) + for it, ms := range []float64{20, 20.2, 19.8, 20.1} { + warm := sample(BackendArango, "w", OutcomePassed, ms, false) + warm.Round, warm.Iteration = round, it+2 + spawned = append(spawned, warm) + } + } + if got := ControlUnstableCells(spawned, StabilityCVLimit); len(got) != 0 { + t.Fatalf("cold outliers of the spawned protocol must not flag a stable warm cell unstable: %v", got) + } + // Control of the control: relabel those same cold outliers as warm + // and the cell must be flagged -- CV is really computed, and the + // regime filter is what saves the spawned cell above. + relabelled := append([]Sample(nil), spawned...) + for i := range relabelled { + if relabelled[i].Millis == 400 { + relabelled[i].Cache = CacheWarm + } + } + if got := ControlUnstableCells(relabelled, StabilityCVLimit); len(got) != 1 || got[0] != "w/"+BackendArango { + t.Fatalf("warm-spread outlier must flag the cell unstable, got %v", got) + } + // Genuinely unstable warm cell is flagged. + var wild []Sample + for i, ms := range []float64{20, 200, 20, 200} { + s := sample(BackendArango, "w", OutcomePassed, ms, false) + s.Round, s.Iteration = i/2+1, i%2+1 + wild = append(wild, s) + } + if got := ControlUnstableCells(wild, StabilityCVLimit); len(got) != 1 || got[0] != "w/"+BackendArango { + t.Fatalf("wildly varying warm cell must be unstable, got %v", got) + } + // Fewer than three steady samples: never silently trusted. + thin := wild[:2] + if got := ControlUnstableCells(thin, StabilityCVLimit); len(got) != 1 || got[0] != "w/"+BackendArango { + t.Fatalf("two-sample cell cannot demonstrate stability, got %v", got) + } + // Cold-only cells carry no claims and need no stability verdict. + coldOnly := []Sample{spawned[0]} + if got := ControlUnstableCells(coldOnly, StabilityCVLimit); len(got) != 0 { + t.Fatalf("process-cold-only samples are a different regime, not an unstable cell: %v", got) + } +} diff --git a/internal/dataframe/benchmark/report.go b/internal/dataframe/benchmark/report.go index f2d4620e0..1aa2bbc31 100644 --- a/internal/dataframe/benchmark/report.go +++ b/internal/dataframe/benchmark/report.go @@ -284,13 +284,31 @@ func VerifyReport(report *Report) error { } } } else { - // A run that measured anything must show the controls ran. - if report.Controls.StabilityCVLimit <= 0 || report.Controls.RatioPolicy == "" { - return fmt.Errorf("benchmark report: measurement controls were not run (missing stability limit or ratio policy)") + // A run that measured anything must show the controls ran, at + // the shipped limit: a report may not smuggle its own ceiling + // (review r4561644049), so the limit is pinned to the exported + // constant the control functions enforce. + if report.Controls.StabilityCVLimit != StabilityCVLimit || report.Controls.RatioPolicy == "" { + return fmt.Errorf("benchmark report: measurement controls were not run (stability limit must equal %v or ratio policy missing)", StabilityCVLimit) } if report.Controls.EmptyFloorMillis <= 0 { return fmt.Errorf("benchmark report: empty-result floor control was not run") } + // The stability control must be re-derivable from raw samples: + // an omitted unstable cell would otherwise let claims rest on + // cells the control would have excluded. + want := ControlUnstableCells(report.Samples, StabilityCVLimit) + sort.Strings(want) + got := append([]string(nil), report.Controls.UnstableCells...) + sort.Strings(got) + if len(got) != len(want) { + return fmt.Errorf("benchmark report: unstable cells %v do not match the %v recomputed from raw samples", got, want) + } + for i := range want { + if got[i] != want[i] { + return fmt.Errorf("benchmark report: unstable cells %v do not match the %v recomputed from raw samples", got, want) + } + } } } else { // Planned report: nothing ran, so a control that claims to have run @@ -340,6 +358,16 @@ func VerifyReport(report *Report) error { if math.Abs(sr.value-1) > 0.05 { return fmt.Errorf("benchmark report: %s self-ratio %v off 1.0 beyond 5%% — environment unfit for ratios", sr.backend, sr.value) } + // The value must be re-derivable from the raw samples + // (review r4561644049): a crafted artifact can pass the band + // above with a borrowed 1.0 while its halves say otherwise. + recomputed, ok := ControlSelfRatio(report.Samples, sr.backend) + if !ok { + return fmt.Errorf("benchmark report: %s self-ratio %v cannot be recomputed from the raw samples (unsplittable cells or no cells): the control is forged", sr.backend, sr.value) + } + if math.Abs(recomputed-sr.value) > 1e-9 { + return fmt.Errorf("benchmark report: %s self-ratio %v does not match the %v recomputed from raw samples", sr.backend, sr.value, recomputed) + } } // A not-run leg must be disclosed per workload and honest in the gates: // blocked arm, zero samples, zero claims. Anything else is a forgery diff --git a/internal/dataframe/benchmark/report_test.go b/internal/dataframe/benchmark/report_test.go index d50601d81..be543263d 100644 --- a/internal/dataframe/benchmark/report_test.go +++ b/internal/dataframe/benchmark/report_test.go @@ -24,14 +24,6 @@ func validReport(t *testing.T) *Report { ClickHouseDisabled: false, Config: RunConfig{Warmup: 1, Iterations: 4, Concurrency: 1, Rounds: 2}, } - controls := ControlRuns{ - StabilityCVLimit: StabilityCVLimit, UnstableCells: nil, - SelfRatioArango: 1.0, SelfRatioPostgres: 1.0, - EmptyFloorMillis: 0.4, RatioPolicy: "ratio-only-both-passed", - } - // Live battery law: proven workloads carry passing gates and samples; - // registry-excluded workloads carry disclosed blocked rows and - // disclosure rows, never samples. liveExcluded := LiveBatteryExclusions() var gates []WorkloadGateReport var samples []Sample @@ -51,11 +43,39 @@ func validReport(t *testing.T) *Report { if backend == BackendPostgres { base = 10.0 } + // Halves [base, base, base+3%, base+3%]: the honest self-ratio + // (worst half-median deviation) is 1.03, inside the 5% band, + // and the stable-regime CV is well under the limit. VerifyRe- + // port recomputes both controls from these samples, so the + // fixture must state what the samples actually show. for i := 0; i < 4; i++ { - samples = append(samples, sample(backend, spec.ID, OutcomePassed, base+float64(i), false)) + offset := 0.0 + if i >= 2 { + offset = base * 0.03 + } + samples = append(samples, sample(backend, spec.ID, OutcomePassed, base+offset, false)) } } } + // The controls are exactly what the control functions derive from the + // samples: the verifier re-derives them (review r4561644049), so an + // honest fixture states honest values. + controls := ControlRuns{ + StabilityCVLimit: StabilityCVLimit, + UnstableCells: ControlUnstableCells(samples, StabilityCVLimit), + EmptyFloorMillis: 0.4, RatioPolicy: "ratio-only-both-passed", + } + for _, backend := range []string{BackendArango, BackendPostgres} { + value, ok := ControlSelfRatio(samples, backend) + if !ok { + t.Fatalf("%s self-ratio not computable from fixture samples", backend) + } + if backend == BackendArango { + controls.SelfRatioArango = value + } else { + controls.SelfRatioPostgres = value + } + } timingPlan := BothArmsPlan() timingPlan.Publication = &PublicationPlan{Run: true} gates = append(gates, ExcludedWorkloadGateReports(timingPlan.Legs, liveExcluded)...) @@ -175,7 +195,25 @@ func TestReportForgerySuite(t *testing.T) { // Claims left in place: a claim on a non-claimable workload // must not verify even when the status was honestly flipped. }, - "self-ratio-forged-high": func(r *Report) { r.Controls.SelfRatioArango = 3.0 }, + "self-ratio-forged-high": func(r *Report) { r.Controls.SelfRatioArango = 3.0 }, + // Review r4561644049: values inside the plausibility band are no + // longer accepted on faith; they must recompute from raw samples. + "self-ratio-borrowed-perfect": func(r *Report) { + // The fixture samples honestly show a 1.03 worst half-median + // deviation; the artifact claims the perfect 1.0 instead. + // The band check cannot see it (1.0 is in band) — only + // recomputation from samples can. + r.Controls.SelfRatioArango = 1.0 + }, + "unstable-cell-hidden-from-control": func(r *Report) { + // Inject a wildly unstable warm cell for the primary timing + // on arango; the honest control list (still empty) claims no + // unstable cells, so the claim path hides an unstable cell. + for i, ms := range []float64{1, 100, 1} { + _ = i + r.Samples = append(r.Samples, sample(BackendArango, primaryTiming(), OutcomePassed, ms, false)) + } + }, "empty-floor-never-run": func(r *Report) { r.Controls.EmptyFloorMillis = 0 }, "stability-limit-never-set": func(r *Report) { r.Controls.StabilityCVLimit = 0 }, "unsupported-list-drifted": func(r *Report) { r.Unsupported = []string{primaryTiming() + "/postgres"} }, From 15d83751cc69a3ac09b6bad645c27de29c74a450 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 10:47:59 +0000 Subject: [PATCH 42/45] test(benchmark): unpinned forgery cases now fail solely on their target guards Review r4562338071: the two new verifier guards needed cases that fail only on them. - unstable-cell-hidden-from-control was confounded: injecting three wild samples desynced the summaries and pushed the half-median deviation far past the band, so an earlier check could reject for reasons unrelated to the unstable-cell rule. Rewritten per the review: the primary timing's arango warm samples become alternating 1,100,1,100 (equal half medians keep the honest self-ratio match), the summary row is recomputed with Summarize, claims are rebuilt from those summaries, and Controls.UnstableCells stays cleared -- only the ControlUnstableCells recompute catches this shape. - stability-limit-smuggled-ceiling: 0.9 passes every rule the old verifier had (it rejected only <= 0), exercising solely the new equality with the exported constant. - forgeErr lets cases name the rejecting rule; the three guard cases now pin their rule by error message. Verified by deleting each guard: all three artifacts then survive verification, so each case is the exclusive witness of its rule. --- internal/dataframe/benchmark/report_test.go | 61 ++++++++++++++++++--- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/internal/dataframe/benchmark/report_test.go b/internal/dataframe/benchmark/report_test.go index be543263d..5f4d7456a 100644 --- a/internal/dataframe/benchmark/report_test.go +++ b/internal/dataframe/benchmark/report_test.go @@ -132,6 +132,14 @@ func TestReportRoundTrips(t *testing.T) { } func forge(t *testing.T, mutate func(*Report)) func(*testing.T) { + return forgeErr(t, mutate, "") +} + +// forgeErr additionally pins WHICH rule rejected the artifact: a case +// meant to exercise a specific guard must not survive by tripping some +// earlier check (review r4562338071) — otherwise deleting the guard +// would leave the case falsely green. +func forgeErr(t *testing.T, mutate func(*Report), wantErrSubstr string) func(*testing.T) { return func(t *testing.T) { t.Helper() report := validReport(t) @@ -142,6 +150,8 @@ func forge(t *testing.T, mutate func(*Report)) func(*testing.T) { } if _, err := LoadReport(data); err == nil { t.Fatal("forged report survived verification") + } else if wantErrSubstr != "" && !strings.Contains(err.Error(), wantErrSubstr) { + t.Fatalf("forged report rejected by the wrong rule (%v); want an error naming %q", err, wantErrSubstr) } } } @@ -205,14 +215,44 @@ func TestReportForgerySuite(t *testing.T) { // recomputation from samples can. r.Controls.SelfRatioArango = 1.0 }, + // Review r4562338071: each new verifier guard needs a case that + // fails ONLY on it. Both cases below keep every other derived + // value honest and pin the rejecting rule by message, so deleting + // the guard turns the case RED instead of silently re-routing it + // to an earlier check. "unstable-cell-hidden-from-control": func(r *Report) { - // Inject a wildly unstable warm cell for the primary timing - // on arango; the honest control list (still empty) claims no - // unstable cells, so the claim path hides an unstable cell. - for i, ms := range []float64{1, 100, 1} { - _ = i - r.Samples = append(r.Samples, sample(BackendArango, primaryTiming(), OutcomePassed, ms, false)) + // Rewrite the primary timing's arango warm samples as + // alternating 1,100,1,100: half medians stay equal, so the + // honest self-ratio (worst deviation across cells) still + // matches the reported 1.03. Summaries are recomputed from + // the samples and claims rebuilt from those summaries, so + // every other derived value stays in sync. The cell's warm + // CV (~0.98) is far over the ceiling: only the + // ControlUnstableCells recompute can catch this shape. + ms := []float64{1, 100, 1, 100} + i := 0 + for j := range r.Samples { + s := &r.Samples[j] + if s.WorkloadID == primaryTiming() && s.Backend == BackendArango && !s.Warmup { + s.Millis = ms[i] + i++ + } } + for j := range r.Summaries { + row := &r.Summaries[j] + if row.WorkloadID == primaryTiming() && row.Backend == BackendArango { + row.Summary = Summarize(cellSamples(r.Samples, row.WorkloadID, row.Backend)) + } + } + r.Claims = buildClaims(r.GateReports, r.Summaries, r.Controls) + r.Controls.UnstableCells = nil + }, + "stability-limit-smuggled-ceiling": func(r *Report) { + // 0.9 passes every plausibility rule the old verifier had + // (it only rejected <= 0): a run may not smuggle its own + // ceiling. Only the equality with the exported constant + // catches this. + r.Controls.StabilityCVLimit = 0.9 }, "empty-floor-never-run": func(r *Report) { r.Controls.EmptyFloorMillis = 0 }, "stability-limit-never-set": func(r *Report) { r.Controls.StabilityCVLimit = 0 }, @@ -286,8 +326,15 @@ func TestReportForgerySuite(t *testing.T) { }) }, } + // Cases exercising one specific guard name the rule that must reject + // them (review r4562338071); everything else just has to RED. + wantErr := map[string]string{ + "self-ratio-borrowed-perfect": "self-ratio 1 does not match", + "unstable-cell-hidden-from-control": "unstable cells", + "stability-limit-smuggled-ceiling": "stability limit must equal", + } for name, mutate := range cases { - t.Run(name, forge(t, mutate)) + t.Run(name, forgeErr(t, mutate, wantErr[name])) } } From 79e72ed4367cb54ccfbf81ec0be9ffe61e0fc1cf Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 17:12:40 +0000 Subject: [PATCH 43/45] fix(benchmark): decode explorer readback from real ExplorerStateV1 shape The pre-timing setup proof read GET /explorers/{id} into top-level draftDigest/activeRevisionId, but the route answers ExplorerStateV1 (internal/explorer/state_v1.go), which nests the digest at draft.digest and the revision identity at active.revisionId. Both fields therefore always decoded empty against the real server and SetupProofEvidence aborted fail-closed ("explorer readback on arango omitted the draft digest") on the first participant. The paired run at 7c5ef71e (job 132029741650) is the first run to reach this stage because every earlier hop died in postgres authoring before the proofs loop. The unit suite stayed green because the harness mock answered with the same top-level fields the client wrongly expected: client and mock shared the bug. Matcher amendment granted by the coordinator, scoped to: - FetchExplorerActiveState/ExplorerActiveState: decode draft.digest and active.revisionId from the ExplorerStateV1 shape. Gate semantics unchanged: SetupProofEvidence keeps the trim-space empty-digest abort; revision identity stays record-only; PublishReceipt materialization checks and preview-row readback untouched. - harness mock now MARSHALS the real internal/explorer.ExplorerStateV1 instead of a hand-rolled map, so the mock cannot drift from the server shape; plus a forceEmptyActiveState flag so the mock can express the server-legal empty-digest state the old mock hid. - New permanent regression tests: real-marshal decode (fails on the pre-fix client: both fields lost) and empty-digest abort through SetupProofEvidence. Receipts (local, head 7c5ef71e): full benchmark package suite ok; catalog and postgres renderer suites ok; gofmt/vet clean; control run with the client fix reverted fails the decode test on both fields. --- .../dataframe/benchmark/authoringpublish.go | 19 +++-- internal/dataframe/benchmark/harness_test.go | 35 ++++++++-- .../benchmark/readback_shape_test.go | 69 +++++++++++++++++++ 3 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 internal/dataframe/benchmark/readback_shape_test.go diff --git a/internal/dataframe/benchmark/authoringpublish.go b/internal/dataframe/benchmark/authoringpublish.go index 8fb9ff5ee..db7ef8543 100644 --- a/internal/dataframe/benchmark/authoringpublish.go +++ b/internal/dataframe/benchmark/authoringpublish.go @@ -145,8 +145,8 @@ func (a *Arm) PublishReceipt(ctx context.Context, token, project, explorer, rece // Timestamps are excluded on purpose; digests are content-derived, so // cross-arm equality is the setup-equivalence proof. type ExplorerActiveState struct { - DraftDigest string `json:"draftDigest"` - ActiveRevisionID string `json:"activeRevisionId"` + DraftDigest string + ActiveRevisionID string } // FetchExplorerActiveState reads the explorer's active runtime projection @@ -163,11 +163,20 @@ func (a *Arm) FetchExplorerActiveState(ctx context.Context, token, project, expl return ExplorerActiveState{}, fmt.Errorf("benchmark: explorer readback on %s: %w", a.Backend, err) } raw, _ := json.Marshal(value) - var state ExplorerActiveState - if err := json.Unmarshal(raw, &state); err != nil { + // The route answers ExplorerStateV1: the draft digest is at + // draft.digest and the revision identity at active.revisionId. + var wire struct { + Draft struct { + Digest string `json:"digest"` + } `json:"draft"` + Active struct { + RevisionID string `json:"revisionId"` + } `json:"active"` + } + if err := json.Unmarshal(raw, &wire); err != nil { return ExplorerActiveState{}, fmt.Errorf("benchmark: explorer readback shape on %s: %w", a.Backend, err) } - return state, nil + return ExplorerActiveState{DraftDigest: wire.Draft.Digest, ActiveRevisionID: wire.Active.RevisionID}, nil } // SetupProof is everything the pre-timing matched-state proof compares for diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index a8c3175d6..bcf952e36 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -14,6 +14,8 @@ import ( "sync" "testing" "time" + + loomexplorer "github.com/calypr/loom/internal/explorer" ) // fakeArm serves the public routes the harness uses, with scripted rows per @@ -43,7 +45,10 @@ type fakeArm struct { publishNoOutputs bool // answers with an empty outputs array activeDigest string activeRevision string - activationBody string // when set, publish/explorer-config answer 503 with this body + // forceEmptyActiveState serves the server-legal state whose draft + // digest is empty, so the fail-closed abort path is testable. + forceEmptyActiveState bool + activationBody string // when set, publish/explorer-config answer 503 with this body } func (f *fakeArm) publishedBodies() [][]byte { @@ -151,15 +156,31 @@ func newFakeArm(backend string) *fakeArm { return case r.Method == http.MethodGet && strings.Contains(path, "/explorers/") && !strings.Contains(path[strings.LastIndex(path, "/explorers/")+len("/explorers/"):], "/"): f.mu.Lock() - digest, rev := f.activeDigest, f.activeRevision + digest, rev, forceEmpty := f.activeDigest, f.activeRevision, f.forceEmptyActiveState f.mu.Unlock() - if digest == "" { - digest = "sha256:active-default" + if !forceEmpty { + if digest == "" { + digest = "sha256:active-default" + } + if rev == "" { + rev = "authoring-active-default" + } } - if rev == "" { - rev = "authoring-active-default" + // The mock follows the server: it marshals the real + // ExplorerStateV1 the route actually answers, so the + // client and the mock cannot share a wrong shape. + id := path[strings.LastIndex(path, "/explorers/")+len("/explorers/"):] + state := loomexplorer.ExplorerStateV1{ + APIVersion: loomexplorer.ExplorerStateV1APIVersion, + Kind: loomexplorer.ExplorerStateV1Kind, + Project: "proj", + ExplorerID: id, + Management: loomexplorer.ManagementInteractive, + Draft: loomexplorer.ExplorerStateV1Draft{Version: 1, Digest: digest}, + Active: loomexplorer.ExplorerStateV1Active{RevisionID: rev, Status: "PUBLISHED"}, } - _ = json.NewEncoder(w).Encode(map[string]any{"draftDigest": digest, "activeRevisionId": rev}) + state.ActiveURL = "/api/v1/projects/proj/explorers/" + id + _ = json.NewEncoder(w).Encode(state) return case strings.Contains(path, "/authoring/v2/preview"): var body map[string]any diff --git a/internal/dataframe/benchmark/readback_shape_test.go b/internal/dataframe/benchmark/readback_shape_test.go new file mode 100644 index 000000000..0a9d6287c --- /dev/null +++ b/internal/dataframe/benchmark/readback_shape_test.go @@ -0,0 +1,69 @@ +package benchmark + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + loomexplorer "github.com/calypr/loom/internal/explorer" +) + +// The explorer route answers ExplorerStateV1 (internal/explorer +// state_v1.go): the draft digest is at draft.digest and the revision +// identity at active.revisionId. The paired run at 7c5ef71e aborted +// "explorer readback on arango omitted the draft digest" because the +// client decoded top-level draftDigest/activeRevisionId, which that +// shape never carries. This test serves a real-marshal ExplorerStateV1 +// and fails on the pre-fix client. + +func serverStateV1(digest, revisionID string) loomexplorer.ExplorerStateV1 { + return loomexplorer.ExplorerStateV1{ + APIVersion: loomexplorer.ExplorerStateV1APIVersion, + Kind: loomexplorer.ExplorerStateV1Kind, + Project: "loom-bench", + ExplorerID: "bench-workload", + Title: "bench-workload", + Management: loomexplorer.ManagementInteractive, + Draft: loomexplorer.ExplorerStateV1Draft{Version: 1, Digest: digest}, + Active: loomexplorer.ExplorerStateV1Active{RevisionID: revisionID, Status: "PUBLISHED"}, + ActiveURL: "/api/v1/projects/loom-bench/explorers/bench-workload", + UpdatedBy: "bench", + UpdatedAt: time.Now().UTC(), + } +} + +func TestFetchExplorerActiveStateReadsRealServerStateShape(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(serverStateV1("sha256:server-draft-digest", "authoring-rev-1")) + })) + defer srv.Close() + arm := &Arm{Backend: BackendArango, BaseURL: srv.URL, Client: srv.Client()} + state, err := arm.FetchExplorerActiveState(context.Background(), "t", "loom-bench", "bench-workload") + if err != nil { + t.Fatalf("readback errored: %v", err) + } + if state.DraftDigest != "sha256:server-draft-digest" { + t.Errorf("DraftDigest lost: got %q, want draft.digest", state.DraftDigest) + } + if state.ActiveRevisionID != "authoring-rev-1" { + t.Errorf("ActiveRevisionID lost: got %q, want active.revisionId", state.ActiveRevisionID) + } +} + +func TestSetupProofEvidenceAbortsOnEmptyDraftDigest(t *testing.T) { + f := newFakeArm(BackendArango) + defer f.close() + f.mu.Lock() + f.forceEmptyActiveState = true + f.rows["out"] = []map[string]any{{"patient_id": "p1"}} + f.mu.Unlock() + _, err := f.arm(BackendArango).SetupProofEvidence(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-out", "out") + if err == nil || !strings.Contains(err.Error(), "omitted the draft digest") { + t.Fatalf("empty draft digest must abort fail-closed, got: %v", err) + } +} From d437550293255272dfc85a858ecabb2e8ecb3cbc Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 17:21:03 +0000 Subject: [PATCH 44/45] fix(benchmark): decode preview columns from the real ContractColumn wire shape The authoring-v2 preview route answers ExplorerBuilderPreview with columns as generated loomapi.ContractColumn objects (internal/server explorer_preview_response.go v2EmissionColumns; public name at .column, api.gen.go:7137). decodePreview unmarshaled columns into []string, so the paired run at 79e72ed4 (job 132040946330) aborted fail-closed with "json: cannot unmarshal object into Go struct field PreviewResult.columns of type string". The harness mock served the same string array plus a top-level sampled field the route never emits, so the unit suite was structurally blind - the second instance of the client-and-mock shared-shape class. Coordinator-granted matcher amendment #2, scoped to three hunks: - harness.go decodePreview: columns decoded via []loomapi.ContractColumn, names from .column, with a new fail-closed abort when a column omits its public name. The rows loop, Bag/CanonicalizeRows digest, and the omitted-columns/rows aborts are byte-identical; the matched-bytes law carriers are untouched. - harness_test.go: the preview mock now marshals real loomapi.ContractColumn values inside the server-shaped envelope (apiVersion, kind, receiptId, outputId, columns, rows, rowCount, diagnostics). The phantom sampled script is removed: the encoder never emits it, and no test scripted sampled=true. - preview_shape_test.go (new, permanent): real-marshal decode plus the nameless-column abort case. Receipts at 79e72ed4: control with the client fix reverted fails both new tests, reproducing the exact CI error string; with the fix applied the full benchmark package suite is ok, gofmt/vet clean. --- internal/dataframe/benchmark/harness.go | 21 +++++- internal/dataframe/benchmark/harness_test.go | 25 +++++-- .../dataframe/benchmark/preview_shape_test.go | 67 +++++++++++++++++++ 3 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 internal/dataframe/benchmark/preview_shape_test.go diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go index 0cb5658c1..23f0d5741 100644 --- a/internal/dataframe/benchmark/harness.go +++ b/internal/dataframe/benchmark/harness.go @@ -10,6 +10,8 @@ import ( "net/http" "net/url" "strings" + + "github.com/calypr/loom/generated/loomapi" ) // The harness drives BOTH arms exclusively through public HTTP routes: @@ -170,14 +172,27 @@ func (a *Arm) ExecuteOutput(ctx context.Context, token, project, explorer, recei } func decodePreview(value map[string]any) (PreviewResult, error) { - var result PreviewResult raw, _ := json.Marshal(value) - if err := json.Unmarshal(raw, &result); err != nil { + // The authoring-v2 preview route answers ExplorerBuilderPreview with + // columns as ContractColumn objects (internal/server + // v2EmissionColumns); the public column name is at .column. + var wire struct { + Columns []loomapi.ContractColumn `json:"columns"` + RowCount int `json:"rowCount"` + } + if err := json.Unmarshal(raw, &wire); err != nil { return PreviewResult{}, err } - if result.Columns == nil { + if wire.Columns == nil { return PreviewResult{}, fmt.Errorf("preview response omitted columns") } + result := PreviewResult{RowCount: wire.RowCount} + for _, column := range wire.Columns { + if strings.TrimSpace(column.Column) == "" { + return PreviewResult{}, fmt.Errorf("preview column omitted its public name") + } + result.Columns = append(result.Columns, column.Column) + } rows, ok := value["rows"].([]any) if !ok { return PreviewResult{}, fmt.Errorf("preview response omitted rows") diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index bcf952e36..f667fa252 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "github.com/calypr/loom/generated/loomapi" loomexplorer "github.com/calypr/loom/internal/explorer" ) @@ -26,7 +27,6 @@ type fakeArm struct { calls []string // ordered "backend:route:output" rows map[string][]map[string]any columns []string - sampled bool leakScoped bool failWith map[string]int // output -> status uploads int @@ -194,14 +194,31 @@ func newFakeArm(backend string) *fakeArm { f.mu.Lock() f.calls = append(f.calls, fmt.Sprintf("%s:preview:%s", backend, output)) status := f.failWith[output] - columns := f.columns - sampled := f.sampled + names := f.columns + receipt, _ := body["receiptId"].(string) f.mu.Unlock() if status != 0 { http.Error(w, `{"error":"scripted failure"}`, status) return } - _ = json.NewEncoder(w).Encode(map[string]any{"columns": columns, "rows": rows, "rowCount": len(rows), "sampled": sampled}) + // The mock follows the server: the preview route answers + // ContractColumn objects whose name is at .column + // (internal/server v2EmissionColumns); there is no + // sampled field on the wire. + cols := make([]loomapi.ContractColumn, 0, len(names)) + for _, name := range names { + cols = append(cols, loomapi.ContractColumn{Column: name, Label: name, LogicalType: "string"}) + } + _ = json.NewEncoder(w).Encode(map[string]any{ + "apiVersion": "loom.calypr.org/explorer-authoring/v2", + "kind": "ExplorerBuilderPreview", + "receiptId": receipt, + "outputId": output, + "columns": cols, + "rows": rows, + "rowCount": len(rows), + "diagnostics": []any{}, + }) return case strings.Contains(path, "/authoring/v2/reconcile"): var body map[string]any diff --git a/internal/dataframe/benchmark/preview_shape_test.go b/internal/dataframe/benchmark/preview_shape_test.go new file mode 100644 index 000000000..ac4b4cb24 --- /dev/null +++ b/internal/dataframe/benchmark/preview_shape_test.go @@ -0,0 +1,67 @@ +package benchmark + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/calypr/loom/generated/loomapi" +) + +// The authoring-v2 preview route answers ExplorerBuilderPreview with +// columns as loomapi.ContractColumn objects (internal/server +// explorer_preview_response.go: v2EmissionColumns, api.gen.go:7137) +// whose public name is at .column. The client decoded columns as +// []string, so the paired run at 79e72ed4 (job 132040946330) aborted: +// "json: cannot unmarshal object into Go struct field +// PreviewResult.columns of type string". The mock served the same +// string array the client expected. This test marshals the real wire +// types and fails on the pre-fix client. + +func serverPreviewBody(receiptID, outputID string, names []string, rows []map[string]any) map[string]any { + cols := make([]loomapi.ContractColumn, 0, len(names)) + for _, n := range names { + cols = append(cols, loomapi.ContractColumn{Column: n, Label: n, LogicalType: "string"}) + } + return map[string]any{ + "apiVersion": "loom.calypr.org/explorer-authoring/v2", + "kind": "ExplorerBuilderPreview", + "receiptId": receiptID, + "outputId": outputID, + "columns": cols, + "rows": rows, + "rowCount": len(rows), + "diagnostics": []any{}, + } +} + +func TestExecuteOutputDecodesRealPreviewWireShape(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(serverPreviewBody("rcpt-1", "out", + []string{"patient_id", "gender"}, + []map[string]any{{"patient_id": "p1", "gender": "female"}})) + })) + defer srv.Close() + arm := &Arm{Backend: BackendArango, BaseURL: srv.URL, Client: srv.Client()} + res, err := arm.ExecuteOutput(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-1", "out", 10) + if err != nil { + t.Fatalf("preview decode errored: %v", err) + } + if strings.Join(res.Columns, ",") != "patient_id,gender" { + t.Errorf("columns lost: got %v", res.Columns) + } + if res.RowCount != 1 || len(res.Rows) != 1 || res.Digest == "" { + t.Errorf("rows/rowCount/digest lost: %+v", res) + } +} + +func TestDecodePreviewAbortsOnNamelessColumn(t *testing.T) { + value := serverPreviewBody("rcpt-1", "out", []string{"patient_id", " "}, []map[string]any{{"patient_id": "p1"}}) + if _, err := decodePreview(value); err == nil || !strings.Contains(err.Error(), "public name") { + t.Fatalf("nameless column must abort fail-closed, got: %v", err) + } +} From 9764e8333890ce792c996b4454df49d22062e3a0 Mon Sep 17 00:00:00 2001 From: Matthew Peterkort <33436238+matthewpeterkort@users.noreply.github.com> Date: Fri, 13 Nov 2026 19:47:27 +0000 Subject: [PATCH 45/45] fix(benchmark): carry sampled from the preview wire into the completeness gate Review r4567448566: the amendment #2 wire struct dropped the sampled field on my wrong claim that the route never emits it. I verified only the response encoder (explorer_preview_response.go Finish) and missed the route wrapper previewResponseFromEncoder (openapi_explorer_authoring_direct.go:559-571), which sets Sampled = !summary.Complete || summary.Truncated on loomapi.PreviewResponse (json:"sampled", no omitempty -- always on the wire). With sampled omitted, PreviewResult.Sampled was permanently false: a preview that hit the row limit or byte budget would have been silently timed as complete, violating this harness's contract that "sampled=true is surfaced, never silently accepted". - decodePreview: wire struct carries Sampled bool with the sampled tag; PreviewResult.Sampled is restored from the decode. - fakeArm: the sampled script returns (the preview mock now marshals the real loomapi.PreviewResponse type, including per-row rowSources and the scripted Sampled flag), so client and mock cannot share a wrong shape. - preview_shape_test.go: pins the full chain -- sampled=true wire -> ExecuteOutput Sampled -> MeasureArms sample.Truncated -> backendResultFrom BackendResult.Truncated -> EvaluateGates fails completeness and blocks claimability; plus the complete-preview false-path and the restored nameless-column abort. Receipts: control with only the carry reverted fails exactly the two chain tests at the named seams; with the fix the full benchmark package suite is ok, gofmt/vet clean. --- internal/dataframe/benchmark/harness.go | 13 +- internal/dataframe/benchmark/harness_test.go | 34 ++-- .../dataframe/benchmark/preview_shape_test.go | 146 +++++++++++++++--- 3 files changed, 154 insertions(+), 39 deletions(-) diff --git a/internal/dataframe/benchmark/harness.go b/internal/dataframe/benchmark/harness.go index 23f0d5741..7a7028337 100644 --- a/internal/dataframe/benchmark/harness.go +++ b/internal/dataframe/benchmark/harness.go @@ -173,12 +173,17 @@ func (a *Arm) ExecuteOutput(ctx context.Context, token, project, explorer, recei func decodePreview(value map[string]any) (PreviewResult, error) { raw, _ := json.Marshal(value) - // The authoring-v2 preview route answers ExplorerBuilderPreview with - // columns as ContractColumn objects (internal/server - // v2EmissionColumns); the public column name is at .column. + // The authoring-v2 preview route answers with loomapi.PreviewResponse: + // columns are ContractColumn objects (internal/server + // v2EmissionColumns) whose public name is at .column, and Sampled is + // set by the route wrapper from !summary.Complete || summary.Truncated + // (internal/server/openapi_explorer_authoring_direct.go + // previewResponseFromEncoder). The sampled tag has no omitempty, so + // the field is always on the wire and never silently defaults. var wire struct { Columns []loomapi.ContractColumn `json:"columns"` RowCount int `json:"rowCount"` + Sampled bool `json:"sampled"` } if err := json.Unmarshal(raw, &wire); err != nil { return PreviewResult{}, err @@ -186,7 +191,7 @@ func decodePreview(value map[string]any) (PreviewResult, error) { if wire.Columns == nil { return PreviewResult{}, fmt.Errorf("preview response omitted columns") } - result := PreviewResult{RowCount: wire.RowCount} + result := PreviewResult{RowCount: wire.RowCount, Sampled: wire.Sampled} for _, column := range wire.Columns { if strings.TrimSpace(column.Column) == "" { return PreviewResult{}, fmt.Errorf("preview column omitted its public name") diff --git a/internal/dataframe/benchmark/harness_test.go b/internal/dataframe/benchmark/harness_test.go index f667fa252..c822eb150 100644 --- a/internal/dataframe/benchmark/harness_test.go +++ b/internal/dataframe/benchmark/harness_test.go @@ -27,6 +27,7 @@ type fakeArm struct { calls []string // ordered "backend:route:output" rows map[string][]map[string]any columns []string + sampled bool // scripts a truncated/incomplete preview: the route sets sampled = !Complete || Truncated leakScoped bool failWith map[string]int // output -> status uploads int @@ -195,6 +196,7 @@ func newFakeArm(backend string) *fakeArm { f.calls = append(f.calls, fmt.Sprintf("%s:preview:%s", backend, output)) status := f.failWith[output] names := f.columns + sampled := f.sampled receipt, _ := body["receiptId"].(string) f.mu.Unlock() if status != 0 { @@ -202,22 +204,30 @@ func newFakeArm(backend string) *fakeArm { return } // The mock follows the server: the preview route answers - // ContractColumn objects whose name is at .column - // (internal/server v2EmissionColumns); there is no - // sampled field on the wire. + // loomapi.PreviewResponse (internal/server + // previewResponseFromEncoder) -- ContractColumn objects + // whose name is at .column (v2EmissionColumns) and a + // sampled flag the route sets from + // !summary.Complete || summary.Truncated. cols := make([]loomapi.ContractColumn, 0, len(names)) for _, name := range names { cols = append(cols, loomapi.ContractColumn{Column: name, Label: name, LogicalType: "string"}) } - _ = json.NewEncoder(w).Encode(map[string]any{ - "apiVersion": "loom.calypr.org/explorer-authoring/v2", - "kind": "ExplorerBuilderPreview", - "receiptId": receipt, - "outputId": output, - "columns": cols, - "rows": rows, - "rowCount": len(rows), - "diagnostics": []any{}, + sources := make([]loomapi.PreviewRowSource, 0, len(rows)) + for range rows { + sources = append(sources, loomapi.PreviewRowSource{Kind: loomapi.PreviewRowSourceKindUNAVAILABLE}) + } + _ = json.NewEncoder(w).Encode(loomapi.PreviewResponse{ + ApiVersion: loomapi.LoomCalyprOrgexplorerAuthoringv2, + Kind: loomapi.ExplorerBuilderPreview, + ReceiptId: receipt, + OutputId: output, + Columns: cols, + Rows: rows, + RowSources: sources, + RowCount: len(rows), + Sampled: sampled, + Diagnostics: []loomapi.Diagnostic{}, }) return case strings.Contains(path, "/authoring/v2/reconcile"): diff --git a/internal/dataframe/benchmark/preview_shape_test.go b/internal/dataframe/benchmark/preview_shape_test.go index ac4b4cb24..16b198511 100644 --- a/internal/dataframe/benchmark/preview_shape_test.go +++ b/internal/dataframe/benchmark/preview_shape_test.go @@ -7,43 +7,54 @@ import ( "net/http/httptest" "strings" "testing" + "time" "github.com/calypr/loom/generated/loomapi" ) -// The authoring-v2 preview route answers ExplorerBuilderPreview with -// columns as loomapi.ContractColumn objects (internal/server -// explorer_preview_response.go: v2EmissionColumns, api.gen.go:7137) -// whose public name is at .column. The client decoded columns as -// []string, so the paired run at 79e72ed4 (job 132040946330) aborted: -// "json: cannot unmarshal object into Go struct field -// PreviewResult.columns of type string". The mock served the same -// string array the client expected. This test marshals the real wire -// types and fails on the pre-fix client. +// The authoring-v2 preview route answers with loomapi.PreviewResponse +// (internal/server previewResponseFromEncoder, +// openapi_explorer_authoring_direct.go): columns are ContractColumn +// objects whose public name is at .column (v2EmissionColumns, +// api.gen.go), and Sampled is set from +// !summary.Complete || summary.Truncated with no omitempty, so it is +// always on the wire. The paired run at 79e72ed4 (job 132045898495) +// died on "cannot unmarshal object into Go struct field +// PreviewResult.columns of type string" because the client decoded +// columns as []string. An earlier revision of this fix also dropped +// Sampled from the decode on a wrong reading of the encoder alone; +// these tests pin the full chain so a truncated preview can never be +// silently treated as complete: sampled=true -> PreviewResult.Sampled +// -> sample/result Truncated -> GateCompleteness refuses the arm. -func serverPreviewBody(receiptID, outputID string, names []string, rows []map[string]any) map[string]any { +func serverPreviewBody(receiptID, outputID string, names []string, rows []map[string]any, sampled bool) loomapi.PreviewResponse { cols := make([]loomapi.ContractColumn, 0, len(names)) for _, n := range names { cols = append(cols, loomapi.ContractColumn{Column: n, Label: n, LogicalType: "string"}) } - return map[string]any{ - "apiVersion": "loom.calypr.org/explorer-authoring/v2", - "kind": "ExplorerBuilderPreview", - "receiptId": receiptID, - "outputId": outputID, - "columns": cols, - "rows": rows, - "rowCount": len(rows), - "diagnostics": []any{}, + sources := make([]loomapi.PreviewRowSource, 0, len(rows)) + for range rows { + sources = append(sources, loomapi.PreviewRowSource{Kind: loomapi.PreviewRowSourceKindUNAVAILABLE}) + } + return loomapi.PreviewResponse{ + ApiVersion: loomapi.LoomCalyprOrgexplorerAuthoringv2, + Kind: loomapi.ExplorerBuilderPreview, + ReceiptId: receiptID, + OutputId: outputID, + Columns: cols, + Rows: rows, + RowSources: sources, + RowCount: len(rows), + Sampled: sampled, + Diagnostics: []loomapi.Diagnostic{}, } } func TestExecuteOutputDecodesRealPreviewWireShape(t *testing.T) { + rows := []map[string]any{{"patient_id": "p1", "gender": "female"}} srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(serverPreviewBody("rcpt-1", "out", - []string{"patient_id", "gender"}, - []map[string]any{{"patient_id": "p1", "gender": "female"}})) + _ = json.NewEncoder(w).Encode(serverPreviewBody("rcpt-1", "out", []string{"patient_id", "gender"}, rows, false)) })) defer srv.Close() arm := &Arm{Backend: BackendArango, BaseURL: srv.URL, Client: srv.Client()} @@ -57,11 +68,100 @@ func TestExecuteOutputDecodesRealPreviewWireShape(t *testing.T) { if res.RowCount != 1 || len(res.Rows) != 1 || res.Digest == "" { t.Errorf("rows/rowCount/digest lost: %+v", res) } + if res.Sampled { + t.Errorf("a complete preview must decode Sampled=false") + } +} + +func TestExecuteOutputSurfacesSampledTrue(t *testing.T) { + rows := []map[string]any{{"patient_id": "p1", "gender": "female"}} + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(serverPreviewBody("rcpt-1", "out", []string{"patient_id", "gender"}, rows, true)) + })) + defer srv.Close() + arm := &Arm{Backend: BackendArango, BaseURL: srv.URL, Client: srv.Client()} + res, err := arm.ExecuteOutput(context.Background(), "Bearer x", "proj", "exp-1", "rcpt-1", "out", 10) + if err != nil { + t.Fatalf("preview decode errored: %v", err) + } + if !res.Sampled { + t.Fatal("sampled=true on the wire must survive decode") + } } func TestDecodePreviewAbortsOnNamelessColumn(t *testing.T) { - value := serverPreviewBody("rcpt-1", "out", []string{"patient_id", " "}, []map[string]any{{"patient_id": "p1"}}) + resp := serverPreviewBody("rcpt-1", "out", []string{"patient_id", " "}, []map[string]any{{"patient_id": "p1"}}, false) + value := map[string]any{} + raw, _ := json.Marshal(resp) + if err := json.Unmarshal(raw, &value); err != nil { + t.Fatal(err) + } if _, err := decodePreview(value); err == nil || !strings.Contains(err.Error(), "public name") { t.Fatalf("nameless column must abort fail-closed, got: %v", err) } } + +// TestSampledTrueChainFailsCompletenessGate pins the full disclosure +// contract: a preview the route flags sampled=true must reach timing +// samples as Truncated (MeasureArms), reach the BackendResult as +// Truncated (backendResultFrom), and make GateCompleteness refuse the +// arm (EvaluateGates) -- so a truncated output can never be timed as +// complete or back a speed claim. +func TestSampledTrueChainFailsCompletenessGate(t *testing.T) { + rows := []map[string]any{{"patient_id": "p1", "gender": "female"}} + fa, fp := newFakeArm(BackendArango), newFakeArm(BackendPostgres) + defer fa.close() + defer fp.close() + fa.rows["w"] = rows + fp.rows["w"] = rows + fa.mu.Lock() + fa.sampled = true // only the arango arm serves an incomplete preview + fa.mu.Unlock() + + clock := &scriptedClock{base: time.Unix(1700000000, 0)} + opts := RunOptions{Project: "p", Generation: "g", Explorer: "w", UnrestrictedToken: "Bearer x", PreviewLimit: 100, + Config: RunConfig{Warmup: 0, Iterations: 2, Concurrency: 1, Rounds: 1}} + samples, finals, err := MeasureArms(context.Background(), clock, "w", fa.arm(BackendArango), fp.arm(BackendPostgres), opts, + map[string]string{BackendArango: "r-a", BackendPostgres: "r-p"}, + map[string]string{BackendArango: "w", BackendPostgres: "w"}, + func(ctx context.Context, arm *Arm) error { return nil }) + if err != nil { + t.Fatal(err) + } + measured := 0 + for _, s := range samples { + if s.Warmup { + continue + } + measured++ + switch s.Backend { + case BackendArango: + if !s.Truncated { + t.Fatal("sampled=true preview must surface as sample.Truncated (MeasureArms)") + } + case BackendPostgres: + if s.Truncated { + t.Fatal("complete arm must not be marked truncated") + } + } + } + if measured == 0 { + t.Fatal("no measured samples produced") + } + columns := []string{"patient_id", "gender"} + a, p := backendResultFrom(BackendArango, samples, finals), backendResultFrom(BackendPostgres, samples, finals) + if !a.Truncated { + t.Fatal("sampled=true final must reach BackendResult.Truncated (backendResultFrom)") + } + if p.Truncated { + t.Fatal("complete final must not be truncated") + } + report := EvaluateGates("w", columns, mustBag(t, columns, rows), a, p, rows, rows, &StorageIDMap{}) + if report.Claimable { + t.Fatalf("a sampled=true arm must fail GateCompleteness: %+v", report.Gates) + } + if !strings.HasPrefix(report.ExclusionReason, "completeness:") { + t.Fatalf("exclusion must name the completeness gate, got %q", report.ExclusionReason) + } +}