- resolver (rules RTC-R0..R4/A1..A3/G1..G2) over the official cClassTrib table 2026-06-22 and LC 214/2025 annexes; local vs official catalog comparison - ONE root-cause RTC blocker per product (missing/unresolved/ambiguous/conflict/official mismatch/catalog outdated/evidence missing) - auto-apply: C000025.CCLASSTRIB only, three switches (FIREBIRD_WRITES + RTC_AUTO_APPLY_ENABLED + Hub authorization), per-product re-read and re-decision, never overwrites, rowcount 1, audit table rtc_write_audits (migration 0027) - copy-only write E2E (280/280 applied, only CCLASSTRIB changed, 2nd run 0 writes, stale decision 0 writes); real databases untouched - docs: cclasstrib_resolution, rtc_auto_apply, unified_fiscal_completeness Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
43 lines
2.0 KiB
Python
43 lines
2.0 KiB
Python
"""RTC auto-apply: per-installation authorization (default OFF) + immutable write audit.
|
|
|
|
Revision ID: 0027_rtc_auto_apply
|
|
Revises: 0026_central_processing_control
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = "0027_rtc_auto_apply"
|
|
down_revision = "0026_central_processing_control"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade():
|
|
op.add_column("installations", sa.Column("rtc_auto_apply_authorized", sa.Boolean(), nullable=False, server_default="false"))
|
|
op.create_table(
|
|
"rtc_write_audits",
|
|
sa.Column("id", sa.Integer(), primary_key=True),
|
|
sa.Column("tenant_id", sa.Integer(), sa.ForeignKey("tenants.id"), nullable=False, index=True),
|
|
sa.Column("installation_id", sa.Integer(), sa.ForeignKey("installations.id"), nullable=False, index=True),
|
|
sa.Column("agent_id", sa.Integer(), sa.ForeignKey("agents.id"), nullable=False, index=True),
|
|
sa.Column("job_id", sa.Integer(), nullable=True, index=True),
|
|
sa.Column("client", sa.String(255), nullable=True),
|
|
sa.Column("product_code", sa.String(64), nullable=False, index=True),
|
|
sa.Column("field", sa.String(32), nullable=False),
|
|
sa.Column("outcome", sa.String(16), nullable=False, index=True),
|
|
sa.Column("reason", sa.String(96), nullable=True),
|
|
sa.Column("old_value", sa.String(64), nullable=True),
|
|
sa.Column("new_value", sa.String(64), nullable=True),
|
|
sa.Column("rule_id", sa.String(32), nullable=True),
|
|
sa.Column("evidence", sa.JSON(), nullable=False, server_default="{}"),
|
|
sa.Column("official_source_version", sa.JSON(), nullable=False, server_default="{}"),
|
|
sa.Column("local_catalog_hash", sa.String(64), nullable=True),
|
|
sa.Column("written_at", sa.String(40), nullable=True),
|
|
sa.Column("recorded_at", sa.DateTime(timezone=True), server_default=sa.func.now(), index=True),
|
|
)
|
|
|
|
|
|
def downgrade():
|
|
op.drop_table("rtc_write_audits")
|
|
op.drop_column("installations", "rtc_auto_apply_authorized")
|