fix(platformctl): allow approved no-op apply #164

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

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

Canary Context Pack

Product story

If runtime already matches desired state, apply should finish cleanly instead of failing just because no mutation was needed.

What changed

  • Approved, source-bound no-op plans now return success without crossing the runtime phase gate.
  • Blocked/invalid plans are rejected before runtime.
  • Added tests for no-op and blocked-plan behavior.

Why it changed

The old #160 behavior made harmless in-sync plans operationally noisy while the real risk is unapproved mutation, not no-op success.

Files touched

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

Relevant context

Stacked after approval-binding and plan-provenance splits.

Runtime evidence

No runtime mutation in this PR. Local tests only.

Known constraints

No-op still requires approved PR and matching clean plan source.

Explicit out-of-scope

Compose execution, status artifacts, and Actions wiring.

Requested decision

Approve safe no-op semantics.

Merge blockers

Any no-op path that bypasses approval or plan source verification.

Spec sources read

  • AGENTS.md — security-sensitive split rule.
  • control-plane/platformctl/apply.py — no-op gate.
  • control-plane/platformctl/tests/test_apply_phase3.py — tests.

Refs #142
Supersedes part of #160

Canary status: missing — fire canary 3+3 manually before merge ## Canary Context Pack ### Product story If runtime already matches desired state, apply should finish cleanly instead of failing just because no mutation was needed. ### What changed - Approved, source-bound no-op plans now return success without crossing the runtime phase gate. - Blocked/invalid plans are rejected before runtime. - Added tests for no-op and blocked-plan behavior. ### Why it changed The old #160 behavior made harmless in-sync plans operationally noisy while the real risk is unapproved mutation, not no-op success. ### Files touched - `control-plane/platformctl/apply.py` - `control-plane/platformctl/tests/test_apply_phase3.py` ### Relevant context Stacked after approval-binding and plan-provenance splits. ### Runtime evidence No runtime mutation in this PR. Local tests only. ### Known constraints No-op still requires approved PR and matching clean plan source. ### Explicit out-of-scope Compose execution, status artifacts, and Actions wiring. ### Requested decision Approve safe no-op semantics. ### Merge blockers Any no-op path that bypasses approval or plan source verification. ## Spec sources read - `AGENTS.md` — security-sensitive split rule. - `control-plane/platformctl/apply.py` — no-op gate. - `control-plane/platformctl/tests/test_apply_phase3.py` — tests. Refs #142 Supersedes part of #160
fix(platformctl): allow approved no-op apply
All checks were successful
canary-required / collect-diff (pull_request) Successful in 3s
pyfallow / Pyfallow gate (control-plane) (pull_request) Successful in 16s
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 45s
canary-required / canary (pull_request) Successful in 11s
f8feed24b3
Author
Collaborator

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

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

Per-dim scoring

  • Correctness: 4/9
  • Security: 4/9 (stale-replay path)
  • Observability: 5/9
  • Test coverage: 5/9
  • Scope discipline: 7/9

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


BLOCKER 1 — Plan freshness check (TTL)

WHAT: apply.py lines 320-333: new no-op short-circuit accepts plan jeśli exitCode == 0 && spec_changes empty BEZ żadnej weryfikacji że plan jest current.

WHY: Stale-replay attack:

# Operator generates clean plan in calm state
platformctl plan some-module --out plan.json   # in-sync, exitCode 0
# … infrastructure drifts (e.g., attacker shuts down service) …
# Approved no-op plan can be replayed indefinitely
platformctl apply plan.json --approved-pr 999  # → noop, "no remote apply needed"
# False sense of synchronization. Drift masked.

PR's stated purpose ("allow approved no-op apply") expands attack surface bez time-binding.

HOW (drafted patch w apply.py linie 320-333):

Add przed no-op short-circuit:

# Constants — add to top of apply.py (line ~30)
import time
from datetime import datetime, timezone, timedelta

PLAN_FRESHNESS_TTL_SECONDS = int(os.environ.get("PLATFORMCTL_PLAN_TTL_SECONDS", "300"))  # 5 min default


def _plan_age_seconds(plan: dict) -> float | None:
    """Return age of plan in seconds, or None if unparseable."""
    ts = plan.get("generated_at")
    if not isinstance(ts, str):
        return None
    try:
        # Plan writes ISO format "%Y-%m-%dT%H:%M:%SZ"
        gen = datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
    except ValueError:
        return None
    return (datetime.now(timezone.utc) - gen).total_seconds()


# In apply_plan, BEFORE the no-op short-circuit (currently line 330):
# SECURITY (ralph #164 BLOCKER 1): plan freshness check.
# Stale plans cannot satisfy approval — closes replay-after-drift attack.
plan_age = _plan_age_seconds(plan)
if plan_age is None:
    result.update({
        "status": "blocked",
        "error": "plan missing or unparseable generated_at timestamp — regenerate plan",
        "plan_status": plan.get("status"),
        "exitCode": EXIT_TOOL_ERROR,
    })
    return result, EXIT_TOOL_ERROR

if plan_age > PLAN_FRESHNESS_TTL_SECONDS:
    result.update({
        "status": "blocked",
        "error": (
            f"plan is stale (age {plan_age:.0f}s > TTL {PLAN_FRESHNESS_TTL_SECONDS}s); "
            "regenerate plan before apply"
        ),
        "plan_age_seconds": plan_age,
        "exitCode": EXIT_APPROVAL_MISMATCH,
    })
    return result, EXIT_APPROVAL_MISMATCH

# Now the existing no-op short-circuit:
if plan.get("exitCode") == 0 and not spec_changes:
    result["status"] = "noop"
    result["message"] = f"plan is in-sync (age {plan_age:.0f}s); no remote apply needed"
    return result, EXIT_NO_CHANGES

VERIFY (add to tests/test_apply_phase3.py):

def test_apply_rejects_stale_plan(tmp_path, monkeypatch):
    """Stale plan must be rejected (ralph BLOCKER 1)."""
    # Plan generated 10 minutes ago
    stale_ts = (datetime.now(timezone.utc) - timedelta(minutes=10)).strftime("%Y-%m-%dT%H:%M:%SZ")
    plan = {"source_sha": "a"*40, "source_dirty": False, "exitCode": 0, "changes": [],
            "generated_at": stale_ts}
    plan_path = tmp_path / "stale.plan.json"
    plan_path.write_text(json.dumps(plan))
    monkeypatch.setattr("platformctl.apply.verify_approved_sha",
                        lambda *a, **kw: ApprovalCheck(True, "ok", head_sha="a"*40))
    monkeypatch.setenv("PLATFORMCTL_PLAN_TTL_SECONDS", "300")
    result, exit_code = apply_plan(plan_path, "a"*40, approved_pr=1)
    assert exit_code == EXIT_APPROVAL_MISMATCH
    assert "stale" in result["error"]


def test_apply_rejects_plan_without_generated_at(tmp_path, monkeypatch):
    """Plan missing generated_at must be rejected (ralph BLOCKER 1)."""
    plan = {"source_sha": "a"*40, "source_dirty": False, "exitCode": 0, "changes": []}
    # No generated_at
    plan_path = tmp_path / "no-ts.plan.json"
    plan_path.write_text(json.dumps(plan))
    monkeypatch.setattr("platformctl.apply.verify_approved_sha",
                        lambda *a, **kw: ApprovalCheck(True, "ok", head_sha="a"*40))
    result, exit_code = apply_plan(plan_path, "a"*40, approved_pr=1)
    assert exit_code == EXIT_TOOL_ERROR
    assert "unparseable generated_at" in result["error"]

BLOCKER 2 — Exit-code semantic hardening

WHAT: apply.py linia 320: plan.get("exitCode") not in (0, 1). Implicit None handling, no consistency guard between exitCode and spec_changes content.

WHY: Plan z exitCode == 0 ale spec_changes non-empty (lub vice versa) reprezentuje malformed/tampered state. Current code silently accepts.

HOW (drafted patch w apply.py przed no-op check):

Use plan.py constants instead of magic numbers:

# Import explicit constants:
from .plan import (
    EXIT_NO_CHANGES, EXIT_TOOL_ERROR, EXIT_POLICY_VIOLATION,
    EXIT_APPROVAL_MISMATCH,
    EXIT_DESTRUCTIVE_NEEDS_APPROVAL,
    PLAN_EXIT_NO_DRIFT, PLAN_EXIT_DRIFT, PLAN_EXIT_ERROR,
)

# In apply_plan, replace existing blocked check (linia 320):
plan_exit = plan.get("exitCode")
if plan_exit is None or plan_exit not in (PLAN_EXIT_NO_DRIFT, PLAN_EXIT_DRIFT):
    result.update({
        "status": "blocked",
        "error": f"plan exitCode {plan_exit!r} is not 0 or 1; regenerate plan",
        "plan_status": plan.get("status"),
        "plan_exitCode": plan_exit,
        "exitCode": EXIT_TOOL_ERROR,
    })
    return result, EXIT_TOOL_ERROR

# SECURITY (ralph #164 BLOCKER 2): consistency guard.
# A coherent plan must have matching exitCode and spec_changes presence.
if plan_exit == PLAN_EXIT_NO_DRIFT and spec_changes:
    result.update({
        "status": "blocked",
        "error": (
            f"plan exitCode=0 (no-drift) but contains {len(spec_changes)} changes; "
            "inconsistent plan state, regenerate"
        ),
        "exitCode": EXIT_TOOL_ERROR,
    })
    return result, EXIT_TOOL_ERROR

if plan_exit == PLAN_EXIT_DRIFT and not spec_changes:
    result.update({
        "status": "blocked",
        "error": (
            "plan exitCode=1 (drift) but contains no changes; "
            "inconsistent plan state, regenerate"
        ),
        "exitCode": EXIT_TOOL_ERROR,
    })
    return result, EXIT_TOOL_ERROR

VERIFY:

def test_apply_rejects_inconsistent_plan_exit_0_with_changes(tmp_path, monkeypatch):
    """exitCode=0 + spec_changes non-empty must reject (ralph BLOCKER 2)."""
    plan = {
        "source_sha": "a"*40, "source_dirty": False,
        "exitCode": 0, "changes": [{"action": "update", "target": "x"}],
        "generated_at": _now_iso(),
    }
    # ... assert blocked


def test_apply_rejects_inconsistent_plan_exit_1_no_changes(tmp_path, monkeypatch):
    """exitCode=1 + spec_changes empty must reject (ralph BLOCKER 2)."""
    # ... assert blocked

Follow-up issues

NO-OP-OBSERVABILITY-01 — Structured logging + counter metric

Approved zero-change applies pass silently (return result, EXIT_NO_CHANGES). Operator/CI may want frequency metric: "how often does apply hit no-op? for which modules?"

Proposed: emit structured JSON log entry on noop return; expose counter via platformctl health rollup.

PLAN-EXIT-NONE-EXPLICIT-01 — Replace implicit None check

Even after BLOCKER 2 patch, plan_exit not in (0, 1) z plan_exit=None works correctly but reliance on falsy-truthy is fragile. Explicit if plan_exit is None: ... is clearer.

PLAN-STATE-CROSS-TESTS-01 — Cover missing plan-state combinations

Add test matrix for: exitCode={0,1,2,None,"bad"} × spec_changes={[],[change],None}. Ensure each combo handled consistently per BLOCKER 2 logic.


Action items

  1. Apply 2 BLOCKER patches → apply.py + tests
  2. Force-push do codex/issues/142-apply-noop-safety
  3. File 3 follow-up issues
  4. Comment "ready for re-review" tutaj
  5. Operator może odpalić re-ralph

— 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. ### Per-dim scoring - Correctness: 4/9 - Security: 4/9 (stale-replay path) - Observability: 5/9 - Test coverage: 5/9 - Scope discipline: 7/9 Evidence: `~/Iskra-i-Piotr/05 System/Swarmheart Backups/ralph-phase3-apply/164/`. --- ## BLOCKER 1 — Plan freshness check (TTL) **WHAT:** `apply.py` lines 320-333: new no-op short-circuit accepts plan jeśli `exitCode == 0 && spec_changes empty` BEZ żadnej weryfikacji że plan jest current. **WHY:** Stale-replay attack: ```bash # Operator generates clean plan in calm state platformctl plan some-module --out plan.json # in-sync, exitCode 0 # … infrastructure drifts (e.g., attacker shuts down service) … # Approved no-op plan can be replayed indefinitely platformctl apply plan.json --approved-pr 999 # → noop, "no remote apply needed" # False sense of synchronization. Drift masked. ``` PR's stated purpose ("allow approved no-op apply") expands attack surface bez time-binding. **HOW (drafted patch w `apply.py` linie 320-333):** Add przed no-op short-circuit: ```python # Constants — add to top of apply.py (line ~30) import time from datetime import datetime, timezone, timedelta PLAN_FRESHNESS_TTL_SECONDS = int(os.environ.get("PLATFORMCTL_PLAN_TTL_SECONDS", "300")) # 5 min default def _plan_age_seconds(plan: dict) -> float | None: """Return age of plan in seconds, or None if unparseable.""" ts = plan.get("generated_at") if not isinstance(ts, str): return None try: # Plan writes ISO format "%Y-%m-%dT%H:%M:%SZ" gen = datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc) except ValueError: return None return (datetime.now(timezone.utc) - gen).total_seconds() # In apply_plan, BEFORE the no-op short-circuit (currently line 330): # SECURITY (ralph #164 BLOCKER 1): plan freshness check. # Stale plans cannot satisfy approval — closes replay-after-drift attack. plan_age = _plan_age_seconds(plan) if plan_age is None: result.update({ "status": "blocked", "error": "plan missing or unparseable generated_at timestamp — regenerate plan", "plan_status": plan.get("status"), "exitCode": EXIT_TOOL_ERROR, }) return result, EXIT_TOOL_ERROR if plan_age > PLAN_FRESHNESS_TTL_SECONDS: result.update({ "status": "blocked", "error": ( f"plan is stale (age {plan_age:.0f}s > TTL {PLAN_FRESHNESS_TTL_SECONDS}s); " "regenerate plan before apply" ), "plan_age_seconds": plan_age, "exitCode": EXIT_APPROVAL_MISMATCH, }) return result, EXIT_APPROVAL_MISMATCH # Now the existing no-op short-circuit: if plan.get("exitCode") == 0 and not spec_changes: result["status"] = "noop" result["message"] = f"plan is in-sync (age {plan_age:.0f}s); no remote apply needed" return result, EXIT_NO_CHANGES ``` **VERIFY (add to `tests/test_apply_phase3.py`):** ```python def test_apply_rejects_stale_plan(tmp_path, monkeypatch): """Stale plan must be rejected (ralph BLOCKER 1).""" # Plan generated 10 minutes ago stale_ts = (datetime.now(timezone.utc) - timedelta(minutes=10)).strftime("%Y-%m-%dT%H:%M:%SZ") plan = {"source_sha": "a"*40, "source_dirty": False, "exitCode": 0, "changes": [], "generated_at": stale_ts} plan_path = tmp_path / "stale.plan.json" plan_path.write_text(json.dumps(plan)) monkeypatch.setattr("platformctl.apply.verify_approved_sha", lambda *a, **kw: ApprovalCheck(True, "ok", head_sha="a"*40)) monkeypatch.setenv("PLATFORMCTL_PLAN_TTL_SECONDS", "300") result, exit_code = apply_plan(plan_path, "a"*40, approved_pr=1) assert exit_code == EXIT_APPROVAL_MISMATCH assert "stale" in result["error"] def test_apply_rejects_plan_without_generated_at(tmp_path, monkeypatch): """Plan missing generated_at must be rejected (ralph BLOCKER 1).""" plan = {"source_sha": "a"*40, "source_dirty": False, "exitCode": 0, "changes": []} # No generated_at plan_path = tmp_path / "no-ts.plan.json" plan_path.write_text(json.dumps(plan)) monkeypatch.setattr("platformctl.apply.verify_approved_sha", lambda *a, **kw: ApprovalCheck(True, "ok", head_sha="a"*40)) result, exit_code = apply_plan(plan_path, "a"*40, approved_pr=1) assert exit_code == EXIT_TOOL_ERROR assert "unparseable generated_at" in result["error"] ``` --- ## BLOCKER 2 — Exit-code semantic hardening **WHAT:** `apply.py` linia 320: `plan.get("exitCode") not in (0, 1)`. Implicit None handling, no consistency guard between `exitCode` and `spec_changes` content. **WHY:** Plan z `exitCode == 0` ale `spec_changes` non-empty (lub vice versa) reprezentuje malformed/tampered state. Current code silently accepts. **HOW (drafted patch w `apply.py` przed no-op check):** Use plan.py constants instead of magic numbers: ```python # Import explicit constants: from .plan import ( EXIT_NO_CHANGES, EXIT_TOOL_ERROR, EXIT_POLICY_VIOLATION, EXIT_APPROVAL_MISMATCH, EXIT_DESTRUCTIVE_NEEDS_APPROVAL, PLAN_EXIT_NO_DRIFT, PLAN_EXIT_DRIFT, PLAN_EXIT_ERROR, ) # In apply_plan, replace existing blocked check (linia 320): plan_exit = plan.get("exitCode") if plan_exit is None or plan_exit not in (PLAN_EXIT_NO_DRIFT, PLAN_EXIT_DRIFT): result.update({ "status": "blocked", "error": f"plan exitCode {plan_exit!r} is not 0 or 1; regenerate plan", "plan_status": plan.get("status"), "plan_exitCode": plan_exit, "exitCode": EXIT_TOOL_ERROR, }) return result, EXIT_TOOL_ERROR # SECURITY (ralph #164 BLOCKER 2): consistency guard. # A coherent plan must have matching exitCode and spec_changes presence. if plan_exit == PLAN_EXIT_NO_DRIFT and spec_changes: result.update({ "status": "blocked", "error": ( f"plan exitCode=0 (no-drift) but contains {len(spec_changes)} changes; " "inconsistent plan state, regenerate" ), "exitCode": EXIT_TOOL_ERROR, }) return result, EXIT_TOOL_ERROR if plan_exit == PLAN_EXIT_DRIFT and not spec_changes: result.update({ "status": "blocked", "error": ( "plan exitCode=1 (drift) but contains no changes; " "inconsistent plan state, regenerate" ), "exitCode": EXIT_TOOL_ERROR, }) return result, EXIT_TOOL_ERROR ``` **VERIFY:** ```python def test_apply_rejects_inconsistent_plan_exit_0_with_changes(tmp_path, monkeypatch): """exitCode=0 + spec_changes non-empty must reject (ralph BLOCKER 2).""" plan = { "source_sha": "a"*40, "source_dirty": False, "exitCode": 0, "changes": [{"action": "update", "target": "x"}], "generated_at": _now_iso(), } # ... assert blocked def test_apply_rejects_inconsistent_plan_exit_1_no_changes(tmp_path, monkeypatch): """exitCode=1 + spec_changes empty must reject (ralph BLOCKER 2).""" # ... assert blocked ``` --- ## Follow-up issues ### `NO-OP-OBSERVABILITY-01` — Structured logging + counter metric > Approved zero-change applies pass silently (`return result, EXIT_NO_CHANGES`). Operator/CI may want frequency metric: "how often does apply hit no-op? for which modules?" > > Proposed: emit structured JSON log entry on noop return; expose counter via `platformctl health` rollup. ### `PLAN-EXIT-NONE-EXPLICIT-01` — Replace implicit None check > Even after BLOCKER 2 patch, `plan_exit not in (0, 1)` z `plan_exit=None` works correctly but reliance on falsy-truthy is fragile. Explicit `if plan_exit is None: ...` is clearer. ### `PLAN-STATE-CROSS-TESTS-01` — Cover missing plan-state combinations > Add test matrix for: `exitCode={0,1,2,None,"bad"} × spec_changes={[],[change],None}`. Ensure each combo handled consistently per BLOCKER 2 logic. --- ## Action items 1. Apply 2 BLOCKER patches → `apply.py` + tests 2. Force-push do `codex/issues/142-apply-noop-safety` 3. File 3 follow-up issues 4. Comment "ready for re-review" tutaj 5. Operator może odpalić re-ralph — ralph batch 2026-05-10, claude-opus-4.7 (Pan Herbata) dispatching via codex identity
codex force-pushed codex/issues/142-apply-noop-safety from f8feed24b3
All checks were successful
canary-required / collect-diff (pull_request) Successful in 3s
pyfallow / Pyfallow gate (control-plane) (pull_request) Successful in 16s
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 45s
canary-required / canary (pull_request) Successful in 11s
to 70f1c19d62
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 43s
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 11s
2026-05-12 00:49:15 +02:00
Compare
Author
Collaborator

Codex iteration — ralph BLOCKERs addressed

Updated branch codex/issues/142-apply-noop-safety at 70f1c19, rebased on updated #163.

Applied:

  • BLOCKER 1: no-op apply now requires a fresh generated_at; stale or missing timestamps are rejected.
  • BLOCKER 2: apply validates plan exitCode against change presence (0 with changes and 1 without changes are malformed).

Verification:

  • PYTHONPATH=control-plane pytest control-plane/platformctl/tests/test_apply_phase3.py -q → 24 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:

  • #195 NO-OP-OBSERVABILITY-01
  • #196 PLAN-EXIT-NONE-EXPLICIT-01
  • #197 PLAN-STATE-CROSS-TESTS-01

Ready for re-review / operator review.

## Codex iteration — ralph BLOCKERs addressed Updated branch `codex/issues/142-apply-noop-safety` at `70f1c19`, rebased on updated #163. Applied: - BLOCKER 1: no-op apply now requires a fresh `generated_at`; stale or missing timestamps are rejected. - BLOCKER 2: apply validates plan `exitCode` against change presence (`0` with changes and `1` without changes are malformed). Verification: - `PYTHONPATH=control-plane pytest control-plane/platformctl/tests/test_apply_phase3.py -q` → 24 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: - #195 NO-OP-OBSERVABILITY-01 - #196 PLAN-EXIT-NONE-EXPLICIT-01 - #197 PLAN-STATE-CROSS-TESTS-01 Ready for re-review / operator review.
pdurlej merged commit d53147f280 into codex/issues/142-apply-plan-provenance 2026-05-12 01:28:37 +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!164
No description provided.