loomrail 0.1.0-alpha.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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +266 -0
- package/apps/cli/dist/index.js +11154 -0
- package/apps/cli/migrations/0001_initial.sql +99 -0
- package/apps/cli/migrations/0002_mock_workflow.sql +169 -0
- package/apps/cli/migrations/0003_budget_pause_recovery.sql +154 -0
- package/apps/cli/migrations/0004_pipeline_started_budget_backfill.sql +72 -0
- package/apps/cli/migrations/0005_acceptance_evidence.sql +129 -0
- package/apps/cli/migrations/0006_session_handoff.sql +166 -0
- package/apps/cli/migrations/0007_pack_share_backoff.sql +11 -0
- package/apps/cli/migrations/0008_stage_attempt_counter_backfill.sql +169 -0
- package/apps/cli/migrations/0009_session_window_occupancy.sql +44 -0
- package/apps/cli/migrations/0010_provider_session_pid.sql +5 -0
- package/apps/cli/migrations/0011_work_item_workspaces.sql +94 -0
- package/apps/cli/migrations/0012_registered_repository_projects.sql +54 -0
- package/apps/cli/migrations/0013_stage_attempt_result_tree.sql +166 -0
- package/apps/cli/migrations/0014_live_evidence_provider.sql +49 -0
- package/apps/web/dist/assets/index-DjwvCSSY.js +5 -0
- package/apps/web/dist/assets/index-DjwvCSSY.js.map +1 -0
- package/apps/web/dist/assets/index-Z88TqCIf.css +1 -0
- package/apps/web/dist/assets/radix-vendor-CRdHXIPR.js +42 -0
- package/apps/web/dist/assets/radix-vendor-CRdHXIPR.js.map +1 -0
- package/apps/web/dist/assets/react-vendor-BicOiEU0.js +10 -0
- package/apps/web/dist/assets/react-vendor-BicOiEU0.js.map +1 -0
- package/apps/web/dist/assets/rolldown-runtime-CbXtAM7H.js +1 -0
- package/apps/web/dist/assets/tanstack-vendor-ec08LVZ_.js +2 -0
- package/apps/web/dist/assets/tanstack-vendor-ec08LVZ_.js.map +1 -0
- package/apps/web/dist/assets/validation-vendor-CGrOSWd3.js +65 -0
- package/apps/web/dist/assets/validation-vendor-CGrOSWd3.js.map +1 -0
- package/apps/web/dist/favicon.svg +5 -0
- package/apps/web/dist/index.html +60 -0
- package/apps/web/dist/theme-bootstrap.js +46 -0
- package/fixtures/projects/api-service-b/README.md +3 -0
- package/fixtures/projects/api-service-b/loomrail-fixture.json +6 -0
- package/fixtures/projects/web-app-a/README.md +3 -0
- package/fixtures/projects/web-app-a/loomrail-fixture.json +6 -0
- package/package.json +46 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
-- Spec docs/plans/15-e1-5-change-visibility-spec.ru.md D3, §4, §6 step 5 and §12.3.
|
|
2
|
+
--
|
|
3
|
+
-- The tree a stage's worktree held when the stage succeeded. Nothing in E1.5 reads it: spec §11
|
|
4
|
+
-- puts the per-stage breakdown among the milestone's non-goals, so this is precomputed for the
|
|
5
|
+
-- milestone that closes the second half of GD-002 (checkpoint commits, squash and the Conventional
|
|
6
|
+
-- Commit message), which requires GD-001. `stageAttemptSchema.resultTree` in @loomrail/contracts
|
|
7
|
+
-- carries the full statement: which milestone, what will read it, that the milestone has no ID in
|
|
8
|
+
-- the approved order yet, and the measured limit on what a reader arriving later can do with the
|
|
9
|
+
-- value (`git write-tree` leaves the tree UNREACHABLE, so `git gc` prunes it after
|
|
10
|
+
-- `gc.pruneExpire`; equality between two labels survives that, diffing from one does not).
|
|
11
|
+
--
|
|
12
|
+
-- Nullable with no default, because `null` is a fact rather than a gap: no tree was measured for
|
|
13
|
+
-- this stage. Every StageAttempt recorded before this migration stays null forever (spec §12.3),
|
|
14
|
+
-- and so does one whose stage never ran in a worktree or whose worktree was gone when the label was
|
|
15
|
+
-- taken. A stage that ran and changed NOTHING is not null -- it records the tree it started on --
|
|
16
|
+
-- so the two can never be confused.
|
|
17
|
+
--
|
|
18
|
+
-- The CHECK is the same shape assertion `treeShaSchema` makes at the contract boundary, restated
|
|
19
|
+
-- here because the database is written by more than the one code path and a garbled sha is worth
|
|
20
|
+
-- refusing at the moment of the write rather than at the moment of the read.
|
|
21
|
+
|
|
22
|
+
ALTER TABLE stage_attempts
|
|
23
|
+
ADD COLUMN result_tree TEXT
|
|
24
|
+
CHECK (result_tree IS NULL OR (length(result_tree) = 40 AND result_tree NOT GLOB '*[^0-9a-f]*'));
|
|
25
|
+
|
|
26
|
+
-- The rest of this migration is the history half, and it is the same repair 0008 performed for
|
|
27
|
+
-- `unproductive_sessions` and `pack_share_backoffs`: the column above gives every stored ROW a
|
|
28
|
+
-- correct value, but `stageAttemptSchema` is `.strict()` and the entity is embedded verbatim in the
|
|
29
|
+
-- payloads of PIPELINE_STARTED, STAGE_ATTEMPT_CHANGED, PIPELINE_PAUSED, PIPELINE_RESUMED,
|
|
30
|
+
-- PIPELINE_CANCELLED, BUDGET_OVERRIDE_APPROVED, RECOVERY_REPORT_CREATED, ACCEPTANCE_REQUESTED,
|
|
31
|
+
-- ACCEPTANCE_RESOLVED and PIPELINE_COMPLETED. Left alone, every such payload written before this
|
|
32
|
+
-- milestone fails `domainEventSchema.parse` inside `eventFromRow`, which makes the whole activity
|
|
33
|
+
-- timeline and the events endpoint unreadable -- and `commands.result_json` is replayed through
|
|
34
|
+
-- `stateCommandResultSchema`, so a receipt written before this milestone fails its replay the same
|
|
35
|
+
-- way. No test starting from an empty database can see either, which is exactly why 0008 exists.
|
|
36
|
+
--
|
|
37
|
+
-- Structure follows 0008 statement for statement, including the reasons behind its less obvious
|
|
38
|
+
-- choices, which all still hold: the paths cannot be spelled out literally (a StageAttempt appears
|
|
39
|
+
-- at the top level of a command result AND inside `$.events[i].data`, and the events array's length
|
|
40
|
+
-- varies by command type), so occurrences are found with json_tree and folded over with a recursive
|
|
41
|
+
-- CTE, one json_insert each; adding an object key never shifts an array index, so the fold order
|
|
42
|
+
-- does not matter; `$.stage IS NOT NULL` narrows the match to actual StageAttempts rather than
|
|
43
|
+
-- anything that happens to reuse the key name; and the occurrence list is materialised into an
|
|
44
|
+
-- indexed TEMP TABLE because a non-materialised CTE is re-evaluated per fold step, which made the
|
|
45
|
+
-- pass quadratic in history size (measured in 0008 at 89s for 2000 events plus 2000 receipts).
|
|
46
|
+
--
|
|
47
|
+
-- One difference from 0008 worth naming: the value inserted is JSON null rather than 0, and
|
|
48
|
+
-- `json_insert` with an SQL NULL writes JSON null (probed). The guard `json_type(...) IS NULL`
|
|
49
|
+
-- therefore still means "the key is absent" after a pass -- `json_type` answers the string 'null'
|
|
50
|
+
-- for a key present and null -- so the pass stays idempotent, and a re-run finds nothing to do.
|
|
51
|
+
|
|
52
|
+
DROP TRIGGER events_are_append_only_update;
|
|
53
|
+
|
|
54
|
+
CREATE TEMP TABLE legacy_event_result_tree (
|
|
55
|
+
sequence INTEGER NOT NULL,
|
|
56
|
+
path TEXT NOT NULL,
|
|
57
|
+
n INTEGER NOT NULL
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
INSERT INTO legacy_event_result_tree (sequence, path, n)
|
|
61
|
+
SELECT events.sequence, tree.fullkey,
|
|
62
|
+
ROW_NUMBER() OVER (PARTITION BY events.sequence ORDER BY tree.fullkey)
|
|
63
|
+
FROM events, json_tree(events.data_json) AS tree
|
|
64
|
+
WHERE tree.key IN ('stageAttempt', 'previousStageAttempt')
|
|
65
|
+
AND tree.type = 'object'
|
|
66
|
+
AND json_type(tree.value, '$.stage') IS NOT NULL
|
|
67
|
+
AND json_type(tree.value, '$.resultTree') IS NULL;
|
|
68
|
+
|
|
69
|
+
CREATE INDEX legacy_event_result_tree_fold_idx ON legacy_event_result_tree (sequence, n);
|
|
70
|
+
|
|
71
|
+
CREATE TEMP TABLE backfilled_event (
|
|
72
|
+
sequence INTEGER PRIMARY KEY,
|
|
73
|
+
data_json TEXT NOT NULL
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
INSERT INTO backfilled_event (sequence, data_json)
|
|
77
|
+
WITH RECURSIVE
|
|
78
|
+
backfilled(sequence, n, data_json) AS (
|
|
79
|
+
SELECT events.sequence, 0, events.data_json
|
|
80
|
+
FROM events
|
|
81
|
+
WHERE events.sequence IN (SELECT sequence FROM legacy_event_result_tree)
|
|
82
|
+
UNION ALL
|
|
83
|
+
SELECT backfilled.sequence, legacy_event_result_tree.n,
|
|
84
|
+
json_insert(backfilled.data_json, legacy_event_result_tree.path || '.resultTree', NULL)
|
|
85
|
+
FROM backfilled
|
|
86
|
+
JOIN legacy_event_result_tree
|
|
87
|
+
ON legacy_event_result_tree.sequence = backfilled.sequence
|
|
88
|
+
AND legacy_event_result_tree.n = backfilled.n + 1
|
|
89
|
+
)
|
|
90
|
+
SELECT backfilled.sequence, backfilled.data_json
|
|
91
|
+
FROM backfilled
|
|
92
|
+
JOIN (SELECT sequence, MAX(n) AS n FROM legacy_event_result_tree GROUP BY sequence) AS folded
|
|
93
|
+
ON folded.sequence = backfilled.sequence
|
|
94
|
+
AND folded.n = backfilled.n;
|
|
95
|
+
|
|
96
|
+
UPDATE events
|
|
97
|
+
SET data_json = (SELECT data_json FROM backfilled_event WHERE backfilled_event.sequence = events.sequence)
|
|
98
|
+
WHERE events.sequence IN (SELECT sequence FROM backfilled_event);
|
|
99
|
+
|
|
100
|
+
DROP TABLE backfilled_event;
|
|
101
|
+
DROP TABLE legacy_event_result_tree;
|
|
102
|
+
|
|
103
|
+
CREATE TRIGGER events_are_append_only_update
|
|
104
|
+
BEFORE UPDATE ON events
|
|
105
|
+
BEGIN
|
|
106
|
+
SELECT RAISE(ABORT, 'events are append-only');
|
|
107
|
+
END;
|
|
108
|
+
|
|
109
|
+
DROP TRIGGER commands_are_append_only_update;
|
|
110
|
+
|
|
111
|
+
CREATE TEMP TABLE legacy_command_result_tree (
|
|
112
|
+
command_id TEXT NOT NULL,
|
|
113
|
+
path TEXT NOT NULL,
|
|
114
|
+
n INTEGER NOT NULL
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
INSERT INTO legacy_command_result_tree (command_id, path, n)
|
|
118
|
+
SELECT commands.command_id, tree.fullkey,
|
|
119
|
+
ROW_NUMBER() OVER (PARTITION BY commands.command_id ORDER BY tree.fullkey)
|
|
120
|
+
FROM commands, json_tree(commands.result_json) AS tree
|
|
121
|
+
WHERE tree.key IN ('stageAttempt', 'previousStageAttempt')
|
|
122
|
+
AND tree.type = 'object'
|
|
123
|
+
AND json_type(tree.value, '$.stage') IS NOT NULL
|
|
124
|
+
AND json_type(tree.value, '$.resultTree') IS NULL;
|
|
125
|
+
|
|
126
|
+
CREATE INDEX legacy_command_result_tree_fold_idx ON legacy_command_result_tree (command_id, n);
|
|
127
|
+
|
|
128
|
+
CREATE TEMP TABLE backfilled_command (
|
|
129
|
+
command_id TEXT PRIMARY KEY,
|
|
130
|
+
result_json TEXT NOT NULL
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
INSERT INTO backfilled_command (command_id, result_json)
|
|
134
|
+
WITH RECURSIVE
|
|
135
|
+
backfilled(command_id, n, result_json) AS (
|
|
136
|
+
SELECT commands.command_id, 0, commands.result_json
|
|
137
|
+
FROM commands
|
|
138
|
+
WHERE commands.command_id IN (SELECT command_id FROM legacy_command_result_tree)
|
|
139
|
+
UNION ALL
|
|
140
|
+
SELECT backfilled.command_id, legacy_command_result_tree.n,
|
|
141
|
+
json_insert(backfilled.result_json, legacy_command_result_tree.path || '.resultTree', NULL)
|
|
142
|
+
FROM backfilled
|
|
143
|
+
JOIN legacy_command_result_tree
|
|
144
|
+
ON legacy_command_result_tree.command_id = backfilled.command_id
|
|
145
|
+
AND legacy_command_result_tree.n = backfilled.n + 1
|
|
146
|
+
)
|
|
147
|
+
SELECT backfilled.command_id, backfilled.result_json
|
|
148
|
+
FROM backfilled
|
|
149
|
+
JOIN (SELECT command_id, MAX(n) AS n FROM legacy_command_result_tree GROUP BY command_id) AS folded
|
|
150
|
+
ON folded.command_id = backfilled.command_id
|
|
151
|
+
AND folded.n = backfilled.n;
|
|
152
|
+
|
|
153
|
+
UPDATE commands
|
|
154
|
+
SET result_json = (
|
|
155
|
+
SELECT result_json FROM backfilled_command WHERE backfilled_command.command_id = commands.command_id
|
|
156
|
+
)
|
|
157
|
+
WHERE commands.command_id IN (SELECT command_id FROM backfilled_command);
|
|
158
|
+
|
|
159
|
+
DROP TABLE backfilled_command;
|
|
160
|
+
DROP TABLE legacy_command_result_tree;
|
|
161
|
+
|
|
162
|
+
CREATE TRIGGER commands_are_append_only_update
|
|
163
|
+
BEFORE UPDATE ON commands
|
|
164
|
+
BEGIN
|
|
165
|
+
SELECT RAISE(ABORT, 'commands are append-only');
|
|
166
|
+
END;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
DROP TRIGGER evidence_artifacts_are_append_only_update;
|
|
2
|
+
DROP TRIGGER evidence_artifacts_are_append_only_delete;
|
|
3
|
+
DROP INDEX evidence_artifacts_run_created_idx;
|
|
4
|
+
|
|
5
|
+
ALTER TABLE evidence_artifacts RENAME TO evidence_artifacts_v13;
|
|
6
|
+
|
|
7
|
+
CREATE TABLE evidence_artifacts (
|
|
8
|
+
id TEXT PRIMARY KEY,
|
|
9
|
+
schema_version INTEGER NOT NULL CHECK (schema_version = 1),
|
|
10
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE RESTRICT,
|
|
11
|
+
work_item_id TEXT NOT NULL REFERENCES work_items(id) ON DELETE RESTRICT,
|
|
12
|
+
pipeline_run_id TEXT NOT NULL REFERENCES pipeline_runs(id) ON DELETE RESTRICT,
|
|
13
|
+
stage_attempt_id TEXT NOT NULL REFERENCES stage_attempts(id) ON DELETE RESTRICT,
|
|
14
|
+
stage TEXT NOT NULL CHECK (stage IN ('REVIEW', 'QA')),
|
|
15
|
+
kind TEXT NOT NULL CHECK (kind IN ('REVIEW_REPORT', 'QA_REPORT')),
|
|
16
|
+
status TEXT NOT NULL CHECK (status = 'PASSED'),
|
|
17
|
+
provider TEXT NOT NULL CHECK (provider IN ('MOCK', 'CODEX', 'CLAUDE_CODE')),
|
|
18
|
+
title TEXT NOT NULL CHECK (length(title) BETWEEN 1 AND 200),
|
|
19
|
+
summary TEXT NOT NULL CHECK (length(summary) BETWEEN 1 AND 4000),
|
|
20
|
+
checks_json TEXT NOT NULL CHECK (json_valid(checks_json)),
|
|
21
|
+
created_at TEXT NOT NULL,
|
|
22
|
+
UNIQUE (pipeline_run_id, kind)
|
|
23
|
+
) STRICT;
|
|
24
|
+
|
|
25
|
+
INSERT INTO evidence_artifacts (
|
|
26
|
+
id, schema_version, project_id, work_item_id, pipeline_run_id, stage_attempt_id,
|
|
27
|
+
stage, kind, status, provider, title, summary, checks_json, created_at
|
|
28
|
+
)
|
|
29
|
+
SELECT
|
|
30
|
+
id, schema_version, project_id, work_item_id, pipeline_run_id, stage_attempt_id,
|
|
31
|
+
stage, kind, status, provider, title, summary, checks_json, created_at
|
|
32
|
+
FROM evidence_artifacts_v13;
|
|
33
|
+
|
|
34
|
+
DROP TABLE evidence_artifacts_v13;
|
|
35
|
+
|
|
36
|
+
CREATE INDEX evidence_artifacts_run_created_idx
|
|
37
|
+
ON evidence_artifacts(pipeline_run_id, created_at, id);
|
|
38
|
+
|
|
39
|
+
CREATE TRIGGER evidence_artifacts_are_append_only_update
|
|
40
|
+
BEFORE UPDATE ON evidence_artifacts
|
|
41
|
+
BEGIN
|
|
42
|
+
SELECT RAISE(ABORT, 'evidence artifacts are append-only');
|
|
43
|
+
END;
|
|
44
|
+
|
|
45
|
+
CREATE TRIGGER evidence_artifacts_are_append_only_delete
|
|
46
|
+
BEFORE DELETE ON evidence_artifacts
|
|
47
|
+
BEGIN
|
|
48
|
+
SELECT RAISE(ABORT, 'evidence artifacts are append-only');
|
|
49
|
+
END;
|