Documentation
¶
Overview ¶
Package schemahistory is the append-only version history of tenant schemas (VERSION-S1) — the base of the productive trust layer. STATE-AUDIT-V1 §2 established that a deploy OVERWROTE public.tenants.json_schema (the previous version was gone); this package records every schema a tenant has run, so the history is visible and "roll back to version N" is a re-deploy of a stored schema through the EXISTING migration machinery (schemadiff is direction- agnostic — the audit's §3 finding).
Invariants:
- APPEND-ONLY: a rollback is a NEW version whose content equals an old one ("rollback to v3" creates v6) — the trace is never rewritten.
- One row per DISTINCT schema state: appending a schema whose canonical hash equals the LATEST version's is a no-op (re-deploying an unchanged schema — e.g. the resumable fan-out re-run — does not spam the history).
- The history mirrors public.tenants.json_schema: every site that persists json_schema appends here in the same flow (register / deploy / rollback / fan-out), so the LATEST version is always the tenant's current schema.
It lives in its own package (not pkg/controlplane) because the fan-out orchestrator (pkg/migration) must also append, and controlplane already imports migration — this breaks the would-be cycle.
Index ¶
- Constants
- Variables
- func Append(ctx context.Context, pool *pgxpool.Pool, tenantID string, schemaJSON []byte, ...) (version int, appended bool, err error)
- func EnsureSeeded(ctx context.Context, pool *pgxpool.Pool, tenantID string)
- func EnsureTable(ctx context.Context, pool *pgxpool.Pool) error
- func Hash(schemaJSON []byte) string
- func Latest(ctx context.Context, pool *pgxpool.Pool, tenantID string) (int, error)
- func TenantsNeedingBackfill(ctx context.Context, pool *pgxpool.Pool) ([]string, error)
- type Page
- type Version
Constants ¶
const ( SourceRegister = "register" // tenant registration (the first version) SourceDeploy = "deploy" // control-plane / editor deploy SourceRollback = "rollback" // rollback to a prior version (append-only: new version, old content) SourceFanout = "fanout" // migrate --all-tenants fan-out SourceBackfill = "backfill" // pre-versioning schema captured at first boot with this feature )
Sources — who appended a version. Free-text in the table; these are the engine's canonical values.
Variables ¶
var ErrVersionNotFound = errors.New("schema version not found")
ErrVersionNotFound marks a Get/rollback against a version the tenant's history does not contain.
Functions ¶
func Append ¶
func Append(ctx context.Context, pool *pgxpool.Pool, tenantID string, schemaJSON []byte, source, note string) (version int, appended bool, err error)
Append records schemaJSON as the tenant's next version. If the latest version already holds the same canonical hash it appends NOTHING and returns that version with appended=false (re-deploying an unchanged schema is a history no-op, mirroring the idempotent migration diff).
func EnsureSeeded ¶
EnsureSeeded records the tenant's CURRENT schema as version 1 when it has no history at all — the same backfill the engine does at boot, done lazily right before a schema is overwritten.
It exists because the history is not just a timeline: it is the record of which database objects the SCHEMA ever declared, and ENG-9 uses it to tell an operator's removed field (an approvable destructive drop) from a consumer's own column (external, never proposed). Overwriting json_schema without that record ERASES the only evidence that a now-removed column was ever declared — after which the drop is classified external and can never be approved, and an `--approve-drops` run reports "no-op" while the column stays. Seeding first keeps the evidence.
Best-effort by contract: it is called on the persist path, where failing the deploy over a bookkeeping row would be worse than the gap it prevents.
func EnsureTable ¶
EnsureTable creates public.schema_history idempotently (the outbox pattern — existing databases predate the canonical DDL in migrations/001).
func Hash ¶
Hash returns the canonical identity of a schema: sha256 hex over the engine-marshaled JSON bytes. Every append site marshals *schema.APISchema* with encoding/json (deterministic: sorted map keys, fixed struct order), so the same logical schema always hashes identically.
func Latest ¶
Latest returns the tenant's current (highest) version number — 0 when the tenant has no history. Used to anchor a flow-test regression run to the schema version it ran against (FLOWTEST-S1).
func TenantsNeedingBackfill ¶
TenantsNeedingBackfill returns the ids of tenants that HAVE a stored schema but NO history rows — pre-versioning tenants whose current schema should be captured as v1 at upgrade (the caller re-marshals through schema.APISchema so the hash is canonical; raw jsonb text would hash differently than the engine's own marshaling and break the dedup invariant).
Types ¶
type Page ¶
type Page struct {
Versions []Version `json:"versions"`
Total int `json:"total"`
Page int `json:"page"`
PerPage int `json:"per_page"`
}
Page is one page of a tenant's history, newest first.
type Version ¶
type Version struct {
Version int `json:"version"`
Hash string `json:"hash"` // sha256 hex of the canonical (engine-marshaled) schema JSON
Source string `json:"source"`
Note string `json:"note,omitempty"`
CreatedAt time.Time `json:"created_at"`
Resources []string `json:"resources"` // resource names in this version (the timeline summary)
SchemaJSON []byte `json:"-"`
}
Version is one recorded schema version. SchemaJSON is populated by Get, not by List (the listing stays light).