fix(platformctl): bind apply plans to source sha #163

Merged
pdurlej merged 2 commits from codex/issues/142-apply-plan-provenance into codex/issues/142-apply-approval-binding 2026-05-12 01:28:31 +02:00
Collaborator

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

Canary Context Pack

Product story

An apply should consume the exact plan produced from the approved code, not an old, dirty, or unrelated artifact.

What changed

  • platformctl plan now records source_sha and source_dirty.
  • platformctl apply rejects missing, dirty, or mismatched plan sources.
  • Added tests for source metadata and apply rejection behavior.

Why it changed

PR #160 review flagged that apply was not sufficiently bound to the approved PR and that plan provenance could disappear into artifacts.

Files touched

  • control-plane/platformctl/apply.py
  • control-plane/platformctl/plan.py
  • control-plane/platformctl/tests/test_apply_phase3.py
  • control-plane/platformctl/tests/test_plan_phase3.py

Relevant context

Stacked after approval-binding split PR.

Runtime evidence

No runtime mutation in this PR. Local tests only.

Known constraints

This records and enforces provenance; durable apply evidence is in a later split.

Explicit out-of-scope

Compose execution, status artifacts, and Actions wiring.

Requested decision

Approve plan provenance as a separate invariant.

Merge blockers

Any path that lets a plan generated from a dirty or different checkout pass apply.

Spec sources read

  • AGENTS.md — security-sensitive split rule.
  • control-plane/platformctl/apply.py — plan source gate.
  • control-plane/platformctl/plan.py — plan artifact metadata.
  • control-plane/platformctl/tests/test_apply_phase3.py and test_plan_phase3.py — tests.

Refs #142
Supersedes part of #160

Canary status: missing — fire canary 3+3 manually before merge ## Canary Context Pack ### Product story An apply should consume the exact plan produced from the approved code, not an old, dirty, or unrelated artifact. ### What changed - `platformctl plan` now records `source_sha` and `source_dirty`. - `platformctl apply` rejects missing, dirty, or mismatched plan sources. - Added tests for source metadata and apply rejection behavior. ### Why it changed PR #160 review flagged that apply was not sufficiently bound to the approved PR and that plan provenance could disappear into artifacts. ### Files touched - `control-plane/platformctl/apply.py` - `control-plane/platformctl/plan.py` - `control-plane/platformctl/tests/test_apply_phase3.py` - `control-plane/platformctl/tests/test_plan_phase3.py` ### Relevant context Stacked after approval-binding split PR. ### Runtime evidence No runtime mutation in this PR. Local tests only. ### Known constraints This records and enforces provenance; durable apply evidence is in a later split. ### Explicit out-of-scope Compose execution, status artifacts, and Actions wiring. ### Requested decision Approve plan provenance as a separate invariant. ### Merge blockers Any path that lets a plan generated from a dirty or different checkout pass apply. ## Spec sources read - `AGENTS.md` — security-sensitive split rule. - `control-plane/platformctl/apply.py` — plan source gate. - `control-plane/platformctl/plan.py` — plan artifact metadata. - `control-plane/platformctl/tests/test_apply_phase3.py` and `test_plan_phase3.py` — tests. Refs #142 Supersedes part of #160
fix(platformctl): bind apply plans to source sha
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 44s
python-ci / Python 3.12 (pull_request) Successful in 46s
python-ci / Python 3.13 (pull_request) Successful in 46s
canary-required / canary (pull_request) Successful in 12s
9783f2d457
Author
Collaborator

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

Niezależny 5-iter ralph chain (kimi / glm / deepseek-pro / kimi / deepseek-pro). Verdict + drafted patches poniżej.

Per-dim scoring

  • Correctness: 3/9
  • Security: 2/9 ⚠️ ("no meaningful security improvement in current state" — arbiter)
  • Observability: 5/9
  • Test coverage: 4/9
  • Scope discipline: 8/9

Evidence: ~/Iskra-i-Piotr/05 System/Swarmheart Backups/ralph-phase3-apply/163/ (full chain).


BLOCKER 1 — Remove env-var overrides for source SHA/dirty

WHAT: plan.py lines 64-66 i 72-76: current_source_sha() i source_tree_dirty() accept env-var overrides (PLATFORMCTL_SOURCE_SHA, PLATFORMCTL_SOURCE_DIRTY) bez verification.

WHY: Insider/CI z env-var write access trivially forges provenance:

export PLATFORMCTL_SOURCE_SHA=$(git rev-parse main)
export PLATFORMCTL_SOURCE_DIRTY=false
platformctl plan some-module --out plan.json
# Plan claims pristine source even from dirty working tree

To dokładnie to czego provenance binding ma zapobiegać.

HOW (drafted patch w plan.py linie 62-91):

def current_source_sha(*, repo_root: Path = Path(".")) -> str | None:
    """Return the git commit that produced a plan artifact.

    SECURITY (ralph #163 BLOCKER 1): env-var self-attestation removed.
    For CI use, gate behind explicit --ci-provenance flag at caller level.
    """
    return _git_stdout(["rev-parse", "HEAD"], repo_root=repo_root)


def source_tree_dirty(*, repo_root: Path = Path(".")) -> bool:
    """Return whether tracked files differ from HEAD.

    SECURITY (ralph #163 BLOCKER 1+3): fail-CLOSED on any git error.
    Env-var override removed.
    """
    try:
        result = subprocess.run(
            ["git", "-C", str(repo_root), "diff-index", "--quiet", "HEAD", "--"],
            check=False,
            capture_output=True,
            text=True,
            timeout=5,
        )
    except (OSError, subprocess.TimeoutExpired):
        # Cannot determine clean state → must assume dirty (fail-closed).
        return True
    if result.returncode == 0:
        return False
    # returncode == 1 (dirty) or anything else → treat as dirty.
    return True

Plus update _new_plan (linia 100) jeśli potrzeba — source_dirty jest teraz bool, nie bool | None.

VERIFY (add to tests/test_plan_phase3.py):

def test_source_tree_dirty_fail_closed_on_git_error(monkeypatch):
    """Git failure must report dirty=True (ralph BLOCKER 1+3)."""
    def raise_oserror(*a, **kw): raise OSError("git not found")
    monkeypatch.setattr(subprocess, "run", raise_oserror)
    assert source_tree_dirty(repo_root=Path("/nonexistent")) is True


def test_current_source_sha_no_env_override(monkeypatch):
    """Env-var override removed (ralph BLOCKER 1)."""
    monkeypatch.setenv("PLATFORMCTL_SOURCE_SHA", "deadbeef" * 5)
    # Real git rev-parse must run; env var ignored.
    sha = current_source_sha()
    assert sha != "deadbeef" * 5  # not env value

BLOCKER 2 — Hard-fail on missing git provenance

WHAT: current_source_sha returns None if git fails. _new_plan (linia 99) writes "source_sha": None to artifact. Then _validate_plan_source (linia 228) catches not isinstance(...str) — but only at apply time, after artifact is shipped.

WHY: Plan with source_sha: null should NEVER exist. Fail w plan generation, nie w apply review.

HOW (drafted patch w plan.py _new_plan linia 94):

class PlanProvenanceError(Exception):
    """Raised when plan cannot establish git provenance."""


def _new_plan(manifest: Manifest) -> dict:
    sha = current_source_sha()
    if sha is None:
        raise PlanProvenanceError(
            "cannot determine source git SHA — refuse to write plan with null provenance "
            "(ralph #163 BLOCKER 2). Run platformctl plan from inside a git checkout."
        )
    return {
        "apiVersion": "platform.pdurlej.com/plan/v1",
        "command": "plan",
        "module": manifest.id,
        "source_sha": sha,
        "source_dirty": source_tree_dirty(),
        # ...rest unchanged
    }

Plus plan_module (linia 390) catches PlanProvenanceError and returns EXIT_TOOL_ERROR.

VERIFY:

def test_new_plan_raises_when_git_unavailable(monkeypatch):
    """Plan with null provenance must NOT be written (ralph BLOCKER 2)."""
    monkeypatch.setattr("platformctl.plan.current_source_sha", lambda **kw: None)
    with pytest.raises(PlanProvenanceError):
        _new_plan(fake_manifest())

BLOCKER 3 — Fail-closed dirty check (covered in BLOCKER 1)

Już w patch 1 — source_tree_dirty zwraca True na każdy git error.


BLOCKER 4 — Strict boolean validation in apply.py

WHAT: _validate_plan_source linia 230: if plan.get("source_dirty") is True. Akceptuje None, missing key, string "false", integer 0 — wszystko jako "not dirty".

WHY: Type-unsafe check pozwala plans z manipulated source_dirty (np. usunięty z artifact, lub set to "clean") pass approval.

HOW (drafted patch w apply.py linie 226-234):

def _validate_plan_source(plan: dict, approved_sha: str, approval: ApprovalCheck) -> str | None:
    source_sha = plan.get("source_sha")
    if not isinstance(source_sha, str) or not _valid_sha(source_sha):
        return "plan source_sha is missing or invalid; regenerate the plan from the approved PR checkout"

    # SECURITY (ralph #163 BLOCKER 4): strict bool check.
    # None, missing, strings, ints must all be rejected — not just `is True`.
    source_dirty = plan.get("source_dirty", "<missing>")
    if source_dirty is True:
        return "plan was generated from a dirty checkout; regenerate it from a clean approved PR checkout"
    if not isinstance(source_dirty, bool):
        return (
            f"plan source_dirty must be a bool (got {type(source_dirty).__name__}: "
            f"{source_dirty!r}); regenerate the plan with current platformctl"
        )
    # source_dirty is False — OK.

    if source_sha not in _approved_sha_values(approved_sha, approval):
        return "plan source_sha does not match the approved PR head/merge SHA"
    return None

VERIFY:

@pytest.mark.parametrize("bad_value", [None, "false", 0, "clean", {"x": 1}])
def test_validate_plan_source_rejects_non_bool_dirty(bad_value):
    """source_dirty must be a real bool — ralph BLOCKER 4."""
    plan = {"source_sha": "a" * 40, "source_dirty": bad_value}
    approval = ApprovalCheck(True, "ok", head_sha="a" * 40)
    err = _validate_plan_source(plan, "a" * 40, approval)
    assert err is not None and "source_dirty must be a bool" in err

BLOCKER 5 — Remove merge_sha from approved set

WHAT: _approved_sha_values (linie 217-223 apply.py) accepts BOTH head_sha and merge_sha jako valid binding.

WHY: merge_sha reprezentuje Forgejo merge commit moment — automatically generated, not reviewed. Tylko head_sha (Codex's commit) lub approved_sha (operator's checkbox) reprezentuje reviewed state. Akceptując merge_sha, opens "merge_sha overreach" attack vector (arbiter finding).

HOW (drafted patch w apply.py linie 217-223):

def _approved_sha_values(approved_sha: str, approval: ApprovalCheck) -> set[str]:
    """Return the set of SHAs that count as 'reviewed' binding.

    SECURITY (ralph #163 BLOCKER 5): merge_sha is the Forgejo-generated merge
    commit, NOT a reviewed state. Reviewed state is approved_sha + head_sha.
    Removing merge_sha here closes the merge-sha overreach attack vector.
    """
    values = {approved_sha}
    if approval.head_sha:
        values.add(approval.head_sha)
    # NOTE: approval.merge_sha intentionally NOT included (ralph BLOCKER 5).
    return values

VERIFY:

def test_approved_sha_values_excludes_merge_sha():
    """merge_sha must NOT bind plan provenance (ralph BLOCKER 5)."""
    approval = ApprovalCheck(True, "ok", head_sha="h" * 40, merge_sha="m" * 40)
    values = _approved_sha_values("a" * 40, approval)
    assert "a" * 40 in values
    assert "h" * 40 in values
    assert "m" * 40 not in values

Follow-up issues (file as separate)

PROVENANCE-GIT-ROOT-01 — Anchor repo_root to git toplevel

_git_stdout accepts repo_root parameter defaulting to Path("."). If platformctl plan runs z subdir, may bind to wrong repo (jeśli nested checkouts).

Proposed: resolve repo_root = git rev-parse --show-toplevel przy each call; refuse run jeśli --show-toplevel fails.

PROVENANCE-HASH-01 — Cryptographic hash of plan payload

source_sha binds plan to git commit but nie do plan content. Insider z access do plan file mógłby modify it post-generation (tamper attack).

Proposed: add plan_hash field z SHA-256 of plan canonical JSON (excluding plan_hash itself). Apply verifies hash przed proceed.

PROVENANCE-ADVERSARIAL-TESTS-01 — Adversarial test suite

Test cases dla: env-var forgery (post BLOCKER 1), null dirty (post BLOCKER 4), fake-repo (git in unexpected location), corrupted plan JSON, type-confused source_dirty.


Action items

  1. Apply 5 BLOCKER patches → plan.py + apply.py + tests
  2. Force-push do codex/issues/142-apply-plan-provenance
  3. File 3 follow-up issues (titles above)
  4. Comment "ready for re-review" tutaj
  5. Operator może odpalić re-ralph dla potwierdzenia score ≥7/9

— 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 (kimi / glm / deepseek-pro / kimi / deepseek-pro). Verdict + drafted patches poniżej. ### Per-dim scoring - Correctness: 3/9 - **Security: 2/9** ⚠️ ("no meaningful security improvement in current state" — arbiter) - Observability: 5/9 - Test coverage: 4/9 - Scope discipline: 8/9 Evidence: `~/Iskra-i-Piotr/05 System/Swarmheart Backups/ralph-phase3-apply/163/` (full chain). --- ## BLOCKER 1 — Remove env-var overrides for source SHA/dirty **WHAT:** `plan.py` lines 64-66 i 72-76: `current_source_sha()` i `source_tree_dirty()` accept env-var overrides (`PLATFORMCTL_SOURCE_SHA`, `PLATFORMCTL_SOURCE_DIRTY`) bez verification. **WHY:** Insider/CI z env-var write access trivially forges provenance: ```bash export PLATFORMCTL_SOURCE_SHA=$(git rev-parse main) export PLATFORMCTL_SOURCE_DIRTY=false platformctl plan some-module --out plan.json # Plan claims pristine source even from dirty working tree ``` To **dokładnie** to czego provenance binding ma zapobiegać. **HOW (drafted patch w `plan.py` linie 62-91):** ```python def current_source_sha(*, repo_root: Path = Path(".")) -> str | None: """Return the git commit that produced a plan artifact. SECURITY (ralph #163 BLOCKER 1): env-var self-attestation removed. For CI use, gate behind explicit --ci-provenance flag at caller level. """ return _git_stdout(["rev-parse", "HEAD"], repo_root=repo_root) def source_tree_dirty(*, repo_root: Path = Path(".")) -> bool: """Return whether tracked files differ from HEAD. SECURITY (ralph #163 BLOCKER 1+3): fail-CLOSED on any git error. Env-var override removed. """ try: result = subprocess.run( ["git", "-C", str(repo_root), "diff-index", "--quiet", "HEAD", "--"], check=False, capture_output=True, text=True, timeout=5, ) except (OSError, subprocess.TimeoutExpired): # Cannot determine clean state → must assume dirty (fail-closed). return True if result.returncode == 0: return False # returncode == 1 (dirty) or anything else → treat as dirty. return True ``` Plus update `_new_plan` (linia 100) jeśli potrzeba — `source_dirty` jest teraz `bool`, nie `bool | None`. **VERIFY (add to `tests/test_plan_phase3.py`):** ```python def test_source_tree_dirty_fail_closed_on_git_error(monkeypatch): """Git failure must report dirty=True (ralph BLOCKER 1+3).""" def raise_oserror(*a, **kw): raise OSError("git not found") monkeypatch.setattr(subprocess, "run", raise_oserror) assert source_tree_dirty(repo_root=Path("/nonexistent")) is True def test_current_source_sha_no_env_override(monkeypatch): """Env-var override removed (ralph BLOCKER 1).""" monkeypatch.setenv("PLATFORMCTL_SOURCE_SHA", "deadbeef" * 5) # Real git rev-parse must run; env var ignored. sha = current_source_sha() assert sha != "deadbeef" * 5 # not env value ``` --- ## BLOCKER 2 — Hard-fail on missing git provenance **WHAT:** `current_source_sha` returns `None` if git fails. `_new_plan` (linia 99) writes `"source_sha": None` to artifact. Then `_validate_plan_source` (linia 228) catches `not isinstance(...str)` — but only at apply time, after artifact is shipped. **WHY:** Plan with `source_sha: null` should NEVER exist. Fail w plan generation, nie w apply review. **HOW (drafted patch w `plan.py` `_new_plan` linia 94):** ```python class PlanProvenanceError(Exception): """Raised when plan cannot establish git provenance.""" def _new_plan(manifest: Manifest) -> dict: sha = current_source_sha() if sha is None: raise PlanProvenanceError( "cannot determine source git SHA — refuse to write plan with null provenance " "(ralph #163 BLOCKER 2). Run platformctl plan from inside a git checkout." ) return { "apiVersion": "platform.pdurlej.com/plan/v1", "command": "plan", "module": manifest.id, "source_sha": sha, "source_dirty": source_tree_dirty(), # ...rest unchanged } ``` Plus `plan_module` (linia 390) catches `PlanProvenanceError` and returns `EXIT_TOOL_ERROR`. **VERIFY:** ```python def test_new_plan_raises_when_git_unavailable(monkeypatch): """Plan with null provenance must NOT be written (ralph BLOCKER 2).""" monkeypatch.setattr("platformctl.plan.current_source_sha", lambda **kw: None) with pytest.raises(PlanProvenanceError): _new_plan(fake_manifest()) ``` --- ## BLOCKER 3 — Fail-closed dirty check (covered in BLOCKER 1) Już w patch 1 — `source_tree_dirty` zwraca `True` na każdy git error. --- ## BLOCKER 4 — Strict boolean validation in `apply.py` **WHAT:** `_validate_plan_source` linia 230: `if plan.get("source_dirty") is True`. Akceptuje `None`, missing key, string `"false"`, integer `0` — wszystko jako "not dirty". **WHY:** Type-unsafe check pozwala plans z manipulated `source_dirty` (np. usunięty z artifact, lub set to `"clean"`) pass approval. **HOW (drafted patch w `apply.py` linie 226-234):** ```python def _validate_plan_source(plan: dict, approved_sha: str, approval: ApprovalCheck) -> str | None: source_sha = plan.get("source_sha") if not isinstance(source_sha, str) or not _valid_sha(source_sha): return "plan source_sha is missing or invalid; regenerate the plan from the approved PR checkout" # SECURITY (ralph #163 BLOCKER 4): strict bool check. # None, missing, strings, ints must all be rejected — not just `is True`. source_dirty = plan.get("source_dirty", "<missing>") if source_dirty is True: return "plan was generated from a dirty checkout; regenerate it from a clean approved PR checkout" if not isinstance(source_dirty, bool): return ( f"plan source_dirty must be a bool (got {type(source_dirty).__name__}: " f"{source_dirty!r}); regenerate the plan with current platformctl" ) # source_dirty is False — OK. if source_sha not in _approved_sha_values(approved_sha, approval): return "plan source_sha does not match the approved PR head/merge SHA" return None ``` **VERIFY:** ```python @pytest.mark.parametrize("bad_value", [None, "false", 0, "clean", {"x": 1}]) def test_validate_plan_source_rejects_non_bool_dirty(bad_value): """source_dirty must be a real bool — ralph BLOCKER 4.""" plan = {"source_sha": "a" * 40, "source_dirty": bad_value} approval = ApprovalCheck(True, "ok", head_sha="a" * 40) err = _validate_plan_source(plan, "a" * 40, approval) assert err is not None and "source_dirty must be a bool" in err ``` --- ## BLOCKER 5 — Remove `merge_sha` from approved set **WHAT:** `_approved_sha_values` (linie 217-223 apply.py) accepts BOTH `head_sha` and `merge_sha` jako valid binding. **WHY:** `merge_sha` reprezentuje Forgejo merge commit moment — automatically generated, not reviewed. Tylko `head_sha` (Codex's commit) lub `approved_sha` (operator's checkbox) reprezentuje reviewed state. Akceptując `merge_sha`, opens "merge_sha overreach" attack vector (arbiter finding). **HOW (drafted patch w `apply.py` linie 217-223):** ```python def _approved_sha_values(approved_sha: str, approval: ApprovalCheck) -> set[str]: """Return the set of SHAs that count as 'reviewed' binding. SECURITY (ralph #163 BLOCKER 5): merge_sha is the Forgejo-generated merge commit, NOT a reviewed state. Reviewed state is approved_sha + head_sha. Removing merge_sha here closes the merge-sha overreach attack vector. """ values = {approved_sha} if approval.head_sha: values.add(approval.head_sha) # NOTE: approval.merge_sha intentionally NOT included (ralph BLOCKER 5). return values ``` **VERIFY:** ```python def test_approved_sha_values_excludes_merge_sha(): """merge_sha must NOT bind plan provenance (ralph BLOCKER 5).""" approval = ApprovalCheck(True, "ok", head_sha="h" * 40, merge_sha="m" * 40) values = _approved_sha_values("a" * 40, approval) assert "a" * 40 in values assert "h" * 40 in values assert "m" * 40 not in values ``` --- ## Follow-up issues (file as separate) ### `PROVENANCE-GIT-ROOT-01` — Anchor repo_root to git toplevel > `_git_stdout` accepts `repo_root` parameter defaulting to `Path(".")`. If `platformctl plan` runs z subdir, may bind to wrong repo (jeśli nested checkouts). > > Proposed: resolve `repo_root = git rev-parse --show-toplevel` przy each call; refuse run jeśli `--show-toplevel` fails. ### `PROVENANCE-HASH-01` — Cryptographic hash of plan payload > `source_sha` binds plan to git commit but nie do plan content. Insider z access do plan file mógłby modify it post-generation (tamper attack). > > Proposed: add `plan_hash` field z SHA-256 of plan canonical JSON (excluding plan_hash itself). Apply verifies hash przed proceed. ### `PROVENANCE-ADVERSARIAL-TESTS-01` — Adversarial test suite > Test cases dla: env-var forgery (post BLOCKER 1), `null` dirty (post BLOCKER 4), fake-repo (git in unexpected location), corrupted plan JSON, type-confused source_dirty. --- ## Action items 1. Apply 5 BLOCKER patches → `plan.py` + `apply.py` + tests 2. Force-push do `codex/issues/142-apply-plan-provenance` 3. File 3 follow-up issues (titles above) 4. Comment "ready for re-review" tutaj 5. Operator może odpalić re-ralph dla potwierdzenia score ≥7/9 — ralph batch 2026-05-10, claude-opus-4.7 (Pan Herbata) dispatching via codex identity
codex force-pushed codex/issues/142-apply-plan-provenance from 9783f2d457
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 44s
python-ci / Python 3.12 (pull_request) Successful in 46s
python-ci / Python 3.13 (pull_request) Successful in 46s
canary-required / canary (pull_request) Successful in 12s
to a959b97e65
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 44s
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
2026-05-12 00:47:17 +02:00
Compare
Author
Collaborator

Codex iteration — ralph BLOCKERs addressed

Updated branch codex/issues/142-apply-plan-provenance at a959b97, rebased on the updated #162 branch.

Applied:

  • BLOCKER 1: removed PLATFORMCTL_SOURCE_SHA / PLATFORMCTL_SOURCE_DIRTY self-attestation overrides.
  • BLOCKER 2: plan generation now fails closed when git SHA provenance is unavailable; no source_sha: null artifact is written.
  • BLOCKER 3: dirty check now fails closed on git errors.
  • BLOCKER 4: source_dirty must be a real bool; missing/null/string/int values are rejected at apply time.
  • BLOCKER 5: merge_sha is no longer included in _approved_sha_values().

Verification:

  • PYTHONPATH=control-plane pytest control-plane/platformctl/tests/test_apply_phase3.py control-plane/platformctl/tests/test_plan_phase3.py -q → 29 passed.
  • Full ./tests/run-verify.sh still has the same pre-existing origin/main prompt debt noted on #162; not part of this PR.

Filed follow-up issues:

  • #192 PROVENANCE-GIT-ROOT-01
  • #193 PROVENANCE-HASH-01
  • #194 PROVENANCE-ADVERSARIAL-TESTS-01

Ready for re-review / operator review.

## Codex iteration — ralph BLOCKERs addressed Updated branch `codex/issues/142-apply-plan-provenance` at `a959b97`, rebased on the updated #162 branch. Applied: - BLOCKER 1: removed `PLATFORMCTL_SOURCE_SHA` / `PLATFORMCTL_SOURCE_DIRTY` self-attestation overrides. - BLOCKER 2: plan generation now fails closed when git SHA provenance is unavailable; no `source_sha: null` artifact is written. - BLOCKER 3: dirty check now fails closed on git errors. - BLOCKER 4: `source_dirty` must be a real bool; missing/null/string/int values are rejected at apply time. - BLOCKER 5: `merge_sha` is no longer included in `_approved_sha_values()`. Verification: - `PYTHONPATH=control-plane pytest control-plane/platformctl/tests/test_apply_phase3.py control-plane/platformctl/tests/test_plan_phase3.py -q` → 29 passed. - Full `./tests/run-verify.sh` still has the same pre-existing `origin/main` prompt debt noted on #162; not part of this PR. Filed follow-up issues: - #192 PROVENANCE-GIT-ROOT-01 - #193 PROVENANCE-HASH-01 - #194 PROVENANCE-ADVERSARIAL-TESTS-01 Ready for re-review / operator review.
pdurlej merged commit 7d6d8e1658 into codex/issues/142-apply-approval-binding 2026-05-12 01:28:31 +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!163
No description provided.