Skip to main content

Deployment behavior-to-test contract (#4305)

Written before implementation. Risk is high: infrastructure lifecycle, database mutation/recovery, RBAC/secret boundaries, and a published deployment interface. The contract is maintenance-only v1.

Every row has the ten required design fields. Commands name executable test entrypoints; the integration ledger below distinguishes completed evidence from required qualification. A PENDING: downstream rollout needs separate target authorization and is not claimed complete. Rendering proves manifest behavior only. Mock observations cannot replace the disposable controller/database exercise or real rollout.

In the table, PG means the initial deployed PostgreSQL boundary (14+ under the existing permission contract). The controller feature floor is helm-controller 1.4.5 with matching CRDs; record actual tested versions for each qualification. SQLite/DuckDB/PGlite are not deployed chart targets. A public core API supporting those dialects needs its own package matrix across every declared supported dialect before shipping.

Behavior / invariantReachable triggerPositive caseNegative / failure caseActor / contextData executor / transactionSupported runtime / dialectExternal contract edgeTest levelValidation command
Existing database/bucket retained on install, failure and uninstallBootstrap release against sentinel resources, then uninstallSame provider identity and sentinel contents remainOwnership conflict or attempted provisioning refuses without deletionApp releaser; platform-owned providersPG sentinel transaction; bucket object roundtrip; chart owns neitherNode/Kubernetes; PGExisting Secret/provider bindings and Helm ownershipRender + disposable integrationnode --test scripts/deployment-chart.test.mjs; SMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Explicit adoption preserves unrelated objectsAdopt existing names into chartReviewed ownership transfer only; independent apps remain in their own namespaceUnreviewed name collision refuses; unrelated Studio object unchangedReleaser and IaC owner; dedicated application namespaceN/A: metadata ownership, no app SQLHelm/KubernetesExisting labels, annotations, field ownersContract + integrationnode --test scripts/deployment-release.test.mjs; SMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Strict validated values and complete preflight before maintenanceRender/plan candidate valuesValid optional omission, multiple digest images and complete inventory passUnknown enum/field, malformed input, mutable image, secret literal, unsupported capability, missing Secret/key/RBAC/admission fails before route changeAuthorized releaser with allowed and denied service accountsN/A: validation and read-only resource inventoryNode/Helm/KubernetesSchema, RBAC, admission, CRDs, pull/config/secret refs; upstream failureUnit + render + integrationnode --test scripts/deployment-chart.test.mjs scripts/deployment-release.test.mjs
Named independent workloads and singleton schedulingRender Iolaus and Work fixturesDistinct images/commands, capacity, services, probes, node constraints, scratch/drain retained; combined worker singletonSchedule replicas >1 and missing required readiness refuseApplication declaring web, combined scheduler, workers, monitorN/A: manifest generationHelm/KubernetesWorkload role enum and field omissionsRender contracthelm template iolaus charts/smrt-app -f charts/smrt-app/fixtures/iolaus.json; helm template work charts/smrt-app -f charts/smrt-app/fixtures/work.json
Maintenance independent of applicationBootstrap or upgrade maintenance phasePublic hostname returns expected 503 while app stopsWrong host/body/backend or responder unavailable blocks advancementExternal client, ingress/controllerN/A: static HTTP, no DBKubernetes/HTTPTLS/DNS/ingress and independent responderIntegrationSMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Old images preserved until quiescedPublish target with maintenance desiredRunning current image unchanged until actual stopEarly target rollout or revision skipping rejectedReleaser, Flux, existing runtimeN/A: runtime identity observationKubernetes/FluxHelm rendered image, pod imageID and accepted Git revisionState-machine + integrationnode --test scripts/deployment-release.test.mjs; SMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Complete writers stopped/fenced; monitors classified accuratelyStop phase with Deployments, CronJobs and active JobsTermination/drain and platform fencing proven for complete inventory; restricted monitor identifiedDesired zero with terminating pod, unknown/external writer, active Job, timeout or unobservable inventory blocks migrationRuntime/migration/monitor identities, namespace and external clientsPG runtime sessions and migration lock; read-only monitor privilegesKubernetes/PGWriter inventory, SIGTERM grace, CronJob suspend, RBAC and fenceContract + disposable integrationnode --test scripts/deployment-release.test.mjs; SMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Migrator lock and static/runtime guard precede writesMigration Job starts/restartsExclusive migrator and agreed inventory allow planned mutationsLock contention, new writer, mismatch, missing permissions or wrong image causes zero mutationMigration-only role; runtime excludedPG lock plus supported SQL MigrationTracker transaction boundariesNode/PGDedicated Job image/command, registry and schema inventoryPackage + integrationSMRT_TEST_POSTGRES_URL=<isolated> pnpm --filter @happyvertical/smrt-core exec vitest run src/migrations/__tests__/deployment-phase-tracker-postgres.optional.test.ts src/migrations/__tests__/deployment-roles-postgres.optional.test.ts; SMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Owning migration instructions assign schema creation to migration, before runtimeFollow the historical Products SKU upgrade instructionsReviewed migration identity creates required schema before startup; runtime checks existenceA runtime boot is presented as an application-schema creatorApplication operator and documentation readerReviewed migration executor; runtime table verifier performs no DDLNode/application schema; historical copy/drop SQL is not requalified hereProducts guide and core table-verifier contractDocumentation/source contractpnpm knowledge:check --strict --format markdown; pnpm check:agents-chain; pnpm check:standards
Transactional data step marker is atomicInterrupt before marker, after marker, before/after commitData and checksum marker commit together; completed step skipped on resumeFailure rolls both back; wrong executor affinity and checksum mismatch refuseSerialized migration ownerSame PG transaction handle for mutation and marker; never outer dbNode/PG; owning API dialects must be enumeratedTracker persistence and connection failuresPackage persistence + disposable integrationSMRT_TEST_POSTGRES_URL=<isolated> pnpm --filter @happyvertical/smrt-core exec vitest run src/migrations/__tests__/deployment-phase-tracker-postgres.optional.test.ts src/migrations/__tests__/deployment-roles-postgres.optional.test.ts
Committed steps survive later migration failureCommit A, fail B, restart retained JobA preserved and skipped; B resumes; seed/verify ordered after dependenciesRepeated execution, changed A checksum, partial B failure, missing marker blocked or reconciledMigration owner after process deathSQL MigrationTracker plus durable data ledger; explicit transaction per stepNode/PostgreSQL onlyProcess interruption and persistent database statePersistence + integrationSMRT_TEST_POSTGRES_URL=<isolated> pnpm --filter @happyvertical/smrt-core exec vitest run src/migrations/__tests__/deployment-phase-tracker-postgres.optional.test.ts src/migrations/__tests__/deployment-roles-postgres.optional.test.ts; SMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Nontransactional ambiguity never silently rerunsKill external/nontransactional phase between side effect and markerIdempotent checkpoint reconciliation or explicit needs-repairAmbiguous partial side effect cannot authorize completion or blind retryMigration owner/operator; external serviceN/A transaction: nontransactional by definition; durable checkpoint requiredNode/PG plus declared external targetUpstream failure, network loss, repeated executionContract + fault injectionSMRT_TEST_POSTGRES_URL=<isolated> pnpm --filter @happyvertical/smrt-core exec vitest run src/migrations/__tests__/deployment-phase-tracker-postgres.optional.test.ts src/migrations/__tests__/deployment-roles-postgres.optional.test.ts
Resume from accepted state at every phaseKill runner in each phase and restart on another processGit + live evidence + durable ledger determine next safe actionMissing/stale evidence, local-only success, superseded release, stale Ready and skipped Git revision cannot advanceAuthorized runner in same app/environmentPG ledger read; Git CAS for desired stateNode/Git/Flux/KubernetesRevision acceptance, generation and evidence identityState-machine + integrationnode --test scripts/deployment-release.test.mjs
Concurrent releases serialize and preserve unrelated Git updatesTwo runners target same app; third changes another appOne release owner; CAS rebases unrelated changes safelyCompeting phase/release, forced stale update or lost unrelated change refusesTwo authorized releasers and unrelated monorepo actorGit ref CAS; database migration lock is separateNode/GitProtected ref, remote fetch/push failureLocal-Git integration + state-machinenode --test scripts/deployment-release.test.mjs
Flux drift and Helm failure cannot reactivate old writers/trafficReconcile repeatedly while stopped or migration failsMaintenance/stopped desired state persists; retained failure evidenceImperative drift repair or automatic rollback restores active old release: failFlux/Helm controllers; release runnerPG sentinel unchanged after failed step; no old writesQualified Flux/Helm/Kubernetes; PGController remediation APIs/version and protected writesDisposable controller integrationSMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Verification binds exact images and actual behaviorStart target behind maintenanceAll required generations/imageIDs/endpoints and workers ready; app assertions passOld healthy endpoint, stale Ready, self-reported digest only, wrong image, missing/duplicate workload, alive HTTP500 failInternal verifier; new runtime/worker identitiesRuntime PG role, read-only schema readiness; no migration credentialsKubernetes/HTTP/PGEndpointSlice/pod identity, readiness, worker heartbeat/drainContract + integrationnode --test scripts/deployment-release.test.mjs; SMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Activation/failure recovery respects changed DBSwitch public route; fail serving assertionTarget serving matches verified evidence; failure returns maintenanceAutomatic old-image restore or corrective migration with new writers active refusesPublic verifier and authorized recovery operatorPG runtime writes; stop/fence before corrective mutationFlux/Kubernetes/HTTP/PGRouting revision, public serving target, repair transitionState-machine + disposable integrationnode --test scripts/deployment-release.test.mjs; SMRT_DEPLOYMENT_EXERCISE=1 KUBECONFIG=<isolated-kind> node scripts/deployment-exercise.mjs
Secrets and tenant/role boundaries remain privatePlan, fail, status and execute with synthetic credential canariesOnly references/redacted diagnostics; migration/runtime/monitor allow/deny proofs; worker-only secret absent from webCredential URL/error leakage, migration secret in runtime, monitor write or undeclared read failsMigration/runtime/monitor roles; allowed/denied resource and active application contextPG role-specific grants; permission apply atomic verificationNode/Kubernetes/PGSecret refs, logs, provider failure text, SQL ACL and RBACUnit + permission integrationnode --test scripts/deployment-release.test.mjs; SMRT_TEST_POSTGRES_URL=<isolated> pnpm --filter @happyvertical/smrt-core exec vitest run src/migrations/__tests__/deployment-phase-tracker-postgres.optional.test.ts src/migrations/__tests__/deployment-roles-postgres.optional.test.ts; existing permission package tests remain prerequisites
Artifact validation remains identity-boundPackage chart and associate source/config/image/validationVersioned chart installs; evidence matches tree/lock/toolchain/imagesMissing/incorrect #4306 receipt never bypasses checks; mutable tag association refusesPublisher/releaserN/A: build metadata, no app SQLHelm/Node/OCIRegistry publication, source and validation identityPackaging + contracthelm package charts/smrt-app; node --test scripts/deployment-release.test.mjs
Real downstream adoption is distinguished from fixture successSelect and authorize a concrete application/environmentReviewed downstream changes deploy; actual accepted phases/new image and sentinel/recovery evidence recordedFixture render or mock run labeled rollout; unauthorized live mutationApp owner, platform owner, authorized releaserTarget PG/bucket under approved maintenance/recovery planExact deployed tool/controller/database versionsSeparate repositories, protected deployment and operational authorizationAuthorized real application rolloutPENDING: target-specific reviewed rollout command and evidence

This documentation alignment covers migration-before-runtime responsibilities; it does not requalify the historical Products copy/drop SQL.

Qualification evidence​

This matrix was drafted before implementation. Record current results alongside an exact reviewed revision in the PR and retained validation artifacts, including:

  • Helm schema/render/packaging and runner contracts, including local Git races.
  • Real PostgreSQL durable-step and role-boundary tests, including interruption.
  • The isolated Flux/Kubernetes/Ingress/PostgreSQL/S3 exercise, its tool/controller versions, sentinel retention, failure/resume and cleanup outcomes.
  • The required touched-package/root validation and independent high-risk review.

The exercise uses synthetic applications and test-only HelmRelease changes. It does not prove protected Git publication in a real cluster, downstream wrapper adoption, production writer fencing, or a real downstream rollout. Those require reviewed downstream changes and explicit target/environment authorization. Keep #4305 open until its actual adoption and rollout criteria have evidence.

Run focused package checks first and include focused deployment commands in normal validation. Full validation follows root AGENTS.md and TESTING_STANDARD.md; heavy work uses the prescribed cluster-test workflow. The integration owner must record disposable teardown even on failure. Do not replace failed documented commands with a different toolchain and report green.

Chart 0.2.0 boundaryPositive / negativeActor and evidence
Security and observer schema/renderBounded non-root contexts, scratch and explicit observer SA render; root/privilege/unknown fields, runtime observer or missing SA failHelm; node --test scripts/deployment-chart.test.mjs
Platform preflightDeclared SA/CA/read-only RBAC and synthetic Pod admission pass; missing SA/CA/RBAC/create permission/admission fail before maintenanceRunner, reconciler and observer; node --test scripts/deployment-release.test.mjs
Actual Pod envelopeExact account/security/volumes pass; changed IDs, account, added token volumes or host/privilege escape failActual Pod identity; same runner suite
Hardened executionWeb/task/migration execute with non-root 10001, RuntimeDefault, dropped ALL, no escalation, read-only root and /tmp emptyDirExisting disposable deployment exercise; integration owner runs isolated stack