---ci---
phase: 4
milestone: v1.0
status: verify
requirements:
covered: [REQ-10, REQ-12]
partial: []
lessons:
- 'on: <trigger>' YAML key must be quoted ('on':) because PyYAML
interprets bare 'on:' as the boolean True (YAML 1.1 spec quirk).
Structural validators using 'd.get("on")' fail without the quote.
- Gitea Actions has no native approval-gate UI; the 3-dispatch
topology (initial dev, qa-approve, prod-approve+finalize) is the
documented fallback. State persists via audit.json on acdl-evidence
between dispatches (D-028), NOT via artifacts (artifacts do not
survive re-dispatch).
- urllib.error.URLError is a separate exception from HTTPError and
must be caught explicitly; otherwise a network failure (connection
refused, DNS, timeout) stack-traces instead of returning a clean
JSON failure.
review:
p0: 1
p1: 0
p0_items:
- id: P0-002
location: contracts-repo/.gitea/workflows/issue-to-contract.yml 'Parse Issue body' step
issue: gitea.event.issue.body was interpolated directly into a double-quoted shell argument ('"${{ gitea.event.issue.body }}"'), enabling shell injection from arbitrary Issue text
severity: P0 (a malicious Issue could execute arbitrary commands in the workflow step)
stride: Elevation of Privilege
fix: pass the body via an env var (ISSUE_BODY: ${{ gitea.event.issue.body }}) and reference $ISSUE_BODY in the run: step; Gitea Actions escapes env var values, preventing shell metacharacter interpretation
---/ci---
Layer 1 STRUCTURAL: PASS (4/4 files; bash -n + py_compile + yaml load clean; no TODO/FIXME).
Layer 2 BEHAVIORAL: PASS (verify_phase04.sh 12/12 checks).
Layer 3 SECURITY: PASS with 1 P0 fixed in this commit (P0-002: shell injection in issue body interpolation -> use env var).
Layer 4 QUALITY: PASS.
Note: P0-001 (finalize_evidence URLError) was fixed inline during T-4.4 (commit 7a93a92). Both P0s are now resolved.
---ci---
phase: 4
milestone: v1.0
status: execute
persona: backend-engineer
task: T-4.3
requirements:
covered: [REQ-12]
---/ci---
Wave 1, task T-4.3. Replaces the Phase 01 issue-to-contract.yml skeleton
with the real L3B trigger (D-030). On issue opened, the workflow checks out
the acdl repo at milestone/v1.0-initial (branch-pin rule) to obtain
l3b_agent_stub.py, parses the Issue body into contract.yaml, commits it to
a new branch contract/<issue-number> on acdl-contracts via the Gitea
file-contents API using the new_branch field (single POST creates the
branch + file), comments on and closes the Issue, then dispatches the
pipeline workflow on acdl via POST .../actions/workflows/pipeline.yml/
dispatches with inputs contract-ref=contract/<issue-number>
(cross-repo trigger per D-014). The `on:` key is quoted as "on": so
PyYAML parses it as a string key.
---ci---
phase: 4
milestone: v1.0
status: execute
persona: backend-engineer
task: T-4.2
requirements:
covered: [REQ-10]
---/ci---
Wave 1, task T-4.2. Replaces the Phase 01 pipeline.yml skeleton with the
real 3-dispatch approval-gate topology (D-027/D-028). workflow_dispatch
inputs contract-ref/approve_qa/approve_prod drive four jobs: dev (policy +
confidence gate + mock_executor + evidence, persist via finalize_evidence),
qa-gate (records approval), prod-gate (records approval), finalize
(needs: prod-gate, commits final audit.json to acdl-evidence). Each stage
persists its audit.json to acdl-evidence via the file-contents API since
Gitea Actions artifacts do not survive re-dispatch. The `on:` key is quoted
as "on": so PyYAML parses it as a string key (it would otherwise be coerced
to the boolean True).
---ci---
phase: 4
milestone: v1.0
status: execute
persona: backend-engineer
task: T-4.1
requirements:
covered: [REQ-10]
---/ci---
Wave 1, task T-4.1. Adds scripts/finalize_evidence.py, a stdlib-only helper
that uploads a local audit.json to the acdl-evidence Gitea repo via the
file-contents API. It GETs the existing file to discover its sha (update
mode) and falls back to create mode on 404, then PUTs/POSTs the base64-encoded
content with the Authorization: token <token> header. This is the per-stage
state-persistence mechanism (D-028) used by the pipeline jobs between
re-dispatches, and the finalize-step writer (D-029).
---ci---
phase: 4
milestone: v1.0
status: research
research:
gitea_actions_capabilities:
upload_artifact_v3: supported (v4 NOT supported by act_runner)
artifacts_cross_dispatch: NOT preserved (re-dispatch starts a new run)
workflow_dispatch_api: POST /actions/workflows/{filename}.yml/dispatches
workflow_call: supported; uses: <owner>/<repo>/.gitea/workflows/<file>@<ref>
checkout_cross_repo: actions/checkout@v4 with repository + ref + token
secrets: ${{ secrets.GITEA_TOKEN }} must be a manually-created PAT (auto-token is current-repo only)
approval_gate: workflow_dispatch input (approve_qa, approve_prod); no native pause-and-wait
pipeline_design:
topology: single pipeline.yml with workflow_dispatch inputs (contract-ref, approve_qa, approve_prod)
stages: 3 separate dispatches (initial dev, qa-approve, prod-approve+finalize)
state_persistence: each stage writes evidence to acdl-evidence via file-contents API (PUT audit.json)
job_if_conditions: dev runs when !approve_qa && !approve_prod; qa-gate when approve_qa && !approve_prod; prod-gate+finalize when approve_prod
personas: no change; backend-engineer owns the workflow YAML + finalize + issue trigger; lead-developer owns verify_phase04.sh
---/ci---
ARCHITECTURE.md gains a 'Phase 04 pipeline topology (research)' section:
the artifact-vs-re-dispatch limitation forces 3-dispatch topology with
evidence persisted to acdl-evidence (PUT audit.json) between dispatches.
PERSONAS.md is unchanged for Phase 04 (backend-engineer + lead-developer
active; infra-stub-engineer idle; frontend-engineer still off).
---ci---
phase: 4
milestone: v1.0
status: clarify
decisions:
- id: D-027
decision: Two workflows: pipeline.yml (workflow_call + workflow_dispatch) and issue-to-contract.yml (issues.opened); gates via workflow_dispatch approve_qa/approve_prod inputs
rationale: Gitea has no environment reviewers, no repository_dispatch, no native approval UI; D-013 fallback
confidence: 0.85
alternatives: [single long-running workflow that polls, external approval bot]
- id: D-028
decision: All 4 stages in one workflow run; state passed via upload/download-artifact; gates re-dispatched with approve inputs
rationale: Gitea supports upload/download-artifact; alternative is committing state between jobs (heavier)
confidence: 0.80
alternatives: [commit state.json + audit.json to a state branch between jobs]
- id: D-029
decision: Finalize commits audit.json to acdl-evidence main via the Gitea file-contents API (same mechanism as Phase 01 index.html push)
rationale: D-012 raw-URL requires the file to be on main; the API is the only way to put it there from a step
confidence: 0.90
alternatives: [clone acdl-evidence in the step, copy file, commit, push]
- id: D-030
decision: Issue-to-contract workflow checks out l3b_agent_stub from acdl, parses Issue, commits contract.yaml to contract/<issue-number> branch on acdl-contracts, then dispatches pipeline via workflow_dispatch API
rationale: Gitea Actions cannot trigger cross-repo without an explicit API call; the branch carries the contract ref
confidence: 0.85
alternatives: [push contract to a sentinel branch on the acdl repo itself]
---/ci---
Phase 04 has real pipeline-topology ambiguities (Gitea's gaps vs the spec's
approval-gate intent). Four decisions logged: D-027 two-workflow topology
+ dispatch-input gates, D-028 artifact-based state passing, D-029 finalize
commits via file-contents API, D-030 issue workflow checks out
l3b_agent_stub from acdl and dispatches the pipeline.