Skip to content

Commit 378c835

Browse files
committed
feat: Add parent_mandate_id and delegation_depth columns to ExecutionMandate model
1 parent af4ddfc commit 378c835

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""
2+
Copyright (C) 2026 Garudex Labs. All Rights Reserved.
3+
Caracal, a product of Garudex Labs
4+
5+
Add parent_mandate_id and delegation_depth columns to execution_mandates
6+
7+
Revision ID: j9k0l1m2n3o4
8+
Revises: i8j9k0l1m2n3
9+
Create Date: 2026-02-25 00:00:00.000000
10+
11+
Changes:
12+
- execution_mandates: add parent_mandate_id (UUID, FK to self, nullable)
13+
add delegation_depth (INTEGER, nullable, default 0)
14+
15+
These columns were defined in the initial h7i8j9k0l1m2 migration's CREATE
16+
TABLE but were missing from the SQLAlchemy ORM model. Databases that were
17+
bootstrapped via ``Base.metadata.create_all()`` instead of Alembic will not
18+
have them. This migration adds the columns idempotently.
19+
"""
20+
from typing import Sequence, Union
21+
22+
from alembic import op
23+
import sqlalchemy as sa
24+
from sqlalchemy.dialects import postgresql
25+
from sqlalchemy import inspect as sa_inspect
26+
27+
28+
# revision identifiers, used by Alembic.
29+
revision: str = 'j9k0l1m2n3o4'
30+
down_revision: Union[str, Sequence[str], None] = 'i8j9k0l1m2n3'
31+
branch_labels: Union[str, Sequence[str], None] = None
32+
depends_on: Union[str, Sequence[str], None] = None
33+
34+
35+
def _column_exists(table: str, column: str) -> bool:
36+
"""Check whether *column* already exists on *table*."""
37+
bind = op.get_bind()
38+
inspector = sa_inspect(bind)
39+
columns = [c["name"] for c in inspector.get_columns(table)]
40+
return column in columns
41+
42+
43+
def upgrade() -> None:
44+
if not _column_exists("execution_mandates", "parent_mandate_id"):
45+
op.add_column(
46+
"execution_mandates",
47+
sa.Column(
48+
"parent_mandate_id",
49+
postgresql.UUID(as_uuid=True),
50+
sa.ForeignKey("execution_mandates.mandate_id"),
51+
nullable=True,
52+
),
53+
)
54+
op.create_index(
55+
"ix_execution_mandates_parent_mandate_id",
56+
"execution_mandates",
57+
["parent_mandate_id"],
58+
)
59+
60+
if not _column_exists("execution_mandates", "delegation_depth"):
61+
op.add_column(
62+
"execution_mandates",
63+
sa.Column(
64+
"delegation_depth",
65+
sa.Integer(),
66+
nullable=True,
67+
server_default="0",
68+
),
69+
)
70+
71+
72+
def downgrade() -> None:
73+
op.drop_index(
74+
"ix_execution_mandates_parent_mandate_id",
75+
table_name="execution_mandates",
76+
)
77+
op.drop_column("execution_mandates", "delegation_depth")
78+
op.drop_column("execution_mandates", "parent_mandate_id")

caracal/db/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,15 @@ class ExecutionMandate(Base):
312312
# Intent constraint (optional)
313313
intent_hash = Column(String(64), nullable=True) # SHA-256 hash of intent
314314

315+
# Delegation hierarchy
316+
parent_mandate_id = Column(
317+
PG_UUID(as_uuid=True),
318+
ForeignKey("execution_mandates.mandate_id"),
319+
nullable=True,
320+
index=True,
321+
)
322+
delegation_depth = Column(Integer, nullable=True, default=0)
323+
315324
# Relationships
316325
issuer = relationship("Principal", foreign_keys=[issuer_id], backref="issued_mandates")
317326
subject = relationship("Principal", foreign_keys=[subject_id], backref="received_mandates")

0 commit comments

Comments
 (0)