stack

package
v0.0.0-...-5a20295 Latest Latest
Warning

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

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

Documentation

Overview

Package stack implements the `palai` local-stack lifecycle: it initialises the .palai data layout, drives the four-service Docker Compose distribution up and down, and runs the doctor health surface. It shells out to `docker compose` (no Docker SDK dependency) and speaks the public API and the durable spine over the ports `init` minted, so the same binary an operator runs is what the e2e proof drives.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AdminDefaults

func AdminDefaults() (baseURL, apiKey string, err error)

AdminDefaults returns the base URL and the bootstrap API key of the initialised .palai stack — the last rung of the admin CLI's flag → env → .palai fallback chain. It errors only when .palai is absent, which the caller treats as fatal only when a flag/env did not already supply the value.

func AuditCheckpoint

func AuditCheckpoint(dir, signingKey string, allowEmpty bool) error

AuditCheckpoint reads the journal, folds it into a checkpoint, and writes the signed envelope into dir. signingKey is the release signing key (PALAI_AUDIT_SIGNING_KEY when the flag is empty); there is no unsigned mode, because an unsigned anchor anyone can regenerate is not an anchor.

allowEmpty (--allow-empty) is the explicit opt-in for genuinely empty journals. Without it a zero-row read is REFUSED rather than blessed with a signature — see WriteSigned.

func AuditVerify

func AuditVerify(checkpointPath, pubkey string, notOlderThan time.Duration, minAnchored int, jsonOut bool) error

AuditVerify recomputes the chain from the journal rows and compares it to the signed checkpoint. It returns a non-nil error — and therefore a non-zero exit — on any alert.

notOlderThan / minAnchored are the operator's ROLLBACK policy. Compare can only say "this checkpoint's own prefix is intact", which an attacker's older-but-validly-signed copy satisfies perfectly; only the operator knows their cadence, so only they can declare the window. The report carries the checkpoint's age whether or not one is declared.

func GetResponse

func GetResponse(id string) error

GetResponse retrieves a response by id and prints its NORMALIZED projection — {"id","output_text","status"} as canonical JSON — on stdout. It is the palai CLI's leg of the E16 T8 four-client parity journey: the SAME shared run, retrieved and normalized by all four clients, is asserted byte-identical. Base URL + API key resolve from PALAI_BASE_URL/PALAI_API_KEY (the journey sets these) or fall back to the .palai config. A 410 tombstone (a purged store:false response) is reported honestly: it prints {"status":410,...} and returns an error so the caller can assert the typed-gone surface. The output text is the concatenation of the response output items' text fields — the same extraction every client applies to the same retrieved bytes.

func InstallBackup

func InstallBackup(outPath string) error

InstallBackup captures the running stack into one archive at outPath (default palai-backup-<project>-<UTC>.tar.gz in the cwd). It dumps Postgres consistently (pg_dump -Fc), copies the object-store data volume, and writes a manifest of ids + checksums.

func InstallRestore

func InstallRestore(archivePath string) error

InstallRestore restores an archive into a running EMPTY target stack. It REFUSES a target that already holds tenant rows (fail-closed — no data is ever overwritten), verifies the archive's member checksums, then replaces Postgres (pg_restore --clean) and the object-store volume with the writers stopped for the swap.

func InstallRestoreVerify

func InstallRestoreVerify(archivePath string) error

InstallRestoreVerify proves a restored target matches its backup manifest: the archive's member checksums verify, the live migration version + tenant ids match the manifest, and the sample response is retrievable from the restored database (proving the tenant data is queryable).

func ProductionDoctor

func ProductionDoctor(envFile string, jsonOut bool) error

ProductionDoctor runs the health checks against the production stack described by envFile. With jsonOut it prints the Report as JSON and exits 0 (the verdict is in the body, like `local doctor`); human output prints the table and returns an error when any check FAILED.

func SupportBundle

func SupportBundle(outPath string, tailLines int) error

SupportBundle gathers the diagnostics for the initialised stack and writes them to outPath as a redacted tar.gz. It never fails the whole bundle on one missing input: a compose command that errors (e.g. the stack is down) records its error text as that part's content, so an operator diagnosing a broken stack still gets the doctor report and the config.

func Upgrade

func Upgrade(opts UpgradeOptions) error

Upgrade runs the §48.4 N->N+1 compose sequence: backup + restore-status -> compat verify -> expand + control-plane swap -> runner drain -> new-run engine-alias roll -> smoke. Expand is folded into the control-plane swap for the single-node compose profile (the swapped control-plane applies the idempotent, advisory-locked migration chain at boot, gated by the backup marker); the separate pre-swap migration Job is the Kubernetes path (T3). The active run stays on its PINNED engine because the engine alias is rolled ONLY AFTER the runner drains — a drained run has already completed on the engine digest that was current when it started.

func UpgradeRollback

func UpgradeRollback(opts RollbackOptions) error

UpgradeRollback is the §48.5 APPLICATION rollback: it swaps the control-plane (and runner) image back to N and rolls the engine alias back to N's engine for NEW runs, while the SCHEMA stays expanded (a contract's dropped shape is not re-created — a real downgrade past a contract restores from the pre-upgrade backup, spec §48.5). The N binary boots on the expanded schema because a contract only drops shapes no in-rollback-window binary reads, so the schema head equals N's chain head. It DRAINS first: the runner recreate SIGTERMs the active run, and without a drain E10 recovery would reopen that attempt on the N control-plane's N engine — silently MIGRATING a run pinned to the N+1 engine. So an active run is drained (warn-on-timeout) before the recreate; a run still active past the window then completes on N's engine, and the operator is warned (never silent).

Types

type BackupManifest

type BackupManifest struct {
	Kind              string    `json:"kind"`
	Version           int       `json:"version"`
	CreatedAt         time.Time `json:"created_at"`
	Project           string    `json:"project"`                      // source stack project — reference only
	MigrationVersion  int       `json:"migration_version"`            // max(schema_migrations.version)
	ProjectIDs        []string  `json:"project_ids"`                  // tenant ids captured (RLS-bypassed superuser read)
	SampleResponseID  string    `json:"sample_response_id,omitempty"` // one run id verify re-retrieves
	DBDumpSHA256      string    `json:"db_dump_sha256"`
	ObjectStoreSHA256 string    `json:"object_store_sha256"`
	// Objects is the per-object sha256 of the object-store copy. The packaged control-plane sets
	// no PALAI_S3_ENDPOINT, so no S3 objects are wired and these are the object-store DATA VOLUME's
	// files (byte-for-byte). Each entry lets a restore prove the copy is intact file-by-file.
	// ponytail: the volume is tar'd LIVE — crash-consistent-enough for today's empty/idle store, but
	// once artifacts are actually written to S3 a consistent copy must quiesce the store or enumerate
	// S3 objects (docs/operations/backup-restore.md ceiling), and those entries become the objects.
	Objects []objectChecksum `json:"objects"`
}

BackupManifest is the machine-readable index inside a backup archive. It records what the backup captured (migration version, the tenant ids — which are the PROJECT ids since A.2 Task 6, a sample response id for the run-retrieval verify) and the integrity checksums (whole-member + per-object). It holds NO secret — restore/verify treat it as the source of truth for what a healthy restore must match.

type Check

type Check struct {
	Status string `json:"status"`
	Detail string `json:"detail"`
}

Check is one doctor result: a status ("ok" is green) and a human detail.

type Config

type Config struct {
	Project       string `json:"project"`
	DataDir       string `json:"data_dir"`
	APIPort       int    `json:"api_port"`
	RunnerPort    int    `json:"runner_port"`
	PgPort        int    `json:"pg_port"`
	S3Port        int    `json:"s3_port"`
	BaseURL       string `json:"base_url"`
	ControllerDNS string `json:"controller_dns"`

	// ‼️ THE STACK'S OWN SETTINGS, AND THEY LIVE HERE BECAUSE .env.local IS NOT A CONFIGURATION
	// SURFACE. The owner's instruction, twice: "sakın env local kullanma ... öyle bir şey olmaması
	// lazım palai için deamon ve cli için."
	//
	// A dotfile beside a checkout is invisible to every operator who did not create it, carries no
	// schema, cannot be read back, and this tree has already paid for that three times — a `$` eaten by
	// dotenv expansion, a value the child process never received, and a workspace root that disagreed
	// with the daemon's for an hour. This file is written by `init`, read by every command, and printed
	// by `doctor`, so a setting is a thing an operator can SEE.
	//
	// WHAT IS NOT HERE IS AS DELIBERATE AS WHAT IS. Credentials are not: they belong in the sealed
	// store behind a model connection, which is the path the console already builds. And a device's own
	// numbers — capacity, concurrency, pool — are not: those are the ADMIN PLANE's, delivered by desired
	// configuration to every machine at once, which is the whole point of that surface. What is left is
	// exactly the set that describes THIS stack on THIS disk, which no web form can know.
	WorkspaceRoot   string `json:"workspace_root,omitempty"`
	EngineWallTime  string `json:"engine_wall_time,omitempty"`
	SandboxWallTime string `json:"sandbox_wall_time,omitempty"`
}

Config is the .palai/config.json contract the CLI writes at init and the e2e harness reads: the compose project identity, the data dir, the published host ports, and the surfaces derived from them. The field names are the JSON the harness decodes.

func (Config) Setting

func (c Config) Setting(name string) string

Setting resolves one stack setting: the saved configuration first, then the process environment.

THE ORDER IS THE POINT. The variable still works — a container deployment sets these in its own environment and always has — but it is now the FALLBACK rather than the record. A value an operator saved is the one that survives a reboot, a new shell, and a launchd job that inherits nothing.

type ReleaseManifest

type ReleaseManifest struct {
	Version string `json:"version"`
	Stamp   string `json:"stamp"`
	Commit  string `json:"commit"`
	Images  struct {
		ControlPlane imageRef `json:"control_plane"`
		Runner       imageRef `json:"runner"`
		Engine       imageRef `json:"engine"`
	} `json:"images"`
}

ReleaseManifest is the scripts/release/build.sh output the upgrade reads: the target version stamp and the OCI image digests the swap pins. The engine digest is the alias new runs pin AFTER the roll.

type Report

type Report struct {
	OK     bool             `json:"ok"`
	Checks map[string]Check `json:"checks"`
}

Report is the doctor --json contract: an overall verdict plus the check map.

type RollbackOptions

type RollbackOptions struct {
	To string
}

RollbackOptions drives `palai upgrade rollback`: To is the N (previous) release manifest to return to.

type UpgradeOptions

type UpgradeOptions struct {
	Manifest   string
	From       string
	SkipBackup bool
	DrainRun   string        // optional response id to wait terminal before the engine-alias roll
	DrainWait  time.Duration // cap on the drain wait (default 90s)
}

UpgradeOptions drives `palai upgrade`. Manifest is the target (N+1) release manifest; From is the currently-running version for the compat check (defaults to the VERSION file). SkipBackup skips the pre-upgrade backup (a drill convenience — never the operator default).

Jump to

Keyboard shortcuts

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