diff --git a/.ciagent/ARCHITECTURE.md b/.ciagent/ARCHITECTURE.md index 6d35414..87b77aa 100644 --- a/.ciagent/ARCHITECTURE.md +++ b/.ciagent/ARCHITECTURE.md @@ -67,4 +67,37 @@ The demo is a three-repo, stub-driven system that simulates an autonomous cloud 5. Reusable pipeline workflow (Dev → QA → Prod → Finalize) + environment gates. 6. Issue-triggered L3B workflow in `acdl-contracts`. 7. Evidence UI (`index.html` + Pages config). -8. Demo dry-run + the four scripted acts. \ No newline at end of file +8. Demo dry-run + the four scripted acts. + +## Gitea API Surface (Phase 01 research) + +Authoritative findings from the Gitea docs (added in RESEARCH; supersedes any +GitHub-Pages / GitHub-Environments assumptions carried over from the spec): + +| Capability | Gitea support | ACDL approach | +|------------|---------------|---------------| +| Org-scoped repo create | `POST /api/v1/orgs/{org}/repos` (`CreateRepoOption`) | Used to create `acdl-contracts` + `acdl-evidence` | +| Native Pages | **None** (no `[pages]` config section) | Serve `acdl-evidence` via raw file URLs: `https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html`; `index.html` fetches `audit.json` from the same raw path. Requires `[cors] ENABLED=true` on the server if the UI is loaded cross-origin. | +| Environments API | **None**; `jobs..environment` is ignored by act_runner | Model QA/Prod gates as `workflow_dispatch` approval inputs (D-004 / D-013); optionally create `qa` and `prod` branches as a visible stand-in | +| `repository_dispatch` trigger | **Not supported** | Cross-repo trigger via `workflow_dispatch` API: `POST /api/v1/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches` called from a step using `$GITEA_TOKEN` | +| Reusable workflows (`workflow_call`) | Supported | `acdl/.gitea/workflows/pipeline.yml` called via `uses: continuous-intelligence/acdl/.gitea/workflows/pipeline.yml@milestone/v1.0-initial` | +| `workflow_dispatch` | Supported (trigger + API) | Used for the manual-approval fallback and the issue workflow's cross-repo trigger | +| `issues.opened` trigger | Supported | Drives the L3B issue-trigger workflow in `acdl-contracts` | +| `act_runner` labels | Single label only (`runs-on: ubuntu-latest`) | All workflows use `runs-on: ubuntu-latest` | +| Context | `${{ gitea.* }}` and `${{ github.* }}` both work | Workflows use `gitea.*` for clarity | + +### Branch pinning rule + +The reusable workflow in the `acdl` repo lives on `milestone/v1.0-initial` +(that is the repo's default branch). `uses:` references from `acdl-contracts` +must pin to `@milestone/v1.0-initial`, not `@main` (the `acdl` repo has no +`main` branch). The new repos `acdl-contracts` and `acdl-evidence` use +`default_branch: "main"` (D-015) so their default branch exists immediately +for pushes. + +### Default verification toolchain + +There is no `package.json`; ACDL is bash + python stubs. The verification gate +substitutes `bash -n` and `python -m py_compile` for `npm run typecheck`, and +per-phase `scripts/verify_phaseNN.sh` for `npm test`. `npm run build` is a +no-op (no build step). See PERSONAS.md / VERIFICATION note. \ No newline at end of file diff --git a/.ciagent/PERSONAS.md b/.ciagent/PERSONAS.md new file mode 100644 index 0000000..097be0d --- /dev/null +++ b/.ciagent/PERSONAS.md @@ -0,0 +1,91 @@ +--- +project: acdl +milestone: v1.0 +generated_at: 2026-07-21 +generator: lead-developer +verification_toolchain: + typecheck: "bash -n scripts/**/*.sh modules/**/*.sh && python3 -m py_compile scripts/**/*.py" + test: "scripts/verify_phaseNN.sh" + build: "no-op (no build step; bash + python stubs)" + note: | + ACDL has no package.json. The execute/verify/ship workflows substitute + bash -n and python -m py_compile for npm run typecheck, a per-phase + verify script for npm test, and treat npm run build as a no-op. This + override is documented here as the single source of truth; the ci-* + agents read PERSONAS.md before running verification commands. +--- + +# ACDL — Persona Roster (project-level) + +## Active personas + +### lead-developer +- **Domain:** coordination +- **Active:** true +- **Phase-specific:** false +- **Frameworks:** (none) +- **Constraints:** pragmatic, battle-tested defaults, no-cross-territory-edits +- **Territory:** `.ciagent/**`, `scripts/verify_phase*.sh`, `README.md`, `.gitignore` +- **Reason:** Owns CIAgent metadata and cross-phase verification scripts. + +### backend-engineer +- **Domain:** backend +- **Active:** true +- **Phase-specific:** false +- **Frameworks:** gitea-actions, act_runner, bash, python, yaml +- **Constraints:** no-cloud, no-ai, stub-only, hash-chain-must-be-deterministic, max-depth-5 +- **Territory:** `.gitea/workflows/**`, `scripts/**` (except `scripts/verify_phase*.sh`), `modules/l2/**/manifest.yaml` +- **Reason:** Owns workflow YAML, core scripts (mock_executor, policy_checker, confidence_signal, evidence_writer, l3b_agent_stub), and L2 composition manifests. + +### infra-stub-engineer (custom) +- **Domain:** backend +- **Active:** true +- **Phase-specific:** false +- **Frameworks:** bash, yaml +- **Constraints:** mock-only, echo-contract-from-D-007, sleep-1s-exit-0, substrate-agnostic, single-purpose +- **Territory:** `modules/l1/**` +- **Reason:** Created to own L1 stub modules (Phase 02) and their uniform mock_apply.sh behavior per D-007. Domain is backend (bash stubs) but territory is strictly L1 modules to keep L1/L2 concerns separated from workflow YAML. + +## Deactivated personas + +### data-engineer +- **Domain:** data +- **Active:** false +- **Reason:** No persistence layer. ACDL state is flat JSON files (`audit.json`, `state.json`) written by bash/python scripts; no ORM, no migrations, no DB. Schema contracts live in `manifest.yaml` (owned by backend-engineer / infra-stub-engineer). +- **Phase-specific:** false +- **Frameworks:** (would have been: drizzle, prisma) +- **Constraints:** (would have been: schema-first, type-safe-orm) +- **Territory:** (would have been: `**/db/**`, `**/migrations/**`) + +### frontend-engineer +- **Domain:** frontend +- **Active:** false +- **Reason:** No UI in Phases 01-04. The single UI artifact (`index.html`, vanilla JS) is built in Phase 05. frontend-engineer is reactivated for Phase 05 only (see Phase-specific overrides below). +- **Phase-specific:** true (reactivates in Phase 05) +- **Frameworks:** vanilla-js, dom-api, fetch-api +- **Constraints:** no-frameworks, single-file, fetch-from-same-origin-raw-url +- **Territory:** `acdl-evidence/index.html` (Phase 05) + +## Phase-specific overrides + +| Phase | Personas active | Reactivations / notes | +|-------|-----------------|----------------------| +| 01 repo-scaffolding | lead-developer, backend-engineer | infra-stub-engineer idle (no L1 work this phase) | +| 02 l1-modules | lead-developer, backend-engineer, infra-stub-engineer | infra-stub-engineer owns L1 stubs | +| 03 l2-modules-and-core-scripts | lead-developer, backend-engineer, infra-stub-engineer | backend-engineer owns core scripts + L2 manifests; infra-stub-engineer only updates L1 manifests if referenced | +| 04 pipeline-and-approval-gates | lead-developer, backend-engineer | infra-stub-engineer idle; frontend-engineer still off | +| 05 evidence-ui-and-demo-dry-run | lead-developer, backend-engineer, frontend-engineer | frontend-engineer REACTIVATED for `index.html` only; backend-engineer owns the dry-run script and audit.json wiring | + +## Domain priority (used by TaskDecomposer) + +`coordination -> backend -> infra-stub-engineer -> frontend-engineer (Phase 05 only)` + +## Conflict resolutions (lead-developer arbitration) + +- `backend-engineer` vs `infra-stub-engineer` over `modules/l2/**/manifest.yaml`: backend-engineer owns L2 manifests; infra-stub-engineer owns L1 manifests. No overlap. +- `backend-engineer` vs `frontend-engineer` over `acdl-evidence/index.html`: frontend-engineer owns the file in Phase 05; backend-engineer provides the `audit.json` schema contract (event shape) via `evidence_writer.py` and a `SCHEMA.md` note in ARCHITECTURE.md. +- `lead-developer` vs any: lead-developer owns `.ciagent/**` and verification scripts; persona engineers do not edit CIAgent metadata. + +## Territory enforcement mode + +`warn` — config.json has no `personas.territory_enforcement` field, so the default per execute.md is `warn`. Cross-territory edits are logged in the commit message but do not fail the task. \ No newline at end of file diff --git a/.ciagent/PLAN.md b/.ciagent/PLAN.md new file mode 100644 index 0000000..c8618eb --- /dev/null +++ b/.ciagent/PLAN.md @@ -0,0 +1,96 @@ +--- +phase: 01 +name: repo-scaffolding +milestone: v1.0 +milestone_type: feature +status: planned +requirements: [REQ-01, REQ-09, REQ-10] +must_haves: + - "Repo acdl-contracts exists under continuous-intelligence and is pushable (HTTP 200 on GET /repos/continuous-intelligence/acdl-contracts)" + - "Repo acdl-evidence exists under continuous-intelligence and is pushable (HTTP 200 on GET /repos/continuous-intelligence/acdl-evidence)" + - "Raw URL https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html returns HTTP 200 with placeholder HTML (D-012/D-016 substitute for Pages check)" + - "Branches qa and prod exist on acdl-contracts (visible stand-in for unsupported Gitea environments; D-013)" + - "scripts/gitea_setup.sh is idempotent and exits 0 (re-running against existing repos is a no-op)" + - "scripts/verify_phase01.sh passes: enumerates repos, fetches the raw index.html, lists qa/prod branches, exits 0" +verification: + typecheck: "bash -n scripts/*.sh" + test: "scripts/verify_phase01.sh" + build: no-op +--- + +# Phase 01 — repo-scaffolding PLAN + +## Goal + +Stand up the three-repo scaffold under the `continuous-intelligence` Gitea org +and the Phase 01 visible artifacts in the local `acdl` checkout: the +Gitea-setup script, the workflow skeletons, and the Phase 01 verification +script. After this phase, Phases 02-05 can push stub modules, core scripts, +and workflows into the right repos. + +## Requirements covered + +- REQ-01: All demo code lives under `continuous-intelligence` Gitea org +- REQ-09: Three repos exist (`acdl`, `acdl-contracts`, `acdl-evidence`) +- REQ-10: reusable pipeline runs Dev → QA (approval) → Prod (approval) → Finalize; Phase 01 builds the skeleton (gates wired in Phase 04) + +## Waves (vertical slices, executed in domain priority order) + +### Wave 1 — coordination (lead-developer) + +**Tasks:** + +- **T-1.1** Add `.gitignore` (ignore `audit.json` artifacts, `__pycache__/`, `*.pyc`, `state.json`, `.env`). Create repo directory layout markers: `scripts/.gitkeep`, `modules/l1/.gitkeep`, `modules/l2/.gitkeep`, `.gitea/workflows/.gitkeep`. Add a top-level `README.md` with the project name, the 4-act demo summary, and a pointer to `.ciagent/PROJECT.md`. +- **T-1.2** Add `scripts/verify_phase01.sh` — the Phase 01 verification script. It reads `$ACDL_GITEA_TOKEN` from the env, calls the Gitea API to confirm both new repos exist, curls the raw `index.html` URL, lists branches on `acdl-contracts` looking for `qa` and `prod`, and prints a PASS/FAIL summary. Exits 0 on success, non-zero on any failure. Idempotent. + +**Files owned (territory):** +- `.gitignore`, `README.md` +- `scripts/.gitkeep`, `modules/l1/.gitkeep`, `modules/l2/.gitkeep`, `.gitea/workflows/.gitkeep` +- `scripts/verify_phase01.sh` + +**Commits:** one per task, `---ci---` block has `phase: 1, status: plan-as-execute, persona: lead-developer, task: T-1.x, requirements.covered: [REQ-01]`. + +### Wave 2 — backend (backend-engineer) + +**Tasks:** + +- **T-2.1** Add `scripts/gitea_setup.sh`. Idempotent. Reads `$ACDL_GITEA_TOKEN` and `$GITEA_HOST` (default `https://git.cloudinit.dev`). Creates `acdl-contracts` and `acdl-evidence` under `continuous-intelligence` if missing (POST `/orgs/continuous-intelligence/repos` with `auto_init: true`, `default_branch: "main"`, `private: true`). Pushes a placeholder `index.html` to `acdl-evidence` main via the Gitea file-contents API (POST `/repos/{owner}/{repo}/contents/{path}` with base64 content + "Initial placeholder" commit message). Creates `qa` and `prod` branches on `acdl-contracts` from `main` via the Gitea branch API (POST `/repos/{owner}/{repo}/branches`). All HTTP errors are logged with status + body; the script is idempotent (409 / "already exists" treated as success). Uses `curl` + `python3 -c` for base64 encoding; no jq dependency. +- **T-2.2** Add `acdl/.gitea/workflows/pipeline.yml` skeleton. `on: workflow_call`. Four jobs: `dev` (runs-on ubuntu-latest, placeholder "Dev stage" step), `qa-gate` (needs dev, runs-on ubuntu-latest, placeholder "Awaiting QA approval" step; in Phase 04 this becomes a `workflow_dispatch` approval input per D-013), `prod-gate` (needs qa-gate, placeholder "Awaiting Prod approval"), `finalize` (needs prod-gate, placeholder "Commit audit.json to acdl-evidence"). All steps are explicit placeholders marked `# Phase 04 will implement`. Comment at top documents the branch-pin rule (`@milestone/v1.0-initial`). +- **T-2.3** Add `acdl-contracts/.gitea/workflows/issue-to-contract.yml` skeleton (committed to the `acdl` repo under `contracts-repo/.gitea/workflows/` as a reference copy; pushed to the actual `acdl-contracts` repo in Phase 04). `on: issues` with `types: [opened]`. One job `parse-and-trigger` with placeholder steps for: checkout, run `l3b_agent_stub.py`, commit `contract.yaml`, push, trigger the `acdl` pipeline via `workflow_dispatch` API (D-014). Marked `# Phase 04 will implement`. + +**Files owned (territory):** +- `scripts/gitea_setup.sh` +- `.gitea/workflows/pipeline.yml` +- `contracts-repo/.gitea/workflows/issue-to-contract.yml` (reference copy in the `acdl` repo; source of truth for Phase 04) + +**Commits:** one per task, `---ci---` block has `persona: backend-engineer, task: T-2.x, requirements.covered: [REQ-01 or REQ-09 or REQ-10]`. + +### Wave 3 — coordination (lead-developer, verification wiring) + +**Tasks:** + +- **T-3.1** Run `scripts/gitea_setup.sh` against the live Gitea (executed as part of EXECUTE; recorded as a commit only if it modifies repo state — it does not, so no commit. Instead, the verify run in Wave 3 confirms the artifacts exist via the API.) Add `scripts/verify_phase01.sh` invocation note to README.md ("Run `scripts/verify_phase01.sh` after `scripts/gitea_setup.sh` to confirm Phase 01 success criteria"). Update `.ciagent/REQUIREMENTS.md` Traceability table to mark REQ-01/09/10 as `covered` (pending VERIFY confirmation). Update `.ciagent/ROADMAP.md` Phase 01 status to `executing` (will flip to `complete` on SHIP). + +**Files owned (territory):** +- `README.md` (update) +- `.ciagent/REQUIREMENTS.md` (traceability update only) +- `.ciagent/ROADMAP.md` (phase status update only) + +**Commits:** one for T-3.1 (a `chore(P01)` update with `phase: 1, status: execute, persona: lead-developer, task: T-3.1`). + +## Wave ordering rationale + +- Wave 1 (coordination) creates the directory skeleton + verification script so Wave 2's scripts have a place to live and a check to satisfy. +- Wave 2 (backend) builds the Gitea setup script and workflow skeletons. +- Wave 3 (coordination) wires the verification script into README and updates traceability after Wave 2's scripts exist. +- infra-stub-engineer and frontend-engineer have 0 tasks this phase (per PERSONAS.md), so their persona groups are skipped. + +## Dependencies on other phases + +None. Phase 01 is self-contained. Phases 02-05 depend on Phase 01 having created the two new repos and the workflow skeletons. + +## Risk notes + +- If `gitea_setup.sh` hits a 401/403, treat as an escalation (token scope insufficient). Per run.md Step 4: retry once, then escalate. Do NOT proceed to SHIP with uncreated repos. +- The Gitea file-contents API requires the file to NOT already exist on first POST. The script must check existence first (GET) and skip POST if 200. Otherwise 422. +- Creating branches requires the default branch to exist first (`auto_init: true` handles this). \ No newline at end of file diff --git a/.ciagent/PROJECT.md b/.ciagent/PROJECT.md index bea672f..3ce13be 100644 --- a/.ciagent/PROJECT.md +++ b/.ciagent/PROJECT.md @@ -69,4 +69,10 @@ Build a runnable demo (Linux + GitHub/Gitea Actions) that walks executives throu | D-007 | Each `mock_apply.sh` echoes `[L1: ] 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 | \ No newline at end of file +| 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..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 (`ACDL Evidence` + "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 | \ No newline at end of file diff --git a/.ciagent/REQUIREMENTS.md b/.ciagent/REQUIREMENTS.md index d0d7abf..2b0d8ce 100644 --- a/.ciagent/REQUIREMENTS.md +++ b/.ciagent/REQUIREMENTS.md @@ -35,6 +35,14 @@ (None — v1 covers the complete demo.) +## Clarifications (Phase 01) + +| REQ | Original criterion | Clarified criterion (effective) | Decision | +|-----|--------------------|----------------------------------|----------| +| REQ-09 | Three repos exist | Three repos exist (`acdl`, `acdl-contracts`, `acdl-evidence`) under `continuous-intelligence`; new repos use `default_branch: "main"`, `auto_init: true` | D-015 | +| REQ-10 | "Pages returns 200 with placeholder `index.html`" on `acdl-evidence` | Gitea has no Pages; substitute: an HTTP GET against the raw file URL `https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html` returns 200 with the placeholder HTML body | D-012, D-016 | +| REQ-10 | "`qa` and `prod` environments exist on `acdl-contracts`" | Gitea has no environments API and ignores `environment:` blocks; substitute: the reusable workflow defines `qa-gate` and `prod-gate` jobs gated by `workflow_dispatch` approval inputs (D-004 fallback); a `qa` and `prod` branch may be created on `acdl-contracts` as a visible stand-in for environments | D-013 | + ## Out of Scope | Feature | Reason | @@ -49,7 +57,7 @@ | Requirement | Phase | Status | |-------------|-------|--------| -| REQ-01 | 1 | pending | +| REQ-01 | 1 | covered (pending VERIFY) | | REQ-02 | 2 | pending | | REQ-03 | 2 | pending | | REQ-04 | 3 | pending | @@ -57,10 +65,10 @@ | REQ-06 | 3 | pending | | REQ-07 | 3 | pending | | REQ-08 | 3 | pending | -| REQ-09 | 1 | pending | -| REQ-10 | 4 | pending | +| REQ-09 | 1 | covered (pending VERIFY) | +| REQ-10 | 4 | partial (skeleton in Phase 01; full impl in Phase 04) | | REQ-11 | 3 | pending | -| REQ-12 | 4 | pending | +| REQ-12 | 4 | partial (skeleton in Phase 01; full impl in Phase 04) | | REQ-13 | 5 | pending | | REQ-14 | 5 | pending | | REQ-15 | 5 | pending | \ No newline at end of file diff --git a/.ciagent/ROADMAP.md b/.ciagent/ROADMAP.md index ccfbaf2..0825362 100644 --- a/.ciagent/ROADMAP.md +++ b/.ciagent/ROADMAP.md @@ -8,7 +8,7 @@ Five-phase breakdown to take ACDL from empty repo to a reproducible 4-act execut ### Phase 01 — repo-scaffolding - **Description:** Create the three repos under `continuous-intelligence` (`acdl-contracts`, `acdl-evidence`; `acdl` already exists), seed directory layouts, configure Pages on `acdl-evidence`, add environment protection for `qa` and `prod` on `acdl-contracts`. -- **Status:** not_started +- **Status:** executing - **Depends on:** — - **Requirements:** REQ-01, REQ-09, REQ-10 - **Success Criteria:** diff --git a/.ciagent/config.json b/.ciagent/config.json index 67967aa..0725f33 100644 --- a/.ciagent/config.json +++ b/.ciagent/config.json @@ -1,4 +1,5 @@ { + "mode": "single", "projects": [ { "slug": "acdl", @@ -47,7 +48,7 @@ }, "gitea": { "base_url": "https://git.cloudinit.dev", - "api_token_env": "b8fa72220e350314704a8a28a0e570b418bb4655", + "api_token_env": "ACDL_GITEA_TOKEN", "owner": "continuous-intelligence", "repo": "acdl" } diff --git a/.gitea/workflows/.gitkeep b/.gitea/workflows/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.gitea/workflows/pipeline.yml b/.gitea/workflows/pipeline.yml new file mode 100644 index 0000000..c8a9e26 --- /dev/null +++ b/.gitea/workflows/pipeline.yml @@ -0,0 +1,75 @@ +# ACDL reusable pipeline workflow (Phase 01 skeleton). +# +# This workflow is called from acdl-contracts via: +# uses: continuous-intelligence/acdl/.gitea/workflows/pipeline.yml@milestone/v1.0-initial +# +# Branch pinning rule (see .ciagent/ARCHITECTURE.md): the `acdl` repo's default +# branch is `milestone/v1.0-initial`, so `uses:` references must pin to +# `@milestone/v1.0-initial`, NOT `@main`. +# +# Phase 04 will implement the actual stage logic + approval gates (D-013: +# Gitea has no environments API; gates become workflow_dispatch approval +# inputs). +name: acdl-pipeline + +on: + workflow_call: + inputs: + contract-ref: + description: "Ref on acdl-contracts that triggered the pipeline" + required: false + type: string + default: main + +jobs: + dev: + name: "Dev (autonomous)" + runs-on: ubuntu-latest + steps: + # Phase 04 will implement: checkout acdl + acdl-contracts, run + # policy_checker.py, mock_executor.sh, confidence_signal.py, write + # evidence via evidence_writer.py. + - name: "Dev stage placeholder" + run: | + echo "Dev stage placeholder (Phase 01 skeleton)" + echo "Phase 04 will run policy_checker, mock_executor, confidence_signal, evidence_writer" + exit 0 + + qa-gate: + name: "QA (manual approval)" + needs: dev + runs-on: ubuntu-latest + steps: + # Phase 04 will implement: gate via workflow_dispatch approval input + # (D-013 fallback; Gitea ignores jobs..environment). + - name: "QA gate placeholder" + run: | + echo "QA gate placeholder (Phase 01 skeleton)" + echo "Phase 04 will pause here for human approval via workflow_dispatch" + exit 0 + + prod-gate: + name: "Prod (manual approval)" + needs: qa-gate + runs-on: ubuntu-latest + steps: + # Phase 04 will implement: same approval-input gate as qa-gate. + - name: "Prod gate placeholder" + run: | + echo "Prod gate placeholder (Phase 01 skeleton)" + echo "Phase 04 will pause here for human approval via workflow_dispatch" + exit 0 + + finalize: + name: "Finalize (publish evidence)" + needs: prod-gate + runs-on: ubuntu-latest + steps: + # Phase 04/05 will implement: commit audit.json to acdl-evidence main + # via the Gitea file-contents API; raw URL republishes index.html + + # audit.json for the timeline UI (D-012). + - name: "Finalize placeholder" + run: | + echo "Finalize placeholder (Phase 01 skeleton)" + echo "Phase 04/05 will commit audit.json to acdl-evidence main" + exit 0 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2e3b86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +__pycache__/ +*.pyc +*.pyo +.env +.env.* +state.json +audit.json +*.tmp +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fed4272 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# ACDL — Agentic Cloud Delivery Platform + +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. + +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. + +## 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/Gitea 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. + +## Repositories + +All under the `continuous-intelligence` Gitea org at +`https://git.cloudinit.dev`: + +- `acdl` (this repo) — platform + stubs + reusable workflows +- `acdl-contracts` — developer surface (`contract.yaml` + issue trigger) +- `acdl-evidence` — audit timeline (served via raw file URLs; Gitea has no + native Pages — see `.ciagent/ARCHITECTURE.md` Gitea API Surface table) + +## Project metadata + +See `.ciagent/PROJECT.md` for the full spec, `.ciagent/ROADMAP.md` for the +5-phase breakdown, `.ciagent/REQUIREMENTS.md` for traceable requirements, +and `.ciagent/PERSONAS.md` for the active persona roster. + +## Phase 01 verification + +After running `scripts/gitea_setup.sh` (which creates `acdl-contracts` and +`acdl-evidence` in the org and pushes the placeholder `index.html`), run: + +```bash +ACDL_GITEA_TOKEN= scripts/verify_phase01.sh +``` + +The script confirms: +- both new repos exist via the Gitea API +- the raw `index.html` URL on `acdl-evidence` returns HTTP 200 +- the `qa` and `prod` branches exist on `acdl-contracts` + +Exit 0 = Phase 01 success criteria met. \ No newline at end of file diff --git a/contracts-repo/.gitea/workflows/.gitkeep b/contracts-repo/.gitea/workflows/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/contracts-repo/.gitea/workflows/issue-to-contract.yml b/contracts-repo/.gitea/workflows/issue-to-contract.yml new file mode 100644 index 0000000..651f79d --- /dev/null +++ b/contracts-repo/.gitea/workflows/issue-to-contract.yml @@ -0,0 +1,40 @@ +# ACDL issue-to-contract workflow (Phase 01 skeleton, reference copy). +# +# This file is the source-of-truth copy kept in the `acdl` repo under +# contracts-repo/.gitea/workflows/. Phase 04 will push it to the actual +# `acdl-contracts` repo under .gitea/workflows/ and implement the real +# step bodies. +# +# Trigger: a new Issue is opened in acdl-contracts. The workflow runs +# l3b_agent_stub.py to map the Issue body to a contract.yaml, commits the +# contract to a new branch, closes the Issue, and triggers the main +# pipeline in the `acdl` repo via the workflow_dispatch API (D-014; Gitea +# Actions does not support repository_dispatch). +name: issue-to-contract + +on: + issues: + types: [opened] + +jobs: + parse-and-trigger: + runs-on: ubuntu-latest + steps: + # Phase 04 will implement: + # 1. checkout acdl-contracts (so l3b_agent_stub.py is available). + # 2. run: python3 scripts/l3b_agent_stub.py "${{ gitea.event.issue.body }}" > contract.yaml + # 3. parse the generated contract; commit it to a new branch + # (e.g. contract/). + # 4. push the branch. + # 5. close the Issue with a comment linking to the pipeline run. + # 6. trigger the main pipeline: + # curl -X POST \ + # -H "Authorization: token ${GITEA_TOKEN}" \ + # https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/acdl/actions/workflows//dispatches \ + # -d '{"ref":"milestone/v1.0-initial","inputs":{"contract-ref":""}}' + - name: "Issue-trigger placeholder" + run: | + echo "issue-to-contract placeholder (Phase 01 skeleton)" + echo "Issue body: ${{ gitea.event.issue.body }}" + echo "Phase 04 will run l3b_agent_stub.py, commit contract.yaml, close issue, dispatch pipeline" + exit 0 \ No newline at end of file diff --git a/modules/l1/.gitkeep b/modules/l1/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/modules/l2/.gitkeep b/modules/l2/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/gitea_setup.sh b/scripts/gitea_setup.sh new file mode 100755 index 0000000..adcff18 --- /dev/null +++ b/scripts/gitea_setup.sh @@ -0,0 +1,228 @@ +#!/usr/bin/env bash +# Phase 01 Gitea scaffolding. Idempotent. +# +# Creates the two new repos under the continuous-intelligence org, pushes a +# placeholder index.html to acdl-evidence, and creates qa + prod branches on +# acdl-contracts. Running against existing repos / branches / files is a +# no-op (409 or 422 is treated as success). +# +# Usage: ACDL_GITEA_TOKEN= scripts/gitea_setup.sh +# Exit codes: 0 = success (created or already existed); 1 = unrecoverable error. + +set -euo pipefail + +GITEA_HOST="${GITEA_HOST:-https://git.cloudinit.dev}" +ORG="continuous-intelligence" +TOKEN="${ACDL_GITEA_TOKEN:?ACDL_GITEA_TOKEN is required}" +API="${GITEA_HOST}/api/v1" + +AUTH=(-H "Authorization: token ${TOKEN}" -H "Content-Type: application/json") + +log() { printf '[setup] %s\n' "$*"; } +warn() { printf '[setup][WARN] %s\n' "$*" >&2; } +err() { printf '[setup][ERROR] %s\n' "$*" >&2; } + +# --- helpers ---------------------------------------------------------------- + +# http_status_code URL +http_get_status() { + local url="$1" + curl -sS -o /dev/null -w "%{http_code}" "${AUTH[@]}" "$url" +} + +# repo_exists NAME -> 0 if exists, 1 otherwise +repo_exists() { + local name="$1" + local status + status=$(http_get_status "${API}/repos/${ORG}/${name}") + [ "$status" = "200" ] +} + +# create_repo NAME DESCRIPTION +create_repo() { + local name="$1" + local description="$2" + local body + body=$(python3 -c " +import json, sys +print(json.dumps({ + 'name': '${name}', + 'description': ${description@Q}, + 'private': True, + 'default_branch': 'main', + 'auto_init': True, + 'gitignores': 'Python', + 'license': '', + 'readme': 'Default' +})) +") + log "Creating repo ${ORG}/${name} (default_branch=main, auto_init=true)" + local status body_out + status=$(curl -sS -o /tmp/setup_repo_create.json -w "%{http_code}" \ + "${AUTH[@]}" -X POST -d "$body" \ + "${API}/orgs/${ORG}/repos") + case "$status" in + 201) log " created (HTTP 201)" ;; + 409) log " already exists (HTTP 409); skipping" ;; + *) + err "create_repo ${name} failed: HTTP ${status}" + cat /tmp/setup_repo_create.json >&2 || true + return 1 + ;; + esac +} + +# set_repo_visibility REPO VISIBILITY (public|private) +set_repo_visibility() { + local repo="$1" + local visibility="$2" + local body + body=$(python3 -c " +import json +is_private = ('${visibility}' == 'private') +print(json.dumps({'private': is_private, 'visibility': '${visibility}'})) +") + log "Setting ${repo} visibility to ${visibility}" + local status + status=$(curl -sS -o /tmp/setup_vis.json -w "%{http_code}" \ + "${AUTH[@]}" -X PATCH -d "$body" \ + "${API}/repos/${ORG}/${repo}") + case "$status" in + 200) log " ok (HTTP 200)" ;; + *) warn "set_repo_visibility ${repo} -> ${visibility} returned HTTP ${status} (continuing)"; cat /tmp/setup_vis.json >&2 || true ;; + esac +} + +# file_exists REPO PATH -> 0 if the file already exists on the default branch +file_exists_on_default() { + local repo="$1" + local path="$2" + local status + status=$(http_get_status "${API}/repos/${ORG}/${repo}/contents/${path}?ref=main") + [ "$status" = "200" ] +} + +# create_placeholder_index REPO +create_placeholder_index() { + local repo="$1" + local path="index.html" + local placeholder + placeholder=' + + + + ACDL Evidence + + + +

ACDL Evidence Stream

+

Evidence timeline will appear here in Phase 05.

+

Placeholder served via Gitea raw file URL (D-012; Gitea has no native Pages).

+ +' + + if file_exists_on_default "$repo" "$path"; then + log "index.html already exists on ${repo} main; skipping" + return 0 + fi + + local body + body=$(python3 -c " +import json, base64 +content = '''${placeholder}''' +print(json.dumps({ + 'content': base64.b64encode(content.encode('utf-8')).decode('ascii'), + 'message': 'Initial placeholder index.html (Phase 01, D-016)', + 'branch': 'main' +})) +") + log "Pushing placeholder index.html to ${repo} main" + local status + status=$(curl -sS -o /tmp/setup_index_push.json -w "%{http_code}" \ + "${AUTH[@]}" -X POST -d "$body" \ + "${API}/repos/${ORG}/${repo}/contents/${path}") + case "$status" in + 201) log " pushed (HTTP 201)" ;; + 409|422) log " already exists or conflict (HTTP ${status}); skipping" ;; + *) + err "create_placeholder_index on ${repo} failed: HTTP ${status}" + cat /tmp/setup_index_push.json >&2 || true + return 1 + ;; + esac +} + +# branch_exists REPO BRANCH -> 0 if exists +branch_exists() { + local repo="$1" + local branch="$2" + local status + status=$(http_get_status "${API}/repos/${ORG}/${repo}/branches/${branch}") + [ "$status" = "200" ] +} + +# create_branch REPO BRANCH FROM_REF +create_branch() { + local repo="$1" + local branch="$2" + local from_ref="$3" + if branch_exists "$repo" "$branch"; then + log "Branch ${branch} already exists on ${repo}; skipping" + return 0 + fi + local body + body=$(python3 -c " +import json +print(json.dumps({'new_branch_name': '${branch}', 'old_branch_name': '${from_ref}'})) +") + log "Creating branch ${branch} on ${repo} from ${from_ref}" + local status + status=$(curl -sS -o /tmp/setup_branch.json -w "%{http_code}" \ + "${AUTH[@]}" -X POST -d "$body" \ + "${API}/repos/${ORG}/${repo}/branches") + case "$status" in + 201) log " created (HTTP 201)" ;; + 409) log " already exists (HTTP 409); skipping" ;; + *) + err "create_branch ${branch} on ${repo} failed: HTTP ${status}" + cat /tmp/setup_branch.json >&2 || true + return 1 + ;; + esac +} + +# --- main ------------------------------------------------------------------- + +log "Host: ${GITEA_HOST}" +log "Org: ${ORG}" +log "Token: " + +# Step 1: create acdl-contracts +if ! repo_exists acdl-contracts; then + create_repo acdl-contracts "ACDL developer + agentic entry surface (contract.yaml + issue trigger)" || exit 1 +else + log "acdl-contracts already exists; skipping create" +fi + +# Step 2: create acdl-evidence +if ! repo_exists acdl-evidence; then + create_repo acdl-evidence "ACDL hash-chained audit timeline served as a static site via raw file URLs" || exit 1 +else + log "acdl-evidence already exists; skipping create" +fi + +# Step 2b: make acdl-evidence public so the Phase 05 UI (index.html) can +# fetch audit.json from a browser without exposing the API token (D-012 +# raw-URL approach). acdl-contracts stays private. +set_repo_visibility acdl-evidence public + +# Step 3: push placeholder index.html to acdl-evidence +create_placeholder_index acdl-evidence || exit 1 + +# Step 4: create qa + prod branches on acdl-contracts (visible stand-in for +# the unsupported Gitea environments API; per D-013). +create_branch acdl-contracts qa main || exit 1 +create_branch acdl-contracts prod main || exit 1 + +log "Done. Run scripts/verify_phase01.sh to confirm success criteria." +exit 0 \ No newline at end of file diff --git a/scripts/verify_phase01.sh b/scripts/verify_phase01.sh new file mode 100755 index 0000000..425b464 --- /dev/null +++ b/scripts/verify_phase01.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# Phase 01 verification script. +# Confirms the three-repo scaffold exists under the continuous-intelligence +# Gitea org and that the Phase 01 visible artifacts (placeholder index.html on +# acdl-evidence; qa + prod branches on acdl-contracts) are present. +# +# Usage: ACDL_GITEA_TOKEN= scripts/verify_phase01.sh +# Exit codes: 0 = all checks passed; 1 = one or more checks failed. + +set -euo pipefail + +GITEA_HOST="${GITEA_HOST:-https://git.cloudinit.dev}" +ORG="continuous-intelligence" +TOKEN="${ACDL_GITEA_TOKEN:-}" + +fail_count=0 +note() { printf ' [%s] %s\n' "$1" "$2"; } +pass() { note "PASS" "$1"; } +fail() { note "FAIL" "$1"; fail_count=$((fail_count + 1)); } +warn() { printf ' [WARN] %s\n' "$1" >&2; } + +echo "== Phase 01 verification ==" +echo "Host: $GITEA_HOST" +echo "Org: $ORG" +if [ -n "$TOKEN" ]; then + echo "Token: " +else + echo "Token: " +fi +echo + +# --- Check 1: acdl-contracts repo exists --- +echo "-- Check 1: acdl-contracts repo exists --" +status=$(curl -sS -o /tmp/p01_contracts.json -w "%{http_code}" \ + -H "Authorization: token ${TOKEN}" \ + "${GITEA_HOST}/api/v1/repos/${ORG}/acdl-contracts") +if [ "$status" = "200" ]; then + default_branch=$(python3 -c "import json; print(json.load(open('/tmp/p01_contracts.json')).get('default_branch','?'))") + pass "acdl-contracts exists (default_branch=${default_branch})" +else + fail "acdl-contracts GET returned HTTP ${status}" +fi + +# --- Check 2: acdl-evidence repo exists --- +echo "-- Check 2: acdl-evidence repo exists --" +status=$(curl -sS -o /tmp/p01_evidence.json -w "%{http_code}" \ + -H "Authorization: token ${TOKEN}" \ + "${GITEA_HOST}/api/v1/repos/${ORG}/acdl-evidence") +if [ "$status" = "200" ]; then + default_branch=$(python3 -c "import json; print(json.load(open('/tmp/p01_evidence.json')).get('default_branch','?'))") + pass "acdl-evidence exists (default_branch=${default_branch})" +else + fail "acdl-evidence GET returned HTTP ${status}" +fi + +# --- Check 3: acdl-evidence raw index.html returns 200 (Pages substitute per D-012/D-016) --- +# acdl-evidence is public per gitea_setup.sh step 2b, so the raw URL should +# work without auth. We also try with the auth header as a fallback so the +# check does not spuriously fail if the repo visibility was reset. +echo "-- Check 3: acdl-evidence raw index.html returns 200 --" +index_url="${GITEA_HOST}/${ORG}/acdl-evidence/raw/branch/main/index.html" +status=$(curl -sS -o /tmp/p01_index.html -w "%{http_code}" "${index_url}") +if [ "$status" != "200" ] && [ -n "$TOKEN" ]; then + warn "raw URL returned ${status} unauth; retrying with Authorization header" + status=$(curl -sS -o /tmp/p01_index.html -w "%{http_code}" \ + -H "Authorization: token ${TOKEN}" "${index_url}") +fi +if [ "$status" = "200" ]; then + body_size=$(wc -c < /tmp/p01_index.html) + if grep -q "ACDL Evidence" /tmp/p01_index.html; then + pass "raw index.html returns 200 with placeholder body (${body_size} bytes)" + else + fail "raw index.html returns 200 but body does not contain 'ACDL Evidence' marker" + fi +else + fail "GET ${index_url} returned HTTP ${status}" +fi + +# --- Check 4: qa + prod branches exist on acdl-contracts --- +echo "-- Check 4: qa + prod branches exist on acdl-contracts --" +status=$(curl -sS -o /tmp/p01_branches.json -w "%{http_code}" \ + -H "Authorization: token ${TOKEN}" \ + "${GITEA_HOST}/api/v1/repos/${ORG}/acdl-contracts/branches?limit=50") +if [ "$status" != "200" ]; then + fail "list branches on acdl-contracts returned HTTP ${status}" +else + for want in qa prod; do + if python3 -c " +import json, sys +branches = json.load(open('/tmp/p01_branches.json')) +names = [b.get('name', '') for b in branches] +sys.exit(0 if '${want}' in names else 1) +"; then + pass "branch '${want}' exists on acdl-contracts" + else + fail "branch '${want}' missing on acdl-contracts" + fi + done +fi + +echo +echo "== Summary ==" +if [ "$fail_count" -eq 0 ]; then + echo "Phase 01 verification PASSED (all checks ok)" + exit 0 +else + echo "Phase 01 verification FAILED (${fail_count} check(s) failed)" + exit 1 +fi \ No newline at end of file