ci / lint-test-leaks (pull_request) Canceled after 0s
- 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)
145 lines
5.8 KiB
Python
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}")
|