This is the release contract for pgvector_hypo 0.1.0. It describes what the
extension models, what it deliberately rejects, and which claims are measured
against physical pgvector indexes.
| Component | Supported contract |
|---|---|
| PostgreSQL | Major version 16 |
| pgvector | 0.8.x semantics, tested at 0.8.0 and 0.8.5 |
| Index access method | IVFFlat only |
| Extension behavior | Session-local, top-level plain EXPLAIN planning only |
PostgreSQL 17 and newer are not implied by this table. A new pgvector or PostgreSQL version must rerun the compatibility workflow and public-data gate before it can extend the claim.
| Fixed result type | Official opclass | Ordering operator | Valid dimensions | Default |
|---|---|---|---|---|
vector(n) |
vector_l2_ops |
<-> |
1–2,000 | yes |
vector(n) |
vector_ip_ops |
<#> |
2–2,000 | no |
vector(n) |
vector_cosine_ops |
<=> |
2–2,000 | no |
halfvec(n) |
halfvec_l2_ops |
<-> |
1–4,000 | no |
halfvec(n) |
halfvec_ip_ops |
<#> |
2–4,000 | no |
halfvec(n) |
halfvec_cosine_ops |
<=> |
2–4,000 | no |
bit(n) |
bit_hamming_ops |
<~> |
1–64,000 | no |
The inner-product and cosine minima come from pgvector's spherical-k-means normalization support procedure, not from an opclass-name exception. Cosine page estimation excludes NULLs and sampled values whose official normalization procedure cannot produce a positive norm. Bit has no norm exclusion.
Explicit and default opclasses resolve through PostgreSQL namespaces and
search paths. The resolved type, access method, opclass, opfamily, and support
procedures must all be the official objects owned by the installed vector
extension. A same-named custom opclass is rejected.
Every row in the type/opclass table supports the following forms:
| Form | Contract |
|---|---|
| Ordinary key | One fixed-dimension column; redundant parentheses around a bare column remain an ordinary key. |
| Expression key | One transformed expression that PostgreSQL proves immutable after planning and whose result has the exact fixed type/typmod required by the opclass. |
| Partial index | One PostgreSQL-valid immutable predicate. Normal predicate implication determines eligibility. |
| Partial expression | Structural expression matching and predicate implication are independent gates; both must pass. |
| Partitioned parent | Key and predicate trees map from the registered parent directly to compatible leaves, including reordered physical columns. |
| Generated column | Treated as an ordinary fixed-dimension column. |
| Materialized view | Ordinary and expression definitions use the same rules as a table. |
binary_quantize(embedding)::bit(n) with bit_hamming_ops and
embedding::halfvec(n) with a halfvec opclass are documented expression
examples, not hard-coded expression patterns. PostgreSQL matches prepared
expression trees rather than SQL text.
The pages column returned by pgvector_hypo_list() and
pgvector_hypo_list_indexes is bigint. This preserves the full unsigned
BlockNumber range used by PostgreSQL relation metadata.
An expression index has no physical expression-statistics object at registration time. Its row/page estimate therefore treats every relation row as indexable before applying a partial predicate. Ordinary keys can use column NULL, norm, and dominant-most-common-value evidence. Missing or incomplete statistics always fall back to the conservative policy.
Registrations keep catalog OIDs and transformed trees in backend-local memory. Renames preserve compatible OID/attribute identity. Type or typmod changes, drop/recreate, detach, extension relocation after registration, and missing functions/operators make an old registration fail closed; a new registration can resolve the new catalog state.
The registering role must own the target relation, matching physical
CREATE INDEX. When TABLESPACE is omitted, registration captures
PostgreSQL's current default index tablespace and its permission check; later
GUC changes do not alter that definition. A dropped captured tablespace and
partition-parent lock contention both withhold candidates until the definition
can be modeled safely.
The following are rejected or withheld rather than modeled speculatively:
- HNSW and access methods other than IVFFlat;
sparsevec, variable-dimension vector/halfvec, andbit varyingkeys;- custom opclasses, opclass options, and unsupported type/opclass pairings;
- multicolumn and included-column indexes;
- per-key collation, ordering, or NULL-order modifiers;
- an explicit
TABLESPACEclause,CONCURRENTLY,IF NOT EXISTS, andON ONLY; - mutable expressions or predicates that remain mutable after PostgreSQL planning;
- executable use, including ordinary execution,
EXPLAIN ANALYZE, andEXPLAIN EXECUTE; - PostgreSQL majors other than 16.
A caller-provided index name is syntactically accepted, but the session registration receives a checked fake OID and a generated hypothetical display name. No catalog index or relation file is created.
The release gate compares physical and hypothetical definitions, pages, startup/total costs, selected scan family, and selected index identity. It requires:
- zero safety failures and definition-parity failures;
- zero canonical planner-choice mismatches;
- median relative page/startup/total-cost error at most 10%;
- p95 relative page/startup/total-cost error at most 20%;
- the maximum always published rather than averaged away;
- evidence on both sides of pgvector's TOAST startup-cost branch.
Relative error is abs(hypothetical - physical) / physical. A small numeric
difference that preserves the chosen path is a fidelity measurement. A
planner-choice mismatch is more serious because it changes the advice presented
to the user. Scan-shape, index-identity, rejected-candidate cost, fuzzy-cost
ties, pathkey ties, and physical page variance remain separately named
diagnostics.
The authoritative seven-opclass run was generated on 2026-07-26 at 05:26 UTC
with PostgreSQL 16.14, pgvector 0.8.5,
pgvector_hypo 0.1.0, 100,000 source rows per public dataset, and three
physical builds per page observation. The median-page build supplied the
canonical physical plans.
make evidencecheck \
EVIDENCE_DSN=postgresql:///pgvector_hypo_release_final_20260726 \
EVIDENCE_PYTHON=/tmp/pgvector-hypo-evidence-venv/bin/python \
EVIDENCE_ROWS=100000 \
EVIDENCE_OUTPUT_DIR=artifacts/evidence/issue-14-release-final-source-20260726 \
EVIDENCE_ARGS='--physical-page-samples=3'| Result | Observation |
|---|---|
| Strict overall gate | FAIL |
| Baseline / plan / page cases | 118 / 2,032 / 27 |
| Planner-choice mismatches | 2 |
| Safety failures | 0 |
| Page error p50 / p95 / max | 0.11% / 0.97% / 3.01% |
| Startup-cost error p50 / p95 / max | 0.00% / 0.85% / 2.04% |
| Total-cost error p50 / p95 / max | 0.00% / 0.60% / 2.04% |
| TOAST adjusted / non-adjusted cases | 2 / 4 |
| Official opclasses observed | 7 / 7 |
Both reported mismatches are the same saturated-probe boundary, repeated at
probes=400 and 401 because both clamp to a probe ratio of one:
GloVe-derived bit(100), lists=400, tenant_1pct, LIMIT 10. The median
physical build had 632 pages and selected IVFFlat; the hypothetical model
estimated 651 pages and selected a sequential scan. Physical samples were
[632, 626, 635], and only two of the three physical builds chose IVFFlat for
that query.
This is a measured k-means/build-variance planner boundary, not a definition or
safety mismatch. It is also not waived: results.json retains passed=false
because the strict gate continues to count every canonical planner-choice
mismatch. A fixed-bit-specific headroom change was rejected after it made an
independent five-family bit matrix worse. The known limitation is that standard
column statistics cannot predict the exact k-means page phase closely enough to
guarantee a stable decision at this boundary.
The artifact directory is
artifacts/evidence/issue-14-release-final-source-20260726:
| File | SHA-256 |
|---|---|
manifest.json |
94c23e0dfc72ae43121b7e4a06621402b090a4b673b324b115c8e1071066bd6e |
results.json |
58e42e5f7307cea0edaeec6fbeb2c60e8e5dfc2ef2770e01f8f114bd6ec02f1b |
summary.md |
9d400f9897f65bd4d827fe4d397f5872b33836f7070d70427db45a0219e7d4dc |
The source dataset checksums recorded in the manifest are:
| Source | SHA-256 |
|---|---|
| SIFT | dd6f0a6ed6b7ebb8934680f861a33ed01ff33991eaee4fd60914d854a0ca5984 |
| GloVe | 544af1d5e84e112cd4749571dcfd8ca109818a572f850af75a3a09e093a953c4 |
| Last.fm | 2bb2886857b01fabf939fcb7478717cd9ecf03b3fa66f0f0bef325f510ce38fd |
The public-data adapter reuses the same source rows:
| Source | Derived supported families |
|---|---|
| ANN Benchmarks SIFT | vector and halfvec L2 |
| ANN Benchmarks Last.fm | vector and halfvec inner product |
| ANN Benchmarks GloVe | vector and halfvec cosine; deterministic binary-quantized bit/Hamming |
The generated CI smoke uses deterministic data and downloads nothing. It is a bounded orchestration/compatibility gate, not a replacement for the 100,000-row public-data release run.
Start with an immutable tagged archive, not a dirty checkout:
tag=v0.1.0
git archive --format=tar.gz --prefix=pgvector_hypo-0.1.0/ \
-o pgvector_hypo-0.1.0.tar.gz "$tag"
shasum -a 256 pgvector_hypo-0.1.0.tar.gz
tar -xzf pgvector_hypo-0.1.0.tar.gz
cd pgvector_hypo-0.1.0With PostgreSQL 16 and one declared pgvector version installed for the same
pg_config:
make
make install
make installcheck
make pycheck APP_PYTHON=/path/to/evidence-venv/bin/python
make appcheck \
APP_DSN=postgresql:///pgvector_hypo_evidence \
APP_PYTHON=/path/to/evidence-venv/bin/python
make evidenceintegration \
EVIDENCE_DSN=postgresql:///pgvector_hypo_evidence \
EVIDENCE_PYTHON=/path/to/evidence-venv/bin/python
make evidencesmoke \
EVIDENCE_DSN=postgresql:///pgvector_hypo_evidence \
EVIDENCE_PYTHON=/path/to/evidence-venv/bin/python
make evidencecheck \
EVIDENCE_DSN=postgresql:///pgvector_hypo_evidence \
EVIDENCE_PYTHON=/path/to/evidence-venv/bin/python \
EVIDENCE_ROWS=100000 \
EVIDENCE_ARGS='--physical-page-samples=3' \
EVIDENCE_OUTPUT_DIR=artifacts/evidence/release-0.1.0Run the build/regression/application/generated gates with both pgvector 0.8.0
and 0.8.5. The public release-fidelity run is pinned to pgvector 0.8.5, whose
mirrored IVFFlat implementation is the source baseline. Preserve
manifest.json, results.json, and summary.md together; their canonical
facts are validated before publication.
Mirroring upstream page layout and cost code creates a maintenance obligation. When pgvector changes its type support procedures, dimension limits, tuple/list layout, or IVFFlat cost callback, update the storage policy and model only after rerunning definition parity and both evidence adapters.