Files
acdl/core/hitl_matrix_design.md
T
Jon Chery b758a7c242 refactor(P21): rename acdl_platform/ -> core/ (REQ-53)
---ci---
project: acdl
phase: 21
milestone: v1.6
status: execute
---/ci---

Rename the acdl_platform/ package to core/ across the directory, all
imports in tests/scripts/pipelines/workflows, and doc references. The
package is imported as core.confidence_signal / core.contract_resolver /
core.outbox_writer. The deploy workflow's platform-repo checkout dir is
renamed acdl-platform/ -> platform/ (workspace path, not the python
package). Both .gitea + .github workflows stay byte-identical.

Note: the original target name 'platform/' shadows Python's stdlib
platform module (pytest's import uuid -> platform.system() fails when
the repo root is on sys.path, which every test does). 'core/' avoids
the clash while honoring the intent (drop the verbose acdl_platform).

Tests: 154 pass. run_ci.sh green.
2026-07-22 18:21:12 +00:00

6.2 KiB

ACDL Human-in-the-Loop Matrix + Separation-of-Duties Design (REQ-21)

Status: design authored in Phase 07 (milestone v1.1); v1.2 wires the gates. The spike (Phases 08-10) is dev-only; HITL is not exercised (the spike contract has environment: dev).

The vision's "Lower Environments are Autonomous; Higher Environments are Attested" tenet [1] and the "deliberate human attestation — not as a rubber stamp" requirement [1] are the binding constraints.

Gate model (ARCHITECTURE.md §10.1)

Pre-execution gates. The contract is held in a "validated but not applied" state until the human attests. qa, prod, dr are attestation gates. No partial deployment to roll back on rejection (qa, prod); dr is a separate deployment against a separate cluster/region. The canary/deployment-rollback model is explicitly not in scope for v1.

Gitea-specific gate mechanics (D-042)

Gitea has no Environments API and ignores environment: blocks (v1.0 D-013; re-confirmed in RESEARCH TARGET 1). The pre-execution gate is modeled as a workflow_dispatch with approval inputs:

  • qa gate: workflow_dispatch with approve_qa: true; the dispatch run's gitea.actor is the QA approver.
  • prod gate: workflow_dispatch with approve_prod: true; gitea.actor is the SRE approver.
  • dr gate: workflow_dispatch with approve_dr: true; same.

The approver identity of record = gitea.actor of the dispatch run (D-042). There is no other approval-identity signal in Gitea. The v1.2 real-OIDC path (blocked on go-gitea/gitea#36988) does not change this — OIDC authorizes the runner to AWS, it does not change how the platform records the human approver.

Reviewer routing (ARCHITECTURE.md §10.2)

Gitea CODEOWNERS routes the right reviewer to the right gate:

  • qa → QA team
  • prod → SRE team
  • dr → SRE team

CODEOWNERS routes; it does not enforce identity distinctness (that is the platform-internal outbox check in platform/separation_of_duties.py).

Full 8-concern attestation matrix (§10.4, lifted verbatim)

Env Concern Evidence artifact Freshness Source Attester
qa Functional correctness Last successful run of contract-declared validation.e2eSuite with pass rate ≥ 99% Last 24h Test runner declared in contract QA
qa Performance baseline Load test report (k6 / Gatling / Locust) showing p99 latency < declared NFR and throughput > declared minimum Last 7d Load test runner declared in contract QA
qa Security posture Vulnerability scan (Trivy, Snyk, or contract-declared equivalent) with no criticals/highs, signed by Security on-call Last 24h Security scanner + Security team signature QA
qa Contract NFRs Platform-generated report: schema valid, NFR assertions (latency, throughput, error rate) within declared bounds At submission Platform contract validator QA
prod Operational readiness Runbook published, dashboard exists, on-call rotation assigned, alerts configured At submission, validated against last 30d history Platform + SRE SRE
prod Incident response Sev-1 runbook tabletop or live drill completed Last 90d SRE drill record SRE
prod Capacity / cost FinOps forecast for next 30d within budget envelope, cost anomaly baseline stored, budget alert configured Forecast valid for next 30d FinOps + SRE SRE
prod Resilience DR drill, chaos engineering report, backup verified DR: 180d; chaos: 90d; backup: 30d SRE + Platform SRE
dr dr-region deploy with the most recent prod-bound dr drill as canary evidence dr drill report Last 180d SRE SRE

Timeout behavior (§10.5)

Time State Action
Submission PENDING_ATTESTATION Notify responsible team
1 business day PENDING_ATTESTATION_WARNING Notify team + platform on-call (elevated path); emit PENDING_ATTESTATION_TIMEOUT_WARNING event
2 business days PENDING_ATTESTATION_AUTO_FREEZE Auto-freeze; require re-submission; emit PENDING_ATTESTATION_AUTO_FREEZE event; new submission linked via supersedes

Implementation: a Gitea on: schedule workflow (runs hourly) that scans the DynamoDB outbox for PENDING_ATTESTATION events with ts older than 1/2 business days and emits the warn/freeze events. Not implemented in the spike (dev-only).

Rejection and rollback (§10.6)

Rejection returns the contract to a HELD state with the rejection reason captured as a PROMOTION_REJECTED event. The consumer fixes the cause and re-submits; the new submission is linked to the rejected one via supersedes (a contract-schema field — schemas/contract.schema.json). The audit chain is extended, not torn up (the "Not a mutable audit log" anti-goal). No partial deployment to roll back at any v1 gate.

Separation of duties (§10.3) — pointer to the .py

The identity-distinctness check is platform-internal, not GitHub-native, not Kyverno (in v1). Sequence:

  1. On promotion dev → qa, the platform reads the QA approver's identity from the workflow_dispatch run's gitea.actor and writes it to the DynamoDB outbox keyed by contractId (attribute approver_qa).
  2. On promotion qa → prod, the platform reads the stored approver_qa from the outbox and the new SRE approver's gitea.actor from the prod-dispatch run.
  3. If approver_qa == approver_prod, the platform blocks the prod promotion, writes a SEPARATION_OF_DUTIES_VIOLATION event to the evidence stream, and routes a halt artifact to the SRE on-call.
  4. The check is implemented in platform/separation_of_duties.py (T-7.8). The platform is the only writer to the outbox; the check is in the same process that has authority to block the promotion.

Spike scope note

The spike is dev-only (REQ-27 contract has environment: dev), so HITL is not exercised. Phase 07 authors the design; Phase 10's verify_phase10.sh does not assert HITL behavior. v1.2 wires the gates against this design.

Decision trail

  • D-042 — approver identity = gitea.actor of the workflow_dispatch run; no Environments API in Gitea.
  • D-013 (v1.0) — the workflow_dispatch approval-input fallback, re-used for the real platform's pre-execution gate model.