Skip to content

Commit d9b7548

Browse files
Enable proof-aware binary search indexes for compiled data
1 parent 7bc6938 commit d9b7548

14 files changed

Lines changed: 1116 additions & 56 deletions
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
package Main
22

3+
record table Rows { ID: Int }
4+
5+
fn IDsAreUnique(rows: Rows) -> Bool {
6+
for i in 0..Len(rows) {
7+
for j in (i + 1)..Len(rows) {
8+
if rows[i].ID == rows[j].ID { return false }
9+
}
10+
}
11+
return true
12+
}
13+
314
[Artifact]
415
fn RejectDuplicateIDs() -> Void {
5-
StaticAssert.True(false, "duplicate ID 7")
16+
let rows = Rows { ID: [1, 7, 7, 9] }
17+
StaticAssert.True(IDsAreUnique(rows), "duplicate ID 7")
18+
Artifact.WriteCompiledData("must-not-publish.go", "RowsData", rows)
619
}
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
package Main
22

3+
record table Rows { ID: Int }
4+
5+
fn IDsAreSorted(rows: Rows) -> Bool {
6+
for i in 1..Len(rows) {
7+
if rows[i - 1].ID > rows[i].ID { return false }
8+
}
9+
return true
10+
}
11+
312
[Artifact]
413
fn RejectUnsortedIndex() -> Void {
5-
StaticAssert.True(false, "row-ID index is not sorted")
14+
let rows = Rows { ID: [1, 3, 2, 4] }
15+
StaticAssert.True(IDsAreSorted(rows), "row-ID index is not sorted")
16+
Artifact.WriteCompiledData("must-not-publish.go", "RowsData", rows)
617
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package CompiledDataValid
2+
3+
record table ScopeA { ID: Int Name: String }
4+
record table ScopeB { ID: Int Name: String }
5+
6+
fn ScopeAIDsUnique(table: ScopeA) -> Bool {
7+
for i in 0..Len(table) {
8+
for j in (i + 1)..Len(table) {
9+
if table[i].ID == table[j].ID { return false }
10+
}
11+
}
12+
return true
13+
}
14+
15+
fn ScopeAIDsSorted(table: ScopeA) -> Bool {
16+
for i in 1..Len(table) {
17+
if table[i - 1].ID > table[i].ID { return false }
18+
}
19+
return true
20+
}
21+
22+
[Artifact]
23+
fn PublishProofScopeControls() -> Void {
24+
let proved = ScopeA { ID: [1, 3, 5] Name: ["A", "B", "C"] }
25+
StaticAssert.True(ScopeAIDsUnique(proved), "ScopeA IDs must be unique")
26+
StaticAssert.True(ScopeAIDsSorted(proved), "ScopeA IDs must be sorted")
27+
28+
let sameFieldOtherSubject = ScopeB { ID: [1, 3, 5] Name: ["A", "B", "C"] }
29+
let transformedAfterProof = proved with { Name: ["X", "Y", "Z"] }
30+
31+
Artifact.WriteCompiledData("compiled/proved_scope.generated.go", "ProvedScope", proved)
32+
Artifact.WriteCompiledData("compiled/cross_subject.generated.go", "CrossSubject", sameFieldOtherSubject)
33+
Artifact.WriteCompiledData("compiled/transformed_after_proof.generated.go", "TransformedAfterProof", transformedAfterProof)
34+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# OCT-LAYOUT-CONTRACT-M2 — Typed Static Fact Provenance
2+
3+
## 1. Verdict
4+
5+
**Success**
6+
7+
The artifact evaluator now preserves two bounded semantic facts about an exact evaluated record-table value, compiled-data promotes those facts into the exact publication's `LayoutContract`, and the backend emits a zero-initialization binary-search lookup only when both facts are present. The existing catalog fixture now produces `CatalogDataLookupByID`; same-named fields on another table, a transformed post-proof value, and an unproved table do not.
8+
9+
## 2. M1 limitation
10+
11+
M1 received only a validated typed value. `StaticAssert.True(AllIDsUnique(table))` and `StaticAssert.True(IDsSorted(table))` had already collapsed to ordinary `Bool` results. The evaluator discarded the table instance, field, comparison coverage, assertion site, and proof phase. Consequently, uniqueness or sortedness could not lawfully become a key-related invariant. M1 correctly withheld that promotion.
12+
13+
## 3. Current static-evaluation architecture
14+
15+
The audited path is:
16+
17+
```text
18+
oct artifact
19+
-> package load, bind, and typecheck
20+
-> interpreted execution with ArtifactWriteCapability
21+
-> ordinary helper/function execution
22+
-> evalAssertCallExpr for StaticAssert.*
23+
-> evalArtifactWriteCompiledDataBuiltin
24+
-> compileddata.EmitGo
25+
-> validate typed Dataset
26+
-> derive LayoutContract
27+
-> emit and gofmt static Go source
28+
-> staged publication
29+
```
30+
31+
Static assertions execute in `internal/interpret` only when a compiler-owned artifact capability is present. At that point the evaluator has typed `Value` instances, nominal record-table declarations, ordered field declarations, concrete row indices, scalar comparison operators/results, the `StaticAssert` call line/column, and the artifact entry identity. Before M2, helper calls retained nominal type but not evaluated subject identity. Assertions retained only success/failure; compiled-data received no assertion evidence.
32+
33+
The minimum missing carrier was therefore not a proposition language. It was an evaluated table-instance identity, a subject-scoped field reference, a recognized fact kind, assertion provenance, and a fact attachment passed to compiled-data.
34+
35+
## 4. StaticFact model
36+
37+
The implemented internal model lives beside `LayoutContract`:
38+
39+
```text
40+
StaticFact {
41+
Subject DataSubjectRef
42+
Kind StaticFactKind
43+
Fields []FieldRef
44+
Provenance StaticFactProvenance
45+
}
46+
47+
StaticFactSet { Facts []StaticFact }
48+
```
49+
50+
`StaticFactSet.Add` de-duplicates identical evidence and preserves deterministic evaluation order. There are no connectives, quantifiers, proof terms, dependencies, or general predicates.
51+
52+
## 5. Subject/field identity
53+
54+
Each evaluated record-table value receives a deterministic `DataSubjectRef` of kind `static-evaluation-value`, such as `Catalog#2`. Passing a value through a helper preserves that identity. A table literal, Octagon materialization, or immutable table `with` result receives its own identity.
55+
56+
`FieldRef` is:
57+
58+
```text
59+
FieldRef {
60+
Subject DataSubjectRef
61+
Ordinal int
62+
Name string
63+
}
64+
```
65+
66+
The ordinal is the typed schema position; the name remains diagnostic/generated-name material. Promotion requires the fact subject, field subject, publication proof subject, schema ordinal, and schema name all to agree. A global `Unique("ID")` representation does not exist.
67+
68+
At publication, accepted facts are explicitly rebound from the exact evaluated subject to the exact compiled-data root. This is the only identity transition.
69+
70+
## 6. Provenance model
71+
72+
`StaticFactProvenance` records:
73+
74+
```text
75+
Phase = artifact-static-evaluation
76+
Source = static-assert
77+
Identity = <package>.<artifact-entry>:<line>:<column>/field-comparison-coverage
78+
```
79+
80+
Only `artifact-static-evaluation` plus `static-assert` is accepted by compiled-data promotion. The bounded source enum has no runtime-check or profile-hint variant, so those sources cannot accidentally become semantic invariants. Provenance is compiler metadata and is not emitted into the runtime artifact.
81+
82+
The M1 formatter now prints field ordinal and proof phase/source/site beside promoted facts, for example:
83+
84+
```text
85+
subject=compiled-data-root:CatalogData ...
86+
fact=unique:ID[0] provenance=artifact-static-evaluation/static-assert:CompiledDataValid.PublishCatalog:41:5/field-comparison-coverage
87+
fact=sorted-ascending:ID[0] provenance=artifact-static-evaluation/static-assert:CompiledDataValid.PublishCatalog:42:5/field-comparison-coverage
88+
metadata=binary-search:ID
89+
```
90+
91+
## 7. Supported fact kinds
92+
93+
M2 adds exactly:
94+
95+
- `Unique`
96+
- `SortedAscending`
97+
98+
Extraction is deliberately limited to `Int` record-table fields used by the current fixtures and backend. Exact extent, logical row order, nominal identity, and immutable publication remain authoritative M1 structural facts and are not duplicated as `StaticFact` kinds.
99+
100+
## 8. Proof extraction
101+
102+
While evaluating the condition of `StaticAssert.True`, the evaluator traces typed comparisons whose operands came from table row fields. Each operand carries exact subject, field ordinal/name, row index, and table extent.
103+
104+
The trace recognizes only:
105+
106+
- every unordered row pair compared unequal for one exact field: `Unique`;
107+
- every adjacent pair compared without a descending result: `SortedAscending`.
108+
109+
Coverage must be complete. Merely seeing one comparison, a true helper result, a function named `AllIDsUnique`, or a field named `ID` emits nothing. The implementation never matches helper names. Failed assertions discard the active trace.
110+
111+
This is evaluation evidence, not backend inspection: the compiled-data backend receives typed facts and never sees helper AST or names.
112+
113+
## 9. StaticAssert relationship
114+
115+
`StaticAssert.True`, `False`, `Equal`, `Near`, and `Error` retain their existing behavior. Ordinary assertions validate and disappear. Only `StaticAssert.True` temporarily enables the bounded field-comparison collector; it emits facts only after the condition succeeds and recognized coverage is complete.
116+
117+
No Oct syntax or library API changed. `StaticAssert` did not become a general proof language.
118+
119+
## 10. LayoutContract enrichment
120+
121+
Compiled-data first derives the M1 contract from the validated typed value. It then accepts only facts whose source subject equals `Dataset.ProofSubject`, whose field subject matches that same subject, whose ordinal/name matches the dataset schema, and whose provenance is compile-time `StaticAssert` evaluation.
122+
123+
Accepted facts are rebound to the compiled-data root and promoted separately into:
124+
125+
```text
126+
Invariants.UniqueFields
127+
Invariants.SortedFields
128+
```
129+
130+
The deterministic enrichment layer is upstream of backend planning.
131+
132+
## 11. Key-role restraint
133+
134+
M2 does not define or infer `PrimaryLookupKey`, entity identity, stable identity, or a public key role. `Unique` and `SortedAscending` remain independent invariants. Only their conjunction on the same `Int` field produces internal binary-search eligibility. The metadata describes an optimization candidate, not a primary key.
135+
136+
## 12. Backend consumer
137+
138+
The compiled-data backend consumes `LayoutContract.Metadata.SearchIndexes`. Eligibility requires:
139+
140+
```text
141+
exact same field has Unique
142+
and exact same field has SortedAscending
143+
and field type is Int
144+
and M1 static column projection exists
145+
```
146+
147+
Without that metadata, the backend emits the M1 row array and scalar projections only. It does not inspect values for apparent sortedness and does not inspect source/helper names.
148+
149+
## 13. Generated representation
150+
151+
The proof-aware artifact reuses M1's static projected key column and adds one generated function:
152+
153+
```go
154+
func CatalogDataLookupByID(key int) (CatalogRow, bool)
155+
```
156+
157+
The function performs lower-bound binary search over `CatalogDataIDColumn` and returns the corresponding static row. It creates no map, index array, decoder, reflection path, `init`, append loop, unsafe operation, or runtime constructor.
158+
159+
`SearchIndexEligibility` records the derived-from subject, covered field, and proof basis `[Unique, SortedAscending]`. No universal relationship graph was added.
160+
161+
## 14. Correctness
162+
163+
Generated-source tests compile and execute the real emitted Go. They compare every row against every M1 projection and exercise lookup for:
164+
165+
- first key;
166+
- middle key;
167+
- last key;
168+
- missing below range;
169+
- missing between keys;
170+
- missing above range.
171+
172+
The actual artifact fixture also emits the proof-aware catalog lookup through the production publication path.
173+
174+
## 15. Negative/invalidation tests
175+
176+
Coverage includes:
177+
178+
- duplicate IDs: an actual duplicate table makes the existing uniqueness helper return false; publication fails and `must-not-publish.go` is absent;
179+
- unsorted IDs: an actual out-of-order table makes the existing sorted helper return false; publication fails and output is absent;
180+
- same field name on another table: no fact or lookup crosses the subject boundary;
181+
- old transformed subject: `with` creates a fresh subject, so proof for the old value cannot specialize the transformed value;
182+
- missing proof: M1 projections remain, but no lookup is emitted;
183+
- incomplete comparison coverage: no fact is emitted;
184+
- fact/field subject or schema mismatch: compiled-data drops the fact.
185+
186+
M2 conservatively drops all facts across table `with`, including unrelated-column replacement. Re-proving the new value is cheap for current fixtures and safe. Key-column replacement and unknown transformations therefore cannot retain stale facts.
187+
188+
## 16. Benchmark results
189+
190+
Measurements were taken on Windows/amd64, AMD Ryzen 7 7700X, with `go test ./internal/compileddata -bench '^BenchmarkCatalogLookupScale$' -benchmem -benchtime=300ms`. M1 is a linear scan over the already-materialized projected ID column; M2 is the emitted binary-search strategy. The existing key is near the upper edge.
191+
192+
| Rows | Strategy | Existing lookup ns/op | Missing lookup ns/op | allocs/op | B/op |
193+
| ---: | -------- | --------------------: | -------------------: | --------: | ---: |
194+
| 1,000 | M1 projected linear | 217.0 | 215.3 | 0 | 0 |
195+
| 1,000 | M2 proved binary | 4.385 | 3.152 | 0 | 0 |
196+
| 10,000 | M1 projected linear | 2,025 | 1,992 | 0 | 0 |
197+
| 10,000 | M2 proved binary | 5.572 | 4.670 | 0 | 0 |
198+
| 100,000 | M1 projected linear | 19,866 | 19,652 | 0 | 0 |
199+
| 100,000 | M2 proved binary | 7.251 | 6.220 | 0 | 0 |
200+
201+
Edge-key results were 220.5/4.262 ns at 1k, 2,008/5.690 ns at 10k, and 19,501/7.673 ns at 100k for M1/M2 respectively. Runtime initialization remains zero.
202+
203+
Generated source size is:
204+
205+
| Rows | M1 source bytes | M2 source bytes | M2 delta | Projected key data on amd64 |
206+
| ---: | ---: | ---: | ---: | ---: |
207+
| 1,000 | 108,226 | 108,570 | +344 | 8,000 bytes |
208+
| 10,000 | 1,114,071 | 1,114,415 | +344 | 80,000 bytes |
209+
| 100,000 | 11,532,476 | 11,532,820 | +344 | 800,000 bytes |
210+
211+
M2 adds no data structure: it reuses the M1 key projection. The 344-byte delta is the lookup function.
212+
213+
## 17. Compile/publication cost
214+
215+
The isolated 100k-row contract derivation/enrichment benchmark is 568.4 ns/op, 1,545 B/op, and 14 allocs/op. Three-iteration complete-emitter measurements were noisy but showed no material M2 cost beyond the fixed generated function: 9.66/9.00 ms at 1k, 82.20/84.56 ms at 10k, and 868.77/854.06 ms at 100k for M1/M2. Formatting the large generated literal dominates.
216+
217+
The proof collector plus coverage verification costs 32.1 µs and 6,192 bytes for the six-row-style case. At 1,000 rows, the existing all-pairs uniqueness helper/coverage shape costs 174.7 ms and 55.7 MB in the isolated benchmark. This is not claimed to scale to 100k: the existing uniqueness helper is quadratic, and M2 intentionally does not invent a theorem or new helper syntax to replace it. The 1k/10k/100k lookup measurements isolate the backend payoff once a fact has been proved.
218+
219+
## 18. Hash/semantic compatibility
220+
221+
Tests emit the same dataset through M1 projection-only and M2 proof-aware modes and assert equal logical hash, schema hash, and row count. Hashes are computed from canonical type/value before physical planning. Generated source bytes differ only by the lookup function.
222+
223+
## 19. Non-table sanity check
224+
225+
The M1 `PublishedIDs` static-array test still derives exact extent, logical order, and immutable publication, and still emits a fixed array. It has no nominal table identity, field proof, search index, or fake key. Optional `StaticFact` absence is valid.
226+
227+
## 20. What remains unproved
228+
229+
M2 does not prove primary-key role, entity identity, lookup intent, dense domain, bounded key range, foreign-key relationships, stability across transformations, arbitrary predicates, non-`Int` field order, or facts for empty/single-row tables. It also does not make the current quadratic uniqueness helper suitable for very large static catalogs.
230+
231+
The generated lookup is an internal compiled-data API choice. No compatibility promise is made for its name or representation.
232+
233+
## 21. Complexity assessment
234+
235+
The proof layer stayed bounded: two fact kinds, one provenance source, one subject-scoped field reference, one comparison-coverage collector, one enrichment path, and one backend consumer. The most substantial cost is explicit evidence tracking for all-pairs uniqueness, not a growing proof vocabulary. No parser, typechecker syntax, Concept, Octagon format, runtime format, or theorem infrastructure changed.
236+
237+
## 22. Final recommendation
238+
239+
**1. Keep StaticFact provenance narrow and add more backend consumers opportunistically**
240+
241+
## 23. What NOT to implement next
242+
243+
Do not add source-level key/constraint/proof syntax, helper-name recognition, arbitrary `StaticAssert` promotion, primary-key inference, entity identity inference, proof connectives, quantifiers, theorem dependencies, SMT, symbolic algebra, a general relationship graph, runtime index construction, or a direct dense index without a real domain proof. Do not generalize fact kinds merely to avoid the measured quadratic helper cost.
244+
245+
## 24. Exactly one next recommendation
246+
247+
Evaluate one additional backend consumer of the existing `Unique`/`SortedAscending` facts before adding any new fact kind.

0 commit comments

Comments
 (0)