task-7-report.md 9.8 KB

Task 7 Report — Trusted rule generation and publication gates

Date: 2026-07-24 Branch: codex/data-rule-execution-m3a-m5

Outcome

Task 7 establishes a fail-closed path from an AI-authored natural-language candidate to an immutable published RuleVersion. Neither caller-supplied compile evidence nor caller-supplied test evidence can advance lifecycle state.

The delivered chain is:

  1. /interpret resolves distinct server-owned input/output schema snapshots, a required input sample, and an optional golden output before invoking the authoring model.
  2. A ready rule candidate is persisted with its actor, source hash, candidate hash, RuleSpec hash, model hash, prompt hash, context hash, and all bounded repair attempts.
  3. The server issues a short-lived HMAC-SHA256 generation receipt over that closed claim set.
  4. RuleVersion creation locks the generation row, verifies every claim against canonical database state, consumes the receipt exactly once, and creates a draft RuleVersion plus an immutable validation profile.
  5. Logical validation compiles a closed, validation-only Polars plan against the separately pinned input and output schema snapshots.
  6. Logical testing executes that exact plan in the isolated Polars worker against the pinned MinIO sample artifact. Digest, schema, resource, row, expiry, and ownership checks are enforced by ArtifactStore.
  7. Successful evidence advances RuleVersion draft -> validated and logical plan compiled -> tested.
  8. Publication requires exact successful compile and test evidence and advances both objects to published.
  9. Deployment-bound physical plans independently pass compiled -> tested -> published. They must reference a published RuleVersion and its successful logical test evidence.

The public catalog returns published rules only. DataFlow release continues to consume published plans only, so Task 7 does not weaken the Task 4–6 execution boundary.

Receipt and concurrency boundary

GenerationReceiptSigner signs a compact, closed JSON claim set containing:

  • receipt version;
  • generation run ID;
  • actor UID;
  • source text hash;
  • candidate hash;
  • RuleSpec hash;
  • model hash;
  • prompt hash;
  • validation context hash;
  • issued-at and expiry timestamps.

Receipts use the dedicated required RULE_GENERATION_RECEIPT_SECRET and never fall back to the Flask session secret. Issued-at, expiry, maximum lifetime, and clock skew are checked cryptographically and against current database time during consumption.

Draft creation performs SELECT ... FOR UPDATE on the generation record and a compare-and-set consumption update. A receipt is rejected if it is expired, tampered, belongs to another actor, has a different source/RuleSpec/candidate or validation context, has already been consumed, or is linked to another version.

Publication locks the rule/plan/profile/schema or plan/component/rule/binding/schema rows in one transaction and re-reads the current compiler, RuleSpec, plan, schema, binding, data source, object, dialect, evidence, and audit chain. Same-actor retries after an unknown commit outcome return the canonical published result; a different actor cannot use that replay path. Real PostgreSQL concurrency tests use independent threads, connections, and sessions: same-actor contenders return one canonical result and one audit, while a different actor is rejected after the winning commit.

AI repair boundary

The authoring agent now has a deterministic maximum of two repair attempts. Each attempt persists:

  • attempt number and outcome;
  • candidate hash;
  • deterministic error code;
  • model, prompt, and context hashes.

The prompt hash is calculated from the actual full prompt sent for that attempt, including the output JSON schema and accumulated repair messages. The final generation receipt therefore binds the final repair prompt, not the initial prompt.

Only malformed JSON and closed candidate-contract errors are repairable. Ambiguity and low confidence return clarification requirements immediately. Permission failures, destructive scope, lifecycle failures, and execution failures are never sent into the automatic repair loop.

Logical and physical evidence

Logical compile evidence binds the RuleVersion, compiler version, exact plan hash, schema hashes, capabilities, and actor. Logical test evidence additionally binds the exact trusted run ID and server-generated attestation:

  • input artifact digest;
  • output artifact digest;
  • output schema hash;
  • violation digest;
  • bounded row and violation counts.

All four evidence writers use INSERT ... RETURNING. On a uniqueness conflict they load the canonical database row, compare the complete security-relevant content, and return the real existing evidence ID; divergent replay fails closed.

The logical runner supports assert with both reject and quarantine. Quarantined rows are removed from the primary output, counted separately as rows_quarantined, and are not misreported as rejected. The compiler version was advanced to dataops-polars-1.43.0 for this result-contract change.

Physical SQL preflight validates the exact SQLGlot-bound plan and executes only EXPLAIN <compiled INSERT ... SELECT ...> through the governed data-source manager. It never uses ANALYZE and never executes the DML. The resulting evidence contains dialect, statement digest, and EXPLAIN digest. Real PostgreSQL and MySQL tests assert that destination row counts remain zero.

Physical Polars preflight stages the latest trusted, ready, unexpired input artifact and executes the exact bound plan in the isolated worker. Both physical backends are rejected on current schema, binding, dialect, plan-hash, RuleVersion, or logical-evidence drift.

Logical dry-run supports a distinct output schema and optional golden Parquet result. Golden comparison uses canonical output-field order. The real cross-schema acceptance reads name/mobile, derives name_copy, removes one quarantined row, and compares the output to the pinned golden artifact.

Forward-only migration

Migration 20260723_200 is forward-only and adds:

  • corrected RuleVersion and execution-plan lifecycle constraints;
  • receipt hash, consumption timestamp, validation context, model hash, and prompt hash on generation runs;
  • one-time receipt/version uniqueness constraints;
  • persisted generation attempts;
  • immutable validation profiles;
  • logical plans and logical compile/test evidence;
  • physical compile/test evidence binding fields;
  • validation failures and publication audits;
  • the published-rule catalog index.

The migration refuses to run when revision 190 contains any published RuleVersion or execution plan, because pre-Task-7 rows cannot be silently upgraded into trusted evidence. Physical evidence carries an explicit legacy_untrusted marker, and publication/Runner/DataFlow gates require it to be false. Downgrade is deliberately rejected because deleting publication and generation evidence would break audit and replay guarantees.

API surface

The following server-owned operations are available:

  • POST /api/rules/interpret;
  • POST /api/rules/rule-versions;
  • POST /api/rules/rule-versions/<id>/validate;
  • POST /api/rules/rule-versions/<id>/test;
  • POST /api/rules/rule-versions/<id>/publish;
  • GET /api/rules/rule-versions/<id>/evidence;
  • GET /api/rules/catalog;
  • POST /api/rules/execution-plans/<id>/validate;
  • POST /api/rules/execution-plans/<id>/test;
  • POST /api/rules/execution-plans/<id>/publish.

Request bodies and catalog query parameters use closed shapes. Compile/test endpoints do not accept caller evidence. Editor permissions cover validation/testing while publication keeps the existing administrative permission boundary. The documented canonical catalog path is implemented; the prior nested path remains a compatibility alias.

Acceptance evidence

Fail-first evidence included:

  • publication rejected without exact compile and test evidence;
  • invalid, expired, replayed, actor-mismatched, source-mismatched, and RuleSpec-mismatched receipts;
  • concurrent receipt consumption;
  • caller-forged test evidence;
  • schema, binding, dialect, and plan drift;
  • bounded repair exhaustion and no-repair ambiguity;
  • initial quarantine compilation failure before support was added.

Final verification:

  • focused data-rule/API/migration suite: 183 passed;
  • Runner plus real PostgreSQL/MySQL/Polars regression: 126 passed;
  • real PostgreSQL + MinIO cross-schema receipt/logical lifecycle: 1 passed;
  • real PhysicalPlanPublicationService PostgreSQL/MySQL lifecycle, drift, evidence replay, and PostgreSQL concurrency: 2 passed;
  • full repository suite: 626 passed, 29 skipped, 59 subtests passed;
  • changed-file Ruff: All checks passed!;
  • git diff --check: passed;
  • empty local PostgreSQL full Alembic rebuild: base through 20260723_200 (head);
  • real revision-190 legacy publication upgrade guard: 1 passed.

Skipped tests are environment-gated integration suites. The required real PostgreSQL, MySQL, and MinIO acceptance tests above were run explicitly against the local Docker services and passed.

Residual scope

Task 7 records publication preflight run IDs and exact evidence in dedicated logical/physical evidence tables. It does not fabricate production rule_runs for logical validation because those rows require a real deployment and component binding. Production execution evidence remains owned by the Task 6 Runner path after a physical plan is published.

Task 7 now supports separate input/output snapshots and cross-schema transformations. The remaining scope is production operational rollout: secret provisioning/rotation, multi-replica load and failure testing, monitoring/alert thresholds, and production canary/rollback evidence. Those operational gates are outside this local Docker acceptance and must be closed before a production environment is declared ready.