schemahistory

package
v0.1.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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

View Source
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

View Source
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

func EnsureSeeded(ctx context.Context, pool *pgxpool.Pool, tenantID string)

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

func EnsureTable(ctx context.Context, pool *pgxpool.Pool) error

EnsureTable creates public.schema_history idempotently (the outbox pattern — existing databases predate the canonical DDL in migrations/001).

func Hash

func Hash(schemaJSON []byte) string

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

func Latest(ctx context.Context, pool *pgxpool.Pool, tenantID string) (int, error)

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

func TenantsNeedingBackfill(ctx context.Context, pool *pgxpool.Pool) ([]string, error)

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.

func List

func List(ctx context.Context, pool *pgxpool.Pool, tenantID string, page, perPage int) (*Page, error)

List returns one page of a tenant's history, newest first. Resource names are extracted from the stored JSONB so the timeline can summarize each version without shipping full schemas.

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).

func Get

func Get(ctx context.Context, pool *pgxpool.Pool, tenantID string, version int) (*Version, error)

Get returns one version WITH its full schema JSON. ErrVersionNotFound when the tenant's history has no such version.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL