Files
cayce-ops/alembic/versions/0001_initial.py
T
cayce 34a5f331ac
ci / lint-test-leaks (pull_request) Canceled after 0s
skeleton: package, schema, config, CLI, alembic, CI, deploy, docs
- cayceops core: env-only config (CAYCE_OPS_*), engine/session, ORM models
  with state machines (proposal/mail/content) and audited writes
- provider-agnostic LLM interface (null provider default, stdlib openai-compat)
- Typer CLI with lazy component loading (scanner/executor/maild/publisher/monitor)
- alembic 0001 initial schema; sqlite-portable JSON columns
- tests: 7 passing on throwaway sqlite; fixtures invented, never real data
- ci: ruff + alembic smoke + pytest + gitleaks(full history) in
  .gitea/workflows + .github/workflows; local gate = make check
- deploy: docker-compose (postgres 16) + hardened systemd units/timers
- docs: DECISIONS D1-D9; README project (grant text retired)
2026-10-19 21:35:50 +00:00

145 lines
5.8 KiB
Python

"""initial schema: proposals, audit_log, mail_messages, content_items, daemon_runs
Revision ID: 0001
Revises:
Create Date: 2026-10-19
"""
import sqlalchemy as sa
from alembic import op
revision = "0001"
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"proposals",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column(
"source",
sa.Enum("SCANNER", "MANUAL", name="proposalsource"),
nullable=False,
),
sa.Column("kind", sa.String(length=64), nullable=False),
sa.Column("title", sa.String(length=512), nullable=False),
sa.Column("url", sa.String(length=1024), nullable=True),
sa.Column("detail", sa.JSON(), nullable=False),
sa.Column("risk_score", sa.Float(), nullable=False),
sa.Column(
"status",
sa.Enum(
"PROPOSED", "APPROVED", "REJECTED", "RUNNING", "DONE", "FAILED",
name="proposalstatus",
),
nullable=False,
),
sa.Column("attempts", sa.Integer(), nullable=False),
sa.Column("dedupe_key", sa.String(length=256), nullable=True),
sa.Column("approved_by", sa.String(length=128), nullable=True),
sa.Column("approved_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.UniqueConstraint("dedupe_key"),
)
op.create_index("ix_proposals_status", "proposals", ["status"])
op.create_table(
"audit_log",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("ts", sa.DateTime(timezone=True), nullable=False),
sa.Column("actor", sa.String(length=128), nullable=False),
sa.Column("proposal_id", sa.Integer(), nullable=True),
sa.Column("action", sa.String(length=64), nullable=False),
sa.Column("detail", sa.JSON(), nullable=False),
sa.Column("result", sa.String(length=32), nullable=False),
sa.ForeignKeyConstraint(["proposal_id"], ["proposals.id"], ondelete="SET NULL"),
)
op.create_index("ix_audit_log_ts", "audit_log", ["ts"])
op.create_index("ix_audit_log_proposal_id", "audit_log", ["proposal_id"])
op.create_table(
"mail_messages",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column(
"direction",
sa.Enum("INBOUND", "OUTBOUND", name="maildirection"),
nullable=False,
),
sa.Column("message_id", sa.String(length=998), nullable=True),
sa.Column("in_reply_to", sa.String(length=998), nullable=True),
sa.Column("from_addr", sa.String(length=320), nullable=True),
sa.Column("to_addr", sa.String(length=320), nullable=True),
sa.Column("subject", sa.String(length=998), nullable=True),
sa.Column("body", sa.Text(), nullable=True),
sa.Column(
"classification",
sa.Enum(
"APPROVAL", "REJECTION", "QUESTION", "OTHER", "NEEDS_REVIEW",
name="mailclassification",
),
nullable=True,
),
sa.Column("confidence", sa.Float(), nullable=False),
sa.Column("proposal_id", sa.Integer(), nullable=True),
sa.Column(
"draft_status",
sa.Enum(
"NONE", "DRAFTED", "APPROVED", "SENT", "SKIPPED", name="draftstatus"
),
nullable=False,
),
sa.Column("draft_reply", sa.Text(), nullable=True),
sa.Column("processed_at", sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(["proposal_id"], ["proposals.id"], ondelete="SET NULL"),
)
op.create_index("ix_mail_messages_message_id", "mail_messages", ["message_id"])
op.create_index("ix_mail_messages_proposal_id", "mail_messages", ["proposal_id"])
op.create_table(
"content_items",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("theme", sa.String(length=128), nullable=False),
sa.Column("channel", sa.String(length=32), nullable=False),
sa.Column("caption", sa.Text(), nullable=False),
sa.Column("media_path", sa.String(length=1024), nullable=True),
sa.Column("link", sa.String(length=1024), nullable=True),
sa.Column(
"status",
sa.Enum("DRAFT", "APPROVED", "PUBLISHED", name="contentstatus"),
nullable=False,
),
sa.Column("scheduled_for", sa.DateTime(timezone=True), nullable=True),
sa.Column("published_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("post_ref", sa.String(length=512), nullable=True),
sa.Column("error", sa.Text(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
)
op.create_index("ix_content_items_status", "content_items", ["status"])
op.create_table(
"daemon_runs",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("daemon", sa.String(length=32), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("status", sa.String(length=16), nullable=False),
sa.Column("detail", sa.JSON(), nullable=False),
)
op.create_index("ix_daemon_runs_daemon", "daemon_runs", ["daemon"])
def downgrade() -> None:
op.drop_table("daemon_runs")
op.drop_table("content_items")
op.drop_table("mail_messages")
op.drop_table("audit_log")
op.drop_table("proposals")
for enum_name in (
"proposalsource", "proposalstatus", "maildirection",
"mailclassification", "draftstatus", "contentstatus",
):
op.execute(f"DROP TYPE IF EXISTS {enum_name}")