fix(platformctl): wire apply workflow evidence #167

Merged
Collaborator

Canary status: missing — fire canary 3+3 manually before merge

Canary Context Pack

Product story

The manual apply workflow should call the same guarded apply path and preserve evidence for the operator, without reusing canary or generic Forgejo secrets.

What changed

  • Forgejo Actions apply workflow now uses PLATFORMCTL_FORGEJO_TOKEN.
  • Checkout disables persisted credentials.
  • Workflow passes --approved-pr and PLATFORMCTL_PHASE=05 to platformctl apply.
  • Workflow writes state/AUDIT_LOG.jsonl and uploads .platform/state/modules/*.status.json as platform-apply-<pr>.
  • Added workflow contract tests.

Why it changed

PR #160 review asked to split workflow token/evidence behavior from runtime execution.

Files touched

  • control-plane/forgejo-actions/apply.yaml
  • control-plane/platformctl/tests/test_forgejo_ci_scripts_contract.py

Relevant context

Stacked after approval, provenance, no-op, compose execution, and evidence splits.

Runtime evidence

No workflow run or production mutation performed. Local contract tests validate the YAML behavior.

Known constraints

The workflow still requires operator-dispatched apply and proper Forgejo secret provisioning. This PR does not create or rotate secrets.

Explicit out-of-scope

Merging, production mutation, and changing runner/secret configuration.

Requested decision

Approve workflow wiring once prior stack PRs are approved.

Merge blockers

Any use of canary credentials, generic FORGEJO_TOKEN, persisted checkout credentials, missing --approved-pr, or missing evidence artifact upload.

Spec sources read

  • AGENTS.md — Forgejo/security-sensitive workflow rules.
  • control-plane/forgejo-actions/apply.yaml — workflow under change.
  • control-plane/platformctl/tests/test_forgejo_ci_scripts_contract.py — workflow contract tests.

Refs #142
Supersedes part of #160

Canary status: missing — fire canary 3+3 manually before merge ## Canary Context Pack ### Product story The manual apply workflow should call the same guarded apply path and preserve evidence for the operator, without reusing canary or generic Forgejo secrets. ### What changed - Forgejo Actions apply workflow now uses `PLATFORMCTL_FORGEJO_TOKEN`. - Checkout disables persisted credentials. - Workflow passes `--approved-pr` and `PLATFORMCTL_PHASE=05` to `platformctl apply`. - Workflow writes `state/AUDIT_LOG.jsonl` and uploads `.platform/state/modules/*.status.json` as `platform-apply-<pr>`. - Added workflow contract tests. ### Why it changed PR #160 review asked to split workflow token/evidence behavior from runtime execution. ### Files touched - `control-plane/forgejo-actions/apply.yaml` - `control-plane/platformctl/tests/test_forgejo_ci_scripts_contract.py` ### Relevant context Stacked after approval, provenance, no-op, compose execution, and evidence splits. ### Runtime evidence No workflow run or production mutation performed. Local contract tests validate the YAML behavior. ### Known constraints The workflow still requires operator-dispatched apply and proper Forgejo secret provisioning. This PR does not create or rotate secrets. ### Explicit out-of-scope Merging, production mutation, and changing runner/secret configuration. ### Requested decision Approve workflow wiring once prior stack PRs are approved. ### Merge blockers Any use of canary credentials, generic `FORGEJO_TOKEN`, persisted checkout credentials, missing `--approved-pr`, or missing evidence artifact upload. ## Spec sources read - `AGENTS.md` — Forgejo/security-sensitive workflow rules. - `control-plane/forgejo-actions/apply.yaml` — workflow under change. - `control-plane/platformctl/tests/test_forgejo_ci_scripts_contract.py` — workflow contract tests. Refs #142 Supersedes part of #160
fix(platformctl): wire apply workflow evidence
All checks were successful
canary-required / collect-diff (pull_request) Successful in 3s
pyfallow / Pyfallow gate (control-plane) (pull_request) Successful in 15s
python-ci / Python 3.11 (pull_request) Successful in 42s
python-ci / Python 3.12 (pull_request) Successful in 44s
python-ci / Python 3.13 (pull_request) Successful in 44s
canary-required / canary (pull_request) Successful in 12s
d1ddef4029
Author
Collaborator

Ralph review (5-iter chmurowy) — ITERATE_BLOCKER 4/9

Niezależny 5-iter ralph chain. Verdict + drafted patches poniżej.

⚠️ Note on BLOCKER 1: my re-read aktualnego control-plane/forgejo-actions/apply.yaml linia 141 pokazuje że echo "exit_code=$overall" >> $GITHUB_OUTPUT JEST already present. Ralph chain mógł reviewować diff context bez tej linii. Verify diff state przed apply. Jeśli już present, BLOCKER 1 jest no-op (ship). BLOCKER 2-3 są nadal real.

Per-dim scoring

  • Correctness: 3/9
  • Security: 3/9 ⚠️ (plan artifact substitution attack)
  • Observability: 4/9 (single append-only audit log)
  • Test coverage: 5/9
  • Scope discipline: 7/9

Evidence: ~/Iskra-i-Piotr/05 System/Swarmheart Backups/ralph-phase3-apply/167/.


BLOCKER 1 — Write exit_code to $GITHUB_OUTPUT (VERIFY: may already be present)

WHAT (per ralph finding): Apply step (linia ~141) musi emit exit_code przed exit $overall żeby downstream "Audit log finish" step (linia 150) mógł read ${{ steps.apply.outputs.exit_code }}.

WHY: Bez tego audit_finish event records null jako exit_code → failed applies look successful w audit trail. Evidence chain broken.

HOW (zapewnij że to jest w final state):

Linie 122-142 Apply step musi mieć:

      - name: Apply
        id: apply
        env:
          PLATFORMCTL_FORGEJO_TOKEN: ${{ secrets.PLATFORMCTL_FORGEJO_TOKEN }}
          PLATFORMCTL_FORGEJO_REPO: ${{ github.repository }}
          PLATFORMCTL_PHASE: "05"
        run: |
          set +e
          overall=0
          mkdir -p .platform/state/modules
          # ... apply loop ...
          # CRITICAL (ralph #167 BLOCKER 1): emit exit_code BEFORE exit.
          echo "exit_code=$overall" >> $GITHUB_OUTPUT
          exit $overall

Action item: jeśli to już jest tam → skip. Jeśli nie → add.


BLOCKER 2 — Plan artifact integrity verification

WHAT: Linia 63-67 — workflow downloads plan artifact by name only:

- name: Download plan artifact from PR
  uses: actions/download-artifact@v4
  with:
    name: platform-plan-${{ github.event.inputs.pr_number }}
    path: .plans/

WHY: Plan artifact substitution attack:

  1. Attacker uploads malicious artifact w innym workflow run z taką samą nazwą platform-plan-<PR>
  2. apply.yml downloads latest artifact matching that name
  3. Production apply executes attacker's plan
  4. Evidence trail looks normal (PR was merged, SHA validates, apply ran)

Plus artifact może być modified post-plan-workflow przez insider z artifacts write access.

HOW (drafted patch — dwie warstwy):

Layer 1: Nonce artifact name z plan workflow run_id

W plan workflow (osobny PR — wymaga koordynacji), artifact name powinna zawierać ${{ github.run_id }}:

# plan.yml (separate workflow)
- name: Upload plan artifact
  uses: actions/upload-artifact@v4
  with:
    name: platform-plan-${{ github.event.pull_request.number }}-run-${{ github.run_id }}
    # ↑ scope by run_id; can't be substituted by other run

W apply.yml: query plan workflow's last successful run dla tego PR + use run_id w download.

Layer 2: Plan content digest verification (defense-in-depth, niezależnie od artifact name)

Add new step po download:

      - name: Verify plan artifact integrity
        env:
          PLATFORMCTL_FORGEJO_TOKEN: ${{ secrets.PLATFORMCTL_FORGEJO_TOKEN }}
        run: |
          set -e
          # Verify each plan file's source_sha matches the merged PR SHA + 
          # source_dirty is false. Reject any plan whose source_sha doesn't
          # match merged_sha. This is defense-in-depth — apply.py also checks.
          for p in .plans/*.plan.json; do
            plan_sha=$(jq -r '.source_sha' "$p")
            plan_dirty=$(jq -r '.source_dirty' "$p")
            if [ "$plan_sha" != "${{ steps.pr_check.outputs.merged_sha }}" ]; then
              # Allow plan_sha == head_sha case (PR head before merge commit)
              # — match against PR's head.sha as well
              head_sha=$(curl -fsS -H "Authorization: token $PLATFORMCTL_FORGEJO_TOKEN" \
                "https://git.pdurlej.com/api/v1/repos/${{ github.repository }}/pulls/${{ github.event.inputs.pr_number }}" \
                | jq -r '.head.sha')
              if [ "$plan_sha" != "$head_sha" ]; then
                echo "::error::plan $p source_sha=$plan_sha matches neither merged_sha nor head_sha. Refusing apply."
                exit 1
              fi
            fi
            if [ "$plan_dirty" != "false" ]; then
              echo "::error::plan $p was generated from dirty checkout. Refusing apply."
              exit 1
            fi
          done
          echo "All plan artifacts verified against merged PR identity."

VERIFY (add to tests/test_forgejo_ci_scripts_contract.py):

def test_apply_workflow_has_plan_integrity_check():
    """apply.yml must verify plan source_sha matches PR (ralph BLOCKER 2)."""
    workflow = Path("control-plane/forgejo-actions/apply.yaml").read_text()
    assert "Verify plan artifact integrity" in workflow, (
        "missing plan integrity verification step (ralph #167 BLOCKER 2)"
    )
    assert "source_sha" in workflow
    assert "source_dirty" in workflow

BLOCKER 3 — Include github.run_id in audit entries

WHAT: Lines 108-113 (audit_start) + 144-151 (audit_finish) — both append do state/AUDIT_LOG.jsonl jako single file. No run_id, no per-run scope.

WHY:

  • Forensic analysis impossible — many entries for same PR (if applied retry'd), nie da się distinguish runs
  • Append-only single file = race jeśli concurrent dispatches (mimo concurrency group)
  • Dedup impossible — if run is rerun, both runs' entries coexist confusingly

HOW (drafted patch w apply.yaml linie 108-113 + 144-151):

      # ---- Audit log entry: apply STARTED ----
      - name: Audit log start
        run: |
          mkdir -p state/audit
          ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
          # SECURITY (ralph #167 BLOCKER 3): per-run audit file. 
          # AUDIT_LOG.jsonl per run_id; single global log not append-only-corrupt.
          audit_file="state/audit/AUDIT_LOG_${{ github.run_id }}.jsonl"
          echo "{\"ts\":\"$ts\",\"actor\":\"forgejo-action\",\"run_id\":\"${{ github.run_id }}\",\"action\":\"apply_start\",\"pr\":${{ github.event.inputs.pr_number }},\"sha\":\"${{ steps.pr_check.outputs.merged_sha }}\",\"module\":\"${{ github.event.inputs.module }}\",\"reason\":${{ toJSON(github.event.inputs.reason) }}}" \
            >> "$audit_file"
          echo "audit_file=$audit_file" >> $GITHUB_OUTPUT
        id: audit_start

      # ... (Apply step unchanged) ...

      # ---- Audit log entry: apply FINISHED ----
      - name: Audit log finish
        if: always()
        run: |
          ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
          audit_file="state/audit/AUDIT_LOG_${{ github.run_id }}.jsonl"
          echo "{\"ts\":\"$ts\",\"actor\":\"forgejo-action\",\"run_id\":\"${{ github.run_id }}\",\"action\":\"apply_finish\",\"pr\":${{ github.event.inputs.pr_number }},\"exit_code\":${{ steps.apply.outputs.exit_code }}}" \
            >> "$audit_file"

      - name: Upload apply evidence artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: platform-apply-${{ github.event.inputs.pr_number }}-run-${{ github.run_id }}
          # ↑ also scope this artifact name by run_id (defense-in-depth)
          path: |
            .platform/state/modules/*.status.json
            state/audit/AUDIT_LOG_${{ github.run_id }}.jsonl
          if-no-files-found: warn

Plus consider: post-apply, optionally cat per-run audit file za do global archive (long-term forensic store), but never append directly do global during run.

VERIFY:

def test_audit_log_uses_per_run_file():
    """Audit log must scope by run_id (ralph BLOCKER 3)."""
    workflow = Path("control-plane/forgejo-actions/apply.yaml").read_text()
    assert "AUDIT_LOG_${{ github.run_id }}.jsonl" in workflow, (
        "audit log must be per-run, not global append-only (ralph #167 BLOCKER 3)"
    )
    # Verify no global AUDIT_LOG.jsonl writes (the old name)
    # in audit_start/audit_finish steps:
    assert "state/AUDIT_LOG.jsonl" not in workflow or "audit/AUDIT_LOG_" in workflow

Follow-up issues

STATUS-JSON-TOKEN-SCRUB-01 — Sanitize *.status.json for token leakage

Status artifacts (linia 159) zawierają wynik apply. Combined z BLOCKER's failure modes (ralph #166), tokens z env mogą leak. Add apply.py redaction layer że scrub Forgejo token, Tailscale OAuth patterns przed status write.

SHELL-CRASH-DETECT-01 — Harden shell crash detection

Linia 124: overall=0. Jeśli apply step bash crashes before reaching echo exit_code (np. SIGKILL z OOM), overall jest never set i Audit log finish reads null. Add trap na ERR/EXIT/INT do force exit_code=255 init.

PHASE-DOC-01 — Document PLATFORMCTL_PHASE: "05" semantics

Linia 121 sets PLATFORMCTL_PHASE: "05" jako env. Magic string bez inline comment what this means. Add comment: "Phase 05 enables real apply per PLATFORM_CHARTER.md; before that, apply is dry-run-only."

CONTRACT-TEST-YAML-01 — Refactor test_forgejo_ci_scripts_contract.py

Current test (per PR) uses brittle substring checks (assert "X" in workflow_text). Refactor do parse YAML i assert against structured fields (step names, env vars, action versions).


Action items

  1. Verify BLOCKER 1 status (probably already applied — check diff)
  2. Apply BLOCKER 2 + 3 patches → apply.yaml + tests
  3. Force-push do codex/issues/142-apply-workflow-wiring
  4. File 4 follow-up issues
  5. Comment "ready for re-review" tutaj

— ralph batch 2026-05-10, claude-opus-4.7 (Pan Herbata) dispatching via codex identity

## Ralph review (5-iter chmurowy) — ITERATE_BLOCKER 4/9 Niezależny 5-iter ralph chain. Verdict + drafted patches poniżej. ⚠️ **Note on BLOCKER 1:** my re-read aktualnego `control-plane/forgejo-actions/apply.yaml` linia 141 pokazuje że `echo "exit_code=$overall" >> $GITHUB_OUTPUT` JEST already present. Ralph chain mógł reviewować diff context bez tej linii. **Verify diff state przed apply.** Jeśli już present, BLOCKER 1 jest no-op (ship). BLOCKER 2-3 są nadal real. ### Per-dim scoring - Correctness: 3/9 - **Security: 3/9** ⚠️ (plan artifact substitution attack) - Observability: 4/9 (single append-only audit log) - Test coverage: 5/9 - Scope discipline: 7/9 Evidence: `~/Iskra-i-Piotr/05 System/Swarmheart Backups/ralph-phase3-apply/167/`. --- ## BLOCKER 1 — Write `exit_code` to `$GITHUB_OUTPUT` (VERIFY: may already be present) **WHAT (per ralph finding):** Apply step (linia ~141) musi emit `exit_code` przed `exit $overall` żeby downstream "Audit log finish" step (linia 150) mógł read `${{ steps.apply.outputs.exit_code }}`. **WHY:** Bez tego audit_finish event records `null` jako exit_code → failed applies look successful w audit trail. Evidence chain broken. **HOW (zapewnij że to jest w final state):** Linie 122-142 `Apply` step musi mieć: ```yaml - name: Apply id: apply env: PLATFORMCTL_FORGEJO_TOKEN: ${{ secrets.PLATFORMCTL_FORGEJO_TOKEN }} PLATFORMCTL_FORGEJO_REPO: ${{ github.repository }} PLATFORMCTL_PHASE: "05" run: | set +e overall=0 mkdir -p .platform/state/modules # ... apply loop ... # CRITICAL (ralph #167 BLOCKER 1): emit exit_code BEFORE exit. echo "exit_code=$overall" >> $GITHUB_OUTPUT exit $overall ``` **Action item:** jeśli to już jest tam → skip. Jeśli nie → add. --- ## BLOCKER 2 — Plan artifact integrity verification **WHAT:** Linia 63-67 — workflow downloads plan artifact by name only: ```yaml - name: Download plan artifact from PR uses: actions/download-artifact@v4 with: name: platform-plan-${{ github.event.inputs.pr_number }} path: .plans/ ``` **WHY:** Plan artifact substitution attack: 1. Attacker uploads malicious artifact w innym workflow run z taką samą nazwą `platform-plan-<PR>` 2. apply.yml downloads latest artifact matching that name 3. Production apply executes attacker's plan 4. **Evidence trail looks normal** (PR was merged, SHA validates, apply ran) Plus artifact może być modified post-plan-workflow przez insider z artifacts write access. **HOW (drafted patch — dwie warstwy):** **Layer 1: Nonce artifact name z plan workflow run_id** W plan workflow (osobny PR — wymaga koordynacji), artifact name powinna zawierać `${{ github.run_id }}`: ```yaml # plan.yml (separate workflow) - name: Upload plan artifact uses: actions/upload-artifact@v4 with: name: platform-plan-${{ github.event.pull_request.number }}-run-${{ github.run_id }} # ↑ scope by run_id; can't be substituted by other run ``` W apply.yml: query plan workflow's last successful run dla tego PR + use run_id w download. **Layer 2: Plan content digest verification** (defense-in-depth, niezależnie od artifact name) Add new step po download: ```yaml - name: Verify plan artifact integrity env: PLATFORMCTL_FORGEJO_TOKEN: ${{ secrets.PLATFORMCTL_FORGEJO_TOKEN }} run: | set -e # Verify each plan file's source_sha matches the merged PR SHA + # source_dirty is false. Reject any plan whose source_sha doesn't # match merged_sha. This is defense-in-depth — apply.py also checks. for p in .plans/*.plan.json; do plan_sha=$(jq -r '.source_sha' "$p") plan_dirty=$(jq -r '.source_dirty' "$p") if [ "$plan_sha" != "${{ steps.pr_check.outputs.merged_sha }}" ]; then # Allow plan_sha == head_sha case (PR head before merge commit) # — match against PR's head.sha as well head_sha=$(curl -fsS -H "Authorization: token $PLATFORMCTL_FORGEJO_TOKEN" \ "https://git.pdurlej.com/api/v1/repos/${{ github.repository }}/pulls/${{ github.event.inputs.pr_number }}" \ | jq -r '.head.sha') if [ "$plan_sha" != "$head_sha" ]; then echo "::error::plan $p source_sha=$plan_sha matches neither merged_sha nor head_sha. Refusing apply." exit 1 fi fi if [ "$plan_dirty" != "false" ]; then echo "::error::plan $p was generated from dirty checkout. Refusing apply." exit 1 fi done echo "All plan artifacts verified against merged PR identity." ``` **VERIFY (add to `tests/test_forgejo_ci_scripts_contract.py`):** ```python def test_apply_workflow_has_plan_integrity_check(): """apply.yml must verify plan source_sha matches PR (ralph BLOCKER 2).""" workflow = Path("control-plane/forgejo-actions/apply.yaml").read_text() assert "Verify plan artifact integrity" in workflow, ( "missing plan integrity verification step (ralph #167 BLOCKER 2)" ) assert "source_sha" in workflow assert "source_dirty" in workflow ``` --- ## BLOCKER 3 — Include `github.run_id` in audit entries **WHAT:** Lines 108-113 (audit_start) + 144-151 (audit_finish) — both append do `state/AUDIT_LOG.jsonl` jako single file. No `run_id`, no per-run scope. **WHY:** - **Forensic analysis impossible** — many entries for same PR (if applied retry'd), nie da się distinguish runs - **Append-only single file** = race jeśli concurrent dispatches (mimo concurrency group) - **Dedup impossible** — if run is rerun, both runs' entries coexist confusingly **HOW (drafted patch w `apply.yaml` linie 108-113 + 144-151):** ```yaml # ---- Audit log entry: apply STARTED ---- - name: Audit log start run: | mkdir -p state/audit ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) # SECURITY (ralph #167 BLOCKER 3): per-run audit file. # AUDIT_LOG.jsonl per run_id; single global log not append-only-corrupt. audit_file="state/audit/AUDIT_LOG_${{ github.run_id }}.jsonl" echo "{\"ts\":\"$ts\",\"actor\":\"forgejo-action\",\"run_id\":\"${{ github.run_id }}\",\"action\":\"apply_start\",\"pr\":${{ github.event.inputs.pr_number }},\"sha\":\"${{ steps.pr_check.outputs.merged_sha }}\",\"module\":\"${{ github.event.inputs.module }}\",\"reason\":${{ toJSON(github.event.inputs.reason) }}}" \ >> "$audit_file" echo "audit_file=$audit_file" >> $GITHUB_OUTPUT id: audit_start # ... (Apply step unchanged) ... # ---- Audit log entry: apply FINISHED ---- - name: Audit log finish if: always() run: | ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) audit_file="state/audit/AUDIT_LOG_${{ github.run_id }}.jsonl" echo "{\"ts\":\"$ts\",\"actor\":\"forgejo-action\",\"run_id\":\"${{ github.run_id }}\",\"action\":\"apply_finish\",\"pr\":${{ github.event.inputs.pr_number }},\"exit_code\":${{ steps.apply.outputs.exit_code }}}" \ >> "$audit_file" - name: Upload apply evidence artifacts if: always() uses: actions/upload-artifact@v4 with: name: platform-apply-${{ github.event.inputs.pr_number }}-run-${{ github.run_id }} # ↑ also scope this artifact name by run_id (defense-in-depth) path: | .platform/state/modules/*.status.json state/audit/AUDIT_LOG_${{ github.run_id }}.jsonl if-no-files-found: warn ``` Plus consider: post-apply, optionally cat per-run audit file za do global archive (long-term forensic store), but **never append directly do global** during run. **VERIFY:** ```python def test_audit_log_uses_per_run_file(): """Audit log must scope by run_id (ralph BLOCKER 3).""" workflow = Path("control-plane/forgejo-actions/apply.yaml").read_text() assert "AUDIT_LOG_${{ github.run_id }}.jsonl" in workflow, ( "audit log must be per-run, not global append-only (ralph #167 BLOCKER 3)" ) # Verify no global AUDIT_LOG.jsonl writes (the old name) # in audit_start/audit_finish steps: assert "state/AUDIT_LOG.jsonl" not in workflow or "audit/AUDIT_LOG_" in workflow ``` --- ## Follow-up issues ### `STATUS-JSON-TOKEN-SCRUB-01` — Sanitize `*.status.json` for token leakage > Status artifacts (linia 159) zawierają wynik apply. Combined z BLOCKER's failure modes (ralph #166), tokens z env mogą leak. Add `apply.py` redaction layer że scrub `Forgejo token`, `Tailscale OAuth` patterns przed status write. ### `SHELL-CRASH-DETECT-01` — Harden shell crash detection > Linia 124: `overall=0`. Jeśli apply step bash crashes before reaching `echo exit_code` (np. SIGKILL z OOM), `overall` jest never set i Audit log finish reads `null`. Add `trap` na ERR/EXIT/INT do force exit_code=255 init. ### `PHASE-DOC-01` — Document `PLATFORMCTL_PHASE: "05"` semantics > Linia 121 sets `PLATFORMCTL_PHASE: "05"` jako env. Magic string bez inline comment what this means. Add comment: "Phase 05 enables real apply per PLATFORM_CHARTER.md; before that, apply is dry-run-only." ### `CONTRACT-TEST-YAML-01` — Refactor test_forgejo_ci_scripts_contract.py > Current test (per PR) uses brittle substring checks (`assert "X" in workflow_text`). Refactor do parse YAML i assert against structured fields (step names, env vars, action versions). --- ## Action items 1. Verify BLOCKER 1 status (probably already applied — check diff) 2. Apply BLOCKER 2 + 3 patches → `apply.yaml` + tests 3. Force-push do `codex/issues/142-apply-workflow-wiring` 4. File 4 follow-up issues 5. Comment "ready for re-review" tutaj — ralph batch 2026-05-10, claude-opus-4.7 (Pan Herbata) dispatching via codex identity
codex force-pushed codex/issues/142-apply-workflow-wiring from d1ddef4029
All checks were successful
canary-required / collect-diff (pull_request) Successful in 3s
pyfallow / Pyfallow gate (control-plane) (pull_request) Successful in 15s
python-ci / Python 3.11 (pull_request) Successful in 42s
python-ci / Python 3.12 (pull_request) Successful in 44s
python-ci / Python 3.13 (pull_request) Successful in 44s
canary-required / canary (pull_request) Successful in 12s
to c964686e03
All checks were successful
canary-required / collect-diff (pull_request) Successful in 3s
pyfallow / Pyfallow gate (control-plane) (pull_request) Successful in 17s
python-ci / Python 3.11 (pull_request) Successful in 43s
python-ci / Python 3.12 (pull_request) Successful in 44s
python-ci / Python 3.13 (pull_request) Successful in 45s
canary-required / canary (pull_request) Successful in 11s
2026-05-12 01:02:20 +02:00
Compare
Author
Collaborator

Ready for re-review — ralph blockers addressed

Updated #167 on commit c964686e033375a60520d6a644aff1c85c432a35.

Addressed

  • BLOCKER 1: verified as already present before this iteration: echo \"exit_code=$overall\" >> $GITHUB_OUTPUT remains in the Apply step before exit $overall.
  • BLOCKER 2: added Verify plan artifact integrity; downloaded plan files must have source_sha matching the merged PR SHA or PR head SHA, and source_dirty=false.
  • BLOCKER 3: audit evidence now writes per-run state/audit/AUDIT_LOG_${{ github.run_id }}.jsonl, includes run_id, and apply evidence artifact names include run_id.

Verification

  • PYTHONPATH=control-plane pytest control-plane/platformctl/tests/test_apply_phase3.py control-plane/platformctl/tests/test_forgejo_ci_scripts_contract.py -q → 53 passed
  • Full tests/run-verify.sh still blocked by pre-existing main prompt debt: prompts/codex-rs2000-close-2026-05-12.md token budget + missing P2 image-prune prompt reference.

Follow-ups filed

  • #206 STATUS-JSON-TOKEN-SCRUB-01
  • #207 SHELL-CRASH-DETECT-01
  • #208 PHASE-DOC-01
  • #209 CONTRACT-TEST-YAML-01
## Ready for re-review — ralph blockers addressed Updated #167 on commit `c964686e033375a60520d6a644aff1c85c432a35`. ### Addressed - BLOCKER 1: verified as already present before this iteration: `echo \"exit_code=$overall\" >> $GITHUB_OUTPUT` remains in the Apply step before `exit $overall`. - BLOCKER 2: added `Verify plan artifact integrity`; downloaded plan files must have `source_sha` matching the merged PR SHA or PR head SHA, and `source_dirty=false`. - BLOCKER 3: audit evidence now writes per-run `state/audit/AUDIT_LOG_${{ github.run_id }}.jsonl`, includes `run_id`, and apply evidence artifact names include `run_id`. ### Verification - `PYTHONPATH=control-plane pytest control-plane/platformctl/tests/test_apply_phase3.py control-plane/platformctl/tests/test_forgejo_ci_scripts_contract.py -q` → 53 passed - Full `tests/run-verify.sh` still blocked by pre-existing main prompt debt: `prompts/codex-rs2000-close-2026-05-12.md` token budget + missing P2 image-prune prompt reference. ### Follow-ups filed - #206 STATUS-JSON-TOKEN-SCRUB-01 - #207 SHELL-CRASH-DETECT-01 - #208 PHASE-DOC-01 - #209 CONTRACT-TEST-YAML-01
pdurlej merged commit b3231b9ce3 into codex/issues/142-apply-evidence-artifacts 2026-05-12 01:29:35 +02:00
Sign in to join this conversation.
No reviewers
No labels
W6d-automerge-calibration
agent/claude-code
agent/codex
agent/hermes
agent/iskra
agent/ollama
agent/patchwarden
automerge-candidate
class/security-sensitive
cutover-gate
dependency/blocked
dependency/blocks-others
dependency/cross-repo
dependency/needs-confirmation
domain:agents
domain:ci
domain:docs
domain:forgejo
domain:infra
domain:memory
domain:runtime
domain:signal
domain:ux
flow/architecture
flow/blocked
flow/deployed
flow/done
flow/implementation
flow/intake
flow/maintained
flow/observed
flow/ready
flow/refining
flow/retired
flow/review
iterating
judge/codex-candidate
judge/hermes-candidate
judge/low-confidence
judge/needs-refinement
judge/operator-needed
judge/p0
judge/p1
judge/p2
judge/p3
judge/park
judge/patchwarden-candidate
judge/stale-priority
kind/adr
kind/bug
kind/chore
kind/feature
kind/infra
kind/ops
kind/refactor
kind/research
large-impact
merge/auto
merge/manual
merge/manual-dependency-conflict
merge/manual-failing-tests
merge/manual-merge-conflict
merge/manual-missing-review
merge/manual-operator-preference
merge/manual-red-zone
merge/manual-security-sensitive
merge/manual-unclear-scope
merge/manual-unknown
meta
mode:operator-only
mode:patchwarden-iskra-approved
mode:safe-auto
needs-operator-decision
needs-triage
not-ready
observed/erroring
observed/needs-followup
observed/pending
observed/retire-candidate
observed/unused
observed/used
operator-emotional
owner-attention
phase/02
phase/03
priority:p0
priority:p1
priority:p2
priority:p3
proposed
ready-for-agent
ready-for-operator
recovery
review:claude-reviewed
review:codex-reviewed
review:dziadek-reviewed
review:needs-human
risk/exposure
risk/process
risk/product
risk/runtime
safety:external-write
safety:no-prod-mutation
safety:prod-impact
safety:secret-touch
size/large
size/medium
size/small
size/tiny
size/unknown
source/adr
source/agent-generated
source/manual
source/operator-chat
source/voice-note
status:blocked
status:codex-ready
status:merged:pending-evidence
status:needs-evidence
status:operator-needed
status:parked
tier/full
tier/lite
tier/stacked
tier:0-platform-substrate
tier:1-iskra-value-layer
tier:2-tools-products-modules
type:bug
type:chore
type:docs
type:feat
type:policy
type:research
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pdurlej/platform!167
No description provided.