e00393adfd
---ci---
phase: 3
milestone: v1.0
status: clarify
decisions:
- id: D-020
decision: L2 manifest.yaml schema with l1s: list of {name, inputs: map}
rationale: REQ-04 says L2 composes L1s; REQ-05 caps depth at 5 (L2->L1 is depth 1)
confidence: 0.90
alternatives: [graph-of-L1s with edges, single flat list of L1 names with no inputs]
- id: D-021
decision: contract.yaml schema with stack, inputs, optional public-ingress: bool
rationale: REQ-07 cites public-ingress:true as the forbidden key; REQ-08 keys off policy pass/fail
confidence: 0.90
alternatives: [separate policy file, contract-free intent]
- id: D-022
decision: mock_executor.sh writes state.json with l2, l1s array (name+applied+exit_code), contract
rationale: REQ-06 says writes state.json but does not specify shape
confidence: 0.85
alternatives: [per-L1 state files, no state file]
- id: D-023
decision: evidence_writer.py appends to audit.json (JSON array); each event has seq, ts, stage, event, prev_hash, hash (SHA-256 of canonical JSON); genesis has prev_hash=GENESIS, seq=0
rationale: D-005 mandates hash-chained ledger; canonical JSON for deterministic hashing
confidence: 0.90
alternatives: [signed commits only, append-only log file]
- id: D-024
decision: confidence_signal.py prints {score, reason} JSON to stdout; exit 0 always
rationale: REQ-08 literal: base 0.90, drops to 0.40, gate >= 0.50
confidence: 0.95
alternatives: [exit non-zero on fail, separate side-channel file]
- id: D-025
decision: policy_checker.py exits 1 with stdout POLICY_VIOLATION:PUBLIC_INGRESS on public-ingress:true; else exit 0 with stdout POLICY_PASS
rationale: REQ-07 literal
confidence: 0.99
alternatives: []
- id: D-026
decision: l3b_agent_stub.py reads issue body from argv[1] or stdin, applies D-008 keyword map, writes contract.yaml to stdout (or -o <path>)
rationale: D-008 + Act 3 example; L3B must produce same contract format as L3A
confidence: 0.90
alternatives: [fixed-output contract per stack, no -o flag]
---/ci---
Phase 03 has real ambiguities (no schema was specified for L2 manifests,
contract.yaml, audit.json events, or the L3B output format). Seven
decisions logged: D-020 L2 schema, D-021 contract.yaml schema, D-022
state.json shape, D-023 audit.json event + hash chain, D-024
confidence_signal output, D-025 policy_checker output, D-026 l3b_agent
I/O contract.
88 lines
11 KiB
Markdown
88 lines
11 KiB
Markdown
# ACDL — Agentic Cloud Delivery Platform
|
|
|
|
## Vision / Core Value
|
|
|
|
A 30-minute executive demo proving that infrastructure can be delivered **automatically, safely, and with a complete audit trail** — without the usual weeks of manual tickets, reviews, and copy-pasted configuration. Because the demo runs entirely on **local stubs** (no AWS/GCP/Azure, no external LLM APIs), it shows intent and safety behavior rather than provisioning real cloud resources.
|
|
|
|
## Objective
|
|
|
|
Build a runnable demo (Linux + GitHub/Gitea Actions) that walks executives through four acts:
|
|
|
|
1. **Act 1 — The Friction:** the old manual 2-week deployment process.
|
|
2. **Act 2 — Developer Self-Service:** commit a valid `contract.yaml` for `l2-commodity-price-feed`, watch Dev auto-run, QA + Prod approval gates, then the evidence timeline.
|
|
3. **Act 3 — Citizen Developer:** open a GitHub Issue with natural-language intent; the Python keyword parser generates the same `contract.yaml` and triggers the identical pipeline.
|
|
4. **Act 4 — The Safety Net:** commit a malicious `contract.yaml` (`public-ingress: true`) for `l2-regulatory-reporting`; the pipeline halts in Dev because the confidence signal drops below 0.50, and the rejection is visible on the evidence stream.
|
|
|
|
## Requirements
|
|
|
|
### Validated
|
|
- Three repos under the `continuous-intelligence` Gitea org: `acdl` (platform + stubs + reusable workflows), `acdl-contracts` (developer surface), `acdl-evidence` (GitHub Pages audit timeline).
|
|
- L1 modules (single-purpose, substrate-agnostic, max-depth-1 primitives) as folders with `manifest.yaml` + `mock_apply.sh`.
|
|
- L2 modules (composed stacks, max-depth-5) grouping L1s into deployable service shapes.
|
|
- L3A developer surface: commit `contract.yaml` to `acdl-contracts`.
|
|
- L3B agentic surface: Python keyword parser turning an Issue body into `contract.yaml`.
|
|
- Confidence signal: base 0.90, drops to 0.40 on policy violation; gate threshold ≥ 0.50.
|
|
- Evidence stream: hash-chained `audit.json` published via Pages + vanilla-JS `index.html` timeline.
|
|
- Reusable CI workflow: Dev (autonomous) → QA (manual approval) → Prod (manual approval) → finalize.
|
|
|
|
### Active
|
|
- 8 L1 modules (serverless/container focus): `l1-eks-fargate`, `l1-iam-role`, `l1-lambda`, `l1-api-gateway`, `l1-eventbridge`, `l1-sqs`, `l1-s3`, `l1-cloudwatch`.
|
|
- 4 L2 modules mirroring S&P Global Energy / Platts use cases: `l2-invoice-service`, `l2-commodity-price-feed`, `l2-energy-analytics-api`, `l2-regulatory-reporting`.
|
|
- 5 core scripts: `mock_executor.sh`, `policy_checker.py`, `confidence_signal.py`, `evidence_writer.py`, `l3b_agent_stub.py`.
|
|
- Issue-triggered workflow in `acdl-contracts` that runs the L3B parser, commits a new branch, closes the issue, and triggers the main pipeline.
|
|
- Evidence stream UI (`index.html`) fetching `audit.json` and rendering events as a timeline.
|
|
|
|
### Out of Scope
|
|
- Real cloud provisioning (AWS/GCP/Azure).
|
|
- Real LLM inference / external AI APIs.
|
|
- Production-grade infrastructure or multi-tenant isolation.
|
|
- Real cryptographic tamper-proofing (the hash chain is demonstrative, not adversarially secure).
|
|
|
|
## Constraints
|
|
|
|
- Environment: local Linux OS.
|
|
- CI/CD: GitHub/Gitea Actions + Environments (QA, Prod approval gates).
|
|
- **No cloud** — absolutely no AWS, GCP, or Azure resources.
|
|
- **No AI** — no OpenAI or external LLM APIs; the "Agentic" part is a keyword parser.
|
|
- All state in flat JSON files or CI artifacts.
|
|
- Compute strategy: EKS Fargate + serverless primitives (no VPC module).
|
|
- L1 modules are single-purpose, substrate-agnostic, do not compose with other L1s.
|
|
- L2 modules combine L1 primitives into deployable shapes, max depth 5.
|
|
|
|
## Context
|
|
|
|
- Forge: Gitea at `https://git.cloudinit.dev`, org `continuous-intelligence`.
|
|
- The `acdl` repo already exists (empty) at org root and serves as the platform/meta repo.
|
|
- `acdl-contracts` and `acdl-evidence` will be created as additional repos in the same org.
|
|
- act_runner / Gitea Actions is the CI runtime; "GitHub Actions" workflow YAML is reused as-is.
|
|
|
|
## Key Decisions
|
|
|
|
| ID | Decision | Rationale | Outcome |
|
|
|----|----------|-----------|---------|
|
|
| D-001 | Use Gitea org `continuous-intelligence` for all repos | User-specified target org; already exists | Single source of truth for the demo |
|
|
| D-002 | Map "GitHub Actions" to Gitea Actions (act_runner) | Environment is Gitea; same workflow YAML syntax | Demo runs on the actual forge |
|
|
| D-003 | Collapse `acdl-platform` into the existing `acdl` repo | `acdl` already exists empty at org root | 3 repos total: `acdl`, `acdl-contracts`, `acdl-evidence` |
|
|
| D-004 | Use Gitea `environment` blocks + required reviewers for QA/Prod; fallback to manual `workflow_dispatch` with approval input | Approval gates required by spec; forge supports environment protection | Frictionless approval gates |
|
|
| D-005 | Hash-chained ledger (`prev_hash` + own `hash`) for evidence; declared demonstrative | Spec asks for simple JSON; chain gives visible tamper-evidence | Visible audit timeline without overengineering |
|
|
| D-006 | Confidence gate threshold = 0.50 exactly | Explicit in spec | Acts 2/4 behave as scripted |
|
|
| D-007 | Each `mock_apply.sh` echoes `[L1: <name>] applying...` + `OK`, sleeps 1s, exits 0 | Spec literal; uniformity aids timeline parsing | Predictable evidence events |
|
|
| D-008 | Keyword→stack mapping for L3B: gas/price/ingest/data-lake → commodity-price-feed; invoice/billing → invoice-service; analytics/historical/query → energy-analytics-api; regulatory/compliance/reporting/trading → regulatory-reporting; fallback → invoice-service | Mirrors the 4 L2 modules + Act 3 example issue | Act 3 reproduces deterministic behavior |
|
|
| D-009 | Init milestone = `v1.0`, branch `milestone/v1.0-initial` | init.md Step 5 mandate | Branching strategy follows convention |
|
|
| D-010 | Single-project mode for the `acdl` checkout | User chose standalone single-project | `---ci---` blocks omit `project:` field |
|
|
| D-011 | Single-project mode explicitly enforced via `config.json mode: "single"` overriding `projects[]` length signal | run.md Step 0 reads `projects[]` length as multi-project trigger; explicit flag disambiguates | No `project:` prefix in commits or branches |
|
|
| D-012 | Gitea has no native Pages — serve `acdl-evidence` via raw file URLs (`/raw/branch/main/...`) and a CORS note in ARCHITECTURE.md | Research confirms Gitea has no `[pages]` section | Demo can render `index.html` via raw URL without server-side Pages config |
|
|
| D-013 | Gitea has no environments API and ignores `jobs.<id>.environment` — model QA/Prod gates as `workflow_dispatch` approval inputs (D-004 fallback) | Research confirms `environment:` blocks are ignored by act_runner | Approval gates become dispatch inputs; "environments" become workflow job names + optional branch protection on `qa`/`prod` branches |
|
|
| D-014 | Cross-repo triggering uses the `workflow_dispatch` Gitea API (POST `/actions/workflows/{id}/dispatches`) from inside a step instead of `repository_dispatch` | Gitea Actions does not support `repository_dispatch` | Issue-trigger workflow calls the main pipeline via authenticated dispatch from a step |
|
|
| D-015 | New repos `acdl-contracts` and `acdl-evidence` use `default_branch: "main"` with `auto_init: true` | Matches Gitea `DEFAULT_BRANCH=main`; required for the default branch to exist before any push | Reusable-workflow `uses:` references still pin `acdl` workflows to `@milestone/v1.0-initial` |
|
|
| D-016 | Pages placeholder for Phase 01 is a minimal HTML stub (`<title>ACDL Evidence</title>` + "evidence stream coming soon"); full UI deferred to Phase 05 | Phase 01 success criterion is "Pages returns 200 with placeholder index.html" but Gitea has no Pages | Raw-URL HTTP 200 against `index.html` substitutes for the Pages check; full timeline UI built in Phase 05 |
|
|
| D-017 | Each L1 `manifest.yaml` declares a single `inputs:` map of named string keys with descriptions; no nested types (substrate-agnostic, max-depth-1) | REQ-02/03 say "declared inputs"; spec forbids composition and cloud-specific types | Uniform, parseable schema that Phase 03's `mock_executor.sh` can read with python+yaml |
|
|
| D-018 | L1 `mock_apply.sh` reads its own `manifest.yaml` for self-identification but ignores the input values (uniform stub per D-007) | D-007 mandates a literal echo + 1s sleep + exit 0; inputs are declared for traceability, not consumed | Predictable evidence events + clean separation from Phase 03 where L2s pass inputs to L1s |
|
|
| D-019 | The 8 L1 names are fixed per REQ-02: `l1-eks-fargate`, `l1-iam-role`, `l1-lambda`, `l1-api-gateway`, `l1-eventbridge`, `l1-sqs`, `l1-s3`, `l1-cloudwatch` | REQ-02 literal | Phase 02 enumerates them exactly; no naming freedom |
|
|
| D-020 | L2 `manifest.yaml` schema: `name`, `kind: l2`, `description`, `l1s:` (list of `{name, inputs: map}` entries). L2 references L1s by name (no path); inputs are string maps per L1 manifest declarations | REQ-04 says L2 "composes L1s"; REQ-05 caps depth at 5 (L2→L1 is depth 1) | mock_executor.sh reads `l1s:` and invokes each L1's `mock_apply.sh` |
|
|
| D-021 | `contract.yaml` schema: `stack` (L2 name), `inputs` (string map for the L2's top-level params), optional `public-ingress: bool` (the policy violation key per REQ-07) | REQ-07 cites `public-ingress: true` as the forbidden key; REQ-08's confidence signal keys off policy pass/fail | Single flat schema drives both policy_checker and the mock_executor |
|
|
| D-022 | `mock_executor.sh` writes `state.json` with shape `{"l2": "<name>", "l1s": [{"name":"...","applied":true,"exit_code":0}], "contract": <contract.yaml parsed>}` to the cwd; idempotent (overwrites) | REQ-06 says "writes state.json" but does not specify shape | Deterministic, parseable; Phase 05's evidence UI can include it in the audit trail |
|
|
| D-023 | `evidence_writer.py` appends events to `audit.json` (a JSON array of event objects). Each event: `{"seq": N, "ts": <iso8601>, "stage": "dev|qa|prod|finalize", "event": "<string>", "prev_hash": "<sha256>", "hash": "<sha256 of canonical json of this event with hash field empty>"}`. The genesis event has `prev_hash: "GENESIS"` and `seq: 0` | D-005 mandates hash-chained ledger; canonical JSON for deterministic hashing | Visible tamper-evidence without overengineering; Phase 05 UI reads the array |
|
|
| D-024 | `confidence_signal.py` reads `contract.yaml`, calls `policy_checker.py` (as a subprocess or import), returns base 0.90 on pass and 0.40 with reason code on policy failure; prints `{"score": 0.90|0.40, "reason": "<POLICY_VIOLATION:...|>"}` to stdout; exit 0 always | REQ-08 literal: base 0.90, drops to 0.40, gate ≥ 0.50 | Deterministic JSON output for the pipeline to consume |
|
|
| D-025 | `policy_checker.py` reads `contract.yaml`, fails with exit code 1 and stdout `POLICY_VIOLATION:PUBLIC_INGRESS` if `public-ingress: true`; otherwise exits 0 with stdout `POLICY_PASS` | REQ-07 literal | Single source of policy truth; called by confidence_signal and the pipeline directly |
|
|
| D-026 | `l3b_agent_stub.py` reads Issue body text from argv[1] (or stdin if no argv), applies the D-008 keyword map, writes a `contract.yaml` to stdout (or to `-o <path>`). Output contract uses the D-021 schema with `stack:` set to the mapped L2 name and a fixed `inputs:` map per L2 | D-008 + Act 3 example; L3B must produce the same contract format as L3A | Deterministic keyword parser; no external APIs | |