Files
acdl/acdl_platform/hitl_matrix_design.md
T
Jon Chery 727c87339b fix(P08 prep): rename platform/ -> acdl_platform/ (stdlib shadow fix)
---ci---
project: acdl
phase: 8
milestone: v1.1
status: plan-as-execute
persona: lead-developer
task: T-8.0
type: prerequisite-fix
---/ci---

The Phase 07 P1 ('platform/ package shadows stdlib platform module')
became a Phase 08 blocker: boto3 imports uuid -> platform.system(),
which fails when the repo's platform/ package is on sys.path[0]. Renamed
platform/ -> acdl_platform/ (the verifier's recommended v1.2 fix, pulled
forward because Phase 08 needs boto3).

- git mv platform/ acdl_platform/ (history preserved)
- verify_phase07.sh: updated paths; removed the /tmp workaround (no
  longer needed; the shadow is gone)
- verify_phase06.sh: updated the new-dirs check for the rename
- README.md: layout table updated

Both verify_phase06.sh and verify_phase07.sh still pass; confidence_signal
now imports + runs correctly from the repo root. boto3 imports clean.
2026-07-21 18:53:41 +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.