feat: first-class control-plane lease lifecycle (Closes #601)
Make active leases queryable workflow state with canonical adopt, release, expire/reclaim, and abandon operations on the #613 control-plane DB substrate. - lease_lifecycle.py: policy, proof gates, safe_next_action vocabulary - control_plane_db: schema v3 provenance columns + list/inspect/adopt/abandon - MCP tools: gitea_*_workflow_lease(s) for list/inspect/adopt/release/expire/abandon/reclaim - issue_lock_store: sanctioned reclaim of expired author locks (dead pid / missing wt) - Tests cover lifecycle, foreign steal refusal, allocator compatibility, merger handoff
This commit is contained in:
+598
-25
@@ -11,9 +11,9 @@ Implements the durable coordination store described by
|
||||
shared Postgres or single allocator daemon can replace the connection
|
||||
layer later without changing call sites.
|
||||
|
||||
This module is the **substrate** for #600 (allocator policy/tool) and #612
|
||||
(incident bridge). It does **not** implement ``gitea_allocate_next_work``
|
||||
routing policy or provider adapters.
|
||||
This module is the **substrate** for #600 (allocator policy/tool), #601
|
||||
(first-class lease lifecycle), and #612 (incident bridge). It does **not**
|
||||
implement ``gitea_allocate_next_work`` routing policy or provider adapters.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -29,7 +29,7 @@ from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Iterator, Sequence
|
||||
|
||||
SCHEMA_VERSION = 2
|
||||
SCHEMA_VERSION = 3
|
||||
|
||||
# Assignable work kinds only — raw monitoring incidents are never work items.
|
||||
WORK_KINDS = frozenset({"issue", "pr"})
|
||||
@@ -301,6 +301,7 @@ class ControlPlaneDB:
|
||||
try:
|
||||
conn.executescript(_SCHEMA_SQL)
|
||||
self._migrate_incident_links_null_scope(conn)
|
||||
self._migrate_lease_lifecycle_columns(conn)
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO schema_meta(key, value) VALUES (?, ?)",
|
||||
("schema_version", str(SCHEMA_VERSION)),
|
||||
@@ -604,6 +605,8 @@ class ControlPlaneDB:
|
||||
lease_ttl_seconds: int = DEFAULT_LEASE_TTL_SECONDS,
|
||||
phase: str = "claimed",
|
||||
now: datetime | None = None,
|
||||
worktree_path: str | None = None,
|
||||
owner_pid: int | None = None,
|
||||
) -> AssignmentResult:
|
||||
"""Atomically create assignment + lease for one Gitea work item.
|
||||
|
||||
@@ -746,6 +749,32 @@ class ControlPlaneDB:
|
||||
""",
|
||||
(lease_id, work_item_id, session_id, role, phase, expires, now_s),
|
||||
)
|
||||
# #601 optional lifecycle columns (present after schema v3 migration)
|
||||
try:
|
||||
lcols = {
|
||||
r[1]
|
||||
for r in conn.execute("PRAGMA table_info(leases)").fetchall()
|
||||
}
|
||||
if "worktree_path" in lcols and worktree_path:
|
||||
conn.execute(
|
||||
"UPDATE leases SET worktree_path = ? WHERE lease_id = ?",
|
||||
(worktree_path, lease_id),
|
||||
)
|
||||
if "owner_pid" in lcols:
|
||||
conn.execute(
|
||||
"UPDATE leases SET owner_pid = ? WHERE lease_id = ?",
|
||||
(
|
||||
owner_pid if owner_pid is not None else os.getpid(),
|
||||
lease_id,
|
||||
),
|
||||
)
|
||||
if "expected_head_sha" in lcols and expected_head_sha:
|
||||
conn.execute(
|
||||
"UPDATE leases SET expected_head_sha = ? WHERE lease_id = ?",
|
||||
(expected_head_sha, lease_id),
|
||||
)
|
||||
except sqlite3.Error:
|
||||
pass
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO assignments(
|
||||
@@ -876,32 +905,15 @@ class ControlPlaneDB:
|
||||
}
|
||||
|
||||
def release_lease(self, lease_id: str, *, session_id: str) -> None:
|
||||
with self._tx() as conn:
|
||||
"""Release a lease; unknown ids are no-ops for backward compatibility."""
|
||||
with self._tx(immediate=False) as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM leases WHERE lease_id = ?",
|
||||
"SELECT lease_id FROM leases WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return
|
||||
if row["session_id"] != session_id:
|
||||
raise ForeignLeaseError(
|
||||
f"cannot release lease {lease_id} owned by {row['session_id']}"
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE leases SET status = 'released' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE assignments SET status = 'released' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO events(work_item_id, event_type, message, created_at)
|
||||
VALUES (?, 'lease_released', ?, ?)
|
||||
""",
|
||||
(row["work_item_id"], f"session {session_id} released {lease_id}", _ts()),
|
||||
)
|
||||
self.release_lease_recorded(lease_id, session_id=session_id)
|
||||
|
||||
def require_valid_assignment(
|
||||
self,
|
||||
@@ -1177,3 +1189,564 @@ class ControlPlaneDB:
|
||||
(provider, base_url, p_org, p_project, str(provider_issue_id)),
|
||||
).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
# ── lease lifecycle (#601) ────────────────────────────────────────────
|
||||
|
||||
_LEASE_LIFECYCLE_COLUMNS: tuple[tuple[str, str], ...] = (
|
||||
("worktree_path", "TEXT"),
|
||||
("owner_pid", "INTEGER"),
|
||||
("expected_head_sha", "TEXT"),
|
||||
("adopted_from_session_id", "TEXT"),
|
||||
("adopted_by_session_id", "TEXT"),
|
||||
("provenance_json", "TEXT"),
|
||||
("abandon_proof_json", "TEXT"),
|
||||
)
|
||||
|
||||
def _migrate_lease_lifecycle_columns(self, conn: sqlite3.Connection) -> None:
|
||||
"""Add provenance/worktree columns to leases for first-class lifecycle (#601)."""
|
||||
cols = {
|
||||
row[1]
|
||||
for row in conn.execute("PRAGMA table_info(leases)").fetchall()
|
||||
}
|
||||
if not cols:
|
||||
return
|
||||
for name, decl in self._LEASE_LIFECYCLE_COLUMNS:
|
||||
if name not in cols:
|
||||
conn.execute(f"ALTER TABLE leases ADD COLUMN {name} {decl}")
|
||||
|
||||
def _lease_columns(self, conn: sqlite3.Connection) -> set[str]:
|
||||
return {
|
||||
row[1]
|
||||
for row in conn.execute("PRAGMA table_info(leases)").fetchall()
|
||||
}
|
||||
|
||||
def list_leases(
|
||||
self,
|
||||
*,
|
||||
remote: str | None = None,
|
||||
org: str | None = None,
|
||||
repo: str | None = None,
|
||||
role: str | None = None,
|
||||
statuses: Sequence[str] | None = None,
|
||||
limit: int = 100,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""List leases joined with work_items as first-class workflow state."""
|
||||
clauses: list[str] = []
|
||||
params: list[Any] = []
|
||||
if remote:
|
||||
clauses.append("w.remote = ?")
|
||||
params.append(remote)
|
||||
if org:
|
||||
clauses.append("w.org = ?")
|
||||
params.append(org)
|
||||
if repo:
|
||||
clauses.append("w.repo = ?")
|
||||
params.append(repo)
|
||||
if role:
|
||||
clauses.append("l.role = ?")
|
||||
params.append(role)
|
||||
if statuses:
|
||||
placeholders = ", ".join("?" for _ in statuses)
|
||||
clauses.append(f"l.status IN ({placeholders})")
|
||||
params.extend(statuses)
|
||||
where = ("WHERE " + " AND ".join(clauses)) if clauses else ""
|
||||
sql = f"""
|
||||
SELECT l.*, w.remote, w.org, w.repo, w.kind AS work_kind,
|
||||
w.number AS work_number, w.state AS work_state,
|
||||
w.current_head_sha AS work_head_sha,
|
||||
s.pid AS session_pid, s.profile AS session_profile,
|
||||
s.status AS session_status
|
||||
FROM leases l
|
||||
JOIN work_items w ON w.work_item_id = l.work_item_id
|
||||
LEFT JOIN sessions s ON s.session_id = l.session_id
|
||||
{where}
|
||||
ORDER BY l.expires_at DESC
|
||||
LIMIT ?
|
||||
"""
|
||||
params.append(max(1, int(limit)))
|
||||
with self._tx(immediate=False) as conn:
|
||||
rows = conn.execute(sql, params).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def get_lease_workflow_state(self, lease_id: str) -> dict[str, Any] | None:
|
||||
"""Return lease + assignment + work_item + session for one lease id."""
|
||||
with self._tx(immediate=False) as conn:
|
||||
lease = conn.execute(
|
||||
"SELECT * FROM leases WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
).fetchone()
|
||||
if not lease:
|
||||
return None
|
||||
work = conn.execute(
|
||||
"SELECT * FROM work_items WHERE work_item_id = ?",
|
||||
(lease["work_item_id"],),
|
||||
).fetchone()
|
||||
asn = conn.execute(
|
||||
"""
|
||||
SELECT * FROM assignments
|
||||
WHERE lease_id = ?
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
""",
|
||||
(lease_id,),
|
||||
).fetchone()
|
||||
sess = conn.execute(
|
||||
"SELECT * FROM sessions WHERE session_id = ?",
|
||||
(lease["session_id"],),
|
||||
).fetchone()
|
||||
provenance = None
|
||||
if "provenance_json" in lease.keys() and lease["provenance_json"]:
|
||||
try:
|
||||
provenance = json.loads(lease["provenance_json"])
|
||||
except (TypeError, json.JSONDecodeError):
|
||||
provenance = {"raw": lease["provenance_json"]}
|
||||
return {
|
||||
"lease": dict(lease),
|
||||
"work_item": dict(work) if work else None,
|
||||
"assignment": dict(asn) if asn else None,
|
||||
"session": dict(sess) if sess else None,
|
||||
"provenance": provenance,
|
||||
}
|
||||
|
||||
def attach_lease_provenance(
|
||||
self, lease_id: str, provenance: dict[str, Any]
|
||||
) -> None:
|
||||
payload = json.dumps(provenance)
|
||||
with self._tx() as conn:
|
||||
cols = self._lease_columns(conn)
|
||||
if "provenance_json" not in cols:
|
||||
return
|
||||
conn.execute(
|
||||
"UPDATE leases SET provenance_json = ? WHERE lease_id = ?",
|
||||
(payload, lease_id),
|
||||
)
|
||||
if provenance.get("adopted_from_session_id") and "adopted_from_session_id" in cols:
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE leases
|
||||
SET adopted_from_session_id = ?, adopted_by_session_id = ?
|
||||
WHERE lease_id = ?
|
||||
""",
|
||||
(
|
||||
provenance.get("adopted_from_session_id"),
|
||||
provenance.get("adopted_by_session_id"),
|
||||
lease_id,
|
||||
),
|
||||
)
|
||||
if provenance.get("worktree_path") and "worktree_path" in cols:
|
||||
conn.execute(
|
||||
"UPDATE leases SET worktree_path = ? WHERE lease_id = ?",
|
||||
(provenance.get("worktree_path"), lease_id),
|
||||
)
|
||||
if provenance.get("expected_head_sha") and "expected_head_sha" in cols:
|
||||
conn.execute(
|
||||
"UPDATE leases SET expected_head_sha = ? WHERE lease_id = ?",
|
||||
(provenance.get("expected_head_sha"), lease_id),
|
||||
)
|
||||
|
||||
def release_lease_recorded(
|
||||
self, lease_id: str, *, session_id: str
|
||||
) -> dict[str, Any]:
|
||||
"""Explicit release with audit event + provenance proof (#601)."""
|
||||
now_s = _ts()
|
||||
with self._tx() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM leases WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
).fetchone()
|
||||
if not row:
|
||||
raise ControlPlaneError(f"unknown lease_id {lease_id}")
|
||||
if row["session_id"] != session_id:
|
||||
raise ForeignLeaseError(
|
||||
f"cannot release lease {lease_id} owned by {row['session_id']}"
|
||||
)
|
||||
if row["status"] not in ("active", "expired"):
|
||||
# idempotent-ish for already released
|
||||
if row["status"] == "released":
|
||||
return {
|
||||
"lease_id": lease_id,
|
||||
"status": "released",
|
||||
"session_id": session_id,
|
||||
"released_at": now_s,
|
||||
"idempotent": True,
|
||||
}
|
||||
conn.execute(
|
||||
"UPDATE leases SET status = 'released' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE assignments SET status = 'released' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
proof = {
|
||||
"lease_id": lease_id,
|
||||
"status": "released",
|
||||
"session_id": session_id,
|
||||
"released_at": now_s,
|
||||
"prior_status": row["status"],
|
||||
"work_item_id": row["work_item_id"],
|
||||
}
|
||||
cols = self._lease_columns(conn)
|
||||
if "provenance_json" in cols:
|
||||
prior = {}
|
||||
if row["provenance_json"]:
|
||||
try:
|
||||
prior = json.loads(row["provenance_json"])
|
||||
except (TypeError, json.JSONDecodeError):
|
||||
prior = {}
|
||||
prior["last_release"] = proof
|
||||
conn.execute(
|
||||
"UPDATE leases SET provenance_json = ? WHERE lease_id = ?",
|
||||
(json.dumps(prior), lease_id),
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO events(work_item_id, event_type, message, created_at)
|
||||
VALUES (?, 'lease_released', ?, ?)
|
||||
""",
|
||||
(
|
||||
row["work_item_id"],
|
||||
f"session {session_id} released {lease_id}",
|
||||
now_s,
|
||||
),
|
||||
)
|
||||
return proof
|
||||
|
||||
def force_expire_lease(self, lease_id: str, *, reason: str = "") -> None:
|
||||
now_s = _ts()
|
||||
with self._tx() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM leases WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
).fetchone()
|
||||
if not row:
|
||||
raise ControlPlaneError(f"unknown lease_id {lease_id}")
|
||||
conn.execute(
|
||||
"UPDATE leases SET status = 'expired' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE assignments SET status = 'expired' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO events(work_item_id, event_type, message, created_at)
|
||||
VALUES (?, 'lease_expired', ?, ?)
|
||||
""",
|
||||
(
|
||||
row["work_item_id"],
|
||||
f"lease {lease_id} force-expired: {reason or 'unspecified'}",
|
||||
now_s,
|
||||
),
|
||||
)
|
||||
|
||||
def abandon_lease(
|
||||
self,
|
||||
*,
|
||||
lease_id: str,
|
||||
requester_session_id: str,
|
||||
proof: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
now_s = _ts()
|
||||
with self._tx() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM leases WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
).fetchone()
|
||||
if not row:
|
||||
raise ControlPlaneError(f"unknown lease_id {lease_id}")
|
||||
conn.execute(
|
||||
"UPDATE leases SET status = 'abandoned' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE assignments SET status = 'abandoned' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
cols = self._lease_columns(conn)
|
||||
if "abandon_proof_json" in cols:
|
||||
conn.execute(
|
||||
"UPDATE leases SET abandon_proof_json = ? WHERE lease_id = ?",
|
||||
(json.dumps(proof), lease_id),
|
||||
)
|
||||
msg = (
|
||||
f"session {requester_session_id} abandoned lease {lease_id} "
|
||||
f"(prior owner {row['session_id']})"
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO events(work_item_id, event_type, message, created_at)
|
||||
VALUES (?, 'lease_abandoned', ?, ?)
|
||||
""",
|
||||
(row["work_item_id"], msg, now_s),
|
||||
)
|
||||
return {
|
||||
"lease_id": lease_id,
|
||||
"status": "abandoned",
|
||||
"prior_owner_session_id": row["session_id"],
|
||||
"requester_session_id": requester_session_id,
|
||||
"abandoned_at": now_s,
|
||||
"proof": proof,
|
||||
}
|
||||
|
||||
def adopt_lease(
|
||||
self,
|
||||
*,
|
||||
lease_id: str,
|
||||
adopter_session_id: str,
|
||||
role: str,
|
||||
worktree_path: str | None = None,
|
||||
expected_head_sha: str | None = None,
|
||||
owner_pid: int | None = None,
|
||||
provenance: dict[str, Any] | None = None,
|
||||
lease_ttl_seconds: int = DEFAULT_LEASE_TTL_SECONDS,
|
||||
) -> dict[str, Any]:
|
||||
"""Transfer or refresh a lease with provenance (#601).
|
||||
|
||||
* Same owner + active → refresh (owner-resume).
|
||||
* Expired/abandoned/released → create new assignment+lease with provenance.
|
||||
* Active foreign → raise ForeignLeaseError (never silent steal).
|
||||
"""
|
||||
now = _utc_now()
|
||||
now_s = _ts(now)
|
||||
expires = _ts(now + timedelta(seconds=int(lease_ttl_seconds)))
|
||||
provenance = provenance or {}
|
||||
|
||||
with self._tx(immediate=True) as conn:
|
||||
lease = conn.execute(
|
||||
"SELECT * FROM leases WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
).fetchone()
|
||||
if not lease:
|
||||
raise ControlPlaneError(f"unknown lease_id {lease_id}")
|
||||
work = conn.execute(
|
||||
"SELECT * FROM work_items WHERE work_item_id = ?",
|
||||
(lease["work_item_id"],),
|
||||
).fetchone()
|
||||
if not work:
|
||||
raise ControlPlaneError("lease has no work_item")
|
||||
|
||||
# Expire by time if needed
|
||||
exp = _parse_ts(lease["expires_at"])
|
||||
status = lease["status"]
|
||||
if status == "active" and exp and exp <= now:
|
||||
conn.execute(
|
||||
"UPDATE leases SET status = 'expired' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE assignments SET status = 'expired' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
status = "expired"
|
||||
|
||||
owner = lease["session_id"]
|
||||
if status == "active" and owner != adopter_session_id:
|
||||
raise ForeignLeaseError(
|
||||
f"cannot adopt active foreign lease {lease_id} owned by {owner}"
|
||||
)
|
||||
|
||||
# Ensure adopter session exists
|
||||
sess = conn.execute(
|
||||
"SELECT session_id FROM sessions WHERE session_id = ?",
|
||||
(adopter_session_id,),
|
||||
).fetchone()
|
||||
if not sess:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO sessions(
|
||||
session_id, role, profile, namespace, pid,
|
||||
started_at, last_heartbeat_at, status
|
||||
) VALUES (?, ?, NULL, NULL, ?, ?, ?, 'active')
|
||||
""",
|
||||
(adopter_session_id, role, owner_pid or os.getpid(), now_s, now_s),
|
||||
)
|
||||
|
||||
cols = self._lease_columns(conn)
|
||||
|
||||
if status == "active" and owner == adopter_session_id:
|
||||
# Owner-resume refresh
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE leases
|
||||
SET heartbeat_at = ?, expires_at = ?, phase = ?
|
||||
WHERE lease_id = ?
|
||||
""",
|
||||
(now_s, expires, "adopted", lease_id),
|
||||
)
|
||||
if "worktree_path" in cols and worktree_path:
|
||||
conn.execute(
|
||||
"UPDATE leases SET worktree_path = ? WHERE lease_id = ?",
|
||||
(worktree_path, lease_id),
|
||||
)
|
||||
if "owner_pid" in cols and owner_pid is not None:
|
||||
conn.execute(
|
||||
"UPDATE leases SET owner_pid = ? WHERE lease_id = ?",
|
||||
(owner_pid, lease_id),
|
||||
)
|
||||
if "provenance_json" in cols:
|
||||
prior = {}
|
||||
if lease["provenance_json"]:
|
||||
try:
|
||||
prior = json.loads(lease["provenance_json"])
|
||||
except (TypeError, json.JSONDecodeError):
|
||||
prior = {}
|
||||
prior["last_adopt"] = provenance
|
||||
conn.execute(
|
||||
"UPDATE leases SET provenance_json = ? WHERE lease_id = ?",
|
||||
(json.dumps(prior), lease_id),
|
||||
)
|
||||
asn = conn.execute(
|
||||
"""
|
||||
SELECT * FROM assignments
|
||||
WHERE lease_id = ? AND status = 'active'
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
""",
|
||||
(lease_id,),
|
||||
).fetchone()
|
||||
lease2 = conn.execute(
|
||||
"SELECT * FROM leases WHERE lease_id = ?", (lease_id,)
|
||||
).fetchone()
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO events(work_item_id, event_type, message, created_at)
|
||||
VALUES (?, 'lease_adopted', ?, ?)
|
||||
""",
|
||||
(
|
||||
lease["work_item_id"],
|
||||
f"owner-resume adopt lease {lease_id} by {adopter_session_id}",
|
||||
now_s,
|
||||
),
|
||||
)
|
||||
return {
|
||||
"outcome": "adopted_owner_resume",
|
||||
"lease": dict(lease2) if lease2 else dict(lease),
|
||||
"assignment": dict(asn) if asn else None,
|
||||
"reasons": ["owner-resume: refreshed lease with provenance"],
|
||||
}
|
||||
|
||||
# Non-active: create new lease + assignment (transfer)
|
||||
new_lease_id = f"lease-{uuid.uuid4().hex[:16]}"
|
||||
new_asn_id = f"asn-{uuid.uuid4().hex[:16]}"
|
||||
# Mark prior non-active if still active somehow
|
||||
if status == "active":
|
||||
conn.execute(
|
||||
"UPDATE leases SET status = 'released' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE assignments SET status = 'released' WHERE lease_id = ?",
|
||||
(lease_id,),
|
||||
)
|
||||
|
||||
# Prefer prior assignment allowed/forbidden
|
||||
prior_asn = conn.execute(
|
||||
"""
|
||||
SELECT * FROM assignments WHERE lease_id = ?
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
""",
|
||||
(lease_id,),
|
||||
).fetchone()
|
||||
allowed = (
|
||||
prior_asn["allowed_actions"]
|
||||
if prior_asn
|
||||
else json.dumps(["implement", "comment", "push", "create_pr"])
|
||||
)
|
||||
forbidden = (
|
||||
prior_asn["forbidden_actions"]
|
||||
if prior_asn
|
||||
else json.dumps(
|
||||
["approve", "merge", "request_changes", "self_select_without_assignment"]
|
||||
)
|
||||
)
|
||||
head = expected_head_sha or (
|
||||
prior_asn["expected_head_sha"] if prior_asn else work["current_head_sha"]
|
||||
)
|
||||
|
||||
# Dynamic insert for optional columns
|
||||
base_cols = [
|
||||
"lease_id",
|
||||
"work_item_id",
|
||||
"session_id",
|
||||
"role",
|
||||
"phase",
|
||||
"expires_at",
|
||||
"heartbeat_at",
|
||||
"status",
|
||||
]
|
||||
base_vals: list[Any] = [
|
||||
new_lease_id,
|
||||
lease["work_item_id"],
|
||||
adopter_session_id,
|
||||
role,
|
||||
"adopted",
|
||||
expires,
|
||||
now_s,
|
||||
"active",
|
||||
]
|
||||
optional = {
|
||||
"worktree_path": worktree_path,
|
||||
"owner_pid": owner_pid,
|
||||
"expected_head_sha": head,
|
||||
"adopted_from_session_id": owner,
|
||||
"adopted_by_session_id": adopter_session_id,
|
||||
"provenance_json": json.dumps(provenance),
|
||||
}
|
||||
for col, val in optional.items():
|
||||
if col in cols and val is not None:
|
||||
base_cols.append(col)
|
||||
base_vals.append(val)
|
||||
placeholders = ", ".join("?" for _ in base_cols)
|
||||
conn.execute(
|
||||
f"INSERT INTO leases({', '.join(base_cols)}) VALUES ({placeholders})",
|
||||
base_vals,
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO assignments(
|
||||
assignment_id, work_item_id, session_id, lease_id,
|
||||
allowed_actions, forbidden_actions, expected_head_sha,
|
||||
role, status, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active', ?)
|
||||
""",
|
||||
(
|
||||
new_asn_id,
|
||||
lease["work_item_id"],
|
||||
adopter_session_id,
|
||||
new_lease_id,
|
||||
allowed if isinstance(allowed, str) else json.dumps(list(allowed)),
|
||||
forbidden if isinstance(forbidden, str) else json.dumps(list(forbidden)),
|
||||
head,
|
||||
role,
|
||||
now_s,
|
||||
),
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO events(work_item_id, event_type, message, created_at)
|
||||
VALUES (?, 'lease_adopted', ?, ?)
|
||||
""",
|
||||
(
|
||||
lease["work_item_id"],
|
||||
f"session {adopter_session_id} adopted from {owner} "
|
||||
f"prior={lease_id} new={new_lease_id}",
|
||||
now_s,
|
||||
),
|
||||
)
|
||||
lease2 = conn.execute(
|
||||
"SELECT * FROM leases WHERE lease_id = ?", (new_lease_id,)
|
||||
).fetchone()
|
||||
asn2 = conn.execute(
|
||||
"SELECT * FROM assignments WHERE assignment_id = ?",
|
||||
(new_asn_id,),
|
||||
).fetchone()
|
||||
return {
|
||||
"outcome": "adopted_transfer",
|
||||
"lease": dict(lease2) if lease2 else None,
|
||||
"assignment": dict(asn2) if asn2 else None,
|
||||
"reasons": [
|
||||
f"transferred lease ownership from {owner} to {adopter_session_id}"
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user