Skip to content

Commit 86e5d2c

Browse files
feat(w21-24kl): batch 2 — cutover script skeleton + bridge verification
- Implement cutover-tickets-migration.sh with 5 phase gates, timestamped logging, dry-run flag, and CUTOVER_PHASE_EXIT_OVERRIDE for testing (dso-710r) - Verify inbound bridge infrastructure: cron active, tickets branch exists, workflow advances past checkout (dso-mcq0 — AC2 blocked by ACLI_VERSION env var) - Refactor test PASS output to use assert_pass_if_clean helper (review fix) - Track ACLI_VERSION env var gap as dso-7nos Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f722a45 commit 86e5d2c

File tree

10 files changed

+305
-8
lines changed

10 files changed

+305
-8
lines changed

.tickets/.sync-state.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,8 @@
449449
"last_synced": "2026-03-19T18:38:35Z",
450450
"local_hash": "14c516947a151a3db8bdec4010e2fd6e"
451451
},
452-
"last_pull_timestamp": "2026-03-23T01:06:22Z",
453-
"last_sync_commit": "3358a0579112cd235da53bdb75ea669de33a8f69",
452+
"last_pull_timestamp": "2026-03-23T01:47:06Z",
453+
"last_sync_commit": "f722a45c2050f9e4ae6acbe65c5aeab50457eaf9",
454454
"w21-5cqr": {
455455
"jira_hash": "bce29d76f01c58613ee99cb1dd03920d",
456456
"jira_key": "DIG-61",

.tickets/dso-141j.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: dso-141j
3-
status: open
3+
status: closed
44
deps: []
55
links: []
66
created: 2026-03-22T22:51:10Z

.tickets/dso-710r.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: dso-710r
3-
status: open
3+
status: in_progress
44
deps: [dso-otk0]
55
links: []
66
created: 2026-03-23T00:22:57Z
@@ -53,3 +53,33 @@ TDD FIRST: implement only after T1 tests are confirmed RED.
5353
- [ ] CUTOVER_PHASE_EXIT_OVERRIDE env var causes named phase to exit with specified code
5454
Verify: CUTOVER_PHASE_EXIT_OVERRIDE="PRE_FLIGHT=1" CUTOVER_LOG_DIR=/tmp $(git rev-parse --show-toplevel)/plugins/dso/scripts/cutover-tickets-migration.sh 2>&1; test $? -ne 0
5555

56+
57+
## Notes
58+
59+
**2026-03-23T01:10:55Z**
60+
61+
CHECKPOINT 1/6: Task context loaded ✓
62+
63+
**2026-03-23T01:11:00Z**
64+
65+
CHECKPOINT 2/6: Code patterns understood ✓ — tests use phases: validate, snapshot, migrate, verify, finalize; assert_eq is silent on pass so test file needs assert_pass_if_clean for AC grep checks
66+
67+
**2026-03-23T01:12:24Z**
68+
69+
CHECKPOINT 3/6: Tests written (RED tests pre-exist, updated to add assert_pass_if_clean for AC grep compatibility) ✓
70+
71+
**2026-03-23T01:12:28Z**
72+
73+
CHECKPOINT 4/6: Implementation complete ✓ — created plugins/dso/scripts/cutover-tickets-migration.sh with phases: validate, snapshot, migrate, verify, finalize
74+
75+
**2026-03-23T01:28:10Z**
76+
77+
CHECKPOINT 5/6: Validation passed ✓ — all 8 cutover tests pass (PASSED: 8 FAILED: 0), all ACs verified
78+
79+
**2026-03-23T01:28:54Z**
80+
81+
CHECKPOINT 6/6: Done ✓ — all ACs pass, no discovered out-of-scope work, full test suite was passing before timeout
82+
83+
**2026-03-23T01:34:30Z**
84+
85+
CHECKPOINT 6/6: Done ✓ — Files: plugins/dso/scripts/cutover-tickets-migration.sh (created), tests/scripts/test-cutover-tickets-migration.sh (modified). Tests: 8 pass. AC: all pass.

.tickets/dso-7nos.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
id: dso-7nos
3+
status: open
4+
deps: []
5+
links: []
6+
created: 2026-03-23T01:33:00Z
7+
type: task
8+
priority: 2
9+
assignee: Joe Oakhart
10+
---
11+
# inbound-bridge workflow fails at ACLI_VERSION unset — complete env var config from dso-141j
12+

.tickets/dso-97xo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: dso-97xo
3-
status: open
3+
status: closed
44
deps: []
55
links: []
66
created: 2026-03-22T22:50:50Z

.tickets/dso-9aq2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: dso-9aq2
3-
status: open
3+
status: closed
44
deps: []
55
links: []
66
created: 2026-03-23T00:26:16Z

.tickets/dso-mcq0.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: dso-mcq0
3-
status: open
3+
status: in_progress
44
deps: [dso-97xo, dso-141j]
55
links: []
66
created: 2026-03-22T22:51:21Z
@@ -20,3 +20,40 @@ parent: w21-24kl
2020
<!-- sync: unsynced -->
2121

2222
Uncomment the cron schedule in inbound-bridge.yml after tickets branch exists and env vars are configured. Run a manual workflow_dispatch first to verify end-to-end. Then uncomment the cron. AC: inbound-bridge.yml has active cron schedule; scheduled run succeeds; no more recurring CI failures from missing tickets branch. Depends on dso-97xo (branch) and dso-141j (env vars).
23+
24+
NOTE: The cron was already re-enabled in dso-97xo and env vars configured in dso-141j. This story's remaining work is to verify end-to-end by triggering a manual workflow_dispatch run and confirming it succeeds. If the workflow has already been verified to work, this story can be closed.
25+
26+
## ACCEPTANCE CRITERIA
27+
28+
- [ ] `inbound-bridge.yml` has an active (uncommented) cron schedule
29+
Verify: grep -q '^\s*- cron:' .github/workflows/inbound-bridge.yml
30+
- [ ] Manual workflow_dispatch trigger succeeds (or recent successful run exists)
31+
Verify: gh run list --workflow="Inbound Bridge" --limit=1 --json status,conclusion --jq '.[0].conclusion' 2>/dev/null || echo "no runs yet"
32+
- [ ] `tickets` branch exists on remote
33+
Verify: git ls-remote --heads origin tickets | grep -q tickets
34+
35+
TDD Requirement: TDD exemption — Criterion #3 (verification/infrastructure only): this story verifies existing CI infrastructure configuration with no new code changes.
36+
37+
**2026-03-23T01:29:33Z**
38+
39+
CHECKPOINT 1/6: Task context loaded ✓
40+
41+
**2026-03-23T01:29:45Z**
42+
43+
CHECKPOINT 2/6: Code patterns understood ✓ — cron active at line 8 ('*/30 * * * *'), workflow_dispatch enabled, tickets branch confirmed on remote
44+
45+
**2026-03-23T01:31:57Z**
46+
47+
CHECKPOINT 3/6: Tests written (none required) ✓ — TDD exemption applies (verification/infrastructure only)
48+
49+
**2026-03-23T01:32:05Z**
50+
51+
CHECKPOINT 4/6: Implementation complete ✓ — Verification findings: (1) cron '*/30 * * * *' is active at line 8 of inbound-bridge.yml; (2) tickets branch exists on remote (SHA: 3358a05...); (3) triggered workflow_dispatch run 23417598754 — checkout succeeded but failed at ACLI_VERSION not set (env var from dso-141j). Previously all runs failed at git fetch (tickets branch was missing). Now that tickets branch exists, the workflow advances further. ACLI_VERSION env var must be configured to get a successful end-to-end run.
52+
53+
**2026-03-23T01:32:48Z**
54+
55+
CHECKPOINT 5/6: Validation passed ✓ — AC Results: AC1 PASS (cron active), AC2 FAIL (workflow_dispatch returns 'failure' — blocked by ACLI_VERSION env var not set, owned by dso-141j), AC3 PASS (tickets branch on remote). Note: workflow now advances past checkout step (tickets branch exists) but fails at ACLI validation. This is a dependency on dso-141j completing env var configuration.
56+
57+
**2026-03-23T01:33:06Z**
58+
59+
CHECKPOINT 6/6: Done ✓ — Story verification complete. Summary: (1) cron schedule active ✓, (2) tickets branch on remote ✓, (3) workflow_dispatch triggered — progresses past checkout but fails at ACLI_VERSION env var (dependency on dso-141j). Created dso-7nos to track ACLI_VERSION env var completion.

.tickets/dso-otk0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: dso-otk0
3-
status: open
3+
status: closed
44
deps: []
55
links: []
66
created: 2026-03-23T00:22:33Z
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#!/usr/bin/env bash
2+
# plugins/dso/scripts/cutover-tickets-migration.sh
3+
# Phase-gate skeleton for the tickets migration cutover.
4+
#
5+
# Phases (in order): validate, snapshot, migrate, verify, finalize
6+
# Constant names: PRE_FLIGHT, SNAPSHOT, MIGRATE, VERIFY, FINALIZE
7+
#
8+
# Usage: cutover-tickets-migration.sh [--dry-run] [--repo-root=PATH] [--help]
9+
#
10+
# Environment variables:
11+
# CUTOVER_LOG_DIR Directory for timestamped log file (default: /tmp)
12+
# CUTOVER_STATE_FILE Path for the run state file (default: /tmp/cutover-tickets-migration-state.json)
13+
# CUTOVER_PHASE_EXIT_OVERRIDE "PHASE_NAME=EXIT_CODE" — inject a failure for testing
14+
#
15+
# Exit codes: 0=success, 1=error
16+
17+
set -euo pipefail
18+
19+
# ---------------------------------------------------------------------------
20+
# Phase constants (ordered)
21+
# ---------------------------------------------------------------------------
22+
readonly PHASES=( validate snapshot migrate verify finalize )
23+
# Canonical constant aliases (for --help display)
24+
# PRE_FLIGHT=validate SNAPSHOT=snapshot MIGRATE=migrate VERIFY=verify FINALIZE=finalize
25+
26+
# ---------------------------------------------------------------------------
27+
# Argument parsing
28+
# ---------------------------------------------------------------------------
29+
_DRY_RUN="false"
30+
_REPO_ROOT=""
31+
32+
for _arg in "$@"; do
33+
case "$_arg" in
34+
--help)
35+
cat <<'USAGE'
36+
Usage: cutover-tickets-migration.sh [--dry-run] [--repo-root=PATH] [--help]
37+
38+
--dry-run Execute phase stubs but skip state-file writes and
39+
any git-modifying actions. Prefixes output with [DRY RUN].
40+
--repo-root=PATH Override the git repo root (default: git rev-parse --show-toplevel).
41+
--help Print this usage message and exit.
42+
43+
Phases (run in order):
44+
1. validate (alias: PRE_FLIGHT) — pre-flight checks
45+
2. snapshot (alias: SNAPSHOT) — snapshot current ticket state
46+
3. migrate (alias: MIGRATE) — migrate ticket format
47+
4. verify (alias: VERIFY) — verify migration results
48+
5. finalize (alias: FINALIZE / REFERENCE_UPDATE / CLEANUP) — update references and clean up
49+
50+
Environment variables:
51+
CUTOVER_LOG_DIR Log directory (default: /tmp)
52+
CUTOVER_STATE_FILE State file path (default: /tmp/cutover-tickets-migration-state.json)
53+
CUTOVER_PHASE_EXIT_OVERRIDE Inject a phase failure, e.g. "MIGRATE=1" (for testing only)
54+
55+
USAGE
56+
exit 0
57+
;;
58+
--dry-run)
59+
_DRY_RUN="true"
60+
;;
61+
--repo-root=*)
62+
_REPO_ROOT="${_arg#--repo-root=}"
63+
;;
64+
*)
65+
echo "ERROR: Unknown argument: $_arg" >&2
66+
exit 1
67+
;;
68+
esac
69+
done
70+
71+
# ---------------------------------------------------------------------------
72+
# Resolve REPO_ROOT
73+
# ---------------------------------------------------------------------------
74+
if [[ -z "$_REPO_ROOT" ]]; then
75+
if ! _REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null); then
76+
echo "ERROR: Not a git repository and --repo-root not supplied." >&2
77+
exit 1
78+
fi
79+
fi
80+
81+
# ---------------------------------------------------------------------------
82+
# Log file setup
83+
# ---------------------------------------------------------------------------
84+
: "${CUTOVER_LOG_DIR:=/tmp}"
85+
_LOG_TIMESTAMP=$(date +%Y-%m-%dT%H-%M-%S)
86+
_LOG_FILE="${CUTOVER_LOG_DIR}/cutover-${_LOG_TIMESTAMP}.log"
87+
88+
# Re-exec the script under tee to capture all output to the log file while
89+
# also printing to stdout. PIPESTATUS[0] preserves the real exit code.
90+
# Guard with _CUTOVER_LOGGING to prevent infinite re-exec.
91+
if [[ -z "${_CUTOVER_LOGGING:-}" ]]; then
92+
export _CUTOVER_LOGGING=1
93+
mkdir -p "$CUTOVER_LOG_DIR"
94+
bash "$0" "$@" 2>&1 | tee -a "$_LOG_FILE"
95+
exit "${PIPESTATUS[0]}"
96+
fi
97+
98+
# ---------------------------------------------------------------------------
99+
# State file
100+
# ---------------------------------------------------------------------------
101+
: "${CUTOVER_STATE_FILE:=/tmp/cutover-tickets-migration-state.json}"
102+
103+
_state_append_phase() {
104+
local phase="$1"
105+
if [[ "$_DRY_RUN" == "true" ]]; then
106+
return 0
107+
fi
108+
# Append completed phase to state file
109+
if [[ ! -f "$CUTOVER_STATE_FILE" ]]; then
110+
printf '{"completed_phases":["%s"]}\n' "$phase" > "$CUTOVER_STATE_FILE"
111+
else
112+
# Use python3 for reliable JSON update (stdlib, no new deps)
113+
python3 - "$CUTOVER_STATE_FILE" "$phase" <<'PYEOF'
114+
import sys, json
115+
path, phase = sys.argv[1], sys.argv[2]
116+
with open(path) as fh:
117+
data = json.load(fh)
118+
data.setdefault("completed_phases", []).append(phase)
119+
with open(path, "w") as fh:
120+
json.dump(data, fh)
121+
fh.write("\n")
122+
PYEOF
123+
fi
124+
}
125+
126+
# ---------------------------------------------------------------------------
127+
# Test injection hook: CUTOVER_PHASE_EXIT_OVERRIDE
128+
# Format: "PHASE_NAME=EXIT_CODE", e.g., "MIGRATE=1" or "PRE_FLIGHT=1"
129+
# ---------------------------------------------------------------------------
130+
_check_override() {
131+
local phase_lower="$1"
132+
local phase_upper
133+
phase_upper=$(echo "$phase_lower" | tr '[:lower:]' '[:upper:]')
134+
if [[ -n "${CUTOVER_PHASE_EXIT_OVERRIDE:-}" ]]; then
135+
local override_phase override_code resolved_upper
136+
override_phase="${CUTOVER_PHASE_EXIT_OVERRIDE%%=*}"
137+
override_code="${CUTOVER_PHASE_EXIT_OVERRIDE##*=}"
138+
# Resolve constant aliases to their canonical uppercase phase name
139+
case "$override_phase" in
140+
PRE_FLIGHT) resolved_upper="VALIDATE" ;;
141+
SNAPSHOT) resolved_upper="SNAPSHOT" ;;
142+
MIGRATE) resolved_upper="MIGRATE" ;;
143+
VERIFY) resolved_upper="VERIFY" ;;
144+
FINALIZE|REFERENCE_UPDATE|CLEANUP) resolved_upper="FINALIZE" ;;
145+
*) resolved_upper="$override_phase" ;;
146+
esac
147+
if [[ "$resolved_upper" == "$phase_upper" ]]; then
148+
return "$override_code"
149+
fi
150+
fi
151+
return 0
152+
}
153+
154+
# ---------------------------------------------------------------------------
155+
# Phase handler stubs
156+
# (Actual migration logic added by sibling stories w21-7mlx, w21-wbqz, w21-25mq)
157+
# ---------------------------------------------------------------------------
158+
159+
_phase_validate() {
160+
echo "Running phase: validate"
161+
_check_override "validate"
162+
}
163+
164+
_phase_snapshot() {
165+
echo "Running phase: snapshot"
166+
_check_override "snapshot"
167+
}
168+
169+
_phase_migrate() {
170+
echo "Running phase: migrate"
171+
_check_override "migrate"
172+
}
173+
174+
_phase_verify() {
175+
echo "Running phase: verify"
176+
_check_override "verify"
177+
}
178+
179+
_phase_finalize() {
180+
echo "Running phase: finalize"
181+
_check_override "finalize"
182+
}
183+
184+
# ---------------------------------------------------------------------------
185+
# Dry-run wrapper: prefix every line of a phase's output with [DRY RUN]
186+
# ---------------------------------------------------------------------------
187+
_run_phase_dry() {
188+
local phase="$1"
189+
local phase_fn="_phase_${phase}"
190+
# Run in subshell, capture output, prefix each line
191+
local _out
192+
_out=$("$phase_fn" 2>&1) || return $?
193+
while IFS= read -r _line; do
194+
echo "[DRY RUN] $_line"
195+
done <<< "$_out"
196+
}
197+
198+
# ---------------------------------------------------------------------------
199+
# Phase gate loop
200+
# ---------------------------------------------------------------------------
201+
echo "cutover-tickets-migration: starting (dry_run=${_DRY_RUN})"
202+
203+
for _phase in "${PHASES[@]}"; do
204+
if [[ "$_DRY_RUN" == "true" ]]; then
205+
"_run_phase_dry" "$_phase" || { _rc=$?; echo "[DRY RUN] ERROR: phase ${_phase} failed (exit ${_rc}) — see ${_LOG_FILE}" >&2; exit "$_rc"; }
206+
else
207+
"_phase_${_phase}" || { _rc=$?; echo "ERROR: phase ${_phase} failed — see ${_LOG_FILE}" >&2; exit "$_rc"; }
208+
_state_append_phase "$_phase"
209+
fi
210+
done
211+
212+
echo "cutover-tickets-migration: all phases complete"

0 commit comments

Comments
 (0)