API Stability Policy¶
This document defines the public API contract for the pgokf extension:
what callers may depend on, how it is versioned, how it changes, and what is
explicitly not part of the contract. It is the reference that
CHANGELOG.md classifies changes against and that the
release checklist enforces.
What "public API" means here¶
The public API is the surface an application, dashboard, or migration can bind
to and expect to keep working across compatible releases. For pgokf that
surface is deliberately narrow and fully enumerated below. Everything reachable
in the database that is not on these lists is internal and may change without
notice.
Every public object is self-describing: it carries a SQL COMMENT ON, so
\df+, \dT+, \d+, and obj_description() document the contract from inside
the database. Complete comment coverage is a release gate (see
Documentation coverage).
The stable surface¶
Functions (69)¶
The installed extension defines one function more than this table: the
internal pgokf.bm25_hits helper the optional BM25 backend calls. It is not
part of the stable surface, carries no compatibility promise, and may change
or disappear in any release.
| Function | Role required | Purpose |
|---|---|---|
pgokf.register_bundle(text, text, jsonb) |
pgokf_writer |
Register and sync an OKF bundle root from a filesystem path |
pgokf.register_bundle_content(text, text[], bytea[], jsonb) |
pgokf_writer |
Register or resync a bundle from in-memory (path, bytes) content - the mountless object-store ingestion path (no filesystem, no network I/O in the extension) |
pgokf.refresh_bundle(bigint) |
pgokf_writer |
Incrementally re-sync a filesystem-sourced bundle (content bundles are re-synced via register_bundle_content) |
pgokf.unregister_bundle(bigint) |
pgokf_writer |
Remove a bundle (rows cascade) |
pgokf.set_bundle_enabled(bigint, boolean) |
pgokf_writer |
Enable/disable a bundle (hides it from search and graph without deleting rows; reversible) |
pgokf.retire_bundle(bigint) |
pgokf_writer |
Soft-retire a bundle (hides it, reversibly) into a purge window |
pgokf.unretire_bundle(bigint) |
pgokf_writer |
Reverse a retirement (clears retired_at) |
pgokf.purge_retired(interval) |
pgokf_admin |
Hard-delete bundles retired longer than the given window |
pgokf.list_bundles() |
pgokf_reader |
List registered bundles |
pgokf.bundle_info(bigint) |
pgokf_reader |
Info for one registered bundle |
pgokf.duplicate_concepts(bigint, integer) |
pgokf_reader |
Concepts sharing a BLAKE3 file_hash across bundles (dedup) |
pgokf.concept_search(text, bigint, integer, text, text[], text, text, jsonb) |
pgokf_reader |
Ranked full-text search with optional filters and an optional trailing after_cursor jsonb for stable keyset pagination (trailing args default NULL; the historical 3-arg call still resolves) |
pgokf.search_facets(text, bigint, text, text, text[], text, text) |
pgokf_reader |
Faceted result counts for a query (by type/tag/bundle/status/trust_tier) |
pgokf.search_index_status() |
pgokf_reader |
Search-index availability + coverage report (native/bm25/pgvector) as jsonb |
pgokf.find_similar(text, bigint, integer) |
pgokf_reader |
Content more-like-this for a seed concept (distinct from the link graph) |
pgokf.concept_search_semantic(real[], bigint, integer, text[]) |
pgokf_reader |
Vector nearest-neighbor search by a caller-supplied query embedding (requires pgvector); the optional trailing concept_types filters by type membership inside the ranked query, before candidate truncation |
pgokf.concept_search_hybrid(text, real[], bigint, integer, text[]) |
pgokf_reader |
RRF fusion of lexical + semantic (degrades to lexical when pgvector absent); the optional trailing concept_types constrains both ranked inputs before either is truncated |
pgokf.set_concept_embedding(bigint, text, real[]) |
pgokf_writer |
Store a caller-computed embedding for a concept (no bundled model) |
pgokf.rebuild_embedding_index() |
pgokf_admin |
(Re)build the optional pgvector HNSW index; a no-op with a notice when pgvector is not installed |
pgokf.concept_neighbors(text, integer, bigint) |
pgokf_reader |
Walk the resolved link graph |
pgokf.concept_history(bigint, text, integer) |
pgokf_reader |
Version timeline of a concept (opt-in track_history) |
pgokf.concept_as_of(bigint, text, timestamptz) |
pgokf_reader |
The concept version valid at a point in time |
pgokf.set_config(text, jsonb) |
pgokf_admin |
Set a durable configuration key |
pgokf.reset_config(text) |
pgokf_admin |
Reset one/all configuration keys |
pgokf.get_config() |
pgokf_reader |
Effective configuration as jsonb |
pgokf.list_sync_log(bigint, integer) |
pgokf_reader |
Sync/audit history rows (from the admin-only pgokf_private.sync_log) |
pgokf.list_sync_changes(bigint, integer) |
pgokf_reader |
Per-concept change manifest (added/updated/removed) for one sync |
pgokf.list_access_log(bigint, integer) |
pgokf_admin |
Exfiltration/access audit rows (exports + get_concept_source) |
pgokf.list_bundle_log(bigint, text, integer) |
pgokf_reader |
Reserved OKF log.md activity-log entries, projected per directory |
pgokf.catalog_stats() |
pgokf_reader |
Per-bundle counts, sync recency, and staleness for operators |
pgokf.health() |
pgokf_reader |
Liveness/readiness document as jsonb (counts, backend, in_recovery) |
pgokf.stale_concepts(bigint, timestamptz) |
pgokf_reader |
Concepts past their OKF stale_after |
pgokf.get_concept_source(bigint, text) |
pgokf_reader |
Return one concept's stored source bytes as bytea (no filesystem write) |
pgokf.get_skill(bigint, text) |
pgokf_reader |
Return one Agent Skills package (skill_result): metadata, the exact SKILL.md, and its resource listing; audited |
pgokf.get_script(bigint, text) |
pgokf_reader |
Return one package script's exact bytes and typed metadata (script_result); audited |
pgokf.get_reference(bigint, text, boolean) |
pgokf_reader |
Return one package reference or asset (reference_result), bytes included unless include_bytes is false; audited when bytes are returned |
pgokf.export_parquet(bigint, text) |
pgokf_admin |
Export a bundle projection to Parquet files |
pgokf.export_sources(bigint, text) |
pgokf_admin |
Reconstruct a bundle's stored source files on disk, hash-verified |
pgokf.rebuild_search_index() |
pgokf_admin |
(Re)build the optional BM25 index for the provider bm25_provider resolves to (pg_textsearch or pg_search); a no-op with a notice when none is installed |
pgokf.schedule_refresh(bigint, text) |
pgokf_admin |
Schedule periodic refresh_bundle via pg_cron (raises when pg_cron is absent) |
pgokf.unschedule_refresh(bigint) |
pgokf_admin |
Remove a bundle's scheduled refresh |
pgokf.version() |
pgokf_reader |
Loaded shared-library version |
pgokf.tenant_required() |
any role with USAGE on pgokf |
Whether the require_tenant policy is on (consulted by every RLS policy) |
pgokf.mcp_token_bearer(text) |
pgokf_reader |
The name, role, and tenant of the MCP bearer token with this SHA-256 digest, or no row (SECURITY DEFINER over pgokf_web.mcp_tokens, which no reader may see) |
pgokf.register_bundle_content_with_context(text, text[], bytea[], jsonb, jsonb) |
pgokf_writer |
register_bundle_content with an explicit change-provenance context (when context is NULL, the session pgokf.sync_context GUC supplies the same provenance) |
pgokf.capabilities() |
pgokf_reader |
The capability names and interface versions this release implements, as jsonb |
pgokf.concept_search_fresh(text, bigint, integer, text, text, text[], text, text, jsonb) |
pgokf_reader |
Freshness-aware variant of concept_search: a freshness filter (any/fresh/stale) plus per-hit effective-freshness and embedding-provenance annotations |
pgokf.register_freshness_dependency(text, bigint, text, bigint, text, text, text, text) |
pgokf_writer |
Map a source-bundle selector onto a target bundle/scope so source changes mark matched targets stale |
pgokf.disable_freshness_dependency(bigint) |
pgokf_writer |
Disable a registered freshness dependency |
pgokf.remove_freshness_dependency(bigint) |
pgokf_writer |
Remove a registered freshness dependency |
pgokf.mark_stale(bigint, text[], text, text) |
pgokf_writer |
Mark a bundle stale with reason codes and the observed source revision |
pgokf.mark_reconciling(bigint, text) |
pgokf_writer |
Mark a bundle as reconciling and claim the standing dependency invalidation epoch (returns the attempt's claim token) |
pgokf.mark_blocked(bigint, text[], text) |
pgokf_writer |
Mark a bundle blocked with reason codes |
pgokf.mark_fresh(bigint, bigint, bigint, text, text, jsonb, text) |
pgokf_writer |
Compare-and-set reconciliation completion: refuses (returns false) when the catalog generation, observed source revision, or the attempt's claim token moved meanwhile |
pgokf.mark_scope_stale(bigint, text, text, text[], text) |
pgokf_writer |
Mark one scope (concept/path/group) of a bundle stale |
pgokf.clear_freshness_scope(bigint, text, text) |
pgokf_writer |
Clear a scoped freshness override |
pgokf.list_freshness_dependencies(integer) |
pgokf_admin |
Inspect the freshness-dependency registry |
pgokf.repair_bundle_freshness(bigint, text, text[]) |
pgokf_admin |
Repair a bundle's freshness state directly |
pgokf.issue_publication_fence(bigint, text, bigint, bigint, text, integer) |
pgokf_writer |
Compare-and-set the producer's publication fence slot under the bundle advisory lock; hands out the next fencing token |
pgokf.release_publication_fence(bigint, text, bigint) |
pgokf_writer |
Release a live publication fence token |
pgokf.claim_catalog_change_events(text, integer, integer) |
pgokf_dispatcher |
Claim pending or expired-claim catalog-change outbox events oldest-first (FOR UPDATE SKIP LOCKED) with a lease |
pgokf.ack_catalog_change_event(bigint, text, text) |
pgokf_dispatcher |
Acknowledge a claimed outbox event after durable producer acceptance (idempotent, producer-bound) |
pgokf.list_catalog_change_events(bigint, integer) |
pgokf_admin |
Inspect the catalog-change outbox, newest first |
pgokf.replace_relationships(text, bigint, bigint, bigint, bigint, jsonb) |
pgokf_writer |
Publish a source bundle's complete typed-relationship set, bound to the producer's live publication fence |
pgokf.concept_relationship_neighbors(bigint, text, integer, text, text[], integer) |
pgokf_reader |
Walk the current typed-relationship set cycle-safely (direction, optional type filter, hop and result ceilings) |
pgokf.set_concept_embedding_cas(bigint, text, real[], text, text, text, text) |
pgokf_writer |
Compare-and-set embedding write: stores only when the expected file hash still matches, so a superseded attempt cannot overwrite newer content |
pgokf.list_scheduled_refreshes() |
pgokf_reader |
List the scheduled bundle refreshes the extension manages, as (bundle_id, schedule) rows (SECURITY DEFINER over cron.job, which row-restricts an ordinary login to its own jobs while schedule_refresh registers them under the extension owner; tenant-confined) |
pgokf.registry_set_status(uuid, text) |
pgokf_admin |
Pause/resume one registered repository of the external repository-registry producer service (ast_graph.repository_registry.status, active/paused; runtime-only coupling, curated 22023 when the producer schema is absent; tenant-confined: a cross-tenant id earns the same 22023 as an unknown one) |
pgokf.registry_set_poll_interval(uuid, integer) |
pgokf_admin |
Set one registered repository's poll interval in seconds (5-86400) on the same external registry (same runtime-only coupling, 22023 behavior, and tenant confinement) |
Every row from pgokf.register_bundle_content_with_context down became stable
in the 0.3.0 development cycle; the rows above it predate that cycle.
The function name, schema, argument types, argument order, and result shape
are all part of the contract. Default values that let callers omit trailing
arguments (name/options on register_bundle, bundle_id/limit on search
and neighbors) are contractual too: an existing call that omits them keeps
working.
Composite types (24)¶
pgokf.bundle_sync_result, pgokf.concept_search_result,
pgokf.concept_neighbor, pgokf.bundle_info, pgokf.export_result,
pgokf.sync_log_entry, pgokf.catalog_stat, pgokf.stale_concept,
pgokf.sync_change, pgokf.access_log_entry, pgokf.duplicate_group,
pgokf.search_facet, pgokf.bundle_log_entry, pgokf.concept_version,
pgokf.skill_result, pgokf.script_result, pgokf.reference_result,
pgokf.freshness_dependency_info, pgokf.publication_fence_info,
pgokf.claimed_change_event, pgokf.catalog_change_event_info,
pgokf.concept_search_fresh_result, pgokf.relationship_publication_info,
pgokf.relationship_neighbor (the last seven became stable in the 0.3.0 development cycle).
The set of columns, their names, and their types are stable. New columns are
not added to an existing composite type in a compatible release, because
SELECT * and positional row expansion would break; a new field ships as a new
type or a new function instead.
Tables (14 public + 4 documented-internal)¶
Public, SELECT-able by pgokf_reader: pgokf.bundles, pgokf.concepts,
pgokf.concept_metadata, pgokf.links, pgokf.concept_provenance,
pgokf.concept_verification, pgokf.concept_provenance_source,
pgokf.concept_source, pgokf.concept_embedding, pgokf.bundle_log,
pgokf.concept_history, pgokf.skills, pgokf.scripts,
pgokf.reference_documents.
These are a read projection. Callers may SELECT from them and depend on
existing column names and types; the columns listed in
docs/sql-api.md are the contract. Direct INSERT/UPDATE/
DELETE is not part of the API - mutation goes exclusively through the
SECURITY DEFINER sync functions, and the tables carry no write grant to
pgokf_reader. New columns may be added to these projection tables in a
compatible release; code that pins to named columns (never SELECT * into a
fixed row type) is forward-compatible.
pgokf_private.config, pgokf_private.sync_log, pgokf_private.sync_log_change,
and pgokf_private.access_log are listed here only because they are catalog
tables the documentation gate covers (the fourteen public tables plus these four
private ones). They are internal state, not API - read the
sync history through pgokf.list_sync_log and configuration through
pgokf.get_config; see The private surface.
Roles (4)¶
pgokf_reader < pgokf_writer < pgokf_admin. Their names and the
privilege boundaries between them are stable: readers may search and read
configuration; writers may additionally register/refresh/unregister bundles
(the intended tier for an ingestion pipeline); admins may additionally manage
configuration and run the file-writing exports. Each tier inherits the one
below (pgokf_admin → pgokf_writer → pgokf_reader). These are cluster-wide
roles and survive DROP EXTENSION.
Since the 0.3.0 development cycle a fourth role, pgokf_dispatcher, stands deliberately
outside the ladder: it inherits nothing and is inherited by nothing. It is
the sole claim/acknowledge role for the catalog-change outbox
(pgokf.claim_catalog_change_events, pgokf.ack_catalog_change_event) and
holds no search or ingestion rights; admins inspect the outbox through
pgokf.list_catalog_change_events.
GUC names (9)¶
pgokf.max_file_bytes, pgokf.max_bundle_files, pgokf.max_bundle_bytes,
pgokf.max_frontmatter_bytes, pgokf.max_graph_hops,
pgokf.max_relationship_rows, pgokf.log_level, and
pgokf.tenant (the USERSET
multi-tenant policy selector; empty by default, which preserves the
pre-multi-tenancy see-all behavior), plus - since the 0.3.0 development cycle -
pgokf.sync_context (the USERSET change-provenance context a producer
session sets before a sync; the committed catalog-change event carries it).
The names and their meaning are
stable; default values are tuning knobs and may be adjusted in a minor release
when a change is safe and documented.
Semantic versioning policy¶
pgokf follows Semantic Versioning 2.0.0. Given
MAJOR.MINOR.PATCH, and treating the stable surface above as the contract:
- MAJOR - a breaking change to the stable surface: removing or renaming a function/type/table/role/GUC, changing a function's argument types or order, removing a default that callers relied on, changing a result-type column set, narrowing a privilege in a way that breaks existing callers, or changing documented behavior in an incompatible way.
- MINOR - backward-compatible additions: a new function, a new composite type, a new projection column, a new GUC, a new optional trailing argument with a default, or new forward-compatible behavior.
- PATCH - backward-compatible fixes: bug fixes, performance improvements, documentation, and internal refactors that leave the stable surface unchanged.
Pre-1.0 caveat¶
While the version is 0.x, the surface is not yet frozen. Per SemVer,
0.y.z makes no compatibility promise across y bumps. In practice this
project already treats the enumerated surface as stable and documents every
change in the changelog, but until a 1.0.0 tag is cut a 0.MINOR bump may
carry a breaking change when it is called out explicitly in the changelog under
a Changed or Removed heading. Reaching 1.0.0 is the point at which the
MAJOR/MINOR/PATCH rules above become binding guarantees, and it is a deliberate
human release decision - not an automated version bump.
Development point versions¶
Between releases the working line is point-versioned: the cycle toward
0.3.0 ships as 0.3.0-dev1, 0.3.0-dev2, and so on, with
pgokf.control's default_version and the workspace crate version moving
together on every step. The reason is operational: deployments track
development builds, and a flat 0.3.0-dev gives SQL objects added mid-cycle
no ALTER EXTENSION pgokf UPDATE target - they would have to be applied by
hand, outside the extension's receipts. With point versions, every SQL change
rides its own pgokf--0.3.0-devN--0.3.0-dev<N+1>.sql script through the
normal update machinery.
The naming needs no special dispensation from PostgreSQL: extension version
names are opaque strings to the update machinery. ALTER EXTENSION ...
UPDATE finds its path by exact pgokf--<from>--<to>.sql file-name matching
(a graph walk over the shipped scripts, with the control file's
default_version - itself a string, never a comparison - as the goal of a
bare UPDATE), so 0.3.0-dev -> 0.3.0-dev1 resolves like any other edge
once the script file exists. pgrx accepts the suffix in both the control file
and the Cargo package version (a SemVer pre-release identifier), and the
api_stability suite locks the convention's shape (MAJOR.MINOR.PATCH-devN,
N starting at 1).
Forward discipline, test-enforced. Any change that adds or alters a
shipped SQL object must, in the same change: bump the dev point version
(control file and workspace Cargo.toml together) and ship the
devN -> devN+1 upgrade script whose target equals the new
default_version. tests/api_stability.rs fails the build otherwise: it
requires the control version to match the crate version, the script chain to
walk from 0.1.0 to the default_version, and the default_version to be
the chain's unique terminal node - no script may name it as a source, so a
step shipped without the default bump (a dev1 -> dev2 script while the
default stays dev1) fails the suite, because bare ALTER EXTENSION pgokf
UPDATE would still target the old terminal and never run it. The check
walks the pgokf--<from>--<to>.sql edges as a graph and never compares
version strings; a deliberate historical branch, should one ever ship, must
be listed explicitly in PERMITTED_BRANCH_TERMINALS.
Where a mid-cycle object may already exist in the field (deployed from an
earlier or later point of the cycle, or applied by hand), the step's
statements must be idempotent - CREATE OR REPLACE, guarded DO blocks -
and must adopt hand-applied copies into extension membership.
Collapse at finalization. When the cycle closes, default_version
becomes the clean 0.3.1, the install script regenerates under that name
and is committed (pgrx's entity ordering is unstable across invocations, so
the committed snapshot is validated by the catalog it produces, not by
byte-equality with a regeneration),
and the preserved pgokf--0.3.0-dev3--0.3.0.sql receipt followed by
pgokf--0.3.0--0.3.1.sql (a no-op beyond the
standing register_dump_relations() call) lets point-versioned deployments
reach the release through the same machinery. The upgrade chain from the
previous tag then runs ... -> 0.2.0 -> 0.3.0-dev -> 0.3.0-dev1 -> ... -> 0.3.0 -> 0.3.1; the
fresh-install-vs-upgrade parity harness proves the two routes converge
object-by-object, and that every deployed point version reaches the release
through a bare ALTER EXTENSION pgokf UPDATE.
Deprecation process¶
Nothing on the stable surface is removed abruptly. The process is:
- Announce. Mark the object deprecated in the changelog under
Deprecated, and update its SQLCOMMENT ONto say so and to name the replacement. - Coexist. The deprecated object keeps working for at least one full MINOR release (post-1.0) alongside its replacement, so callers can migrate incrementally.
- Remove. Removal happens only in a subsequent MAJOR release, listed under
Removedin the changelog, with the migration path repeated there.
Extension upgrade scripts (pgokf--<from>--<to>.sql) carry any data migration
a deprecation requires and must remain forward-compatible - see the
upgrade mechanism - so
ALTER EXTENSION pgokf UPDATE never loses catalog data.
The private surface (NOT API)¶
The following are internal implementation detail. They may change, move, or disappear in any release, and callers must not depend on them:
- The entire
pgokf_privateschema, includingpgokf_private.config. It is administrator-only state managed exclusively throughpgokf.set_config/pgokf.reset_config/pgokf.get_config. Read and write it only through those functions. - The internal
pgokf.*state tables behind the 0.3.0-cycle producer surface -pgokf.bundle_freshness,pgokf.concept_freshness,pgokf.freshness_dependency,pgokf.publication_fence,pgokf.catalog_change_event,pgokf.relationship_publication, andpgokf.relationship. They carry no grant to any reader role; reach the data through the documented views (pgokf.effective_freshness,pgokf.current_relationships) and functions. - Indexes, constraints, and trigger internals on the projection tables. Their existence and names are not contractual; query behavior is.
- The
SECURITY DEFINERwiring,search_pathpinning, and grant details beyond the documented reader/writer/admin boundaries. - Rust module layout, the sync engine, and the parser crates. Only the SQL surface is public.
- The exact text of
COMMENT ONstrings and error message wording. SQLSTATE codes raised by public functions (e.g.23505on duplicate registration,22023on invalid configuration, unknown-bundle errors) are documented behavior; the human-readable message text is not.
Documentation coverage is part of the contract¶
Every public object must carry a COMMENT ON. This is enforced two ways:
- At build time -
crates/extension/tests/api_stability.rsreads the extension SQL source and failscargo testif any enumerated function, type, table, or role lacks aCOMMENT ON, and if the count of public functions drifts from the locked surface. - At release time - the release checklist runs an
obj_description/shobj_descriptioncoverage query against a freshly installed extension and blocks the release if any public object is uncommented.
Adding a public object therefore requires, in the same change: the object, its
COMMENT ON, an entry in the enumerated contract test, a changelog entry, and
(if it changes stored data) an upgrade script.
Development-function adoption security policy¶
The dev→dev1 edge repairs the exact signatures
pgokf.list_scheduled_refreshes(), pgokf.registry_set_status(uuid,text) and
pgokf.registry_set_poll_interval(uuid,integer). Missing functions are created.
Existing functions, including pgokf members, must be owned by the extension
owner and have exactly two explicit EXECUTE grants: to that owner and to
pgokf_reader for the listing function, or pgokf_admin for each writer.
Both grants must be issued by the owner without grant options. ACL ordering is
irrelevant. Unexpected owners, additional grants (including PUBLIC), missing
grants, different grantors and grant options refuse the entire upgrade with a
security-metadata diagnostic. Custom grants are never silently revoked; the
operator must review them and deliberately restore this posture before retrying.
A compatible body may be replaced when this security metadata is canonical.
Incompatible return/argument definitions or membership in another extension
still refuse atomically. The live PG18 harness checks catalog and version
rollback, including ownership, ACLs, initial privileges, body and membership.
This edge can inspect only installations that traverse dev→dev1; it does not
repair already-upgraded installations retroactively.
The immutable v0.3.0 tag is an unpublished candidate superseded by 0.3.1:
its tag CI demanded unavailable GA PG19 artifacts and found a Linux strict-Clippy
defect. The preserved graph includes 0.3.0-dev3 → 0.3.0 → 0.3.1, with no
outgoing edge from 0.3.1. Candidate installations can upgrade additively; the
candidate was not a published release or a production deployment.