Skip to content

Commit 55972ca

Browse files
cursoragentGsbreddy
andcommitted
Fix promotion audit sequencing
Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>
1 parent 295db21 commit 55972ca

3 files changed

Lines changed: 55 additions & 9 deletions

File tree

src/flightdeck/cli/main.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,17 @@
1616
from flightdeck.config import DEFAULT_CONFIG_FILENAME, load_config, write_default_config
1717
from flightdeck.doctor import run_doctor
1818
from flightdeck.ledger import diff_releases, parse_window
19-
from flightdeck.models import Policy, PolicyResult, PricingTable, PromotionRecord, ReleaseArtifact, ReleaseRecord, RunEvent
20-
from flightdeck.storage import Storage, utc_now
19+
from flightdeck.models import (
20+
Policy,
21+
PolicyResult,
22+
PricingTable,
23+
PromotionRecord,
24+
ReleaseArtifact,
25+
ReleaseRecord,
26+
RunEvent,
27+
utc_now,
28+
)
29+
from flightdeck.storage import Storage
2130

2231

2332
def read_release_artifact(path: Path) -> ReleaseArtifact:

src/flightdeck/storage.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,12 @@
55
import sqlite3
66
from contextlib import contextmanager
77
from dataclasses import dataclass
8-
from datetime import datetime, timezone
8+
from datetime import datetime
99
from pathlib import Path
1010
from typing import Any, Iterable
1111
from uuid import uuid4
1212

13-
from flightdeck.models import Policy, PolicyResult, PricingTable, PromotionRecord, ReleaseRecord, RunEvent
14-
15-
16-
def utc_now() -> datetime:
17-
return datetime.now(timezone.utc)
13+
from flightdeck.models import Policy, PolicyResult, PricingTable, PromotionRecord, ReleaseRecord, RunEvent, utc_now
1814

1915

2016
def ensure_parent_dir(db_path: str) -> None:
@@ -506,7 +502,7 @@ def _insert_release_action_conn(conn: sqlite3.Connection, record: PromotionRecor
506502
)
507503

508504
def insert_promotion_record(self, record: PromotionRecord) -> None:
509-
with self.connect() as conn:
505+
with self.transaction() as conn:
510506
self._insert_release_action_conn(conn, record)
511507

512508
def commit_promotion(self, record: PromotionRecord, *, new_promoted_release_id: str) -> None:

tests/test_doctor.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from click.testing import CliRunner
88

99
from flightdeck.cli.main import cli
10+
from flightdeck.models import PolicyResult, PromotionRecord
11+
from flightdeck.storage import Storage
1012

1113
from tests.test_spine import write_events, write_policy, write_pricing, write_release
1214

@@ -107,6 +109,45 @@ def test_doctor_fails_on_audit_seq_gap(tmp_path: Path, monkeypatch) -> None:
107109
assert "audit_seq" in res.output.lower()
108110

109111

112+
def test_insert_promotion_record_uses_immediate_transaction(tmp_path: Path) -> None:
113+
storage = Storage(str(tmp_path / "flightdeck.db"))
114+
storage.migrate()
115+
with storage.connect() as conn:
116+
conn.execute(
117+
"""
118+
INSERT INTO releases
119+
(release_id, agent_id, version, environment, checksum, artifact_json, created_at)
120+
VALUES (?, ?, ?, ?, ?, ?, ?)
121+
""",
122+
("rel_1", "agent_support", "1", "local", "sha256:abc", "{}", "2026-05-01T00:00:00+00:00"),
123+
)
124+
125+
record = PromotionRecord(
126+
action_id="act_1",
127+
action="promote",
128+
actor="tester",
129+
release_id="rel_1",
130+
agent_id="agent_support",
131+
environment="local",
132+
reason="test",
133+
policy_result=PolicyResult(passed=True),
134+
created_at=datetime.now(tz=timezone.utc),
135+
)
136+
137+
competing_conn = storage.connect()
138+
try:
139+
competing_conn.execute("BEGIN IMMEDIATE;")
140+
try:
141+
storage.insert_promotion_record(record)
142+
except sqlite3.OperationalError as exc:
143+
assert "database is locked" in str(exc)
144+
else:
145+
raise AssertionError("insert_promotion_record did not request an immediate write lock")
146+
finally:
147+
competing_conn.rollback()
148+
competing_conn.close()
149+
150+
110151
def test_doctor_fails_when_promoted_release_missing(tmp_path: Path, monkeypatch) -> None:
111152
monkeypatch.chdir(tmp_path)
112153
runner = CliRunner()

0 commit comments

Comments
 (0)