ship: phase-01 repo-scaffolding #1

Merged
grimacing merged 12 commits from phase/01-repo-scaffolding into milestone/v1.0-initial 2026-07-21 13:11:02 +00:00
18 changed files with 759 additions and 8 deletions
+34 -1
View File
@@ -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.
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.<id>.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.
+91
View File
@@ -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.
+96
View File
@@ -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).
+7 -1
View File
@@ -69,4 +69,10 @@ Build a runnable demo (Linux + GitHub/Gitea Actions) that walks executives throu
| 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-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 |
+12 -4
View File
@@ -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 |
+1 -1
View File
@@ -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:**
+2 -1
View File
@@ -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"
}
View File
+75
View File
@@ -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.<id>.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
+9
View File
@@ -0,0 +1,9 @@
__pycache__/
*.pyc
*.pyo
.env
.env.*
state.json
audit.json
*.tmp
.DS_Store
+55
View File
@@ -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=<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.
@@ -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/<issue-number>).
# 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/<id>/dispatches \
# -d '{"ref":"milestone/v1.0-initial","inputs":{"contract-ref":"<branch>"}}'
- 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
View File
View File
View File
+228
View File
@@ -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=<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='<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ACDL Evidence</title>
<style>body{font-family:system-ui,sans-serif;margin:2rem;color:#333}</style>
</head>
<body>
<h1>ACDL Evidence Stream</h1>
<p>Evidence timeline will appear here in Phase 05.</p>
<p>Placeholder served via Gitea raw file URL (D-012; Gitea has no native Pages).</p>
</body>
</html>'
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: <set, ${#TOKEN} chars>"
# 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
+109
View File
@@ -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=<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: <set, ${#TOKEN} chars>"
else
echo "Token: <unset>"
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