@@ -0,0 +1,85 @@ |
| 1 | +-- SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | +-- |
| 3 | +-- S41a workflow steps — one row per `steps[]` entry in a job. |
| 4 | +-- |
| 5 | +-- Steps execute serially within a job. status moves queued → running → |
| 6 | +-- completed | cancelled | skipped (skipped fires when an earlier |
| 7 | +-- step fails and continue-on-error wasn't set). |
| 8 | +-- |
| 9 | +-- The `run` text is the shell command. The parser (S41a) tags every |
| 10 | +-- expression value carried into this string with a Tainted flag; the |
| 11 | +-- runner's exec layer (S41d) refuses to interpolate Tainted values |
| 12 | +-- into shell strings — they compile to ${SHITHUB_INPUT_*} envvar refs |
| 13 | +-- set safely by the runner. We don't need a separate "compiled" |
| 14 | +-- column; the render step happens in-runner each execution. |
| 15 | +-- |
| 16 | +-- log_object_key holds the Spaces blob key after the finalize worker |
| 17 | +-- (S41d) concatenates and uploads chunks. NULL while running; chunks |
| 18 | +-- live in workflow_step_log_chunks (0047) until finalize. |
| 19 | +-- |
| 20 | +-- step_index gives the dispatch order (0-based). step_id (text) is |
| 21 | +-- the optional GHA `id:` for cross-step references via |
| 22 | +-- ${{ steps.<id>.outputs.X }} (outputs are v2; the column exists now |
| 23 | +-- so the parser can carry the id without a schema change). |
| 24 | + |
| 25 | +-- +goose Up |
| 26 | + |
| 27 | +CREATE TYPE workflow_step_status AS ENUM ( |
| 28 | + 'queued', 'running', 'completed', 'cancelled', 'skipped' |
| 29 | +); |
| 30 | + |
| 31 | +CREATE TABLE workflow_steps ( |
| 32 | + id bigserial PRIMARY KEY, |
| 33 | + job_id bigint NOT NULL REFERENCES workflow_jobs(id) ON DELETE CASCADE, |
| 34 | + step_index integer NOT NULL, |
| 35 | + step_id text NOT NULL DEFAULT '', |
| 36 | + step_name text NOT NULL DEFAULT '', |
| 37 | + if_expr text NOT NULL DEFAULT '', |
| 38 | + run_command text NOT NULL DEFAULT '', |
| 39 | + uses_alias text NOT NULL DEFAULT '', |
| 40 | + working_directory text NOT NULL DEFAULT '', |
| 41 | + step_env jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 42 | + continue_on_error boolean NOT NULL DEFAULT false, |
| 43 | + status workflow_step_status NOT NULL DEFAULT 'queued', |
| 44 | + conclusion check_conclusion, |
| 45 | + log_object_key text, |
| 46 | + log_byte_count bigint NOT NULL DEFAULT 0, |
| 47 | + started_at timestamptz, |
| 48 | + completed_at timestamptz, |
| 49 | + version integer NOT NULL DEFAULT 0, |
| 50 | + created_at timestamptz NOT NULL DEFAULT now(), |
| 51 | + updated_at timestamptz NOT NULL DEFAULT now(), |
| 52 | + |
| 53 | + UNIQUE (job_id, step_index), |
| 54 | + |
| 55 | + CONSTRAINT workflow_steps_step_id_format CHECK ( |
| 56 | + step_id = '' OR step_id ~ '^[A-Za-z_][A-Za-z0-9_-]*$' |
| 57 | + ), |
| 58 | + CONSTRAINT workflow_steps_name_length CHECK (char_length(step_name) <= 256), |
| 59 | + CONSTRAINT workflow_steps_run_or_uses CHECK ( |
| 60 | + (run_command <> '' AND uses_alias = '') OR |
| 61 | + (run_command = '' AND uses_alias <> '') |
| 62 | + ), |
| 63 | + CONSTRAINT workflow_steps_uses_alias_known CHECK ( |
| 64 | + uses_alias IN ('', 'actions/checkout@v4', |
| 65 | + 'shithub/upload-artifact@v1', |
| 66 | + 'shithub/download-artifact@v1') |
| 67 | + ), |
| 68 | + CONSTRAINT workflow_steps_working_directory_length CHECK ( |
| 69 | + char_length(working_directory) <= 1024 |
| 70 | + ), |
| 71 | + CONSTRAINT workflow_steps_completed_has_conclusion CHECK ( |
| 72 | + status NOT IN ('completed', 'skipped') OR conclusion IS NOT NULL |
| 73 | + ), |
| 74 | + CONSTRAINT workflow_steps_log_byte_nonnegative CHECK (log_byte_count >= 0) |
| 75 | +); |
| 76 | + |
| 77 | +CREATE INDEX workflow_steps_job_idx ON workflow_steps (job_id); |
| 78 | + |
| 79 | +CREATE TRIGGER set_updated_at BEFORE UPDATE ON workflow_steps |
| 80 | + FOR EACH ROW EXECUTE FUNCTION tg_set_updated_at(); |
| 81 | + |
| 82 | + |
| 83 | +-- +goose Down |
| 84 | +DROP TABLE IF EXISTS workflow_steps; |
| 85 | +DROP TYPE IF EXISTS workflow_step_status; |