@nanobpm/nano-workforce 0.202.0 → 0.203.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/SPEC.md +48 -0
- package/app/admittedStaging.test.ts +71 -0
- package/app/agentActivationLedger.ts +5 -0
- package/app/agentCompletion.test.ts +33 -0
- package/app/agentCompletion.ts +108 -2
- package/app/agentic/vocab/agent-sla-boundary.test.ts +6 -1
- package/app/contracts.ts +23 -0
- package/app/deliveryGraphDispatch.ts +6 -0
- package/app/deliveryGraphReadModel.test.ts +2 -2
- package/app/deliveryGraphRun.ts +18 -0
- package/app/deliveryRunner.ts +9 -1
- package/app/escalationSteward.test.ts +452 -0
- package/app/escalationSteward.ts +537 -0
- package/app/escalationStewardPoll.test.ts +123 -0
- package/app/feature.test.ts +16 -0
- package/app/feature.ts +11 -0
- package/app/featureDelivery.test.ts +23 -0
- package/app/mcpToolSurface.ts +17 -1
- package/app/plan.ts +49 -6
- package/app/planLowering.test.ts +26 -1
- package/app/planLowering.ts +5 -0
- package/app/promotionPoll.test.ts +19 -0
- package/app/pullRequestReadModel.test.ts +1 -1
- package/app/service.test.ts +36 -0
- package/app/service.ts +139 -5
- package/db/migrations/120_escalation_auto_resolutions.sql +229 -0
- package/db/migrations/121_delivery_graph_read_model_auto_resolve.sql +54 -0
- package/db/migrations/122_pull_requests_read_model_auto_resolve.sql +56 -0
- package/db/migrations/123_escalation_auto_resolutions_selected_option.sql +47 -0
- package/db/migrations/124_admitted_epics_auto_resolve.sql +21 -0
- package/docs/agent-guide.md +280 -814
- package/docs/mcp-runbook.md +2 -0
- package/e2e/escalation-steward.e2e.ts +211 -0
- package/nano.app.json +10 -1
- package/openapi.yaml +365 -0
- package/operations/dispatchDeliveryGraph.ts +6 -1
- package/operations/listActivePrs.test.ts +60 -1
- package/operations/listActivePrs.ts +3 -3
- package/operations/listAutoResolutions.ts +83 -0
- package/operations/reviewAutoResolution.test.ts +137 -0
- package/operations/reviewAutoResolution.ts +42 -0
- package/operations/startConvergenceLoop.ts +5 -1
- package/operations/startEpicSet.admission.integration.test.ts +2 -2
- package/operations/startEpicSet.ts +8 -2
- package/operations/startFeature.ts +5 -0
- package/operations/startPlanFanout.ts +4 -1
- package/package.json +1 -1
- package/pages/_nav.json +12 -0
- package/pages/auto-resolutions.page.json +191 -0
- package/pages/board.page.json +17 -0
- package/pages/cockpit.page.json +17 -0
- package/pages/delivery-graph-detail.page.json +17 -0
- package/pages/delivery-graphs/staged.mount.js +17 -5
- package/pages/delivery-graphs.page.json +17 -0
- package/pages/epic-detail.page.json +17 -0
- package/pages/epic.page.json +19 -1
- package/pages/feature.page.json +19 -1
- package/pages/home.page.json +22 -0
- package/pages/lineage.page.json +17 -0
- package/pages/mcp.page.json +17 -0
- package/pages/overview.page.json +17 -0
- package/pages/tasks.page.json +17 -0
- package/pages/velocity.page.json +17 -0
- package/resources/processes/escalation-steward.bpmn +162 -0
- package/resources/prompts/escalation-steward.md +87 -0
- package/skills/nano-workforce/SKILL.md +20 -65
- package/test/delivery-graphs-staged-embed.test.ts +13 -0
- package/test/derivation-parity/derivation-parity.test.ts +1 -0
- package/test/derivation-parity/flows.ts +12 -0
- package/workers/converge-feature/worker.ts +6 -1
- package/workers/delivery-connector/worker.test.ts +72 -1
- package/workers/delivery-connector/worker.ts +53 -2
- package/workers/escalation-steward-collect/worker.test.ts +180 -0
- package/workers/escalation-steward-collect/worker.ts +99 -0
- package/workers/escalation-steward-record/worker.test.ts +740 -0
- package/workers/escalation-steward-record/worker.ts +428 -0
- package/workers/record-wave/worker.ts +5 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
-- Issue #889: opt-in escalation steward. Two halves, both additive (expand-and-contract):
|
|
2
|
+
--
|
|
3
|
+
-- 1. `auto_resolve_escalations` (0|1, default 0) on each escalation-raising subject table —
|
|
4
|
+
-- `feature_runs`, `plans`, `pull_requests`, `delivery_graph_runs` — recording whether the work
|
|
5
|
+
-- OPTED IN to having its open escalations auto-resolved by the steward. The steward's
|
|
6
|
+
-- eligibility derivation (app/escalationSteward.ts) reads the flag off the SUBJECT row keyed by
|
|
7
|
+
-- the escalation's owning process instance, so an escalation from non-opted-in work is never
|
|
8
|
+
-- touched. Mirrored into the `delivery_units` aggregate (ADR 0006) — the sync triggers (089) and
|
|
9
|
+
-- compat VIEWs (091) are re-created verbatim plus the new column, exactly as migration 116 did
|
|
10
|
+
-- for `human_approval` (parity-tested in app/deliveryUnit.test.ts).
|
|
11
|
+
--
|
|
12
|
+
-- 2. `escalation_auto_resolutions` — the durable, reviewable record of every escalation the steward
|
|
13
|
+
-- ACTED ON or ABSTAINED from (one row each), backing the Auto-resolutions view and the
|
|
14
|
+
-- dismiss/intervene doors. `review_state` starts `pending`; an operator `dismiss`/`intervene`
|
|
15
|
+
-- folds it to `dismissed`/`intervened` with `reviewed_by`/`reviewed_at`/`review_note`.
|
|
16
|
+
|
|
17
|
+
ALTER TABLE feature_runs ADD COLUMN auto_resolve_escalations INTEGER NOT NULL DEFAULT 0;
|
|
18
|
+
ALTER TABLE plans ADD COLUMN auto_resolve_escalations INTEGER NOT NULL DEFAULT 0;
|
|
19
|
+
ALTER TABLE pull_requests ADD COLUMN auto_resolve_escalations INTEGER NOT NULL DEFAULT 0;
|
|
20
|
+
ALTER TABLE delivery_graph_runs ADD COLUMN auto_resolve_escalations INTEGER NOT NULL DEFAULT 0;
|
|
21
|
+
|
|
22
|
+
-- Mirror the new column through the `delivery_units` aggregate so the compat VIEWs stay row-for-row
|
|
23
|
+
-- equal to their base tables (ADR 0006, parity-tested in app/deliveryUnit.test.ts).
|
|
24
|
+
ALTER TABLE delivery_units ADD COLUMN auto_resolve_escalations INTEGER;
|
|
25
|
+
UPDATE delivery_units SET auto_resolve_escalations = 0 WHERE auto_resolve_escalations IS NULL;
|
|
26
|
+
|
|
27
|
+
DROP TRIGGER IF EXISTS feature_runs__du_ai;
|
|
28
|
+
CREATE TRIGGER feature_runs__du_ai AFTER INSERT ON feature_runs
|
|
29
|
+
BEGIN
|
|
30
|
+
INSERT OR REPLACE INTO delivery_units (unit_id, kind, legacy_key, legacy_id, parent_unit_id, node_index, delivery_status, dispatch_status, repo, issue_number, issue_url, title, base_branch, status, process_key, pr_key, converge, auto_merge, human_approval, auto_resolve_escalations, outcome, delivery_label, acknowledged_at, stage, stage_state, stage_skipped, attention, list_bucket, created_at, updated_at)
|
|
31
|
+
VALUES ('feature:' || NEW.feature_key, 'feature', NEW.feature_key, NULL, NULL, NULL, CASE WHEN NEW.status = 'running' THEN 'running' WHEN NEW.status = 'escalated' THEN 'escalated' WHEN NEW.status = 'opened' THEN 'opened' WHEN NEW.status = 'converging' THEN 'converging' WHEN NEW.status = 'awaiting_operator' THEN 'awaiting_operator' WHEN NEW.status = 'merged' THEN 'merged' WHEN NEW.status = 'converged' THEN 'converged' WHEN NEW.status = 'blocked' THEN 'blocked' WHEN NEW.status = 'skipped' THEN 'skipped' WHEN NEW.status = 'failed' THEN 'failed' WHEN NEW.status = 'abandoned' THEN 'abandoned' ELSE NULL END, CASE WHEN NEW.status IN ('opened', 'converging', 'merged', 'converged', 'blocked', 'skipped', 'failed', 'abandoned') THEN 'settled' WHEN NEW.status IN ('running', 'escalated', 'awaiting_operator') THEN 'dispatched' ELSE NULL END, NEW.repo, NEW.issue_number, NEW.issue_url, NEW.title, NEW.base_branch, NEW.status, NEW.process_key, NEW.pr_key, NEW.converge, NEW.auto_merge, NEW.human_approval, NEW.auto_resolve_escalations, NEW.outcome, NEW.delivery_label, NEW.acknowledged_at, NEW.stage, NEW.stage_state, NEW.stage_skipped, NEW.attention, NEW.list_bucket, NEW.created_at, NEW.updated_at);
|
|
32
|
+
END;
|
|
33
|
+
|
|
34
|
+
DROP TRIGGER IF EXISTS feature_runs__du_au;
|
|
35
|
+
CREATE TRIGGER feature_runs__du_au AFTER UPDATE ON feature_runs
|
|
36
|
+
BEGIN
|
|
37
|
+
INSERT OR REPLACE INTO delivery_units (unit_id, kind, legacy_key, legacy_id, parent_unit_id, node_index, delivery_status, dispatch_status, repo, issue_number, issue_url, title, base_branch, status, process_key, pr_key, converge, auto_merge, human_approval, auto_resolve_escalations, outcome, delivery_label, acknowledged_at, stage, stage_state, stage_skipped, attention, list_bucket, created_at, updated_at)
|
|
38
|
+
VALUES ('feature:' || NEW.feature_key, 'feature', NEW.feature_key, NULL, NULL, NULL, CASE WHEN NEW.status = 'running' THEN 'running' WHEN NEW.status = 'escalated' THEN 'escalated' WHEN NEW.status = 'opened' THEN 'opened' WHEN NEW.status = 'converging' THEN 'converging' WHEN NEW.status = 'awaiting_operator' THEN 'awaiting_operator' WHEN NEW.status = 'merged' THEN 'merged' WHEN NEW.status = 'converged' THEN 'converged' WHEN NEW.status = 'blocked' THEN 'blocked' WHEN NEW.status = 'skipped' THEN 'skipped' WHEN NEW.status = 'failed' THEN 'failed' WHEN NEW.status = 'abandoned' THEN 'abandoned' ELSE NULL END, CASE WHEN NEW.status IN ('opened', 'converging', 'merged', 'converged', 'blocked', 'skipped', 'failed', 'abandoned') THEN 'settled' WHEN NEW.status IN ('running', 'escalated', 'awaiting_operator') THEN 'dispatched' ELSE NULL END, NEW.repo, NEW.issue_number, NEW.issue_url, NEW.title, NEW.base_branch, NEW.status, NEW.process_key, NEW.pr_key, NEW.converge, NEW.auto_merge, NEW.human_approval, NEW.auto_resolve_escalations, NEW.outcome, NEW.delivery_label, NEW.acknowledged_at, NEW.stage, NEW.stage_state, NEW.stage_skipped, NEW.attention, NEW.list_bucket, NEW.created_at, NEW.updated_at);
|
|
39
|
+
END;
|
|
40
|
+
|
|
41
|
+
DROP TRIGGER IF EXISTS plans__du_ai;
|
|
42
|
+
CREATE TRIGGER plans__du_ai AFTER INSERT ON plans
|
|
43
|
+
BEGIN
|
|
44
|
+
INSERT OR REPLACE INTO delivery_units (unit_id, kind, legacy_key, legacy_id, parent_unit_id, node_index, delivery_status, dispatch_status, repo, issue_number, issue_url, title, status, task_count, process_key, outcome, gate_wave, blackboard_token, retro_started_at, base_branch, epic_phase, promotion_pr, promotion_state, acknowledged_at, list_bucket, ack_open, wait_gate, wait_gate_label, bound_artifacts, auto_resolve_escalations, created_at, updated_at)
|
|
45
|
+
VALUES ('epic:' || NEW.plan_key, 'epic', NEW.plan_key, NULL, NULL, NULL, CASE WHEN NEW.status = 'planning' THEN 'requested' WHEN NEW.status = 'dispatched' THEN 'running' WHEN NEW.status = 'done' THEN 'done' WHEN NEW.status = 'failed' THEN 'failed' WHEN NEW.status = 'abandoned' THEN 'abandoned' ELSE NULL END, CASE WHEN NEW.status IN ('planning') THEN 'pending' WHEN NEW.status IN ('done', 'failed', 'abandoned') THEN 'settled' WHEN NEW.status IN ('dispatched') THEN 'dispatched' ELSE NULL END, NEW.repo, NEW.issue_number, NEW.issue_url, NEW.title, NEW.status, NEW.task_count, NEW.process_key, NEW.outcome, NEW.gate_wave, NEW.blackboard_token, NEW.retro_started_at, NEW.base_branch, NEW.epic_phase, NEW.promotion_pr, NEW.promotion_state, NEW.acknowledged_at, NEW.list_bucket, NEW.ack_open, NEW.wait_gate, NEW.wait_gate_label, NEW.bound_artifacts, NEW.auto_resolve_escalations, NEW.created_at, NEW.updated_at);
|
|
46
|
+
END;
|
|
47
|
+
|
|
48
|
+
DROP TRIGGER IF EXISTS plans__du_au;
|
|
49
|
+
CREATE TRIGGER plans__du_au AFTER UPDATE ON plans
|
|
50
|
+
BEGIN
|
|
51
|
+
INSERT OR REPLACE INTO delivery_units (unit_id, kind, legacy_key, legacy_id, parent_unit_id, node_index, delivery_status, dispatch_status, repo, issue_number, issue_url, title, status, task_count, process_key, outcome, gate_wave, blackboard_token, retro_started_at, base_branch, epic_phase, promotion_pr, promotion_state, acknowledged_at, list_bucket, ack_open, wait_gate, wait_gate_label, bound_artifacts, auto_resolve_escalations, created_at, updated_at)
|
|
52
|
+
VALUES ('epic:' || NEW.plan_key, 'epic', NEW.plan_key, NULL, NULL, NULL, CASE WHEN NEW.status = 'planning' THEN 'requested' WHEN NEW.status = 'dispatched' THEN 'running' WHEN NEW.status = 'done' THEN 'done' WHEN NEW.status = 'failed' THEN 'failed' WHEN NEW.status = 'abandoned' THEN 'abandoned' ELSE NULL END, CASE WHEN NEW.status IN ('planning') THEN 'pending' WHEN NEW.status IN ('done', 'failed', 'abandoned') THEN 'settled' WHEN NEW.status IN ('dispatched') THEN 'dispatched' ELSE NULL END, NEW.repo, NEW.issue_number, NEW.issue_url, NEW.title, NEW.status, NEW.task_count, NEW.process_key, NEW.outcome, NEW.gate_wave, NEW.blackboard_token, NEW.retro_started_at, NEW.base_branch, NEW.epic_phase, NEW.promotion_pr, NEW.promotion_state, NEW.acknowledged_at, NEW.list_bucket, NEW.ack_open, NEW.wait_gate, NEW.wait_gate_label, NEW.bound_artifacts, NEW.auto_resolve_escalations, NEW.created_at, NEW.updated_at);
|
|
53
|
+
END;
|
|
54
|
+
|
|
55
|
+
DROP TRIGGER IF EXISTS delivery_graph_runs__du_ai;
|
|
56
|
+
CREATE TRIGGER delivery_graph_runs__du_ai AFTER INSERT ON delivery_graph_runs
|
|
57
|
+
BEGIN
|
|
58
|
+
INSERT OR REPLACE INTO delivery_units (unit_id, kind, legacy_key, legacy_id, parent_unit_id, node_index, delivery_status, dispatch_status, process_key, process_definition_id, digest, status, side_effecting, node_count, human_node_count, side_effect_count, title, phase, phase_node_id, human_labels, acknowledged_at, auto_resolve_escalations, created_at, updated_at)
|
|
59
|
+
VALUES ('delivery-graph:' || NEW.run_key, 'delivery-graph', NEW.run_key, NULL, NULL, NULL, CASE WHEN NEW.status = 'awaiting-approval' THEN 'requested' WHEN NEW.status = 'running' THEN 'running' WHEN NEW.status = 'done' THEN 'done' WHEN NEW.status = 'failed' THEN 'failed' WHEN NEW.status = 'abandoned' THEN 'abandoned' ELSE NULL END, CASE WHEN NEW.status IN ('awaiting-approval') THEN 'pending' WHEN NEW.status IN ('done', 'failed', 'abandoned') THEN 'settled' WHEN NEW.status IN ('running') THEN 'dispatched' ELSE NULL END, NEW.process_key, NEW.process_definition_id, NEW.digest, NEW.status, NEW.side_effecting, NEW.node_count, NEW.human_node_count, NEW.side_effect_count, NEW.title, NEW.phase, NEW.phase_node_id, NEW.human_labels, NEW.acknowledged_at, NEW.auto_resolve_escalations, NEW.created_at, NEW.updated_at);
|
|
60
|
+
END;
|
|
61
|
+
|
|
62
|
+
DROP TRIGGER IF EXISTS delivery_graph_runs__du_au;
|
|
63
|
+
CREATE TRIGGER delivery_graph_runs__du_au AFTER UPDATE ON delivery_graph_runs
|
|
64
|
+
BEGIN
|
|
65
|
+
INSERT OR REPLACE INTO delivery_units (unit_id, kind, legacy_key, legacy_id, parent_unit_id, node_index, delivery_status, dispatch_status, process_key, process_definition_id, digest, status, side_effecting, node_count, human_node_count, side_effect_count, title, phase, phase_node_id, human_labels, acknowledged_at, auto_resolve_escalations, created_at, updated_at)
|
|
66
|
+
VALUES ('delivery-graph:' || NEW.run_key, 'delivery-graph', NEW.run_key, NULL, NULL, NULL, CASE WHEN NEW.status = 'awaiting-approval' THEN 'requested' WHEN NEW.status = 'running' THEN 'running' WHEN NEW.status = 'done' THEN 'done' WHEN NEW.status = 'failed' THEN 'failed' WHEN NEW.status = 'abandoned' THEN 'abandoned' ELSE NULL END, CASE WHEN NEW.status IN ('awaiting-approval') THEN 'pending' WHEN NEW.status IN ('done', 'failed', 'abandoned') THEN 'settled' WHEN NEW.status IN ('running') THEN 'dispatched' ELSE NULL END, NEW.process_key, NEW.process_definition_id, NEW.digest, NEW.status, NEW.side_effecting, NEW.node_count, NEW.human_node_count, NEW.side_effect_count, NEW.title, NEW.phase, NEW.phase_node_id, NEW.human_labels, NEW.acknowledged_at, NEW.auto_resolve_escalations, NEW.created_at, NEW.updated_at);
|
|
67
|
+
END;
|
|
68
|
+
|
|
69
|
+
DROP VIEW IF EXISTS feature_runs__units;
|
|
70
|
+
CREATE VIEW feature_runs__units AS
|
|
71
|
+
SELECT
|
|
72
|
+
du.legacy_key AS feature_key,
|
|
73
|
+
du.repo AS repo,
|
|
74
|
+
du.issue_number AS issue_number,
|
|
75
|
+
du.issue_url AS issue_url,
|
|
76
|
+
du.base_branch AS base_branch,
|
|
77
|
+
du.status AS status,
|
|
78
|
+
du.process_key AS process_key,
|
|
79
|
+
du.pr_key AS pr_key,
|
|
80
|
+
du.converge AS converge,
|
|
81
|
+
du.auto_merge AS auto_merge,
|
|
82
|
+
du.human_approval AS human_approval,
|
|
83
|
+
du.auto_resolve_escalations AS auto_resolve_escalations,
|
|
84
|
+
du.outcome AS outcome,
|
|
85
|
+
du.created_at AS created_at,
|
|
86
|
+
du.updated_at AS updated_at,
|
|
87
|
+
du.delivery_label AS delivery_label,
|
|
88
|
+
du.title AS title,
|
|
89
|
+
du.acknowledged_at AS acknowledged_at,
|
|
90
|
+
du.stage AS stage,
|
|
91
|
+
du.stage_state AS stage_state,
|
|
92
|
+
du.stage_skipped AS stage_skipped,
|
|
93
|
+
du.attention AS attention,
|
|
94
|
+
du.list_bucket AS list_bucket
|
|
95
|
+
FROM delivery_units du
|
|
96
|
+
WHERE du.kind = 'feature';
|
|
97
|
+
|
|
98
|
+
DROP VIEW IF EXISTS plans__units;
|
|
99
|
+
CREATE VIEW plans__units AS
|
|
100
|
+
SELECT
|
|
101
|
+
du.legacy_key AS plan_key,
|
|
102
|
+
du.repo AS repo,
|
|
103
|
+
du.issue_number AS issue_number,
|
|
104
|
+
du.issue_url AS issue_url,
|
|
105
|
+
du.title AS title,
|
|
106
|
+
du.status AS status,
|
|
107
|
+
du.task_count AS task_count,
|
|
108
|
+
du.process_key AS process_key,
|
|
109
|
+
du.outcome AS outcome,
|
|
110
|
+
du.created_at AS created_at,
|
|
111
|
+
du.updated_at AS updated_at,
|
|
112
|
+
du.gate_wave AS gate_wave,
|
|
113
|
+
du.blackboard_token AS blackboard_token,
|
|
114
|
+
du.retro_started_at AS retro_started_at,
|
|
115
|
+
du.base_branch AS base_branch,
|
|
116
|
+
du.epic_phase AS epic_phase,
|
|
117
|
+
du.promotion_pr AS promotion_pr,
|
|
118
|
+
du.promotion_state AS promotion_state,
|
|
119
|
+
du.acknowledged_at AS acknowledged_at,
|
|
120
|
+
du.list_bucket AS list_bucket,
|
|
121
|
+
du.ack_open AS ack_open,
|
|
122
|
+
du.wait_gate AS wait_gate,
|
|
123
|
+
du.wait_gate_label AS wait_gate_label,
|
|
124
|
+
du.bound_artifacts AS bound_artifacts,
|
|
125
|
+
du.auto_resolve_escalations AS auto_resolve_escalations
|
|
126
|
+
FROM delivery_units du
|
|
127
|
+
WHERE du.kind = 'epic';
|
|
128
|
+
|
|
129
|
+
DROP VIEW IF EXISTS delivery_graph_runs__units;
|
|
130
|
+
CREATE VIEW delivery_graph_runs__units AS
|
|
131
|
+
SELECT
|
|
132
|
+
du.legacy_key AS run_key,
|
|
133
|
+
du.process_key AS process_key,
|
|
134
|
+
du.process_definition_id AS process_definition_id,
|
|
135
|
+
du.digest AS digest,
|
|
136
|
+
du.status AS status,
|
|
137
|
+
du.side_effecting AS side_effecting,
|
|
138
|
+
du.node_count AS node_count,
|
|
139
|
+
du.human_node_count AS human_node_count,
|
|
140
|
+
du.side_effect_count AS side_effect_count,
|
|
141
|
+
du.title AS title,
|
|
142
|
+
du.phase AS phase,
|
|
143
|
+
du.phase_node_id AS phase_node_id,
|
|
144
|
+
du.human_labels AS human_labels,
|
|
145
|
+
du.created_at AS created_at,
|
|
146
|
+
du.updated_at AS updated_at,
|
|
147
|
+
du.acknowledged_at AS acknowledged_at,
|
|
148
|
+
du.auto_resolve_escalations AS auto_resolve_escalations
|
|
149
|
+
FROM delivery_units du
|
|
150
|
+
WHERE du.kind = 'delivery-graph';
|
|
151
|
+
|
|
152
|
+
-- The durable, reviewable record of every escalation the steward acted on or abstained from.
|
|
153
|
+
CREATE TABLE escalation_auto_resolutions (
|
|
154
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
155
|
+
-- The escalation user task acted on (the engine `userTaskKey`), its BPMN element id, and the
|
|
156
|
+
-- escalation kind label at the time (denormalised so the record survives the task completing).
|
|
157
|
+
user_task_key TEXT NOT NULL,
|
|
158
|
+
escalation_kind TEXT,
|
|
159
|
+
element_id TEXT,
|
|
160
|
+
-- The subject the escalation belongs to (PR key / feature key / plan key / delivery run key).
|
|
161
|
+
subject_type TEXT,
|
|
162
|
+
subject_key TEXT,
|
|
163
|
+
subject_url TEXT,
|
|
164
|
+
process_instance_key TEXT,
|
|
165
|
+
-- Snapshots of what was asked and the choices on offer, so the record is reviewable after the fact.
|
|
166
|
+
question TEXT,
|
|
167
|
+
options TEXT, -- JSON array of the form's option values/labels
|
|
168
|
+
-- `resolved` (the steward submitted an answer) or `abstained` (it deliberately left it for a human).
|
|
169
|
+
outcome TEXT NOT NULL, -- resolved | abstained
|
|
170
|
+
submitted_variables TEXT, -- JSON of the variables submitted (NULL when abstained)
|
|
171
|
+
rationale TEXT, -- one-line why
|
|
172
|
+
evidence TEXT, -- JSON/markdown: what was checked, with links
|
|
173
|
+
-- Provenance of the steward run that produced this record.
|
|
174
|
+
steward_instance_key TEXT,
|
|
175
|
+
agent_job_key TEXT,
|
|
176
|
+
worker TEXT,
|
|
177
|
+
model TEXT,
|
|
178
|
+
created_at TEXT NOT NULL,
|
|
179
|
+
-- Operator review lifecycle: pending → dismissed | intervened.
|
|
180
|
+
review_state TEXT NOT NULL DEFAULT 'pending', -- pending | dismissed | intervened
|
|
181
|
+
reviewed_by TEXT,
|
|
182
|
+
reviewed_at TEXT,
|
|
183
|
+
review_note TEXT,
|
|
184
|
+
-- Claim/lease bookkeeping (not part of the review surface): which steward instance holds the open
|
|
185
|
+
-- claim and until when. NULL once the row is finalised (resolved/abstained recorded).
|
|
186
|
+
claimed_by TEXT,
|
|
187
|
+
claimed_until TEXT
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
CREATE INDEX idx_auto_resolutions_review_state ON escalation_auto_resolutions(review_state, created_at);
|
|
191
|
+
CREATE INDEX idx_auto_resolutions_subject ON escalation_auto_resolutions(subject_key);
|
|
192
|
+
-- The claim/lease fence: at most ONE open (unfinalised, `outcome='claimed'`) claim per user task, so
|
|
193
|
+
-- two overlapping steward ticks can never both act on the same escalation. Final rows
|
|
194
|
+
-- (`resolved`/`abstained`) are excluded so a re-escalation of the SAME task can be recorded afresh.
|
|
195
|
+
CREATE UNIQUE INDEX ux_auto_resolutions_open_claim ON escalation_auto_resolutions(user_task_key) WHERE outcome = 'claimed';
|
|
196
|
+
CREATE INDEX idx_auto_resolutions_task ON escalation_auto_resolutions(user_task_key);
|
|
197
|
+
|
|
198
|
+
-- The Auto-resolutions page read model (ADR 0042): projects each FINAL record (resolved/abstained —
|
|
199
|
+
-- never an in-flight `claimed` placeholder) with a derived `pending_review` flag (1 iff still
|
|
200
|
+
-- `pending`) for the row-action `showWhenField` gate, which reads JS truthiness. Derivation over
|
|
201
|
+
-- duplication: the grid binds this VIEW, never the base table.
|
|
202
|
+
CREATE VIEW escalation_auto_resolutions__read_model AS
|
|
203
|
+
SELECT
|
|
204
|
+
id,
|
|
205
|
+
user_task_key,
|
|
206
|
+
escalation_kind,
|
|
207
|
+
element_id,
|
|
208
|
+
subject_type,
|
|
209
|
+
subject_key,
|
|
210
|
+
subject_url,
|
|
211
|
+
process_instance_key,
|
|
212
|
+
question,
|
|
213
|
+
options,
|
|
214
|
+
outcome,
|
|
215
|
+
submitted_variables,
|
|
216
|
+
rationale,
|
|
217
|
+
evidence,
|
|
218
|
+
steward_instance_key,
|
|
219
|
+
agent_job_key,
|
|
220
|
+
worker,
|
|
221
|
+
model,
|
|
222
|
+
created_at,
|
|
223
|
+
review_state,
|
|
224
|
+
reviewed_by,
|
|
225
|
+
reviewed_at,
|
|
226
|
+
review_note,
|
|
227
|
+
CASE WHEN review_state = 'pending' THEN 1 ELSE 0 END AS pending_review
|
|
228
|
+
FROM escalation_auto_resolutions
|
|
229
|
+
WHERE outcome IN ('resolved', 'abstained');
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
-- Re-create the `delivery_graph_read_model` VIEW so it re-exports the base column added by
|
|
2
|
+
-- 120_escalation_auto_resolutions.sql (`auto_resolve_escalations`). The read-model VIEW must pass
|
|
3
|
+
-- through EVERY base `delivery_graph_runs` column (the static pages↔schema contract guard +
|
|
4
|
+
-- app/deliveryGraphReadModel.test.ts DRIFT GUARD assert it), so adding a base column obliges a fresh
|
|
5
|
+
-- VIEW definition — 087/096 are immutable and cannot be edited in place.
|
|
6
|
+
--
|
|
7
|
+
-- Every DERIVED column below is emitted VERBATIM from the ONE declaration in
|
|
8
|
+
-- app/deliveryGraphReadModel.ts (`deliveryGraphReadModel.sqlSelectFor(col, { baseAlias: "dg" })`) —
|
|
9
|
+
-- the same closed-DSL AST that drives the runtime TS via `fnFor`, so the two lowerings cannot
|
|
10
|
+
-- diverge. This file is a mechanical re-emission of 096 with the one new base pass-through added;
|
|
11
|
+
-- the derived `stage`/`stage_state`/`list_bucket`/`ack_open`/`park_label` expressions are unchanged.
|
|
12
|
+
-- The drift guard now points at THIS migration (the latest VIEW definition).
|
|
13
|
+
--
|
|
14
|
+
-- Forward-only VIEW definition (DROP then CREATE), sourced off the managed
|
|
15
|
+
-- `delivery_graph_runs__tracking` re-export of the base table so a terminated run classifies on
|
|
16
|
+
-- ENGINE TRUTH. The runner wraps each file in its own transaction, so this file must NOT contain
|
|
17
|
+
-- BEGIN/COMMIT. Numbered after 120.
|
|
18
|
+
|
|
19
|
+
DROP VIEW IF EXISTS delivery_graph_read_model;
|
|
20
|
+
DROP VIEW IF EXISTS delivery_graph_pr_counts;
|
|
21
|
+
|
|
22
|
+
CREATE VIEW IF NOT EXISTS "delivery_graph_pr_counts" AS
|
|
23
|
+
SELECT
|
|
24
|
+
"__urban_rollup_src"."root_request_key" AS "root_request_key",
|
|
25
|
+
SUM(CASE WHEN COALESCE(((NOT COALESCE(("__urban_rollup_src"."root_request_key" IS NULL), 0)) AND (NOT COALESCE(COALESCE((COALESCE(("__urban_rollup_src"."derived_status" = 'converged'), 0) OR COALESCE(("__urban_rollup_src"."derived_status" = 'merged'), 0) OR COALESCE(("__urban_rollup_src"."derived_status" = 'abandoned'), 0)), 0), 0))), 0) THEN 1 ELSE 0 END) AS "prs_in_flight"
|
|
26
|
+
FROM "pull_requests__tracking" "__urban_rollup_src"
|
|
27
|
+
GROUP BY "__urban_rollup_src"."root_request_key";
|
|
28
|
+
|
|
29
|
+
CREATE VIEW delivery_graph_read_model AS
|
|
30
|
+
SELECT
|
|
31
|
+
dg.run_key AS run_key,
|
|
32
|
+
COALESCE(dg.derived_status, dg.status) AS status,
|
|
33
|
+
dg.process_key AS process_key,
|
|
34
|
+
dg.process_definition_id AS process_definition_id,
|
|
35
|
+
dg.digest AS digest,
|
|
36
|
+
dg.side_effecting AS side_effecting,
|
|
37
|
+
dg.node_count AS node_count,
|
|
38
|
+
dg.human_node_count AS human_node_count,
|
|
39
|
+
dg.side_effect_count AS side_effect_count,
|
|
40
|
+
dg.title AS title,
|
|
41
|
+
dg.phase AS phase,
|
|
42
|
+
dg.phase_node_id AS phase_node_id,
|
|
43
|
+
dg.human_labels AS human_labels,
|
|
44
|
+
dg.created_at AS created_at,
|
|
45
|
+
dg.updated_at AS updated_at,
|
|
46
|
+
dg.acknowledged_at AS acknowledged_at,
|
|
47
|
+
dg.auto_resolve_escalations AS auto_resolve_escalations,
|
|
48
|
+
CASE WHEN COALESCE(("dg"."derived_status" = 'done'), 0) THEN 'Done' WHEN COALESCE((COALESCE(("dg"."derived_status" = 'failed'), 0) OR COALESCE(("dg"."derived_status" = 'abandoned'), 0)), 0) THEN 'Done' WHEN COALESCE(("dg"."derived_status" = 'awaiting-approval'), 0) THEN 'Requested' WHEN COALESCE((COALESCE("pc"."prs_in_flight", 0) > 0), 0) THEN 'Converging' ELSE 'Implementing' END AS stage,
|
|
49
|
+
CASE WHEN COALESCE(("dg"."derived_status" = 'done'), 0) THEN 'ok' WHEN COALESCE((COALESCE(("dg"."derived_status" = 'failed'), 0) OR COALESCE(("dg"."derived_status" = 'abandoned'), 0)), 0) THEN 'failed' ELSE NULL END AS stage_state,
|
|
50
|
+
CASE WHEN COALESCE((COALESCE((COALESCE(("dg"."derived_status" = 'done'), 0) OR COALESCE(("dg"."derived_status" = 'failed'), 0) OR COALESCE(("dg"."derived_status" = 'abandoned'), 0)), 0) AND COALESCE(("dg"."acknowledged_at" = "dg"."acknowledged_at"), 0)), 0) THEN 'history' ELSE 'active' END AS list_bucket,
|
|
51
|
+
CASE WHEN COALESCE((COALESCE((COALESCE(("dg"."derived_status" = 'done'), 0) OR COALESCE(("dg"."derived_status" = 'failed'), 0) OR COALESCE(("dg"."derived_status" = 'abandoned'), 0)), 0) AND (NOT COALESCE(COALESCE(("dg"."acknowledged_at" = "dg"."acknowledged_at"), 0), 0))), 0) THEN 1 ELSE 0 END AS ack_open,
|
|
52
|
+
CASE WHEN dg.phase_node_id IS NOT NULL THEN dg.phase ELSE NULL END AS park_label
|
|
53
|
+
FROM delivery_graph_runs__tracking dg
|
|
54
|
+
LEFT JOIN delivery_graph_pr_counts pc ON dg.run_key = pc.root_request_key;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
-- Re-create the `pull_requests_read_model` VIEW so it re-exports the base column added by
|
|
2
|
+
-- 120_escalation_auto_resolutions.sql (`auto_resolve_escalations`). The read-model VIEW must pass
|
|
3
|
+
-- through EVERY base `pull_requests` column (the static pages↔schema contract guard +
|
|
4
|
+
-- app/pullRequestReadModel.test.ts DRIFT GUARD assert it), so adding a base column obliges a fresh
|
|
5
|
+
-- VIEW definition — 094/104/118 are immutable and cannot be edited in place.
|
|
6
|
+
--
|
|
7
|
+
-- Every DERIVED column below is emitted VERBATIM from the ONE declaration in
|
|
8
|
+
-- app/pullRequestReadModel.ts (`pullRequestReadModel.sqlSelectFor(col, { baseAlias: "pr" })`) — the
|
|
9
|
+
-- same closed-DSL AST that drives the runtime TS via `fnFor`, so the two lowerings cannot diverge.
|
|
10
|
+
-- This file is a mechanical re-emission of 118 with the one new base pass-through added; the
|
|
11
|
+
-- derived `list_bucket`/`ack_open` expressions are unchanged. The drift guard now points at THIS
|
|
12
|
+
-- migration (the latest VIEW definition).
|
|
13
|
+
--
|
|
14
|
+
-- Forward-only VIEW definition (DROP then CREATE), sourced off the managed `pull_requests__tracking`
|
|
15
|
+
-- re-export of the base table so a terminated PR classifies on ENGINE TRUTH. SQLite does not validate
|
|
16
|
+
-- a view body at CREATE time. The runner wraps each file in its own transaction, so this file must
|
|
17
|
+
-- NOT contain BEGIN/COMMIT. Numbered after 121.
|
|
18
|
+
|
|
19
|
+
DROP VIEW IF EXISTS pull_requests_read_model;
|
|
20
|
+
|
|
21
|
+
CREATE VIEW pull_requests_read_model AS
|
|
22
|
+
SELECT
|
|
23
|
+
pr.pr_key AS pr_key,
|
|
24
|
+
pr.repo AS repo,
|
|
25
|
+
pr.number AS number,
|
|
26
|
+
pr.url AS url,
|
|
27
|
+
pr.title AS title,
|
|
28
|
+
COALESCE(pr.derived_status, pr.status) AS status,
|
|
29
|
+
pr.current_round AS current_round,
|
|
30
|
+
pr.process_key AS process_key,
|
|
31
|
+
pr.waiting_since AS waiting_since,
|
|
32
|
+
pr.last_review_id AS last_review_id,
|
|
33
|
+
pr.outcome AS outcome,
|
|
34
|
+
pr.created_at AS created_at,
|
|
35
|
+
pr.updated_at AS updated_at,
|
|
36
|
+
pr.converged_at AS converged_at,
|
|
37
|
+
pr.merged_at AS merged_at,
|
|
38
|
+
pr.active_worker AS active_worker,
|
|
39
|
+
pr.lease_until AS lease_until,
|
|
40
|
+
pr.last_nudge_at AS last_nudge_at,
|
|
41
|
+
pr.fresh_head_run_head AS fresh_head_run_head,
|
|
42
|
+
pr.abandon_token AS abandon_token,
|
|
43
|
+
pr.incident_key AS incident_key,
|
|
44
|
+
pr.incident_message AS incident_message,
|
|
45
|
+
pr.last_round_head AS last_round_head,
|
|
46
|
+
pr.last_progress_job_key AS last_progress_job_key,
|
|
47
|
+
pr.last_progress_result AS last_progress_result,
|
|
48
|
+
pr.last_progress_agent_watermark AS last_progress_agent_watermark,
|
|
49
|
+
pr.churn_escalated_round AS churn_escalated_round,
|
|
50
|
+
pr.root_request_key AS root_request_key,
|
|
51
|
+
pr.epic_phase_label AS epic_phase_label,
|
|
52
|
+
pr.acknowledged_at AS acknowledged_at,
|
|
53
|
+
pr.auto_resolve_escalations AS auto_resolve_escalations,
|
|
54
|
+
CASE WHEN COALESCE((COALESCE((COALESCE(("pr"."derived_status" = 'merged'), 0) OR COALESCE(("pr"."derived_status" = 'converged'), 0) OR COALESCE(("pr"."derived_status" = 'abandoned'), 0) OR COALESCE(("pr"."derived_status" = 'closed'), 0) OR COALESCE(("pr"."derived_status" = 'failed'), 0)), 0) AND COALESCE(("pr"."acknowledged_at" = "pr"."acknowledged_at"), 0)), 0) THEN 'history' ELSE 'active' END AS list_bucket,
|
|
55
|
+
CASE WHEN COALESCE((COALESCE((COALESCE(("pr"."derived_status" = 'merged'), 0) OR COALESCE(("pr"."derived_status" = 'converged'), 0) OR COALESCE(("pr"."derived_status" = 'abandoned'), 0) OR COALESCE(("pr"."derived_status" = 'closed'), 0) OR COALESCE(("pr"."derived_status" = 'failed'), 0)), 0) AND (NOT COALESCE(COALESCE(("pr"."acknowledged_at" = "pr"."acknowledged_at"), 0), 0))), 0) THEN 1 ELSE 0 END AS ack_open
|
|
56
|
+
FROM pull_requests__tracking pr;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- Issue #889 review (Copilot "Previously missed"): the Auto-resolutions grid omitted the SELECTED
|
|
2
|
+
-- option, so an operator could not see whether the steward chose retry/continue/etc. without decoding
|
|
3
|
+
-- the raw submitted variables. Add a first-class `selected_option` column (the option value the steward
|
|
4
|
+
-- actually selected, derived host-side by the record worker from the decision's own form variables) and
|
|
5
|
+
-- re-create the read-model VIEW to expose it as a list column.
|
|
6
|
+
--
|
|
7
|
+
-- Expand-and-contract: the column is nullable (existing rows keep NULL — they predate the projection),
|
|
8
|
+
-- and the VIEW is re-created forward-only (DROP then CREATE) to add the new pass-through. Migration 120
|
|
9
|
+
-- is immutable and cannot be edited in place. Numbered after 122.
|
|
10
|
+
|
|
11
|
+
ALTER TABLE escalation_auto_resolutions ADD COLUMN selected_option TEXT;
|
|
12
|
+
|
|
13
|
+
DROP VIEW IF EXISTS escalation_auto_resolutions__read_model;
|
|
14
|
+
CREATE VIEW escalation_auto_resolutions__read_model AS
|
|
15
|
+
SELECT
|
|
16
|
+
id,
|
|
17
|
+
user_task_key,
|
|
18
|
+
escalation_kind,
|
|
19
|
+
element_id,
|
|
20
|
+
subject_type,
|
|
21
|
+
subject_key,
|
|
22
|
+
subject_url,
|
|
23
|
+
process_instance_key,
|
|
24
|
+
-- The cockpit "Agent" transcript link keys on `process_key` (the #833 invariant: every Agent →
|
|
25
|
+
-- cockpit link uses keyField `process_key`). The ANSWERING agent ran under `steward_instance_key`
|
|
26
|
+
-- (the steward process), NOT `process_instance_key` (the escalated SUBJECT's process), so project
|
|
27
|
+
-- the steward key under the canonical `process_key` name for that link (issue #889 review).
|
|
28
|
+
steward_instance_key AS process_key,
|
|
29
|
+
question,
|
|
30
|
+
options,
|
|
31
|
+
outcome,
|
|
32
|
+
submitted_variables,
|
|
33
|
+
selected_option,
|
|
34
|
+
rationale,
|
|
35
|
+
evidence,
|
|
36
|
+
steward_instance_key,
|
|
37
|
+
agent_job_key,
|
|
38
|
+
worker,
|
|
39
|
+
model,
|
|
40
|
+
created_at,
|
|
41
|
+
review_state,
|
|
42
|
+
reviewed_by,
|
|
43
|
+
reviewed_at,
|
|
44
|
+
review_note,
|
|
45
|
+
CASE WHEN review_state = 'pending' THEN 1 ELSE 0 END AS pending_review
|
|
46
|
+
FROM escalation_auto_resolutions
|
|
47
|
+
WHERE outcome IN ('resolved', 'abstained');
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- 124_admitted_epics_auto_resolve.sql — issue #889: carry the escalation-steward opt-in through the
|
|
2
|
+
-- set/batch admission door (`startEpicSet`).
|
|
3
|
+
--
|
|
4
|
+
-- The single-issue door (`startPlanFanout`) already seeds `plans.auto_resolve_escalations` (added by
|
|
5
|
+
-- 120) from the request body's `autoResolveEscalations`. The set/batch door dropped it: `startEpicSet`
|
|
6
|
+
-- stages each admitted epic into `admitted_epics` and `lowerAdmittedSet` later reads that staging to
|
|
7
|
+
-- call `startPlan` — but the staging row had no column for the flag, so every batch-admitted epic (and
|
|
8
|
+
-- every child convergence/merge loop it spawns) stayed opted OUT even when the caller opted in. That
|
|
9
|
+
-- contradicts the issue #889 contract that the opt-in reach "every initiation pathway".
|
|
10
|
+
--
|
|
11
|
+
-- Fix the class at the staging seam: persist the per-epic opt-in on the staged row so the lowering
|
|
12
|
+
-- reads it back and forwards it to `startPlan`. Additive (nullable-free `NOT NULL DEFAULT 0`), so an
|
|
13
|
+
-- existing install's already-staged rows read as opted-out (the safe default) and fresh admissions
|
|
14
|
+
-- record the caller's choice. No read-model view selects from `admitted_epics`, so no VIEW re-emission
|
|
15
|
+
-- is needed (unlike `pull_requests` — cf. 121/122).
|
|
16
|
+
--
|
|
17
|
+
-- Numbered 124: above this branch's highest committed prefix (123) and origin/main's (119), so a
|
|
18
|
+
-- fan-out sibling forking at either point cannot collide. The runner wraps each file in its own
|
|
19
|
+
-- transaction, so this file must NOT contain BEGIN/COMMIT.
|
|
20
|
+
|
|
21
|
+
ALTER TABLE admitted_epics ADD COLUMN auto_resolve_escalations INTEGER NOT NULL DEFAULT 0;
|