Columna Core — the column-foundation analytic framework (multi-table, transport-based, correctness-governed) over a single backend.
Project description
Columna Core (0.7.8-core)
v0.7.8-core — packaging hardening + the disclosure wire adapter. Declares the hard
pyarrowruntime dependency and resolvesCOLUMNA_BENCH_WAREHOUSEabsolutely (WP-0 audit finds); reconcilesbenchmark.cmlwith the code-built Manifold (addsregion_label); clears the v0.7.8 worklist (parserOptionalimport; unused-import / dead-local / f-string cruft). Addscolumna_core.disclosure_wire— the structured{code, materiality, …}wire adapter (ADR-032 D8 / WP-1.3), the one contract every surface serializes. SeeCHANGELOG.md.
v0.7.7-core —
ON UNIVERSEpin wiring (Option A). The population pin recorded byFrame.on_universe(u)is now threaded to the planner (run/plan→_infer), where it asserts the frame's intended population. A measure bound touserves; a measure bound to a different universe is out-of-domain for that population and refuses (out_of_universe); an unknownuis an error. This resolves the multi-universe and D5 co-anchoring ambiguity to the one chosen population — so a cross-universe ratio that clarifies unpinned becomes a serve or an honest refuse once pinned, and a multi-universe frame's coverage caveat is resolved. (Resolving a measure over a universe other than its declared one — true cross-universe confinement — is Option B, future.)coanchor_demo§D covers it. 124 checks across 11 suites.
v0.7.6-core — the no-result is a value, not an exception. The structured no-result is split into a plain
Outcomevalue (kind · discriminator · reason · alternatives) and a private internalRefusalsignal that merely carries it from deep in the recursive walk to the planner's single assembly point.ColumnResult.refusalnow holds anOutcome: a clarify/refuse/error is data every surface and agent reads, never anExceptiona caller could swallow withexcept Exception. (Refusalremains the internal control-flow goto; eliminating even that would mean return-threading the recursive type-inferencer — deliberately not done.)coanchor_demoasserts the clarify is anOutcome, not an exception. 116 checks across 11 suites.
v0.7.5-core — ratio/rate co-anchoring (ADR-032 D5). A ratio
N / Dis determinate only when numerator and denominator resolve over one shared population. The planner now checks this statically: a ratio whose operands span different universes is a clarify (co_anchor_ambiguous, discriminatorambiguous) naming the candidate populations — "which population is the rate over?" — never a silent number. It is not eager (a same-universe ratio, or a constant denominator, just serves) and is distinct from avg-of-averages (a B-anchor hazard). Scoped to the ratio: the same two measures as separate columns still serve with the multi-universe coverage caveat. Newcoanchor_demo; 115 checks across 11 suites.
v0.7.4-core — the two-level correctness contract (ADR-032). The column engine never judges: it attempts and returns either a result or a no-result carrying a discriminator —
ambiguous(no unique answer under the rules) orunsupported(the data can't support a result). The planner owns the four outcomes (serve · disclose · clarify · refuse), pluserrorfor vocabulary/capability failures, and classifies every no-result at one chokepoint (Refusal.classified). The multi-grain attribute case is now a clarify (ambiguous), not anunknownerror lumped with real failures; out-of-universe is a refuse; an unimplemented operator is an error.FrameResult.outcome / .clarifies / .refusals / .errorssurface the verdicts;confine_demoverifies the classification.
v0.7.3-core — attribute-anchor resolution hardened. When a universe predicate references an attribute
T.col, the engine must broadcast it at the levelTis keyed._attr_anchorno longer picks the first edgeTprovides: a single-grain table is unambiguous, a denormalized multi-grain table (onegeotable providingstore→regionandregion→country) is pinned by the delivery frame, and a genuinely ambiguous case is refused with the candidate levels named — never a silent wrong grain.confine_demoproves the pin and the refusal. 107 checks across 10 suites.
v0.7.2-core — universe-predicate evaluation hardened to typed predicates. The universe predicate is evaluated at the base grain by broadcast-and-filter, never a join: each referenced attribute is delivered single-table and broadcast onto the frame along its key (transport), the compared sides are coerced to a common dtype, and the frame is filtered. Fixes a real gap — a numeric (
qty >= 10) or real-Datepredicate previously raised a dtype error because predicate literals arrive as strings, and only the benchmark's ISO-date strings compared correctly by luck.confine_demonow proves numeric / Date / AND-ed predicates alongside out-of-domain exclusion. 101 checks across 10 suites.
v0.7.1-core — B-anchor crossing locus refinement + EXPLAIN-without-execution. Crossing detection moved from the engine (execute time) to the planner (compile time):
blocked_lineagesand the operator'sis_monoidare now surfaced on the shape projection, so a crossing is knowable from structure alone. The served contract is unchanged (still served-with-critical, never refused), butframe(...).plan()/explain(execute=False)returns the would-be annotation — the critical crossing plus spec-only provenance caveats — touching zero backend data.
v0.7.0-core — a custom type + three custom operators, planner untouched (HLL case study). The
distinctfamily is decomposed into a parametric typeHLLSketch(p)and three registered operators —hll_count(deliver),hll_merge(a monoid union, combine),hll_estimate(project) — that slot into the umbrella via the registry and engine only;planner.pyandprojection.pyhold zero sketch references. Precision is type identity (a mismatched merge is a type error; a raw sketch is opaque to arithmetic). A publish-time witness store makes sketches stored, not cached, sodistinct@regionmerges stored witnesses with zero base scans at query time.
v0.6.0-core — inform-and-serve reconciliation (Frame-QL Manual). A B-anchor crossing (e.g. summing a stock over time) is now served with a critical
b_anchor_crossingdisclosure that names the alternative reducer — no longer refused. Refusals are reserved for the planner's static clarify/inform cases (fan-out across an M:N edge — now naming all three remedies; out-of-universe; unknown operator; type error). The engine, once handed an executable atom, never withholds on analytical grounds. Disclosures now carry a severity lattice (none<info<caution<critical) with a frame-level rollup. 57 checks across 7 suites pass.
The column-foundation implementation specified in ADR-031 and the Manifold object model — multi-table, transport-based, validated against the real benchmark warehouse (299,934 transactions).
This is not the ADR-030 kernel. The kernel proved correctness techniques on a single denormalized table; Core implements the architecture: the backend delivers single-table column-atoms and functional relationship-columns; the engine transports and relates them; the backend never joins.
The discipline, in code
connector.py— single-table delivery only.deliver_measure(one-table group-by),deliver_edge(one-table key→key mapping),deliver_base_rows(one-table, for sketches). No SQL join is ever generated.model.py— the Manifold object model. Two layers: universes (populations) and the coordinate DAG (FunctionalEdges). A rollup (day→month) and a relationship (store→region) are the same object — a functional edge the engine transports along, tagged with a lineage. The B-anchor blocks per-lineage.engine.py— the center. For(measure, family-member) @ anchor: find the functional path, B-anchor-check every transport edge and every collapsed dimension, resolve cheapest-faithful (cache vs delivery), transport the measure along functional edges (in-engine; no join pushdown), co-compute disclosure, cache.planner.py— provenance-blind. Parses logicalfamily.member @ anchor, expands derived columns, typechecks addressability — fan-out and out-of-universe are refused HERE, statically — then assembles frames and folds disclosures.disclosure.py— the disclosure shadow-value and its sibling, the structured, dialog-capableRefusal(carries named alternatives).
What build_benchmark.py proves (10/10, real data)
- Transport replaces the join —
revenue@regioncomputed from a single-tablerevenue@storedelivery + thestore→regionmapping, related in the engine; exact vs. ground truth; zero joins pushed down. - Coordinate rollup is the same operation —
revenue@cal.monthvia theday→monthmapping. - ★ Fan-out is inexpressible ★ —
revenue@categoryis refused at the planner (transaction↔categoryis M:N); for contrast, the naive join silently inflates revenue 1.44× — a 44% overcount. The refusal names its alternatives (allocation = Pro; membership = rephrase). The flagship. - B-anchor blocks per-lineage —
level.sum@(region,day)works (additive over the store axis);level.sum@storeis refused (would sum a stock across days — non-reconciling over the calendar lineage). Same metric, opposite verdicts, by edge. - Pre/post-agg boundary —
aov = revenue/orderscomputed post-aggregation = correct AOV (not the avg-of-avgs the shippedmonthly_avg_order_valuewould give). - Sketch reaggregation —
visitors@cal.quarterby HLL-merging per-day sketches; ≈ true distinct, vs. a naive sum-of-daily-distincts that overcounts. - Out-of-universe —
level@productrefused as a distinct reason (out of domain — undefined, not missing).
engine: 5 single-table deliveries, 5 transports, 1 cache-hit, 10 backend fetches (all single-table)
Run
pip install --break-system-packages polars duckdb datasketches pyarrow
python3 build_benchmark.py # 10 checks against the benchmark warehouse
(The script reads the warehouse parquets from the benchmark instance path; adjust WAREHOUSE if needed.)
Ingest-first: the definition language
A written MANIFOLD definition (the .cf successor) parses to the Manifold object and is queryable end-to-end — no hand-construction. See benchmark.cml for the whole benchmark Manifold as text, and run:
python3 parse_benchmark.py # parse benchmark.cml, then the SAME 10 checks on the PARSED Manifold
The parser (parser.py) reproduces the hand-built object with full structural parity, the corrected semantics survive the parse (M_ANCHOR { } → MCAR; FAMILY { sum : additive BLOCKED { calendar } } → per-member B-anchor), and publish-time well-formedness checks reject malformed definitions (impure predicate referencing a measure, unknown level/universe/column, etc.).
Grammar (statement-oriented; # comments; { } blocks):
MANIFOLD <name> VERSION <n>
UNIVERSE <name> = <dim> * <dim> ... [WHERE <predicate>]
LEVEL <name> = <column> [BASE]
EDGE <from> -> <to> ALONG <lineage> VIA <table>(<from_col>, <to_col>)
RELATE <a> <-> <b> VIA <table> [NOTE "<text>"]
MEASURE <name> ON <universe> FROM <table> AS <agg>(<expr>)
MEASURE <name> ON <universe> FROM <table> VALUE <expr>
[M_ANCHOR { <col>, ... }] [FAMILY { <agg> [: <tier>] [BLOCKED { <lineage>, ... }] ... }]
DERIVED <name> = <expr>
Scope (Core, per ADR-031)
Logical types, checked at compile time (types.py, types_demo.py): every measure declares a logical (Polars) dtype and every operator carries a type signature (sum: Numeric∪Duration→same, distinct: any→Int64, median: Numeric∪Temporal→same, last: T→T). The signature is vocabulary the planner holds; the mechanics (witness/combine/deliver_sql) stay engine-side. So "operator not supported" and "wrong type for this operator" are vocabulary errors caught at the planner, before the engine is ever asked — proven by zero backend fetches on a refused frame. A static type-inference pass runs as a compile step ahead of resolution (type_error is its refusal reason, a sibling of unknown-operator). The connector owns logical→physical: a measure declares logical Float64 over a raw column, and the connector supplies the TRY_CAST when the physical type is a dirty VARCHAR — so the author writes no casts, and a cast failure is a coverage fact at resolution, never a type error.
In scope and working: multi-table Manifolds, universes with runtime predicate confinement, the coordinate DAG, transport, measure-families with per-lineage B-anchors, the operator registry (reaggregation as a monoid property, plus type signatures), logical types with a compile-time planner typecheck, derived columns, structured refusals, EXPLAIN, the definition-language parser + well-formedness checks, the universe-support consistency check, the two projections as an enforced boundary. Deliberately out (Pro): multiple backends/federation, cloud, custom operators/types, sophisticated optimization, allocation (the M:N split — Core catches the case rather than computing it).
Two projections, enforced (projection.py, projection_demo.py): one authored Manifold has two projections. The planner holds a PlannerView — vocabulary/shape only: logical names, the DAG topology (frm→to + lineage, no physical columns), family member names, derived formulas, M:N pairs for fan-out. The engine holds the full Manifold — sources, realizations, the universe predicate, missingness, costs, the operator registry. This makes "the planner cannot see provenance" structural: home_table, pre_expr, realized_by, provider_table, and the predicate are simply absent from the planner's object — no source string is reachable from it — yet it still does real work (fan-out and out-of-universe are refused from shape alone). The handoff down is a logical request (measure, member, anchor); the return up is a frame + a Disclosure (caveats, not sources). Disclosures cross the boundary; provenance does not.
Reaggregation is a monoid property of the operator (operators.py — the registry, which is the Core/Pro extension point). An operator is reaggregable iff a possibly-enriched witness makes it a monoid; the engine reduces in witness-space and projects to the answer at the boundary: sum/count carry the value (combine +), min/max the value (combine min/max), distinct an HLL sketch (combine union), last/first the (value, order_key) pair (combine argmax/argmin — the order key is carried as the witness), and median/mode are holistic (no finite witness → recompute-from-base, never reduced). Two independent gates compose: monoid-ness (operator-level, possible) and the B-anchor (column-level, permitted). The headline (holistic_demo.py): level.last@(store, cal.month) works by carrying the day and reducing by argmax, while level.sum@(store, cal.month) refuses — same column, same target, the two gates disagree.
Universe-support consistency (universe_check_demo.py): a universe is one population, so every measure bound to it must reduce to the same base-point support (modulo declared coverage). The check reduces each measure to its universe and compares supports; a mismatch not explained by coverage flags a mis-declared universe. (Benchmark passes; a measure mis-sourced from a half-coverage table is flagged "50% short".) This is the count-reducer instance of a more general path-independence check — reduce a measure to the universe singleton along every anchoring and assert the reduced values agree — which becomes exercisable once a second coordinatization of an axis (e.g. a fiscal calendar forking from day) exists; with a single anchoring there is nothing to reconcile against.
Universe-predicate confinement (confine_demo.py): a measure bound to a predicated universe is confined to its valid points at the base grain, at delivery, before aggregation — logically prior to any query WHERE (domain before selection), inherited through cache and transport. A cross-table predicate like day >= stores.opened_date is evaluated in the engine: opened_date is delivered from stores (single table) and broadcast onto (store, day) by transport, then compared and filtered — never a backend join. The demo injects out-of-domain rows; the engine excludes them while an unconfined Manifold silently includes them.
Still thin in this build (next): the full cached-vs-stored cost search (resolution is correct but the cost model is minimal); hoisting the B-anchor permission refusal (blocked_reaggregation) from the engine to the planner (it is structural and belongs there, but it currently sits in the engine to preserve behaviour — moving it means the PlannerView carries family-member shape); automated Discovery (the parser consumes a written definition; generating one from a warehouse is the layer above).
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file columna_core-0.8.0.tar.gz.
File metadata
- Download URL: columna_core-0.8.0.tar.gz
- Upload date:
- Size: 442.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2afbcaa9782b6d97aceb26a8ea0c867451870c258107383da01b172327753d4f
|
|
| MD5 |
c25155464c45b271ce2bfa67d46f4067
|
|
| BLAKE2b-256 |
affe4760dd2e492abf0d8f979f33a20ea3a6937573073ebef95d8576ccee3c9b
|
Provenance
The following attestation bundles were made for columna_core-0.8.0.tar.gz:
Publisher:
publish.yml on datumwise/columna
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
columna_core-0.8.0.tar.gz -
Subject digest:
2afbcaa9782b6d97aceb26a8ea0c867451870c258107383da01b172327753d4f - Sigstore transparency entry: 2189597057
- Sigstore integration time:
-
Permalink:
datumwise/columna@ece8a399a944326cec9abf239e82392fcb9f76b0 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/datumwise
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ece8a399a944326cec9abf239e82392fcb9f76b0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file columna_core-0.8.0-py3-none-any.whl.
File metadata
- Download URL: columna_core-0.8.0-py3-none-any.whl
- Upload date:
- Size: 97.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3aa57fed33bfad04d82c21bda758a7a17fdf70b0dcd3243c81e60346b5c68f46
|
|
| MD5 |
612a1d8e62c3e4024fb7d024bc3ed6c6
|
|
| BLAKE2b-256 |
d1479b47090967f4a30dd073741ba26c8cf34ee396f503f30adebf716bd25549
|
Provenance
The following attestation bundles were made for columna_core-0.8.0-py3-none-any.whl:
Publisher:
publish.yml on datumwise/columna
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
columna_core-0.8.0-py3-none-any.whl -
Subject digest:
3aa57fed33bfad04d82c21bda758a7a17fdf70b0dcd3243c81e60346b5c68f46 - Sigstore transparency entry: 2189597123
- Sigstore integration time:
-
Permalink:
datumwise/columna@ece8a399a944326cec9abf239e82392fcb9f76b0 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/datumwise
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ece8a399a944326cec9abf239e82392fcb9f76b0 -
Trigger Event:
release
-
Statement type: