/** * Shared contract for the publish pipeline: one ordered list of steps the * server computes for the UI status poll (GET publish-pipeline). Domains * project into these types; rich payloads ride in opaque `detail` and are only * read by that domain's UI renderer. * * Status-only — it does not start execution and does not gate deploy. * Mutations (publish-readiness/start, POST deployments) start work and keep * their own authoritative checks (build, policy, database lifecycle reconcile); * this plan only reflects progress for the rail. */ /** Which domain owns a step. Extend as new gates are added. */ export type PublishStepKind = 'build' | 'database_migration' | 'database_provisioning' | 'policy_gates' | 'publish'; /** * Domain-agnostic status. Ordered here by roll-up precedence (worst first). * `blocked` means this step's own check failed (remediation). Waiting on an * earlier step is `pending` with optional `blockedBy`. * `skipped` is a visible N/A row (e.g. Build when the app has no native-git remote). */ export type PublishStepStatus = | 'failed' | 'blocked' | 'action_required' | 'stale' | 'in_progress' | 'pending' | 'advisory' | 'passed' | 'skipped'; export type BuildPublishStepDetail = { buildStatus: string | null; error?: string; }; export type PublishDeployStepDetail = { error?: string; isLive?: boolean; queueStatus: string; }; export type PublishStep = { /** Stable unique id in the plan (a PublishStepId; typed as string so unknown ids still flow through). */ id: string; kind: PublishStepKind; status: PublishStepStatus; /** Human label for the step row (server-derived). */ displayName: string; /** Static help text under the display name (not an error). */ description?: string; /** Short subtitle / one-line error. */ summary?: string; /** When status is pending and waiting on earlier steps. */ blockedBy?: PublishStepKind[]; /** Machine code when blocked/failed (e.g. POLICY_GATE_BLOCKED). */ blockingReason?: string; /** Opaque domain payload; generic pipeline must not read this. */ detail?: unknown; }; export type PublishPlanTarget = { applicationId: string; commitId: string; }; export type PublishPlan = { status: PublishStepStatus; steps: PublishStep[]; target: PublishPlanTarget; }; /** * Single source of truth for publish-plan step ids. These are wire values: the * server emits them on each step and the UI matches on them to wire custom * detail panels. Keys are lowerCamelCase; values are the lowercase snake_case * wire literals. Add new steps here and they become available to both sides. */ export const PublishStepId = { build: 'build', databaseMigration: 'database_migration', databaseProvisioning: 'database_provisioning', policyGatesBeforeBuild: 'policy_gates:before_build', policyGatesBeforeDeploy: 'policy_gates:before_deploy', publish: 'publish' } as const; export type PublishStepId = (typeof PublishStepId)[keyof typeof PublishStepId]; /** * Fixed spine order. Composer filters to steps that were emitted; order is always * this sequence. Policy may emit before_build and/or before_deploy. */ export const PUBLISH_STEP_SPINE: readonly PublishStepId[] = [ PublishStepId.policyGatesBeforeBuild, PublishStepId.build, PublishStepId.policyGatesBeforeDeploy, PublishStepId.databaseProvisioning, PublishStepId.databaseMigration, PublishStepId.publish ] as const; export * from './displayNames.js'; export * from './plan.js';