b40aadd195
---ci--- project: acdl phase: 7 milestone: v1.1 status: plan plan: waves: 5 tasks: 10 requirements: [REQ-16, REQ-17, REQ-18, REQ-19, REQ-20, REQ-21, REQ-22] ---/ci--- Phase 07 plan authored by ci-planner. 5 waves (strictly ordered): - Wave 1 (lead-developer): REQ-16 architecture-v1.0.md snapshot (umbrella) - Wave 2 (platform + backend parallel): REQ-17 ir.schema.json || REQ-22 contract.schema.json - Wave 3 (security-engineer, 5 files): REQ-18 PolicyCheckResult schema + checkov_adapter.py, REQ-20 audit_ledger_design.md, REQ-21 hitl_matrix_design.md + separation_of_duties.py - Wave 4 (backend + security co-authored): REQ-19 confidence_signal.py (depends on Wave 3) - Wave 5 (lead-developer): verify_phase07.sh + traceability 9 deliverable files + 1 verify script. Cross-persona co-authorship on confidence_signal.py (backend weighted-sum + security penalty mapping) mitigated by the PLAN content checklist fixing the critical-override sentinel + short-circuit location.
1316 lines
76 KiB
Markdown
1316 lines
76 KiB
Markdown
---
|
|
phase: 7
|
|
name: architecture-v1-finalization
|
|
milestone: v1.1
|
|
milestone_type: feature
|
|
status: planned
|
|
requirements: [REQ-16, REQ-17, REQ-18, REQ-19, REQ-20, REQ-21, REQ-22]
|
|
must_haves:
|
|
# REQ-16 — architecture snapshot + status bump
|
|
- "docs/architecture-v1.0.md exists as a snapshot of docs/architecture.md with all 11 open decisions applied; status note bumped from v0.2 to v1.0; a 'v1.1 spike = Phases 08-10' + 'v1.2 build-out' note is present"
|
|
- "All 11 open-decision IDs are recorded in PROJECT.md (W1.A, W1.B, W2.A, W3.D, W3.E, BA.A, BA.B, BA.C, BA.D, BA.E, BA.F) + the OpenTofu timing sub-decision (Q1.3)"
|
|
# REQ-17 — Target Stack IR schema
|
|
- "schemas/ir.schema.json exists, validates as JSON Schema draft 2020-12, and encodes: resources (typed inputs/outputs/NFRs), relationships (single parent per child), composition (tree, max-depth-5), policy hooks; substrate-agnostic (no Terraform-specific terms in the schema body)"
|
|
# REQ-18 — PolicyCheckResult schema + Checkov adapter
|
|
- "schemas/policy_check_result.schema.json exists, validates as JSON Schema draft 2020-12, and matches ARCHITECTURE.md §12.6 canonical shape"
|
|
- "adapters/terraform/policy/checkov_adapter.py exists, py_compiles, and translates Checkov JSON → PolicyCheckResult records; maps CKV_AWS_* to the 4 L2 checks + tag/naming; emits a single ACDL_TAG_NAMING SKIPPED PolicyCheckResult (severity: info) per D-043"
|
|
# REQ-19 — confidence signal
|
|
- "platform/confidence_signal.py exists, py_compiles, and implements the 6-input weighted sum (policy 0.30, validation 0.25, freshness 0.10, source 0.15, history 0.10, nfrs 0.10 per D-040); per-env thresholds (dev 0.50, qa 0.75, prod 0.90, dr 0.95); severity→penalty (critical hard-override, high -0.2, medium -0.05, low -0.01, info 0.0); output {score, band, perInput, reasonCodes}; halts with reason on missing input"
|
|
# REQ-20 — audit ledger design
|
|
- "platform/audit_ledger_design.md exists and specifies: S3 Object Lock compliance mode 7-yr + DynamoDB outbox RPO=0 + JWS detached signature (platform KMS key, quarterly rotation) + prev_event_hash SHA-256 chain over canonical JSON + daily checkpoints; spike scope (D-041) = v1.0 hash chain + outbox write; v1.2 build-out = Object Lock + JWS + async worker + DLQ clearly marked"
|
|
# REQ-21 — HITL matrix + SoD
|
|
- "platform/hitl_matrix_design.md exists and contains the full 8-concern attestation matrix from docs/architecture.md §10.4; pre-execution gate model; timeout 1d warn / 2d freeze; rejection → HELD + supersedes; reviewer routing via CODEOWNERS + Gitea workflow_dispatch gitea.actor (D-042, no Environments API)"
|
|
- "platform/separation_of_duties.py exists, py_compiles, and reads approver_qa from the DynamoDB outbox, compares to the prod-dispatch gitea.actor, blocks + emits SEPARATION_OF_DUTIES_VIOLATION on equality, routes halt artifact to SRE on-call"
|
|
# REQ-22 — contract schema
|
|
- "schemas/contract.schema.json exists, validates as JSON Schema draft 2020-12, and encodes per-env mandatory inputs (dev: stack+environment; qa adds validation.e2eSuite+loadTest; prod adds runbook+dashboard+oncall; dr adds drDrillRef); inputs always optional; profile: agentic marker with naturalLanguageIntent/confidenceAtSubmission/agentTrace optional everywhere"
|
|
# verification
|
|
- "scripts/verify_phase07.sh exists and asserts: all 6 schema/design files exist; the 3 JSON Schemas validate (python3 -m jsonschema); the 2 .py files py_compile; the 2 .md design files are non-empty; all 11 decision IDs + OpenTofu present in PROJECT.md; docs/architecture-v1.0.md exists + status is v1.0"
|
|
- "Traceability updated: REQUIREMENTS.md REQ-16..22 → covered/complete; ROADMAP.md Phase 07 → complete"
|
|
verification:
|
|
typecheck: "python3 -m py_compile platform/confidence_signal.py platform/separation_of_duties.py adapters/terraform/policy/checkov_adapter.py && python3 -m jsonschema schemas/ir.schema.json schemas/policy_check_result.schema.json schemas/contract.schema.json"
|
|
test: "scripts/verify_phase07.sh"
|
|
build: "no-op # Phase 07 is design-authoring; terraform init is Phase 09+"
|
|
---
|
|
|
|
# Phase 07 — architecture-v1-finalization PLAN
|
|
|
|
## Goal
|
|
|
|
Finalize the ACDL architecture to v1.0 by authoring the 6 locked
|
|
schema/design files + 1 architecture snapshot that resolve all 11 open
|
|
decisions recorded in `PROJECT.md` (D-038). This phase is
|
|
**design-authoring, not runtime code**: the files are the contracts the
|
|
v1.1 spike (Phases 08-10) implements against, not the spike itself.
|
|
|
|
The deliverables, mapped to requirements:
|
|
|
|
| REQ | File(s) | Owner persona (PERSONAS.md territory) |
|
|
|-----|---------|---------------------------------------|
|
|
| REQ-16 | `docs/architecture-v1.0.md` (snapshot of `docs/architecture.md` with 11 resolutions applied + status bumped to v1.0) | lead-developer (owns `docs/**` meta) |
|
|
| REQ-17 | `schemas/ir.schema.json` (Target Stack IR) | platform-engineer (owns the IR; `schemas/` co-owned with backend-engineer) |
|
|
| REQ-18 | `schemas/policy_check_result.schema.json` + `adapters/terraform/policy/checkov_adapter.py` | security-engineer (owns `adapters/terraform/policy/**`); schema co-authored |
|
|
| REQ-19 | `platform/confidence_signal.py` (6-input weighted signal) | backend-engineer (6-input weighted sum + thresholds) + security-engineer (severity→penalty + critical-override) — co-owned per PERSONAS.md conflict resolutions |
|
|
| REQ-20 | `platform/audit_ledger_design.md` (tiered ledger) | security-engineer |
|
|
| REQ-21 | `platform/hitl_matrix_design.md` + `platform/separation_of_duties.py` | security-engineer |
|
|
| REQ-22 | `schemas/contract.schema.json` (contract schema) | backend-engineer (owns `schemas/**` contract + IR + PolicyCheckResult) |
|
|
| — | `scripts/verify_phase07.sh` + traceability update | lead-developer |
|
|
|
|
The spike scope (D-041, D-043) means the designs author the **full
|
|
target** but clearly mark what is spike-scope vs v1.2 build-out. The spike
|
|
(Phases 08-10) only runs `terraform plan` + Checkov + confidence + outbox
|
|
write; Object Lock / JWS / HITL-wiring / L3B-skill-catalog are design-only
|
|
in Phase 07.
|
|
|
|
## Requirements covered
|
|
|
|
- **REQ-16** → T-7.1 (`docs/architecture-v1.0.md` snapshot + status bump).
|
|
- **REQ-17** → T-7.2 (`schemas/ir.schema.json`).
|
|
- **REQ-22** → T-7.3 (`schemas/contract.schema.json`).
|
|
- **REQ-18** → T-7.4 (`schemas/policy_check_result.schema.json`) +
|
|
T-7.5 (`adapters/terraform/policy/checkov_adapter.py`).
|
|
- **REQ-20** → T-7.6 (`platform/audit_ledger_design.md`).
|
|
- **REQ-21** → T-7.7 (`platform/hitl_matrix_design.md`) +
|
|
T-7.8 (`platform/separation_of_duties.py`).
|
|
- **REQ-19** → T-7.9 (`platform/confidence_signal.py`, co-authored).
|
|
- **All** → T-7.10 (`scripts/verify_phase07.sh` + traceability update).
|
|
|
|
All 7 requirements (REQ-16..REQ-22) are Phase 07 per REQUIREMENTS.md
|
|
traceability table. Phase 07 depends on Phase 06 (complete, v1.1.1):
|
|
the new repo layout (`schemas/`, `platform/`, `adapters/`) is scaffolded
|
|
empty with `.gitkeep` and ready to receive these files.
|
|
|
|
## Waves
|
|
|
|
Domain priority order from `PERSONAS.md`:
|
|
`coordination → security → platform → backend → frontend`.
|
|
|
|
Phase 07 active personas (PERSONAS.md "Phase-specific overrides"):
|
|
**lead-developer** (coordination — the snapshot + verify script),
|
|
**backend-engineer** (schemas: contract + co-IR + co-PolicyCheckResult +
|
|
co-confidence), **security-engineer** (HITL/ledger/SoD/Checkov-adapter/
|
|
co-confidence), **platform-engineer** (IR). frontend-engineer idle.
|
|
|
|
The files are mostly independent (no runtime dependencies between
|
|
schemas), but there are logical dependencies:
|
|
- REQ-16 (architecture snapshot) is the umbrella — it references all
|
|
the others.
|
|
- The 3 JSON Schemas (REQ-17, REQ-18-partial, REQ-22) are independent.
|
|
- REQ-19 (confidence_signal.py) consumes PolicyCheckResult records
|
|
(REQ-18) — depends on T-7.4.
|
|
- REQ-21 (separation_of_duties.py) reads the outbox schema (REQ-20) —
|
|
depends on T-7.6.
|
|
- REQ-20 + REQ-21 are both security-engineer; one wave, sequential
|
|
within.
|
|
|
|
Five waves, strictly ordered (each wave depends on the prior wave's
|
|
commits being present so the verify script and snapshot can reference
|
|
the authored files):
|
|
|
|
```
|
|
Wave 1 (lead-developer) — T-7.1 architecture-v1.0.md snapshot (umbrella)
|
|
Wave 2 (platform + backend) — T-7.2 ir.schema.json
|
|
T-7.3 contract.schema.json (parallel)
|
|
Wave 3 (security-engineer) — T-7.4 policy_check_result.schema.json
|
|
T-7.5 checkov_adapter.py
|
|
T-7.6 audit_ledger_design.md
|
|
T-7.7 hitl_matrix_design.md
|
|
T-7.8 separation_of_duties.py (sequential)
|
|
Wave 4 (backend + security) — T-7.9 confidence_signal.py (co-authored; depends on T-7.4)
|
|
Wave 5 (lead-developer) — T-7.10 verify_phase07.sh + traceability
|
|
```
|
|
|
|
**Dependency graph (explicit):**
|
|
- Wave 4 → Wave 3 (confidence_signal consumes PolicyCheckResult, T-7.4).
|
|
- Wave 4 → Wave 3 (separation_of_duties reads the outbox schema documented
|
|
in audit_ledger_design.md, T-7.6 — the .py reads the outbox shape
|
|
defined in the .md).
|
|
- Wave 5 → all prior waves (verify script asserts all 6 files exist +
|
|
validate).
|
|
- Wave 1 (snapshot) is umbrella — it references the *decisions* (already
|
|
in PROJECT.md), not the *authored files*, so it can land first. It
|
|
notes that the 6 files are authored in Phases 07 Waves 2-4.
|
|
|
|
**Parallelizable:** Wave 2's two tasks (T-7.2 IR, T-7.3 contract) are
|
|
independent JSON Schemas owned by different personas — run in parallel.
|
|
All other waves are sequential within the wave.
|
|
|
|
---
|
|
|
|
### Wave 1 — lead-developer: REQ-16 architecture-v1.0.md snapshot
|
|
|
|
The umbrella doc. References the 11 resolutions already recorded in
|
|
PROJECT.md (does not duplicate them — points at PROJECT.md's
|
|
"Open-decision resolutions" table + decisions D-034..D-046). Bumps the
|
|
status note from v0.2 → v1.0. Notes the v1.1 spike (Phases 08-10) +
|
|
v1.2 build-out boundary.
|
|
|
|
#### T-7.1 — Author `docs/architecture-v1.0.md`
|
|
|
|
- **Owner:** lead-developer (territory: `docs/**` meta only — not
|
|
architecture authoring; this is a *snapshot* of the upstream
|
|
`docs/architecture.md`, not a re-authoring).
|
|
- **Files owned:** `docs/architecture-v1.0.md` (new).
|
|
- **What the file must contain** (cite sources):
|
|
1. **Header** — copy the upstream `docs/architecture.md` header verbatim,
|
|
then change the status line from
|
|
`Status: Draft v0.2. Section 13 lists 11 open decisions. ...`
|
|
to
|
|
`Status: v1.0 (snapshot taken in ACDL Phase 07, milestone v1.1). All 11 open decisions in §13 are resolved — see PROJECT.md "Open-decision resolutions" table + decisions D-034..D-046.`
|
|
2. **Resolution session log table** — replace the upstream §0
|
|
resolution-session log with a v1.0 resolution log: for each of the
|
|
11 open IDs (W1.A, W1.B, W2.A, W3.D, W3.E, BA.A, BA.B, BA.C, BA.D,
|
|
BA.E, BA.F, Q1.3-OpenTofu), a row with `ID | Resolution (one-line) |
|
|
See PROJECT.md`. Do **not** duplicate the full resolution text —
|
|
point at PROJECT.md.
|
|
3. **§13 Consolidated Open Design Decisions** — replace the upstream
|
|
"🟡 OPEN" markers with "✅ RESOLVED (see PROJECT.md)" for each of the
|
|
11 items, preserving the upstream section structure.
|
|
4. **§14 Document Status** — rewrite to:
|
|
`Status: v1.0. All 11 open items in §13 are resolved. The architecture is internally consistent; the v1.1 implementation spike (ACDL Phases 08-10) validates the locked substrate abstraction + contract→IR→adapter path. The v1.2 build-out (Object Lock, JWS, HITL wiring, L3B skill catalog, Kyverno/OPA, OIDC federation) is design-authored in Phase 07 and implemented post-spike.`
|
|
5. **New §15 — Phase 07 authored artifacts** — a table mapping each
|
|
of the 6 schema/design files to its REQ + owner persona:
|
|
`REQ-17 → schemas/ir.schema.json (platform-engineer)`,
|
|
`REQ-18 → schemas/policy_check_result.schema.json + adapters/terraform/policy/checkov_adapter.py (security-engineer)`,
|
|
`REQ-19 → platform/confidence_signal.py (backend + security co-authored)`,
|
|
`REQ-20 → platform/audit_ledger_design.md (security-engineer)`,
|
|
`REQ-21 → platform/hitl_matrix_design.md + platform/separation_of_duties.py (security-engineer)`,
|
|
`REQ-22 → schemas/contract.schema.json (backend-engineer)`.
|
|
Note that these files land in Waves 2-4 of Phase 07.
|
|
6. **gitea-runner rename (D-046)** — update any `act_runner` reference
|
|
in the snapshot to `gitea-runner` (formerly `act_runner`, renamed
|
|
2026-04 in gitea/runner#850) with a one-line note.
|
|
7. **Body** — the rest of the upstream `docs/architecture.md` body
|
|
(§§1-12) is copied verbatim; the §12.6 PolicyCheckResult shape, §10.4
|
|
HITL matrix, §8 confidence signal, §9 audit stream, §7 contract
|
|
schema are all *locked* — the snapshot preserves them as the v1.0
|
|
record. The authored schema files (Waves 2-4) formalize these
|
|
sections; the snapshot is the prose, the schemas are the contract.
|
|
- **Source citations:**
|
|
- `docs/architecture.md` (upstream v0.2 — the source of truth for how).
|
|
- `PROJECT.md` "Open-decision resolutions" table (the 11 resolutions).
|
|
- `PROJECT.md` decisions D-034..D-046.
|
|
- `ARCHITECTURE.md` §status (the ACDL-repo operating copy, already
|
|
notes "finalizes it to v1.0 in Phase 07").
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: lead-developer, task: T-7.1`
|
|
(followed by a docs commit:
|
|
`docs(P07): snapshot architecture to v1.0 (REQ-16); 11 decisions resolved`)
|
|
- **Self-verify:**
|
|
- [ ] `docs/architecture-v1.0.md` exists.
|
|
- [ ] First status line says `v1.0` (not `v0.2`).
|
|
- [ ] All 11 open-decision IDs appear with `✅ RESOLVED` (grep each:
|
|
`W1.A`, `W1.B`, `W2.A`, `W3.D`, `W3.E`, `BA.A`, `BA.B`, `BA.C`,
|
|
`BA.D`, `BA.E`, `BA.F`, `Q1.3`).
|
|
- [ ] `gitea-runner` appears (D-046 applied); `act_runner` only in a
|
|
"formerly" note.
|
|
- [ ] §15 table lists all 6 authored files.
|
|
|
|
---
|
|
|
|
### Wave 2 — platform-engineer + backend-engineer (parallel): REQ-17 IR + REQ-22 contract schema
|
|
|
|
Two independent JSON Schemas, different owners, parallelizable. Both
|
|
are JSON Schema draft 2020-12, both live under `schemas/`, both must
|
|
`python3 -m jsonschema` validate.
|
|
|
|
#### T-7.2 — Author `schemas/ir.schema.json` (Target Stack IR)
|
|
|
|
- **Owner:** platform-engineer (territory: `modules-ir/**`,
|
|
`adapters/terraform/**`, the IR; `schemas/` co-owned with
|
|
backend-engineer — platform-engineer is the IR author per PERSONAS.md
|
|
conflict resolution).
|
|
- **Files owned:** `schemas/ir.schema.json` (new).
|
|
- **What the file must contain** (cite sources):
|
|
1. **JSON Schema draft 2020-12** — `$schema:
|
|
"https://json-schema.org/draft/2020-12/schema"`, `$id:
|
|
"https://acdl.cloudinit.dev/schemas/ir.schema.json"`, `title:
|
|
"ACDL Target Stack IR"`.
|
|
2. **Top-level shape** — `required: [version, stack, resources]`,
|
|
`properties`: `version` (semver string), `stack` (name/kind/depth),
|
|
`resources` (array of resource, minItems 1), `relationships` (array
|
|
of relationship, optional).
|
|
3. **`stack`** — `name` pattern `^l[12]-[a-z][a-z0-9-]*$`, `kind` enum
|
|
`[l1, l2]`, `depth` integer min 1 max 5 (ARCHITECTURE.md §3 "max
|
|
depth 5").
|
|
4. **`resource` ($defs/resource)** — required `[id, type, module,
|
|
inputs]`; `id` pattern `^[a-z][a-z0-9-]*$`; `type` string
|
|
(substrate-agnostic, e.g. `aws:s3:bucket` — the *IR* type, not a
|
|
Terraform address); `module` pattern
|
|
`^l1-[a-z][a-z0-9-]*@\d+\.\d+\.\d+$` (L1 registry reference
|
|
`name@semver` per W3.D); `parent` (string, absent for root — single
|
|
parent per child per ARCHITECTURE.md §12.1); `inputs` (object,
|
|
additionalProperties free-form — typed by the L1 module's declared
|
|
inputs, validated at contract→IR resolution); `outputs` (object of
|
|
outputSpec — typed output contract; the adapter translates to
|
|
Terraform outputs); `nfrs` (object, opaque to the adapter, consumed
|
|
by the confidence signal's NFR input).
|
|
5. **`outputSpec` ($defs/outputSpec)** — required `[type]`; `type`
|
|
string (IR type, e.g. `string`, `arn`,
|
|
`ref:<resourceId>.<outputName>`); `description` optional.
|
|
6. **`relationship` ($defs/relationship)** — required `[from, to, kind]`;
|
|
`from`/`to` resource ids; `kind` enum `[parent, depends_on,
|
|
uses_output]` with a description noting v1 uses `parent` +
|
|
`uses_output` only (ARCHITECTURE.md §12.1); `shared_keyword`
|
|
(string, reserved for v2 multi-relationship, unused in v1 —
|
|
A-3.2).
|
|
7. **Substrate-agnostic invariant** — the schema body must contain
|
|
**no Terraform-specific terms** (`variable`, `output`, `module`,
|
|
`resource` as Terraform blocks, `aws_` provider prefixes in the
|
|
schema keywords). The `type` field values are IR types
|
|
(`aws:s3:bucket`), not Terraform resource types (`aws_s3_bucket`).
|
|
The adapter (Phase 09) does the translation; the IR is neutral.
|
|
8. **Why this round-trips to Terraform (v1)** — include a
|
|
`$comment` at the top: "v1 ships one adapter (Terraform). The IR is
|
|
nearly isomorphic to Terraform in v1 (ARCHITECTURE.md §12.1); the
|
|
adapter compiles resource.module → module block, resource.inputs →
|
|
variable + arg, resource.outputs → output, relationship.kind=
|
|
uses_output → interpolation, relationship.kind=parent → composition
|
|
ordering hint. As more adapters appear (v2+), the IR gains
|
|
expressiveness; the L1 content + contract YML + thin-composition
|
|
tree do not change."
|
|
- **Source citations:**
|
|
- `ARCHITECTURE.md` §12.1 (substrate abstraction — locked commitments:
|
|
resources typed inputs/outputs/NFRs, relationships single parent per
|
|
child, composition tree max-depth-5, policy hooks).
|
|
- `RESEARCH.md` TARGET 3 (v1 IR shape sketch — the executor uses this
|
|
as the basis; the sketch is a *recommendation*, the authored schema
|
|
must match ARCHITECTURE.md's locked commitments and be
|
|
self-contained).
|
|
- `PROJECT.md` W3.D (L1/L2 standard versioning — `name@semver` pin
|
|
model, MAJOR bump = new registry entry, 12-month deprecation
|
|
window).
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: platform-engineer, task: T-7.2`
|
|
(then `feat(P07): author schemas/ir.schema.json (REQ-17)`)
|
|
- **Self-verify:**
|
|
- [ ] `python3 -m jsonschema schemas/ir.schema.json` exits 0 (schema is
|
|
itself valid JSON Schema).
|
|
- [ ] A minimal valid IR instance validates: `{version:"1.0.0",
|
|
stack:{name:"l2-static-asset",kind:"l2",depth:1},
|
|
resources:[{id:"s3",type:"aws:s3:bucket",
|
|
module:"l1-s3@1.0.0",inputs:{bucket_name:"x",region:"us-east-1"}}]}`
|
|
— write this to a temp file and validate against the schema.
|
|
- [ ] No `aws_s3_bucket` / Terraform-block keywords in the schema body.
|
|
- [ ] `depth` max is 5; `module` pattern enforces `name@semver`;
|
|
`relationship.kind` enum includes `parent` + `uses_output`.
|
|
- [ ] `shared_keyword` is reserved (present, unused in v1).
|
|
|
|
#### T-7.3 — Author `schemas/contract.schema.json`
|
|
|
|
- **Owner:** backend-engineer (territory: `schemas/**` contract + IR +
|
|
PolicyCheckResult per PERSONAS.md; owns the contract schema + the
|
|
contract→IR resolution).
|
|
- **Files owned:** `schemas/contract.schema.json` (new).
|
|
- **What the file must contain** (cite sources):
|
|
1. **JSON Schema draft 2020-12** — `$schema:
|
|
"https://json-schema.org/draft/2020-12/schema"`, `$id:
|
|
"https://acdl.cloudinit.dev/schemas/contract.schema.json"`,
|
|
`title: "ACDL Contract"`.
|
|
2. **Top-level required** — `[stack, environment]` (dev's minimum per
|
|
W3.E). `stack` pattern `^l2-[a-z][a-z0-9-]*$` (L2 reference);
|
|
`environment` enum `[dev, qa, prod, dr]` (no `staging` — Path A
|
|
locked, ARCHITECTURE.md §5).
|
|
3. **`inputs`** — object, `additionalProperties` type
|
|
`[string, number, boolean]`, always optional (W3.E: "`inputs`
|
|
always optional"). Description: "L2-level parameter map; free-form
|
|
in v1, typed per-L1 in v1.2" (A-9.2).
|
|
4. **`validation`** — object with `e2eSuite` (string, ref to the e2e
|
|
suite) + `loadTest` (string, ref to the load test). Mandatory in qa
|
|
via the `allOf` conditional below.
|
|
5. **`runbook`, `dashboard`, `oncall`** — strings, mandatory in prod.
|
|
6. **`drDrillRef`** — string, mandatory in dr.
|
|
7. **`profile`** — enum `[developer, agentic]`, default `developer`.
|
|
The L3B marker (ARCHITECTURE.md §5 "profile: agentic unlocks L3B
|
|
fields").
|
|
8. **`naturalLanguageIntent`, `confidenceAtSubmission`, `agentTrace`**
|
|
— the L3B fields, optional everywhere (W3.E). When `profile:
|
|
agentic`, `naturalLanguageIntent` becomes required (via `allOf`).
|
|
`confidenceAtSubmission` is a number in `[0, 1]`.
|
|
9. **`supersedes`** — string, `format: uuid`, optional. Description:
|
|
"prior contractId this re-submission replaces (after rejection —
|
|
ARCHITECTURE.md §10.6)".
|
|
10. **`allOf` conditionals** (W3.E per-env mandatory table):
|
|
- `if environment == qa → then required: [validation]` (and
|
|
within `validation`, require `e2eSuite` + `loadTest`).
|
|
- `if environment == prod → then required: [runbook, dashboard, oncall]`.
|
|
- `if environment == dr → then required: [drDrillRef]`.
|
|
- `if profile == agentic → then required: [naturalLanguageIntent]`.
|
|
11. **`$comment`** — "Per-env mandatory inputs per W3.E (PROJECT.md).
|
|
dev requires stack+environment; qa adds validation.e2eSuite +
|
|
validation.loadTest; prod adds runbook+dashboard+oncall; dr adds
|
|
drDrillRef. inputs always optional. profile: agentic fields
|
|
optional everywhere (naturalLanguageIntent required when profile
|
|
is agentic)."
|
|
- **Source citations:**
|
|
- `ARCHITECTURE.md` §7 (contract schema — W3.E per-env mandatory
|
|
table).
|
|
- `RESEARCH.md` TARGET 9 (contract schema sketch — the executor uses
|
|
this as the basis; must be self-contained + match ARCHITECTURE.md).
|
|
- `PROJECT.md` W3.E (the locked per-env mandatory/optional table).
|
|
- `PROJECT.md` W2.A (tag for dev/qa, SHA for prod — not a schema field
|
|
but informs the `stack` reference resolution in the CLI, noted in
|
|
the `$comment`).
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: backend-engineer, task: T-7.3`
|
|
(then `feat(P07): author schemas/contract.schema.json (REQ-22)`)
|
|
- **Self-verify:**
|
|
- [ ] `python3 -m jsonschema schemas/contract.schema.json` exits 0.
|
|
- [ ] The spike contract
|
|
`{stack:"l2-static-asset",environment:"dev",inputs:{bucket_name:"acdl-spike-bucket",region:"us-east-1"}}`
|
|
validates (dev requires only stack+environment).
|
|
- [ ] A qa contract missing `validation` *fails* validation.
|
|
- [ ] A prod contract missing `runbook` *fails*.
|
|
- [ ] A dr contract missing `drDrillRef` *fails*.
|
|
- [ ] An agentic contract missing `naturalLanguageIntent` *fails*;
|
|
an agentic contract with it *passes*.
|
|
- [ ] `environment` enum does NOT include `staging`.
|
|
|
|
---
|
|
|
|
### Wave 3 — security-engineer: REQ-18 + REQ-20 + REQ-21
|
|
|
|
One persona, five files, one wave (sequential within the wave). The
|
|
Checkov adapter (T-7.5) depends on the PolicyCheckResult schema (T-7.4);
|
|
the SoD module (T-7.8) depends on the outbox shape in the ledger design
|
|
(T-7.6). Order within the wave: T-7.4 → T-7.5 → T-7.6 → T-7.7 → T-7.8.
|
|
|
|
#### T-7.4 — Author `schemas/policy_check_result.schema.json`
|
|
|
|
- **Owner:** security-engineer (owns `adapters/terraform/policy/**`; the
|
|
schema is co-authored with backend-engineer who owns `schemas/**`
|
|
contract + IR + PolicyCheckResult — security-engineer owns the
|
|
*adapter*, the schema is the contract both share. security-engineer
|
|
authors it here because the adapter's shape is the security concern
|
|
+ the severity enum is the security-engineer's territory per the
|
|
conflict-resolution note on confidence_signal co-ownership).
|
|
- **Files owned:** `schemas/policy_check_result.schema.json` (new).
|
|
- **What the file must contain** (cite sources):
|
|
1. **JSON Schema draft 2020-12** — `$schema`, `$id:
|
|
"https://acdl.cloudinit.dev/schemas/policy_check_result.schema.json"`,
|
|
`title: "ACDL PolicyCheckResult"`.
|
|
2. **Canonical shape** — exactly the JSON shape from
|
|
`ARCHITECTURE.md` §12.6:
|
|
- `contractId` (string, format uuid).
|
|
- `evaluatedAt` (string, format date-time — ISO-8601).
|
|
- `engine` (enum `[checkov, kyverno, opa]`).
|
|
- `ruleId` (string — e.g. `CKV_AWS_24`, `KYVERNO_NO_PRIVILEGED`,
|
|
`ACDL_TAG_NAMING`).
|
|
- `severity` (enum `[critical, high, medium, low, info]`).
|
|
- `result` (enum `[pass, fail, skipped, error]`).
|
|
- `message` (string, human-readable).
|
|
- `evidence` (object, additionalProperties true — engine-specific
|
|
payload, opaque to the confidence signal per §12.6).
|
|
- `resourceRef` (string — IR-typed resource identifier).
|
|
3. **Required** — all of: `contractId, evaluatedAt, engine, ruleId,
|
|
severity, result, message, resourceRef` (evidence is optional —
|
|
some engines may omit; the confidence signal does not read it).
|
|
4. **`$comment`** — "Canonical PolicyCheckResult (ARCHITECTURE.md
|
|
§12.6). The confidence signal (platform/confidence_signal.py)
|
|
consumes a list of these as its `policy` input; it is
|
|
engine-agnostic. The severity enum drives the severity→penalty
|
|
mapping (critical hard-override, high -0.2, medium -0.05, low
|
|
-0.01, info 0.0). Adapters (checkov_adapter.py, future
|
|
kyverno_adapter) translate native engine output to this shape."
|
|
- **Source citations:**
|
|
- `ARCHITECTURE.md` §12.6 (the canonical shape — copy verbatim into
|
|
the schema).
|
|
- `RESEARCH.md` TARGET 4 (PolicyCheckResult + Checkov adapter).
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.4`
|
|
(then `feat(P07): author schemas/policy_check_result.schema.json (REQ-18 schema half)`)
|
|
- **Self-verify:**
|
|
- [ ] `python3 -m jsonschema schemas/policy_check_result.schema.json`
|
|
exits 0.
|
|
- [ ] A valid instance validates:
|
|
`{contractId:"<uuid>",evaluatedAt:"2026-07-21T12:00:00Z",engine:"checkov",ruleId:"CKV_AWS_24",severity:"high",result:"fail",message:"SG 0.0.0.0/0 on 22",evidence:{resource:"aws_security_group.r1"},resourceRef:"r1"}`.
|
|
- [ ] `severity` enum has exactly `[critical, high, medium, low, info]`.
|
|
- [ ] `result` enum has exactly `[pass, fail, skipped, error]`.
|
|
- [ ] `engine` enum has exactly `[checkov, kyverno, opa]`.
|
|
|
|
#### T-7.5 — Author `adapters/terraform/policy/checkov_adapter.py`
|
|
|
|
- **Owner:** security-engineer (territory:
|
|
`adapters/terraform/policy/**`).
|
|
- **Files owned:** `adapters/terraform/policy/checkov_adapter.py` (new);
|
|
create `adapters/terraform/policy/__init__.py` if needed for import.
|
|
- **What the file must contain** (cite sources):
|
|
1. **Module docstring** — "Translate Checkov JSON output to ACDL
|
|
PolicyCheckResult records (schemas/policy_check_result.schema.json).
|
|
Reads Checkov's JSON output (one framework key, e.g.
|
|
`terraform_plan`), emits a list of PolicyCheckResult dicts.
|
|
Run Checkov with `--soft-fail` so Checkov never exits non-zero; the
|
|
confidence signal decides the gate, not Checkov's exit code."
|
|
2. **`RULE_MAP`** — dict mapping Checkov rule IDs to
|
|
`(ACDL L2 check name, default severity when Checkov omits one)`,
|
|
per RESEARCH.md TARGET 4 table:
|
|
- `CKV_AWS_41, CKV_AWS_45, CKV_AWS_46` → `("secrets-in-plaintext", "high")`.
|
|
- `CKV_AWS_20, CKV_AWS_57` → `("public-ingress", "high")` (S3 public ACL).
|
|
- `CKV_AWS_24, CKV_AWS_25` → `("public-ingress", "medium")` (SG 0.0.0.0/0).
|
|
- `CKV_AWS_1` → `("iam-wildcard", "high")`.
|
|
- `CKV_AWS_40` → `("iam-wildcard", "medium")`.
|
|
- `CKV_AWS_7, CKV_AWS_33` → `("kms-key-reference", "medium")`.
|
|
Unknown Checkov rule IDs → `(rule_id, "info")` fallback.
|
|
3. **`_iso8601_now()`** — UTC now in ISO-8601 `Z` suffix.
|
|
4. **`_to_pcr(checkov_record, contract_id, result_str)`** — map a
|
|
Checkov check record to a PolicyCheckResult dict:
|
|
- `contractId` = the contract_id arg.
|
|
- `evaluatedAt` = `_iso8601_now()`.
|
|
- `engine` = `"checkov"`.
|
|
- `ruleId` = `checkov_record["check_id"]`.
|
|
- `severity` = `checkov_record.get("severity", default_sev).lower()`
|
|
(prefer Checkov's severity if present; fall back to RULE_MAP
|
|
default — A-4.1: spike runs without Prisma Cloud API key).
|
|
- `result` = `{"PASSED":"pass","FAILED":"fail",
|
|
"SKIPPED":"skipped"}.get(result_str, "error")`.
|
|
- `message` = `checkov_record.get("check_name", "")`.
|
|
- `evidence` = `{"file_path":..., "resource":...,
|
|
"resource_address":..., "code_block":...}` (engine-specific,
|
|
opaque to the signal).
|
|
- `resourceRef` = `checkov_record.get("resource_address") or
|
|
checkov_record.get("resource", "")` (defensive — A-4.1 0.15
|
|
residual for field-name drift).
|
|
5. **`adapt(checkov_json_path, contract_id)`** — read the Checkov JSON,
|
|
iterate frameworks, for each framework's `results` body iterate
|
|
`passed_checks` (→ "PASSED"), `failed_checks` (→ "FAILED"),
|
|
`skipped_checks` (→ "SKIPPED"), build the list. Tolerate both
|
|
`{framework: {results: {...}}}` and `{framework: {...}}` shapes
|
|
(Checkov minor-version drift).
|
|
6. **`_emit_tag_naming_skipped(contract_id)`** — per D-043, emit a
|
|
single `PolicyCheckResult` with `ruleId: "ACDL_TAG_NAMING"`,
|
|
`result: "skipped"`, `severity: "info"`, `engine: "checkov"`,
|
|
`message: "tag/naming check deferred to v1.2 (D-043)"`. This
|
|
guarantees the confidence signal's `policy` input is non-empty even
|
|
if Checkov finds nothing (the "all six inputs present" dev gate).
|
|
7. **`adapt()` appends the tag/naming skipped record** to the output
|
|
list after the Checkov pass.
|
|
8. **`__main__`** — `checkov_path, contract_id = sys.argv[1],
|
|
sys.argv[2]`; `print(json.dumps(adapt(...), indent=2))`. CLI:
|
|
`python3 adapters/terraform/policy/checkov_adapter.py checkov.json
|
|
<contract-uuid>`.
|
|
9. **No external deps** — stdlib only (`json`, `sys`, `datetime`).
|
|
Checkov is invoked by the workflow, not by this module; this module
|
|
reads Checkov's JSON output file.
|
|
10. **Spike-scope note** — a module-level comment: "Spike scope (D-043):
|
|
tag/naming is a single SKIPPED record. A custom Checkov YAML rule
|
|
for tag presence lands in v1.2."
|
|
- **Source citations:**
|
|
- `ARCHITECTURE.md` §12.4 (policy toolchain — Checkov for the L2
|
|
checks + tag/naming) + §12.6 (PolicyCheckResult shape).
|
|
- `RESEARCH.md` TARGET 4 (Checkov adapter sketch + rule→severity map
|
|
— the executor uses this as the basis; must be self-contained).
|
|
- `PROJECT.md` D-043 (tag/naming deferred — emit SKIPPED).
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.5`
|
|
(then `feat(P07): author checkov_adapter.py (REQ-18 adapter half; D-043 tag/naming skipped)`)
|
|
- **Self-verify:**
|
|
- [ ] `python3 -m py_compile
|
|
adapters/terraform/policy/checkov_adapter.py` exits 0.
|
|
- [ ] A synthetic Checkov JSON fixture (a single failed CKV_AWS_24)
|
|
produces a PolicyCheckResult with `severity:"medium"`,
|
|
`result:"fail"`, `ruleId:"CKV_AWS_24"` + the appended
|
|
`ACDL_TAG_NAMING` skipped record.
|
|
- [ ] Output validates against `schemas/policy_check_result.schema.json`
|
|
(each record).
|
|
- [ ] `RULE_MAP` has all 11 Checkov rule IDs from RESEARCH.md TARGET 4.
|
|
- [ ] No non-stdlib imports.
|
|
|
|
#### T-7.6 — Author `platform/audit_ledger_design.md`
|
|
|
|
- **Owner:** security-engineer (territory:
|
|
`platform/audit_ledger_design.md`).
|
|
- **Files owned:** `platform/audit_ledger_design.md` (new).
|
|
- **What the file must contain** (cite sources):
|
|
1. **Header** — "ACDL Tiered Audit Ledger Design (REQ-20). Status:
|
|
design authored in Phase 07; spike implements the v1.0 hash chain +
|
|
outbox write (D-041); v1.2 builds out Object Lock + JWS + async
|
|
worker + DLQ."
|
|
2. **Tiered ledger overview** — the three tiers:
|
|
- **Cold tier (source of truth):** S3 with Object Lock in
|
|
*compliance mode*, 7-year retention (ARCHITECTURE.md §9, RESEARCH
|
|
TARGET 7). No one — including root — can delete/overwrite until
|
|
retention expires.
|
|
- **Hot tier (query index):** `acdl-evidence` audit repo (the v1.0
|
|
demo's path; unchanged). Not part of the chain; a queryable
|
|
mirror. The evidence UI (`evidence-ui/index.html`) reads it.
|
|
- **Outbox (write path):** DynamoDB, RPO=0 (synchronous write before
|
|
submission ack). Single-region in v1 (us-east-1).
|
|
3. **Spike scope (D-041) — what the spike implements:**
|
|
- DynamoDB outbox: table `acdl-outbox`, `PAY_PER_REQUEST` (D-044),
|
|
PK `contractId`, SK `eventType#eventTs`, TTL `expire_at` = now +
|
|
365d (1-year storage per §8).
|
|
- `prev_event_hash` chain: SHA-256 over canonical JSON
|
|
(`json.dumps(event, sort_keys=True, separators=(",",":"))`),
|
|
lifted from the v1.0 demo's `evidence_writer.py`. Auto-genesis:
|
|
first event has `prev_hash="GENESIS"`.
|
|
- Synchronous write via boto3 `put_item` (strong-consistent by
|
|
default). No separate async worker / DLQ in the spike (RTO =
|
|
workflow re-run; A-5.2).
|
|
- Mirror to `acdl-evidence` (unchanged from v1.0 — the finalize
|
|
step commits `audit.json`).
|
|
- Spike evidence event shape (RESEARCH TARGET 7):
|
|
`{seq, ts, stage, event, prev_hash, hash, contractId,
|
|
environment, stack, score, band}`.
|
|
4. **v1.2 build-out — what Phase 07 designs but the spike defers:**
|
|
- **S3 Object Lock:** bucket `acdl-evidence-lock-<account-id>`,
|
|
Object Lock enabled at creation, compliance mode, 7-yr retention
|
|
(`RetainUntilDate` = now + 7y). The outbox→S3 path is an async
|
|
worker that reads from the outbox and writes to Object Lock.
|
|
- **JWS detached signature (RFC 7515):** the event payload is
|
|
canonical-JSON-serialized, SHA-256 hashed, signed with a private
|
|
key; the signature is stored *detached* alongside the payload.
|
|
Signing key = **platform-level KMS key** (not per-contract —
|
|
RESEARCH A-7.2), rotated **quarterly**. The `jws` field is added
|
|
to the event shape in v1.2.
|
|
- **Async worker + DLQ:** a Lambda (or Gitea Actions scheduled
|
|
workflow) reads the outbox, writes to S3 Object Lock, signs with
|
|
KMS. DLQ = an SQS dead-letter queue for failed writes. RTO = DLQ
|
|
replay.
|
|
- **Daily checkpoints (§9):** a daily job reads the last event hash
|
|
and writes a "checkpoint" event to the ledger (+ optionally to a
|
|
public notarization service). The spike runs in minutes, not
|
|
days — no checkpoint in spike.
|
|
5. **JWS vs chain — orthogonality note:** the `prev_event_hash` chain
|
|
gives ordering/tamper-evidence *within* the log; JWS gives
|
|
authenticity *per event*. The chain is spike-scope; JWS is v1.2.
|
|
6. **Outbox item shape (full, spike+v1.2)** — the DynamoDB item:
|
|
- PK `contractId` (UUID).
|
|
- SK `eventType#eventTs` (e.g. `POLICY_CHECKED#2026-07-21T12:00:00Z`).
|
|
- `payload` (the event body — hash-chained in spike, JWS-signed in
|
|
v1.2).
|
|
- `prev_event_hash` (chain link).
|
|
- `hash` (this event's SHA-256).
|
|
- `approver_qa` (Gitea username of the QA approver; empty in
|
|
dev-only spike; populated on qa-promotion — D-042).
|
|
- `approver_prod` (SRE username; empty in spike).
|
|
- `environment`, `stack`, `score`, `band`.
|
|
- `expire_at` (TTL = now + 365d).
|
|
- v1.2 only: `jws` (detached signature), `checkpoint_ref`.
|
|
7. **RPO/RTO table:**
|
|
- Spike: RPO=0 (sync outbox write), RTO=workflow re-run.
|
|
- v1.2: RPO=0 (sync outbox), RTO=async worker DLQ replay.
|
|
8. **Decision trail** — cite D-041 (spike scope), D-044 (outbox mode +
|
|
TTL), D-042 (approver identities in outbox).
|
|
- **Source citations:**
|
|
- `ARCHITECTURE.md` §9 (audit + evidence stream — locked: S3 Object
|
|
Lock compliance 7-yr + DynamoDB outbox RPO=0 + JWS + chain + daily
|
|
checkpoints).
|
|
- `RESEARCH.md` TARGET 7 (tiered audit ledger — S3 Object Lock +
|
|
JWS + chain; spike minimum).
|
|
- `RESEARCH.md` TARGET 5 (DynamoDB outbox pattern — RPO=0, on-demand,
|
|
PK/SK, TTL).
|
|
- `PROJECT.md` D-041 (spike scope = hash chain + outbox),
|
|
D-044 (outbox mode + TTL), D-042 (approver identities in outbox).
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.6`
|
|
(then `docs(P07): author platform/audit_ledger_design.md (REQ-20; D-041 spike scope)`)
|
|
- **Self-verify:**
|
|
- [ ] File exists, non-empty markdown.
|
|
- [ ] "compliance mode" + "7-year" appear (S3 Object Lock locked).
|
|
- [ ] "RPO = 0" / "RPO=0" appears.
|
|
- [ ] "JWS" + "detached" + "KMS" + "quarterly" appear (v1.2 design).
|
|
- [ ] "prev_event_hash" + "canonical JSON" + "SHA-256" appear (chain).
|
|
- [ ] "daily checkpoint" appears.
|
|
- [ ] "D-041", "D-044", "D-042" appear (decision trail).
|
|
- [ ] A clear "Spike scope" vs "v1.2 build-out" section boundary.
|
|
|
|
#### T-7.7 — Author `platform/hitl_matrix_design.md`
|
|
|
|
- **Owner:** security-engineer (territory:
|
|
`platform/hitl_matrix_design.md`).
|
|
- **Files owned:** `platform/hitl_matrix_design.md` (new).
|
|
- **What the file must contain** (cite sources):
|
|
1. **Header** — "ACDL Human-in-the-Loop Matrix + Separation-of-Duties
|
|
Design (REQ-21). Status: design authored in Phase 07; v1.2 wires
|
|
the gates. Spike (Phases 08-10) is dev-only; HITL is not exercised."
|
|
2. **Gate model (§10.1)** — pre-execution gates. The contract is held
|
|
in "validated but not applied" 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.
|
|
3. **Gitea-specific gate mechanics (D-042)** — Gitea has **no
|
|
Environments API** and ignores `environment:` blocks (v1.0 D-013;
|
|
re-confirmed RESEARCH TARGET 8). 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.
|
|
4. **Reviewer routing (§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's the outbox check in T-7.8).
|
|
5. **Full 8-concern attestation matrix (§10.4)** — the complete table
|
|
from `docs/architecture.md` §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/contract-declared) 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 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 |
|
|
6. **Timeout behavior (§10.5)** — the table:
|
|
| 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 outbox for `PENDING_ATTESTATION` events with `ts` older
|
|
than 1/2 business days and emits the warn/freeze events. Not
|
|
implemented in the spike (A-8.3).
|
|
7. **Rejection and rollback (§10.6)** — rejection returns the contract
|
|
to `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 — T-7.3). 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.
|
|
8. **Separation of duties (§10.3) — pointer to the .py** — a section
|
|
that describes the check sequence (dev→qa writes approver_qa to
|
|
outbox; qa→prod reads approver_qa + new gitea.actor; if equal →
|
|
block + emit SEPARATION_OF_DUTIES_VIOLATION + route halt artifact
|
|
to SRE on-call) and points at `platform/separation_of_duties.py`
|
|
(T-7.8) for the implementation.
|
|
9. **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.
|
|
10. **Decision trail** — cite D-042 (approver identity = gitea.actor),
|
|
D-013 (v1.0 workflow_dispatch fallback, re-used).
|
|
- **Source citations:**
|
|
- `docs/architecture.md` §10 (HITL mechanics — §10.1 gate model,
|
|
§10.2 routing, §10.3 SoD, §10.4 full matrix, §10.5 timeout, §10.6
|
|
rejection).
|
|
- `ARCHITECTURE.md` §10 (ACDL-repo operating copy — same content,
|
|
refined).
|
|
- `RESEARCH.md` TARGET 8 (HITL matrix + SoD — Gitea adaptation,
|
|
design doc sketch).
|
|
- `PROJECT.md` D-042 (approver identity = gitea.actor).
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.7`
|
|
(then `docs(P07): author platform/hitl_matrix_design.md (REQ-21; D-042 gitea.actor)`)
|
|
- **Self-verify:**
|
|
- [ ] File exists, non-empty markdown.
|
|
- [ ] All 8 concerns from §10.4 appear (functional, performance,
|
|
security, NFRs, op-readiness, incident response, capacity/cost,
|
|
resilience).
|
|
- [ ] "gitea.actor" appears (D-042).
|
|
- [ ] "workflow_dispatch" + "approve_qa" + "approve_prod" + "approve_dr"
|
|
appear.
|
|
- [ ] "1 business day" + "2 business days" + "PENDING_ATTESTATION"
|
|
appear.
|
|
- [ ] "supersedes" + "HELD" + "PROMOTION_REJECTED" appear.
|
|
- [ ] "CODEOWNERS" appears.
|
|
- [ ] "no Environments API" / "no Environments API" appears.
|
|
|
|
#### T-7.8 — Author `platform/separation_of_duties.py`
|
|
|
|
- **Owner:** security-engineer (territory:
|
|
`platform/separation_of_duties.py`).
|
|
- **Files owned:** `platform/separation_of_duties.py` (new).
|
|
- **What the file must contain** (cite sources):
|
|
1. **Module docstring** — "Check that qaApprover != prodApprover for a
|
|
contract (ARCHITECTURE.md §10.3, D-042). Reads `approver_qa` from
|
|
the DynamoDB outbox for the contractId, compares to the
|
|
prod-dispatch `gitea.actor`. Blocks on equality, emits
|
|
`SEPARATION_OF_DUTIES_VIOLATION`, routes a halt artifact to SRE
|
|
on-call. Spike: dev-only — the check is a no-op stub that returns
|
|
(True, 'dev-only') because the spike does not exercise HITL
|
|
(A-8.1)."
|
|
2. **`check(outbox_client, contract_id, current_prod_approver)`**
|
|
function:
|
|
- `item = outbox_client.get(contract_id)` — reads the outbox item
|
|
for the contractId (the outbox shape is defined in
|
|
`platform/audit_ledger_design.md` T-7.6: PK `contractId`, SK
|
|
`eventType#eventTs`, attributes include `approver_qa`).
|
|
- If `item is None`: return `(True, "no prior approver (first
|
|
promotion)")` — dev→qa has no SoD check (the first promotion has
|
|
no prior approver to compare against).
|
|
- `qa_approver = item.get("approver_qa")`. If `not qa_approver`:
|
|
return `(True, "no QA approver recorded (dev-only spike)")` —
|
|
the spike's dev-only path.
|
|
- If `qa_approver == current_prod_approver`: return
|
|
`(False, f"SEPARATION_OF_DUTIES_VIOLATION: qaApprover==prodApprover=={qa_approver}")`
|
|
— the platform blocks prod promotion, writes a
|
|
`SEPARATION_OF_DUTIES_VIOLATION` event to the evidence stream
|
|
(the pipeline step does the write; this function returns the
|
|
verdict), and routes a halt artifact to SRE on-call.
|
|
- Else: return `(True, "distinct")`.
|
|
3. **`route_halt_artifact(contract_id, violation_reason, oncall_client)`**
|
|
— a helper that routes the halt artifact to SRE on-call (spike:
|
|
stub that logs the artifact; v1.2 wires a real pager). Not invoked
|
|
in the dev-only spike.
|
|
4. **Spike-scope note** — a module-level comment: "Spike scope
|
|
(A-8.1): the spike is dev-only (REQ-27 contract has
|
|
environment: dev); HITL is not exercised. This module is authored
|
|
to its full v1.2 shape but the spike calls it with
|
|
current_prod_approver=None and a None outbox_client — the check
|
|
returns (True, 'dev-only')."
|
|
5. **No external deps** — stdlib only (`typing` for the signature).
|
|
`outbox_client` is a duck-typed boto3-like client (has `.get(pk)`);
|
|
the spike passes a stub. No `import boto3` (the pipeline step owns
|
|
the boto3 client; this function receives it).
|
|
- **Source citations:**
|
|
- `ARCHITECTURE.md` §10.3 (separation of duties — identity
|
|
distinctness sequence).
|
|
- `RESEARCH.md` TARGET 8 (SoD sketch — the executor uses this as the
|
|
basis).
|
|
- `PROJECT.md` D-042 (approver identity = gitea.actor; the check reads
|
|
approver_qa from the outbox, compares to the prod-dispatch
|
|
gitea.actor).
|
|
- `platform/audit_ledger_design.md` T-7.6 (the outbox item shape —
|
|
PK, SK, `approver_qa` attribute).
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.8`
|
|
(then `feat(P07): author platform/separation_of_duties.py (REQ-21; D-042; reads outbox approver_qa)`)
|
|
- **Self-verify:**
|
|
- [ ] `python3 -m py_compile platform/separation_of_duties.py` exits 0.
|
|
- [ ] `check(None, "any", None)` returns `(True, "no prior approver...")`.
|
|
- [ ] `check(stub_with_approver_qa("alice"), "cid", "alice")` returns
|
|
`(False, "...SEPARATION_OF_DUTIES_VIOLATION...")`.
|
|
- [ ] `check(stub_with_approver_qa("alice"), "cid", "bob")` returns
|
|
`(True, "distinct")`.
|
|
- [ ] No `import boto3` (duck-typed client).
|
|
- [ ] "SEPARATION_OF_DUTIES_VIOLATION" string is present.
|
|
|
|
---
|
|
|
|
### Wave 4 — backend-engineer + security-engineer co-authored: REQ-19 confidence_signal.py
|
|
|
|
One file, one commit, co-authored (PERSONAS.md conflict resolution:
|
|
backend owns the 6-input weighted sum + per-env thresholds; security
|
|
owns the severity→penalty + critical-override). Depends on T-7.4
|
|
(PolicyCheckResult schema — the confidence signal consumes a list of
|
|
PolicyCheckResult records as its `policy` input).
|
|
|
|
#### T-7.9 — Author `platform/confidence_signal.py`
|
|
|
|
- **Owners:** backend-engineer (6-input weighted sum + per-env
|
|
thresholds + `compute()` orchestration + `Signal` dataclass) +
|
|
security-engineer (severity→penalty mapping + critical-override
|
|
semantics + the policy-input penalty loop). Co-authored in one commit.
|
|
lead-developer arbitrates if the two disagree (PERSONAS.md conflict
|
|
resolution).
|
|
- **Files owned:** `platform/confidence_signal.py` (new).
|
|
- **What the file must contain** (cite sources):
|
|
1. **Module docstring** — the 6 inputs + weights + severity→penalty,
|
|
verbatim from D-040 + ARCHITECTURE.md §8:
|
|
```
|
|
Inputs (weights sum to 1.0, D-040):
|
|
1. policy_results (0.30) — list[PolicyCheckResult] (schemas/policy_check_result.schema.json)
|
|
2. validation (0.25) — {schema: bool, ir_resolved: bool, tf_validated: bool, tf_planned: bool}
|
|
3. freshness (0.10) — {age_days: float, max_age_days: float}
|
|
4. source (0.15) — {submitter: str, commit_sha: str, signed: bool}
|
|
5. history (0.10) — {prior_rollbacks: int, prior_policy_fails: int}
|
|
6. nfrs (0.10) — {declared: list[str], conformance: float|None}
|
|
Severity → penalty (locked, §8):
|
|
critical → hard override (score = 0, block)
|
|
high → -0.20
|
|
medium → -0.05
|
|
low → -0.01
|
|
info → 0.00
|
|
Per-env thresholds (locked, §8): dev 0.50, qa 0.75, prod 0.90, dr 0.95.
|
|
Output: {score, band, perInput, reasonCodes}.
|
|
Halt with explicit reason on missing input (§8).
|
|
```
|
|
2. **`WEIGHTS`** dict — `{"policy":0.30, "validation":0.25,
|
|
"freshness":0.10, "source":0.15, "history":0.10, "nfrs":0.10}`
|
|
(D-040). Sum = 1.0.
|
|
3. **`PENALTY`** dict — `{"critical":None, "high":0.20, "medium":0.05,
|
|
"low":0.01, "info":0.0}` (`None` = hard override — security-engineer
|
|
territory).
|
|
4. **`THRESHOLDS`** dict — `{"dev":0.50, "qa":0.75, "prod":0.90,
|
|
"dr":0.95}` (ARCHITECTURE.md §8).
|
|
5. **`Signal` dataclass** — `score: float`, `band: Literal["pass",
|
|
"warn", "block"]`, `perInput: dict`, `reasonCodes: list[str]`.
|
|
6. **`_per_input_score(name, raw)`** — normalize a raw input to
|
|
`[0,1]`. Spike cold-start: unknown/missing inputs → `0.5` (A-6.2)
|
|
with a `INPUT_MISSING:<name>` reason code. The per-input scoring
|
|
rules (documented inline):
|
|
- `policy`: weighted by severity — pass=1.0, skipped=1.0 (treat as
|
|
pass — the ACDL_TAG_NAMING skipped record is neutral), fail=0.0
|
|
per finding, averaged. (The severity penalty is applied
|
|
separately at the score level — see `compute()`.)
|
|
- `validation`: `1.0` if all four bools true, else proportional.
|
|
- `freshness`: `1.0 - (age_days / max_age_days)` clamped to `[0,1]`.
|
|
- `source`: `1.0` if `submitter` + `commit_sha` present (dev gate is
|
|
*presence*, not *identity* — RESEARCH A-6.1).
|
|
- `history`: `1.0 - (prior_rollbacks * 0.2 + prior_policy_fails *
|
|
0.1)` clamped to `[0,1]`. Spike cold-start (first submission) =
|
|
`0.5` (no history — A-6.2).
|
|
- `nfrs`: `1.0` if `conformance` present, else `0.5` (no NFRs
|
|
declared — spike cold-start A-6.2).
|
|
7. **`compute(contract_id, environment, inputs)`** — the orchestrator
|
|
(backend-engineer):
|
|
- If `set(inputs.keys()) != set(WEIGHTS.keys())`: return
|
|
`Signal(0.0, "block", {}, [f"INPUT_MISSING:{m}" for m in
|
|
missing])` — halt with explicit reason on missing input (§8).
|
|
- Build `per_input`, `reasons`, `base` (weighted sum) via
|
|
`_per_input_score` for each input.
|
|
- **Severity penalty loop (security-engineer):** for each `pcr` in
|
|
`inputs.get("policy_results", [])` where `pcr["result"] == "fail"`:
|
|
- If `PENALTY[pcr["severity"]] is None` (critical): return
|
|
`Signal(0.0, "block", per_input, reasons +
|
|
[f"CRITICAL_OVERRIDE:{pcr['ruleId']}"])` — **hard override**,
|
|
one critical finding blocks regardless of all other inputs
|
|
(§8).
|
|
- Else: `penalty += PENALTY[pcr["severity"]]`.
|
|
- `score = max(0.0, min(1.0, base - penalty))` (clamp to `[0,1]`).
|
|
- `threshold = THRESHOLDS[environment]`.
|
|
- `band = "pass" if score >= threshold else ("block" if score <
|
|
threshold - 0.10 else "warn")` (the warn band is 0.10 wide —
|
|
A-6.3).
|
|
- **Dev override:** `if environment == "dev" and band == "warn":
|
|
band = "block"` — dev is autonomous, no reviewer to handle a warn
|
|
(A-6.3).
|
|
- Return `Signal(score, band, per_input, reasons)`.
|
|
8. **`__main__`** — a tiny CLI for spike invocation: read a JSON file
|
|
of inputs + environment, print the Signal as JSON. Spike workflow
|
|
calls `python3 platform/confidence_signal.py <inputs.json> dev`.
|
|
9. **No external deps** — stdlib only (`dataclasses`, `typing`,
|
|
`json`, `sys`). The PolicyCheckResult records arrive as plain dicts
|
|
(the Checkov adapter emits dicts, not a typed class).
|
|
10. **Spike cold-start note** — a module-level comment: "Spike cold-
|
|
start (A-6.2): inputs 3 (freshness), 5 (history), 6 (nfrs) are
|
|
'present + neutral 0.5' because the spike is the first submission
|
|
with no history and no declared NFRs. The gate is *presence*, not
|
|
*conformance* — the 'all six inputs present' dev gate (§5) is
|
|
satisfied by non-null per-input scores."
|
|
- **Source citations:**
|
|
- `ARCHITECTURE.md` §8 (confidence signal — 6 inputs, weighted sum,
|
|
per-env thresholds, severity→penalty, output shape, halt-on-missing).
|
|
- `RESEARCH.md` TARGET 6 (6-input confidence signal sketch — the
|
|
executor uses this as the basis; must be self-contained + match
|
|
ARCHITECTURE.md).
|
|
- `PROJECT.md` D-040 (the 6 inputs + weights frozen for v1).
|
|
- `schemas/policy_check_result.schema.json` T-7.4 (the `policy_results`
|
|
input is a list of these records).
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: backend-engineer+security-engineer, task: T-7.9`
|
|
(then `feat(P07): author platform/confidence_signal.py (REQ-19; D-040 6 inputs; co-authored backend+security)`)
|
|
- **Self-verify:**
|
|
- [ ] `python3 -m py_compile platform/confidence_signal.py` exits 0.
|
|
- [ ] `WEIGHTS` sums to 1.0; values match D-040 exactly.
|
|
- [ ] `PENALTY["critical"] is None` (hard override sentinel).
|
|
- [ ] `THRESHOLDS` matches dev 0.50 / qa 0.75 / prod 0.90 / dr 0.95.
|
|
- [ ] A missing-input call returns `Signal(0.0, "block", ...,
|
|
["INPUT_MISSING:..."])`.
|
|
- [ ] A critical-fail PolicyCheckResult hard-overrides to `0.0, block`
|
|
regardless of other inputs.
|
|
- [ ] A dev `warn` band is flipped to `block` (autonomous, no
|
|
reviewer).
|
|
- [ ] A cold-start spike input set (all 6 present, policy_results =
|
|
[the ACDL_TAG_NAMING skipped record], validation all true,
|
|
freshness/source/history/nfrs neutral 0.5) produces a `pass`
|
|
for dev (score ≥ 0.50).
|
|
- [ ] No non-stdlib imports.
|
|
|
|
---
|
|
|
|
### Wave 5 — lead-developer: verify_phase07.sh + traceability
|
|
|
|
The phase gate. Depends on all prior waves (the verify script asserts
|
|
all 6 files exist + validate). Updates the traceability tables in
|
|
REQUIREMENTS.md + ROADMAP.md.
|
|
|
|
#### T-7.10 — Author `scripts/verify_phase07.sh` + traceability update
|
|
|
|
- **Owner:** lead-developer (territory: `scripts/verify_phase*.sh`,
|
|
`.ciagent/**` traceability).
|
|
- **Files owned:** `scripts/verify_phase07.sh` (new) +
|
|
`.ciagent/REQUIREMENTS.md` (traceability table edit) +
|
|
`.ciagent/ROADMAP.md` (Phase 07 status edit).
|
|
- **What the verify script must assert** (cite the must_haves in this
|
|
PLAN.md frontmatter):
|
|
1. **All 6 schema/design files exist:**
|
|
- `schemas/ir.schema.json`
|
|
- `schemas/policy_check_result.schema.json`
|
|
- `schemas/contract.schema.json`
|
|
- `platform/confidence_signal.py`
|
|
- `platform/audit_ledger_design.md`
|
|
- `platform/hitl_matrix_design.md`
|
|
- `adapters/terraform/policy/checkov_adapter.py`
|
|
- `platform/separation_of_duties.py`
|
|
- `docs/architecture-v1.0.md`
|
|
(9 files — the 6 REQ-mapped files + checkov_adapter + SoD + the
|
|
snapshot; count carefully: REQ-17→1, REQ-18→2, REQ-19→1, REQ-20→1,
|
|
REQ-21→2, REQ-22→1, REQ-16→1 = 9 files total.)
|
|
2. **The 3 JSON Schemas validate** as JSON Schema draft 2020-12:
|
|
```bash
|
|
python3 -m jsonschema schemas/ir.schema.json && \
|
|
python3 -m jsonschema schemas/policy_check_result.schema.json && \
|
|
python3 -m jsonschema schemas/contract.schema.json
|
|
```
|
|
(If `python3 -m jsonschema` is unavailable, fall back to `ajv
|
|
compile -s schemas/<file>` — note this in the script.)
|
|
3. **The 3 .py files py_compile:**
|
|
```bash
|
|
python3 -m py_compile platform/confidence_signal.py \
|
|
platform/separation_of_duties.py \
|
|
adapters/terraform/policy/checkov_adapter.py
|
|
```
|
|
4. **The 3 .md design files are non-empty** (markdown presence):
|
|
```bash
|
|
test -s platform/audit_ledger_design.md && \
|
|
test -s platform/hitl_matrix_design.md && \
|
|
test -s docs/architecture-v1.0.md
|
|
```
|
|
5. **All 11 decision IDs + OpenTofu are present in PROJECT.md:**
|
|
```bash
|
|
for id in W1.A W1.B W2.A W3.D W3.E BA.A BA.B BA.C BA.D BA.E BA.F; do
|
|
grep -q "$id" .ciagent/PROJECT.md || FAIL "missing $id"
|
|
done
|
|
grep -qi "opentofu" .ciagent/PROJECT.md || FAIL "missing OpenTofu"
|
|
```
|
|
6. **`docs/architecture-v1.0.md` exists + status is v1.0:**
|
|
```bash
|
|
test -f docs/architecture-v1.0.md
|
|
grep -q "v1.0" docs/architecture-v1.0.md | head -1 # status line
|
|
```
|
|
7. **D-040, D-041, D-042, D-043, D-044 present in PROJECT.md** (the
|
|
Phase 07-relevant decisions):
|
|
```bash
|
|
for d in D-040 D-041 D-042 D-043 D-044; do
|
|
grep -q "$d" .ciagent/PROJECT.md || FAIL "missing $d"
|
|
done
|
|
```
|
|
8. **The spike contract validates against the contract schema** (a
|
|
cross-file consistency check — the spike contract is the one
|
|
REQ-27 will use):
|
|
```bash
|
|
# Write a minimal dev contract to a temp file, validate against
|
|
# schemas/contract.schema.json. (The real contracts/spike.yaml
|
|
# lands in Phase 10.)
|
|
echo '{"stack":"l2-static-asset","environment":"dev","inputs":{"bucket_name":"x","region":"us-east-1"}}' \
|
|
> /tmp/spike-contract.json
|
|
python3 -c "import jsonschema, json; \
|
|
jsonschema.validate(json.load(open('/tmp/spike-contract.json')), \
|
|
json.load(open('schemas/contract.schema.json')))"
|
|
```
|
|
9. **A minimal IR instance validates against the IR schema:**
|
|
```bash
|
|
echo '{"version":"1.0.0","stack":{"name":"l2-static-asset","kind":"l2","depth":1},"resources":[{"id":"s3","type":"aws:s3:bucket","module":"l1-s3@1.0.0","inputs":{"bucket_name":"x","region":"us-east-1"}}]}' \
|
|
> /tmp/spike-ir.json
|
|
python3 -c "import jsonschema, json; \
|
|
jsonschema.validate(json.load(open('/tmp/spike-ir.json')), \
|
|
json.load(open('schemas/ir.schema.json')))"
|
|
```
|
|
10. **Exit 0 on success, non-zero on any failure** (with a clear
|
|
`FAIL: <reason>` message per assertion). Print `VERIFIED — Phase
|
|
07: architecture v1.0 finalized; 6 files authored + 11 decisions
|
|
resolved` on success.
|
|
- **Traceability update** (in the same commit or a follow-up):
|
|
- `.ciagent/REQUIREMENTS.md` traceability table: REQ-16..22 →
|
|
`complete (v1.1.2)` (the Phase 07 ship tag — Phase 06 was v1.1.1).
|
|
- `.ciagent/ROADMAP.md` Phase 07 status: `pending` → `complete
|
|
(v1.1.2)`.
|
|
- **Source citations:**
|
|
- This PLAN.md's must_haves (the assertions enumerate them).
|
|
- `PERSONAS.md` verification_toolchain (typecheck/test/build
|
|
substitutes — `python3 -m jsonschema`, `py_compile`,
|
|
`scripts/verify_phaseNN.sh`).
|
|
- **Commit message:**
|
|
`phase: 7, status: plan-as-execute, persona: lead-developer, task: T-7.10`
|
|
(then `verify(P07): VERIFIED — 6 files authored, 11 decisions resolved, architecture v1.0`)
|
|
(then `docs(P07): post-ship traceability + roadmap update (v1.1.2)`)
|
|
- **Self-verify:**
|
|
- [ ] `scripts/verify_phase07.sh` exits 0 when run from repo root.
|
|
- [ ] The script fails (non-zero) if any of the 9 files is missing.
|
|
- [ ] The script fails if any of the 3 JSON Schemas is invalid JSON
|
|
Schema.
|
|
- [ ] The script fails if any of the 3 .py files fails py_compile.
|
|
- [ ] The script fails if any of the 11 decision IDs is missing from
|
|
PROJECT.md.
|
|
- [ ] The script fails if `docs/architecture-v1.0.md` does not say
|
|
`v1.0` in its status line.
|
|
- [ ] REQUIREMENTS.md traceability shows REQ-16..22 complete.
|
|
- [ ] ROADMAP.md Phase 07 status is complete.
|
|
|
|
---
|
|
|
|
## Wave ordering + dependencies (summary)
|
|
|
|
```
|
|
Wave 1 (lead-developer) T-7.1 architecture-v1.0.md (umbrella; references decisions)
|
|
↓
|
|
Wave 2 (platform + backend) T-7.2 ir.schema.json (platform-engineer)
|
|
T-7.3 contract.schema.json (backend-engineer) [parallel with T-7.2]
|
|
↓
|
|
Wave 3 (security-engineer) T-7.4 policy_check_result.schema.json
|
|
T-7.5 checkov_adapter.py (depends T-7.4)
|
|
T-7.6 audit_ledger_design.md
|
|
T-7.7 hitl_matrix_design.md
|
|
T-7.8 separation_of_duties.py (depends T-7.6 outbox shape)
|
|
↓
|
|
Wave 4 (backend + security) T-7.9 confidence_signal.py (depends T-7.4 PolicyCheckResult; co-authored)
|
|
↓
|
|
Wave 5 (lead-developer) T-7.10 verify_phase07.sh + traceability (depends all prior)
|
|
```
|
|
|
|
**Parallelizable within a wave:**
|
|
- Wave 2: T-7.2 (IR, platform-engineer) || T-7.3 (contract,
|
|
backend-engineer) — independent schemas, different owners, no shared
|
|
files.
|
|
|
|
**Sequential within a wave (same persona or file dependency):**
|
|
- Wave 3: T-7.4 → T-7.5 (adapter consumes the schema it emits) → T-7.6
|
|
→ T-7.7 → T-7.8 (SoD reads the outbox shape T-7.6 documents). All
|
|
security-engineer; one persona, sequential to avoid territory
|
|
friction.
|
|
|
|
**Cross-wave dependencies:**
|
|
- Wave 4 → Wave 3 T-7.4 (confidence_signal consumes PolicyCheckResult).
|
|
- Wave 4 → Wave 3 T-7.6 (confidence_signal's policy input shape is
|
|
informed by the outbox/ledger design — not a hard dependency, but
|
|
consistency).
|
|
- Wave 5 → all prior waves (verify script asserts all files).
|
|
|
|
---
|
|
|
|
## Per-file content checklist (executor self-verify)
|
|
|
|
### `schemas/ir.schema.json` (REQ-17, T-7.2)
|
|
- [ ] JSON Schema draft 2020-12 (`$schema` + `$id` + `title`).
|
|
- [ ] Top-level `required: [version, stack, resources]`.
|
|
- [ ] `stack.depth` integer min 1 max 5.
|
|
- [ ] `stack.name` pattern `^l[12]-[a-z][a-z0-9-]*$`.
|
|
- [ ] `stack.kind` enum `[l1, l2]`.
|
|
- [ ] `resource` required `[id, type, module, inputs]`.
|
|
- [ ] `resource.module` pattern `^l1-[a-z][a-z0-9-]*@\d+\.\d+\.\d+$`
|
|
(name@semver per W3.D).
|
|
- [ ] `resource.parent` (single parent per child, ARCHITECTURE.md §12.1).
|
|
- [ ] `resource.outputs` (typed outputSpec).
|
|
- [ ] `resource.nfrs` (opaque to adapter, consumed by confidence NFR
|
|
input).
|
|
- [ ] `relationship.kind` enum `[parent, depends_on, uses_output]`.
|
|
- [ ] `relationship.shared_keyword` reserved for v2 (present, unused).
|
|
- [ ] **Substrate-agnostic invariant:** no Terraform-block keywords
|
|
(`variable`, `output` as block, `aws_` provider prefixes) in the
|
|
schema body; `type` values are IR types (`aws:s3:bucket`), not
|
|
Terraform types (`aws_s3_bucket`).
|
|
- [ ] `$comment` explains the v1 round-trip to Terraform.
|
|
- [ ] `python3 -m jsonschema` validates the schema itself.
|
|
- [ ] A minimal valid IR instance validates against it.
|
|
|
|
### `schemas/policy_check_result.schema.json` (REQ-18, T-7.4)
|
|
- [ ] JSON Schema draft 2020-12.
|
|
- [ ] Canonical shape from ARCHITECTURE.md §12.6: `contractId`,
|
|
`evaluatedAt`, `engine`, `ruleId`, `severity`, `result`,
|
|
`message`, `evidence`, `resourceRef`.
|
|
- [ ] `engine` enum `[checkov, kyverno, opa]`.
|
|
- [ ] `severity` enum `[critical, high, medium, low, info]`.
|
|
- [ ] `result` enum `[pass, fail, skipped, error]`.
|
|
- [ ] `evidence` object, additionalProperties true (opaque to signal).
|
|
- [ ] `python3 -m jsonschema` validates; a valid instance passes.
|
|
|
|
### `adapters/terraform/policy/checkov_adapter.py` (REQ-18, T-7.5)
|
|
- [ ] Module docstring (Checkov JSON → PolicyCheckResult).
|
|
- [ ] `RULE_MAP` with all 11 Checkov rule IDs (CKV_AWS_41/45/46/20/57/
|
|
24/25/1/40/7/33).
|
|
- [ ] `_to_pcr()` maps severity (prefer Checkov's, fall back to
|
|
RULE_MAP default).
|
|
- [ ] `_emit_tag_naming_skipped()` emits the ACDL_TAG_NAMING SKIPPED
|
|
record (D-043).
|
|
- [ ] `adapt()` tolerates both Checkov JSON shapes
|
|
(`{framework:{results:{...}}}` and `{framework:{...}}`).
|
|
- [ ] `__main__` CLI: `checkov_adapter.py <checkov.json> <contract-id>`.
|
|
- [ ] stdlib only (no `import boto3` / no third-party).
|
|
- [ ] `python3 -m py_compile` exits 0.
|
|
- [ ] Output records validate against policy_check_result.schema.json.
|
|
|
|
### `schemas/contract.schema.json` (REQ-22, T-7.3)
|
|
- [ ] JSON Schema draft 2020-12.
|
|
- [ ] Top-level `required: [stack, environment]` (dev minimum).
|
|
- [ ] `stack` pattern `^l2-[a-z][a-z0-9-]*$`.
|
|
- [ ] `environment` enum `[dev, qa, prod, dr]` (no `staging`).
|
|
- [ ] `inputs` object, additionalProperties `[string, number, boolean]`,
|
|
always optional.
|
|
- [ ] `validation` (e2eSuite + loadTest), `runbook`, `dashboard`,
|
|
`oncall`, `drDrillRef` all present.
|
|
- [ ] `profile` enum `[developer, agentic]`, default `developer`.
|
|
- [ ] L3B fields `naturalLanguageIntent`, `confidenceAtSubmission`,
|
|
`agentTrace` present, optional everywhere.
|
|
- [ ] `supersedes` (uuid, optional).
|
|
- [ ] `allOf` conditionals: qa→validation required, prod→runbook+
|
|
dashboard+oncall required, dr→drDrillRef required,
|
|
agentic→naturalLanguageIntent required.
|
|
- [ ] Spike contract validates; qa/prod/dr/agentic negative cases fail.
|
|
|
|
### `platform/confidence_signal.py` (REQ-19, T-7.9)
|
|
- [ ] Module docstring with the 6 inputs + weights + severity→penalty.
|
|
- [ ] `WEIGHTS` = `{policy:0.30, validation:0.25, freshness:0.10,
|
|
source:0.15, history:0.10, nfrs:0.10}` (D-040; sums to 1.0).
|
|
- [ ] `PENALTY` = `{critical:None, high:0.20, medium:0.05, low:0.01,
|
|
info:0.0}` (security-engineer; None = hard override).
|
|
- [ ] `THRESHOLDS` = `{dev:0.50, qa:0.75, prod:0.90, dr:0.95}` (§8).
|
|
- [ ] `Signal` dataclass: `score, band, perInput, reasonCodes`.
|
|
- [ ] `_per_input_score()` per-input rules (policy severity-weighted,
|
|
validation all-true, freshness age ratio, source presence, history
|
|
rollback/fail, nfrs conformance or neutral 0.5).
|
|
- [ ] `compute()` halts with `INPUT_MISSING:<name>` on missing input.
|
|
- [ ] `compute()` critical-override: any `fail` + `critical` →
|
|
`Signal(0.0, "block", ..., ["CRITICAL_OVERRIDE:<ruleId>"])`.
|
|
- [ ] `compute()` dev `warn` flipped to `block` (autonomous, no
|
|
reviewer).
|
|
- [ ] Spike cold-start (all 6 present, ACDL_TAG_NAMING skipped,
|
|
validation true, others neutral 0.5) → dev `pass`.
|
|
- [ ] stdlib only; `python3 -m py_compile` exits 0.
|
|
|
|
### `platform/audit_ledger_design.md` (REQ-20, T-7.6)
|
|
- [ ] Header notes Phase 07 design + spike scope (D-041) + v1.2
|
|
build-out.
|
|
- [ ] Three tiers: S3 Object Lock (cold/source of truth), acdl-evidence
|
|
(hot/query index), DynamoDB outbox (write path).
|
|
- [ ] S3 Object Lock: **compliance mode**, **7-year retention**.
|
|
- [ ] DynamoDB outbox: RPO=0, `PAY_PER_REQUEST` (D-044), PK/SK,
|
|
TTL `expire_at` = now + 365d.
|
|
- [ ] `prev_event_hash` chain: SHA-256 over canonical JSON, genesis
|
|
`prev_hash="GENESIS"`.
|
|
- [ ] JWS: detached signature, platform KMS key, quarterly rotation
|
|
(v1.2).
|
|
- [ ] Daily checkpoints (v1.2; spike runs in minutes).
|
|
- [ ] Async worker + DLQ (v1.2; spike RTO = workflow re-run).
|
|
- [ ] Outbox item shape (PK, SK, payload, prev_event_hash, hash,
|
|
approver_qa, approver_prod, environment, stack, score, band,
|
|
expire_at; v1.2: jws, checkpoint_ref).
|
|
- [ ] RPO/RTO table (spike vs v1.2).
|
|
- [ ] Decision trail: D-041, D-044, D-042 cited.
|
|
- [ ] Clear "Spike scope" vs "v1.2 build-out" section boundary.
|
|
- [ ] File non-empty.
|
|
|
|
### `platform/hitl_matrix_design.md` (REQ-21, T-7.7)
|
|
- [ ] Header notes Phase 07 design + v1.2 wiring + spike dev-only.
|
|
- [ ] Pre-execution gate model (§10.1).
|
|
- [ ] Gitea-specific gate mechanics: `workflow_dispatch` +
|
|
`approve_qa`/`approve_prod`/`approve_dr`, `gitea.actor` as
|
|
approver identity (D-042).
|
|
- [ ] "no Environments API" note (Gitea constraint).
|
|
- [ ] CODEOWNERS routing (§10.2).
|
|
- [ ] Full 8-concern matrix from §10.4 (verbatim table).
|
|
- [ ] Timeout 1d warn / 2d freeze (§10.5) + `PENDING_ATTESTATION*`
|
|
states.
|
|
- [ ] Rejection → HELD + `PROMOTION_REJECTED` + `supersedes` (§10.6).
|
|
- [ ] SoD section pointing at `platform/separation_of_duties.py`.
|
|
- [ ] Spike-scope note (dev-only, HITL not exercised).
|
|
- [ ] D-042 cited.
|
|
- [ ] File non-empty.
|
|
|
|
### `platform/separation_of_duties.py` (REQ-21, T-7.8)
|
|
- [ ] Module docstring (qaApprover != prodApprover, D-042, reads outbox).
|
|
- [ ] `check(outbox_client, contract_id, current_prod_approver)` →
|
|
`(ok: bool, reason: str)`.
|
|
- [ ] `None` outbox item → `(True, "no prior approver...")`.
|
|
- [ ] Empty `approver_qa` → `(True, "no QA approver... (dev-only)")`.
|
|
- [ ] `qa_approver == current_prod_approver` → `(False,
|
|
"SEPARATION_OF_DUTIES_VIOLATION: ...")`.
|
|
- [ ] Distinct → `(True, "distinct")`.
|
|
- [ ] `route_halt_artifact()` helper (spike stub).
|
|
- [ ] stdlib only (duck-typed outbox_client; no `import boto3`).
|
|
- [ ] `python3 -m py_compile` exits 0.
|
|
|
|
### `docs/architecture-v1.0.md` (REQ-16, T-7.1)
|
|
- [ ] Status line says `v1.0` (not `v0.2`).
|
|
- [ ] Resolution log table with all 11 IDs (W1.A, W1.B, W2.A, W3.D,
|
|
W3.E, BA.A, BA.B, BA.C, BA.D, BA.E, BA.F, Q1.3).
|
|
- [ ] §13 markers changed from `🟡 OPEN` to `✅ RESOLVED (see
|
|
PROJECT.md)`.
|
|
- [ ] §14 status rewritten to v1.0 + spike/v1.2 note.
|
|
- [ ] §15 new table mapping the 6 files → REQ + owner persona.
|
|
- [ ] `gitea-runner` rename applied (D-046); `act_runner` only in a
|
|
"formerly" note.
|
|
- [ ] Body §§1-12 copied from upstream `docs/architecture.md`.
|
|
|
|
### `scripts/verify_phase07.sh` (T-7.10)
|
|
- [ ] Asserts all 9 files exist (6 REQ-mapped + checkov_adapter + SoD +
|
|
snapshot).
|
|
- [ ] Validates the 3 JSON Schemas (`python3 -m jsonschema` or `ajv`).
|
|
- [ ] py_compiles the 3 .py files.
|
|
- [ ] Markdown presence for the 3 .md files.
|
|
- [ ] Greps PROJECT.md for all 11 decision IDs + OpenTofu.
|
|
- [ ] Confirms `docs/architecture-v1.0.md` exists + status is v1.0.
|
|
- [ ] Confirms D-040..D-044 present in PROJECT.md.
|
|
- [ ] Cross-check: spike contract validates against contract schema;
|
|
minimal IR validates against IR schema.
|
|
- [ ] Exits 0 on success, non-zero with `FAIL:` on any failure.
|
|
- [ ] Prints `VERIFIED — Phase 07: ...` on success.
|
|
- [ ] REQUIREMENTS.md REQ-16..22 → complete.
|
|
- [ ] ROADMAP.md Phase 07 → complete.
|
|
|
|
---
|
|
|
|
## Spike scope vs v1.2 build-out (design-authoring boundary)
|
|
|
|
The Phase 07 designs author the **full target** but clearly mark the
|
|
spike scope (D-041, D-043) vs the v1.2 build-out. This boundary is
|
|
binding for the executor:
|
|
|
|
| Concern | Spike scope (Phases 08-10) | v1.2 build-out (design only in Phase 07) |
|
|
|---------|------------------------------|------------------------------------------|
|
|
| Audit ledger | hash chain + DynamoDB outbox write + acdl-evidence mirror | S3 Object Lock + JWS + KMS key + async worker + DLQ + daily checkpoints |
|
|
| HITL | dev-only (not exercised) | full 8-concern matrix wiring (qa/prod/dr gates, timeout, rejection) |
|
|
| SoD | dev-only stub (returns "distinct") | full outbox read + gitea.actor compare + halt artifact routing |
|
|
| Policy | Checkov + ACDL_TAG_NAMING SKIPPED (D-043) | custom Checkov YAML rule for tag/naming + Kyverno + OPA |
|
|
| L3B | single stub contract submission | 5-skill catalog + real agent |
|
|
| AWS auth | per-run-rotated long-lived key (D-039 waiver) | real OIDC federation (blocked on go-gitea/gitea#36988) |
|
|
| Terraform | `plan` only | `apply` (HITL-gated) |
|
|
| Regions | single-region (us-east-1) | multi-region |
|
|
|
|
The designs must not collapse this boundary — the spike scope is the
|
|
*minimum* that proves the IR commitments hold (REQ-28); the v1.2
|
|
build-out is the production platform. Each design doc has a clear
|
|
"Spike scope" section + a "v1.2 build-out" section.
|
|
|
|
---
|
|
|
|
## Decisions made during planning
|
|
|
|
| ID | Decision | Rationale | Confidence | Alternatives |
|
|
|----|----------|-----------|------------|--------------|
|
|
| D-P07-1 | 5 waves, not 4 | Wave 1 (snapshot) is umbrella — it references the *decisions* (already in PROJECT.md), not the *authored files*, so it can land first and does not need to wait for Waves 2-4. Splitting the security-engineer's 5 files into one wave (Wave 3) keeps one persona's territory coherent. Wave 4 (co-authored confidence_signal) is a distinct co-authorship event, separate from Wave 3. Wave 5 (verify) is the phase gate. | 0.85 | 4 waves (merge Wave 1 into Wave 5; but then the snapshot is authored last, after the files it references — worse); 6 waves (split Wave 3 into two — unnecessary, same persona). |
|
|
| D-P07-2 | Wave 2 parallel (IR || contract) | The two JSON Schemas are independent (no shared `$ref`, no shared file), owned by different personas (platform-engineer + backend-engineer), and both live under `schemas/`. Parallelizing them uses the `parallelization.enabled: true` config + `max_concurrent_agents: 5`. | 0.90 | Sequential (slower, no benefit — no file dependency). |
|
|
| D-P07-3 | Wave 3 sequential (one persona, 5 files) | All 5 files are security-engineer territory. Running them sequentially within the wave avoids territory friction (PERSONAS.md territory enforcement mode = `warn`) and respects the intra-wave file dependencies (T-7.5 → T-7.4; T-7.8 → T-7.6). | 0.85 | Parallel (territory overlap risk; the co-owned `schemas/policy_check_result.schema.json` is authored by security-engineer here but backend-engineer co-owns `schemas/**` — sequential avoids a race). |
|
|
| D-P07-4 | Wave 4 co-authored in one commit | `platform/confidence_signal.py` is co-owned (backend: 6-input weighted sum + thresholds; security: severity→penalty + critical-override). One file, one commit, co-authored. lead-developer arbitrates if the two disagree (PERSONAS.md conflict resolution). | 0.80 | Two commits (backend first, security second) — but that splits a single file across two commits, which is worse for review. |
|
|
| D-P07-5 | verify_phase07.sh cross-checks spike contract + minimal IR against schemas | The verify script does not just assert file presence — it cross-checks that the spike contract (REQ-27's `contracts/spike.yaml` shape) validates against the contract schema, and a minimal IR validates against the IR schema. This catches schema/instance drift early (Phase 10 would otherwise discover it). | 0.85 | Presence-only assertions (weaker; Phase 10 discovers drift later). |
|
|
|
|
---
|
|
|
|
## Cross-persona co-authorship risk
|
|
|
|
Two files are co-owned per PERSONAS.md conflict resolutions:
|
|
|
|
1. **`platform/confidence_signal.py`** (Wave 4, T-7.9) — backend-engineer
|
|
(6-input weighted sum + per-env thresholds + `compute()` orchestration)
|
|
+ security-engineer (severity→penalty + critical-override). **Risk:**
|
|
the two authors must agree on the `PENALTY` dict's sentinel for
|
|
critical (`None` vs a special float vs a separate `hard_override`
|
|
flag) + the `compute()` control flow (where the critical-override
|
|
short-circuit lives). The PLAN.md spec fixes this: `PENALTY["critical"]
|
|
is None` is the sentinel; the critical-override is a short-circuit
|
|
`return` in `compute()` before the score clamp. lead-developer
|
|
arbitrates if the authors disagree (PERSONAS.md). **Mitigation:** one
|
|
commit, co-authored; the PLAN.md content checklist is the contract
|
|
between the two authors.
|
|
|
|
2. **`schemas/policy_check_result.schema.json`** (Wave 3, T-7.4) —
|
|
security-engineer authors it (owns the adapter); backend-engineer
|
|
co-owns `schemas/**` (contract + IR + PolicyCheckResult). **Risk:**
|
|
low — the shape is locked verbatim from ARCHITECTURE.md §12.6; there
|
|
is no design freedom to conflict over. security-engineer authors;
|
|
backend-engineer reviews. **Mitigation:** the §12.6 canonical shape
|
|
is the contract; no co-authoring needed, just review.
|
|
|
|
No other co-authorship risk in Phase 07. The IR schema
|
|
(`schemas/ir.schema.json`) is platform-engineer's (PERSONAS.md conflict
|
|
resolution: platform-engineer owns the IR; backend-engineer owns the
|
|
contract schema + contract→IR resolution — *different* files). The
|
|
contract schema is backend-engineer's alone. The security-engineer's
|
|
five files (Wave 3) are all security-engineer territory with no overlap.
|
|
|
|
---
|
|
|
|
*End of Phase 07 PLAN. Path: `/root/acdl/.ciagent/PLAN.md`.* |