Files
cayce-ops/alembic/versions/0003_operator_signatures.py
cayce 40453aadb4 audit-1 (critical 1): mandatory operator signatures bound to content+deployment
- signing module: canonical content hashes + approval_message
  sha256(cayce-ops:<type>:<deployment_id>:<id>:<content_hash>)
- approve/approve-draft/publisher-approve REQUIRE signature; without
  CAYCE_OPS_OPERATOR_PUBKEY+DEPLOYMENT_ID the daemons refuse to
  execute/send/publish (no degraded mode)
- run-time re-verification at execute/send/publish: unsigned rows (raw SQL
  approval) or edited content revert to pre-approval state, audited as error
- FULL signatures stored on rows (operator_signature, migration 0003) and
  in audit detail — never truncated
- signature failures audited from CLI wrappers too (1f)
- AUTONOMOUS_MODE approval path removed (D13): no non-human approvals

tests: SQL-approved row refused at execute/send/publish; to_addr change
blocks send; foreign deployment/content signatures refused; full-signature
persistence. 65 tests green.
2026-10-20 02:20:09 +00:00

43 lines
1.3 KiB
Python

"""approval hardening: mandatory operator signatures bound to content
Revision ID: 0003
Revises: 0002
Create Date: 2026-10-20
- proposals.operator_signature
- mail_messages.operator_signature
- content_items.operator_signature
Full signatures (128 hex chars) — never truncated. Existing NULL rows are
refused at execution/send/publish time by the signing gate (no degraded mode).
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision = "0003"
down_revision = "0002"
branch_labels = None
depends_on = None
def upgrade() -> None:
with op.batch_alter_table("proposals") as batch:
batch.add_column(sa.Column("operator_signature", sa.Text(), nullable=True))
with op.batch_alter_table("mail_messages") as batch:
batch.add_column(sa.Column("operator_signature", sa.Text(), nullable=True))
with op.batch_alter_table("content_items") as batch:
batch.add_column(sa.Column("operator_signature", sa.Text(), nullable=True))
def downgrade() -> None:
with op.batch_alter_table("content_items") as batch:
batch.drop_column("operator_signature")
with op.batch_alter_table("mail_messages") as batch:
batch.drop_column("operator_signature")
with op.batch_alter_table("proposals") as batch:
batch.drop_column("operator_signature")