|
| 1 | +-- 028 · One Program Work Item moves through the discovery workflow without copies. |
| 2 | +-- |
| 3 | +-- A Work Item is the stable scientific unit of intent. Work Packages are immutable |
| 4 | +-- revisions of its specification; runtime Jobs are execution attempts attached to it. |
| 5 | +-- This prevents Program, Design, Decide, Make and Test from minting private task IDs. |
| 6 | + |
| 7 | +ALTER TYPE app.object_kind ADD VALUE IF NOT EXISTS 'work_item'; |
| 8 | + |
| 9 | +BEGIN; |
| 10 | + |
| 11 | +CREATE TYPE design.workflow_lane AS ENUM ( |
| 12 | + 'understand', 'design', 'decide', 'make', 'test_learn' |
| 13 | +); |
| 14 | + |
| 15 | +CREATE TABLE design.program_work_item ( |
| 16 | + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), |
| 17 | + program_id uuid NOT NULL REFERENCES design.project(id) ON DELETE RESTRICT, |
| 18 | + work_key citext NOT NULL, |
| 19 | + title text NOT NULL CHECK (btrim(title) <> ''), |
| 20 | + current_lane design.workflow_lane NOT NULL DEFAULT 'understand', |
| 21 | + current_package_id uuid, |
| 22 | + created_at timestamptz NOT NULL DEFAULT now(), |
| 23 | + created_by_kind app.actor_kind NOT NULL, |
| 24 | + created_by_id text NOT NULL, |
| 25 | + UNIQUE (program_id, work_key) |
| 26 | +); |
| 27 | + |
| 28 | +-- Existing Work Packages become revisions of one newly-minted stable Work Item. |
| 29 | +INSERT INTO design.program_work_item(program_id,work_key,title,created_at,created_by_kind,created_by_id) |
| 30 | +SELECT DISTINCT ON (program_id,work_key) |
| 31 | + program_id,work_key,title,created_at,created_by_kind,created_by_id |
| 32 | + FROM design.program_work_package |
| 33 | + ORDER BY program_id,work_key,revision; |
| 34 | + |
| 35 | +ALTER TABLE design.program_work_package ADD COLUMN work_item_id uuid; |
| 36 | +UPDATE design.program_work_package package |
| 37 | + SET work_item_id = item.id |
| 38 | + FROM design.program_work_item item |
| 39 | + WHERE item.program_id=package.program_id AND item.work_key=package.work_key; |
| 40 | +ALTER TABLE design.program_work_package |
| 41 | + ALTER COLUMN work_item_id SET NOT NULL, |
| 42 | + ADD CONSTRAINT program_work_package_item_fk |
| 43 | + FOREIGN KEY (work_item_id) REFERENCES design.program_work_item(id) ON DELETE RESTRICT, |
| 44 | + ADD CONSTRAINT program_work_package_item_revision_unique UNIQUE (work_item_id,revision); |
| 45 | + |
| 46 | +UPDATE design.program_work_item item |
| 47 | + SET current_package_id = current.id, |
| 48 | + title = current.title |
| 49 | + FROM ( |
| 50 | + SELECT DISTINCT ON (work_item_id) id,work_item_id,title |
| 51 | + FROM design.program_work_package |
| 52 | + ORDER BY work_item_id,revision DESC |
| 53 | + ) current |
| 54 | + WHERE current.work_item_id=item.id; |
| 55 | +ALTER TABLE design.program_work_item |
| 56 | + ADD CONSTRAINT program_work_item_current_package_fk |
| 57 | + FOREIGN KEY (current_package_id) REFERENCES design.program_work_package(id) ON DELETE RESTRICT; |
| 58 | + |
| 59 | +CREATE TABLE design.program_work_transition ( |
| 60 | + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), |
| 61 | + work_item_id uuid NOT NULL REFERENCES design.program_work_item(id) ON DELETE RESTRICT, |
| 62 | + from_lane design.workflow_lane, |
| 63 | + to_lane design.workflow_lane NOT NULL, |
| 64 | + reason text NOT NULL CHECK (btrim(reason) <> ''), |
| 65 | + transitioned_at timestamptz NOT NULL DEFAULT now(), |
| 66 | + transitioned_by_kind app.actor_kind NOT NULL, |
| 67 | + transitioned_by_id text NOT NULL, |
| 68 | + CHECK (from_lane IS NULL OR from_lane <> to_lane) |
| 69 | +); |
| 70 | +CREATE INDEX program_work_transition_item |
| 71 | + ON design.program_work_transition(work_item_id,transitioned_at DESC); |
| 72 | + |
| 73 | +INSERT INTO design.program_work_transition( |
| 74 | + work_item_id,from_lane,to_lane,reason,transitioned_at,transitioned_by_kind,transitioned_by_id) |
| 75 | +SELECT id,NULL,current_lane,'Migrated into the canonical Program workflow',created_at,created_by_kind,created_by_id |
| 76 | + FROM design.program_work_item; |
| 77 | + |
| 78 | +CREATE TABLE design.program_work_item_dependency ( |
| 79 | + work_item_id uuid NOT NULL REFERENCES design.program_work_item(id) ON DELETE RESTRICT, |
| 80 | + depends_on_work_item_id uuid NOT NULL REFERENCES design.program_work_item(id) ON DELETE RESTRICT, |
| 81 | + PRIMARY KEY (work_item_id,depends_on_work_item_id), |
| 82 | + CHECK (work_item_id <> depends_on_work_item_id) |
| 83 | +); |
| 84 | + |
| 85 | +INSERT INTO design.program_work_item_dependency(work_item_id,depends_on_work_item_id) |
| 86 | +SELECT DISTINCT package.work_item_id,dependency.work_item_id |
| 87 | + FROM design.program_work_dependency edge |
| 88 | + JOIN design.program_work_package package ON package.id=edge.work_package_id |
| 89 | + JOIN design.program_work_package dependency ON dependency.id=edge.depends_on_id |
| 90 | +ON CONFLICT DO NOTHING; |
| 91 | + |
| 92 | +CREATE TABLE design.program_work_execution ( |
| 93 | + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), |
| 94 | + work_item_id uuid NOT NULL REFERENCES design.program_work_item(id) ON DELETE RESTRICT, |
| 95 | + job_id uuid NOT NULL UNIQUE REFERENCES app.job(id) ON DELETE RESTRICT, |
| 96 | + purpose text, |
| 97 | + linked_at timestamptz NOT NULL DEFAULT now(), |
| 98 | + linked_by_kind app.actor_kind NOT NULL, |
| 99 | + linked_by_id text NOT NULL |
| 100 | +); |
| 101 | +CREATE INDEX program_work_execution_item |
| 102 | + ON design.program_work_execution(work_item_id,linked_at DESC); |
| 103 | + |
| 104 | +CREATE FUNCTION app.sync_program_work_item_trigger() RETURNS trigger LANGUAGE plpgsql AS $$ |
| 105 | +BEGIN |
| 106 | + PERFORM app.register_entity( |
| 107 | + 'work_item',NEW.id::text,NEW.program_id::text||':'||NEW.work_key::text, |
| 108 | + NEW.title,'design','program_work_item'); |
| 109 | + RETURN NEW; |
| 110 | +END $$; |
| 111 | + |
| 112 | +SELECT app.register_entity( |
| 113 | + 'work_item',id::text,program_id::text||':'||work_key::text,title, |
| 114 | + 'design','program_work_item') |
| 115 | + FROM design.program_work_item; |
| 116 | +CREATE TRIGGER entity_program_work_item AFTER INSERT OR UPDATE ON design.program_work_item |
| 117 | + FOR EACH ROW EXECUTE FUNCTION app.sync_program_work_item_trigger(); |
| 118 | + |
| 119 | +ALTER TABLE design.program_evidence_binding |
| 120 | + DROP CONSTRAINT program_evidence_binding_subject_kind_check, |
| 121 | + ADD CONSTRAINT program_evidence_binding_subject_kind_check CHECK ( |
| 122 | + subject_kind IN ('program','objective','hypothesis','decision','milestone', |
| 123 | + 'stage_gate','work_item','work_package')); |
| 124 | + |
| 125 | +ALTER TABLE design.program_event DROP CONSTRAINT program_event_event_kind_check; |
| 126 | +ALTER TABLE design.program_event ADD CONSTRAINT program_event_event_kind_check CHECK (event_kind IN ( |
| 127 | + 'program.created','program.updated','objective.recorded','hypothesis.recorded', |
| 128 | + 'decision.recorded','milestone.recorded','object.linked','snapshot.created', |
| 129 | + 'portfolio.assigned','member.assigned','stage_gate.recorded','work_package.recorded', |
| 130 | + 'work_item.transitioned','work_execution.linked','evidence.attached','lineage.recorded' |
| 131 | +)); |
| 132 | + |
| 133 | +COMMENT ON TABLE design.program_work_item IS |
| 134 | + 'Stable Program job identity. It moves between workflow lanes; it is never copied per Workspace.'; |
| 135 | +COMMENT ON TABLE design.program_work_package IS |
| 136 | + 'Immutable revision of a stable Program Work Item specification.'; |
| 137 | +COMMENT ON TABLE design.program_work_execution IS |
| 138 | + 'A runtime Job may belong to exactly one Program Work Item; a Work Item may have many attempts.'; |
| 139 | + |
| 140 | +INSERT INTO meta.migration (filename, sha256, content_sha256, hash_source) |
| 141 | +VALUES ('028_unique_program_work_items.sql', '\x1f10e56fc3d17f816ad4578314639817dda18cc686607159c42976589f6b0e44'::bytea, |
| 142 | + '\x1f10e56fc3d17f816ad4578314639817dda18cc686607159c42976589f6b0e44'::bytea, 'content') |
| 143 | +ON CONFLICT (filename) DO NOTHING; |
| 144 | + |
| 145 | +COMMIT; |
0 commit comments