feat(phase02): n8n-worker — audit + freeze + import + release readiness #29

Merged
pdurlej merged 5 commits from claude/orders/phase-02-n8n-worker into main 2026-05-02 23:24:42 +02:00
Collaborator

Summary

Phase 02 broader vision (per operator 2026-05-02): for each canary-target module, do audit + freeze + import-phase plan + release-readiness checklist, not just metadata.

n8n-worker is first of 2 non-overlapping with PR #27 (which has narrow public_reason scope on internet-exposed modules — n8n-worker is internal-only). Operator chose Option C (split) to keep PR #27 saga separate.

Live RS2000 audit findings

Source: ssh rs2000 docker inspect home-platform-n8n-worker-1 (2026-05-02 09:11Z)

Aspect Value
Status Up 2 days (healthy)
Image n8nio/n8n:2.9.1@sha256:72e2242d… digest pinned
Networks hp_internal only
Mounts none (stateless)
Recent errors 0 in last 1h

Changes

modules/n8n-worker/module.yaml (+15 LOC):

  • v2 fields: user_facing_outcome + acceptance_criteria + data_classification: stateless
  • Runtime audit: image_observed, image_digest_pinned_in_compose: true, image_audit_ts
  • metadata.last_review: 2026-05-02, next_review: 2026-06-02

modules/n8n-worker/runbook.md (+44 / -7 LOC):

  • Replaces 2 TODO placeholders with concrete content
  • New: Live state audit table, Data classification, Import phase plan (N/A stateless), Release readiness checklist (6 items)

Pre-auth

Per operator 2026-05-02: "5 PR-y small, które każde z nich możesz sam zaakceptować" — pre-authorized for self-merge after canary 6/6 OK.

Verification

  • python3 -c "import yaml; yaml.safe_load(open('modules/n8n-worker/module.yaml'))" — parses ✓
  • Manual review of v2 field placement under spec.intent — matches schema v2 design

Co-authors

  • Operator (Phase 02 broader vision)
  • Claude Opus 4.7 (1M context, RS2000 audit + content)
## Summary Phase 02 broader vision (per operator 2026-05-02): for each canary-target module, do **audit + freeze + import-phase plan + release-readiness checklist**, not just metadata. n8n-worker is **first of 2** non-overlapping with PR #27 (which has narrow `public_reason` scope on internet-exposed modules — n8n-worker is internal-only). Operator chose **Option C (split)** to keep PR #27 saga separate. ## Live RS2000 audit findings Source: `ssh rs2000 docker inspect home-platform-n8n-worker-1` (2026-05-02 09:11Z) | Aspect | Value | |---|---| | Status | Up 2 days (healthy) | | Image | `n8nio/n8n:2.9.1@sha256:72e2242d…` ✅ digest pinned | | Networks | `hp_internal` only | | Mounts | none (stateless) | | Recent errors | 0 in last 1h | ## Changes **`modules/n8n-worker/module.yaml`** (+15 LOC): - v2 fields: `user_facing_outcome` + `acceptance_criteria` + `data_classification: stateless` - Runtime audit: `image_observed`, `image_digest_pinned_in_compose: true`, `image_audit_ts` - `metadata.last_review: 2026-05-02`, `next_review: 2026-06-02` **`modules/n8n-worker/runbook.md`** (+44 / -7 LOC): - Replaces 2 TODO placeholders with concrete content - New: Live state audit table, Data classification, Import phase plan (N/A stateless), Release readiness checklist (6 items) ## Pre-auth Per operator 2026-05-02: "5 PR-y small, które każde z nich możesz sam zaakceptować" — pre-authorized for self-merge after canary 6/6 OK. ## Verification - `python3 -c "import yaml; yaml.safe_load(open('modules/n8n-worker/module.yaml'))"` — parses ✓ - Manual review of v2 field placement under `spec.intent` — matches schema v2 design ## Co-authors - Operator (Phase 02 broader vision) - Claude Opus 4.7 (1M context, RS2000 audit + content)
Phase 02 broader vision per operator 2026-05-02: per-module audit, freeze,
data-import phase plan, release readiness checklist. n8n-worker is the
simplest of 5 (stateless, internal-only, already digest-pinned).

Splits from PR #27 narrow scope (just `public_reason` for internet-exposed
modules) — n8n-worker is internal-only so no overlap with PR #27.

## module.yaml additions

`spec.intent`:
- `user_facing_outcome` — what user sees indirectly (n8n workflow results)
- `acceptance_criteria` — 3 testable bullets (healthy, no errors, deps reachable)
- `data_classification: stateless`

`spec.runtime`:
- `image_observed: n8nio/n8n:2.9.1@sha256:72e2242d…` — current digest from RS2000
- `image_digest_pinned_in_compose: true` — n8n-worker is already pinned 
- `image_audit_ts: 2026-05-02T09:11:00Z`

`metadata.last_review: 2026-05-02`, `next_review: 2026-06-02` (was 2026-04-30 / 2026-05-14).

## runbook.md restructure

Replaces 2 TODO placeholders (How to recover, Known issues) with concrete content
based on live RS2000 audit. Adds 4 new sections:

- **Live state audit (2026-05-02)** — table from `ssh rs2000 docker inspect`
- **Data classification** — stateless, all state upstream (postgres+redis)
- **Import phase plan** — N/A (stateless, auto-resume from queue)
- **Release readiness checklist** — 6 items, 5 checked, 1 cross-module open loop

## Live RS2000 findings (n8n-worker specific)

- Up 2 days (healthy)
- Image digest pinned 
- Networks: hp_internal only (no public/tailnet)
- Mounts: none
- Recent errors: 0 in last 1h
- Stateless — no data to migrate on rebuild

## Scope (per charter §3)

Single-module change, 2 files (module.yaml + runbook.md), +83/-7 LOC.
No behavior change. No charter touch. No cross-module impact.
Pre-auth A applies — push directly per operator's "5 PR-y które każde
możesz sam zaakceptować" 2026-05-02.

## Co-authors

- Operator (Phase 02 broader vision: audit + freeze + import + release)
- Claude Opus 4.7 (1M context, live RS2000 audit + content)
claude force-pushed claude/orders/phase-02-n8n-worker from 36f0dd923f to aaaa55b4d4 2026-05-02 19:40:19 +02:00 Compare
Author
Collaborator

3+3 ensemble review by claude — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.75)

Risks

  • low — Verification claim in PR body inconsistent with where fields actually land
    • Evidence: PR body states 'Manual review of v2 field placement under spec.intent — matches schema v2 design', but image_observed / image_digest_pinned_in_compose / image_audit_ts are added under spec.runtime (module.yaml lines added inside the runtime: block), not spec.intent. The other v2 verification (yaml.s
    • Recommendation: Before self-merge, run actual schema validation (e.g. jsonschema against the v2 module schema) — not just yaml.safe_load — and update the PR body to reflect that spec.runtime gained three new fields too, so the audit trail matches the diff.

Opportunities

  • image_observed snapshot will silently drift — image_observed + image_audit_ts are descriptive point-in-time values stored in module.yaml. If the compose-file digest gets bumped between audits, the value here goes stale without any signal — a future reader (or agent) could trust an outdated digest. next_review at 1 month bounds the staleness, which is fine for a stateless internal worker, but worth noting that this field is informational only,

Product hat: OK (confidence 0.82)

Risks

  • low — Acceptance criteria are not independently verifiable by operator
    • Evidence: module.yaml acceptance_criteria: 'Worker container reports healthy', 'zero ERROR/FATAL/PANIC', 'Postgres + redis dependencies reachable'
    • Recommendation: Either link to a one-liner command per criterion (e.g. the exact ssh rs2000 docker inspect ... / docker logs --since 1h | grep -iE you already used) or accept that these are review-time-only checks. Otherwise, on the next monthly review the operator has to reconstruct the audit recipe from memory — exactly the ADHD trap this metadata is meant to close.

Opportunities

  • Open loop on tests/smoke.sh is correctly flagged but unowned — Runbook honestly notes tests/smoke.sh is declared-but-not-written and calls it cross-module. Good. Suggest filing a single tracking issue (or adding to L3/OPEN_LOOPS.md) so it doesn't get re-flagged on every per-module audit — otherwise next month's review of redis/postgres/n8n-main will each surface the same unchecked checkbox and burn attention.
<!-- platform-review:claude:pdurlej/platform:PR-29:aaaa55b4 --> # 3+3 ensemble review by `claude` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.75) ### Risks - **`low`** — Verification claim in PR body inconsistent with where fields actually land - Evidence: `PR body states 'Manual review of v2 field placement under spec.intent — matches schema v2 design', but image_observed / image_digest_pinned_in_compose / image_audit_ts are added under spec.runtime (module.yaml lines added inside the runtime: block), not spec.intent. The other v2 verification (yaml.s` - Recommendation: Before self-merge, run actual schema validation (e.g. jsonschema against the v2 module schema) — not just yaml.safe_load — and update the PR body to reflect that spec.runtime gained three new fields too, so the audit trail matches the diff. ### Opportunities - **image_observed snapshot will silently drift** — image_observed + image_audit_ts are descriptive point-in-time values stored in module.yaml. If the compose-file digest gets bumped between audits, the value here goes stale without any signal — a future reader (or agent) could trust an outdated digest. next_review at 1 month bounds the staleness, which is fine for a stateless internal worker, but worth noting that this field is informational only, ## Product hat: ✅ **OK** (confidence 0.82) ### Risks - **`low`** — Acceptance criteria are not independently verifiable by operator - Evidence: `module.yaml acceptance_criteria: 'Worker container reports healthy', 'zero ERROR/FATAL/PANIC', 'Postgres + redis dependencies reachable'` - Recommendation: Either link to a one-liner command per criterion (e.g. the exact `ssh rs2000 docker inspect ...` / `docker logs --since 1h | grep -iE` you already used) or accept that these are review-time-only checks. Otherwise, on the next monthly review the operator has to reconstruct the audit recipe from memory — exactly the ADHD trap this metadata is meant to close. ### Opportunities - **Open loop on tests/smoke.sh is correctly flagged but unowned** — Runbook honestly notes `tests/smoke.sh` is declared-but-not-written and calls it cross-module. Good. Suggest filing a single tracking issue (or adding to L3/OPEN_LOOPS.md) so it doesn't get re-flagged on every per-module audit — otherwise next month's review of redis/postgres/n8n-main will each surface the same unchecked checkbox and burn attention.
Collaborator

3+3 ensemble review by codex — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: NOT_OK (confidence 0.92)

Risks

  • blocker — Manifest adds runtime keys that the schema rejects
    • Evidence: modules/n8n-worker/module.yaml:37-39 adds image_observed/image_digest_pinned_in_compose/image_audit_ts, but schema/module.schema.json:160 defines spec.runtime with additionalProperties: false and does not list these keys.
    • Recommendation: Remove these audit-only fields from module.yaml and keep them in runbook.md, or add schema support for the new runtime audit fields before merging this manifest.
  • medium — Recovery command uses a truncated digest
    • Evidence: modules/n8n-worker/runbook.md:59 uses docker pull n8nio/n8n@sha256:72e2242d…, which is not an executable image reference.
    • Recommendation: Replace the ellipsis with the full digest or change the instruction to pull using the compose-pinned image reference.

Product hat: NOT_OK (confidence 0.90)

Risks

  • blocker — Keep observed image audit out of module.yaml
    • Evidence: modules/n8n-worker/module.yaml:37-39 adds image_observed/image_digest_pinned_in_compose/image_audit_ts under spec.runtime; schema/module.schema.json:160-164 makes runtime additionalProperties:false; PLATFORM_CHARTER.md:165-170 says observed state lives only in .platform/state, not source manifests.
    • Recommendation: Remove these three runtime audit fields from module.yaml. Keep the observed image/digest audit in runbook.md or move it into the proper generated observed-state/inventory path after schema support exists.
  • medium — Recovery path bypasses the platform mutation flow
    • Evidence: modules/n8n-worker/runbook.md:57 replaces the previous platformctl flow with direct docker compose up from /opt/vps-home-platform-infra; PLATFORM_CHARTER.md:174+ defines PR/platformctl apply as the only mutation path.
    • Recommendation: Make platformctl/approved apply the normal recovery path again, and label the direct docker compose command as break-glass only with an audit-log requirement.
<!-- platform-review:codex:pdurlej/platform:PR-29:aaaa55b4 --> # 3+3 ensemble review by `codex` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ❌ **NOT_OK** (confidence 0.92) ### Risks - **`blocker`** — Manifest adds runtime keys that the schema rejects - Evidence: `modules/n8n-worker/module.yaml:37-39 adds image_observed/image_digest_pinned_in_compose/image_audit_ts, but schema/module.schema.json:160 defines spec.runtime with additionalProperties: false and does not list these keys.` - Recommendation: Remove these audit-only fields from module.yaml and keep them in runbook.md, or add schema support for the new runtime audit fields before merging this manifest. - **`medium`** — Recovery command uses a truncated digest - Evidence: `modules/n8n-worker/runbook.md:59 uses `docker pull n8nio/n8n@sha256:72e2242d…`, which is not an executable image reference.` - Recommendation: Replace the ellipsis with the full digest or change the instruction to pull using the compose-pinned image reference. ## Product hat: ❌ **NOT_OK** (confidence 0.90) ### Risks - **`blocker`** — Keep observed image audit out of module.yaml - Evidence: `modules/n8n-worker/module.yaml:37-39 adds image_observed/image_digest_pinned_in_compose/image_audit_ts under spec.runtime; schema/module.schema.json:160-164 makes runtime additionalProperties:false; PLATFORM_CHARTER.md:165-170 says observed state lives only in .platform/state, not source manifests.` - Recommendation: Remove these three runtime audit fields from module.yaml. Keep the observed image/digest audit in runbook.md or move it into the proper generated observed-state/inventory path after schema support exists. - **`medium`** — Recovery path bypasses the platform mutation flow - Evidence: `modules/n8n-worker/runbook.md:57 replaces the previous platformctl flow with direct docker compose up from /opt/vps-home-platform-infra; PLATFORM_CHARTER.md:174+ defines PR/platformctl apply as the only mutation path.` - Recommendation: Make platformctl/approved apply the normal recovery path again, and label the direct docker compose command as break-glass only with an audit-log requirement.
First-time contributor

3+3 ensemble review by glm — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.95)

Opportunities

  • Image digest snapshot captured — The image_observed field with full SHA256 digest provides a permanent snapshot of the exact image verified at audit time (2026-05-02). Good for future forensics if upstream registry republishes tags.

Product hat: OK (confidence 0.95)

Opportunities

  • Cross-module smoke test gap visible — The checklist correctly flags tests/smoke.sh as missing. Since this is a platform-wide gap (not n8n-worker specific), filing a separate platform-level task rather than blocking this module's audit would keep velocity high while ensuring the debt isn't lost.
<!-- platform-review:glm:pdurlej/platform:PR-29:aaaa55b4 --> # 3+3 ensemble review by `glm` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.95) ### Opportunities - **Image digest snapshot captured** — The `image_observed` field with full SHA256 digest provides a permanent snapshot of the exact image verified at audit time (2026-05-02). Good for future forensics if upstream registry republishes tags. ## Product hat: ✅ **OK** (confidence 0.95) ### Opportunities - **Cross-module smoke test gap visible** — The checklist correctly flags `tests/smoke.sh` as missing. Since this is a platform-wide gap (not n8n-worker specific), filing a separate platform-level task rather than blocking this module's audit would keep velocity high while ensuring the debt isn't lost.
Author
Collaborator

Review decision

Status: BLOCKER — recommended action: defer

Single-reviewer blockers

  • Manifest adds runtime keys that the schema rejects
    • Seen by: tech-gpt
    • Decision: Remove these audit-only fields from module.yaml and keep them in runbook.md, or add schema support for the new runtime audit fields before merging this manifest.
  • Keep observed image audit out of module.yaml
    • Seen by: product-gpt
    • Decision: Remove these three runtime audit fields from module.yaml. Keep the observed image/digest audit in runbook.md or move it into the proper generated observed-state/inventory path after schema support exists.

Reviewer dissents

  • tech-gpt voted NOT_OK (confidence 0.92)
  • product-gpt voted NOT_OK (confidence 0.90)

Operator decisions (yes/no)

  1. Risk 'Manifest adds runtime keys that the schema rejects' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated?
  2. Risk 'Keep observed image audit out of module.yaml' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated?
  3. Risk 'Recovery command uses a truncated digest': mitigated, accepted, or convert to open_loop?

Per-actor evidence: see comments by claude, codex, glm above. Tech: 2/3 OK · Product: 2/3 OK.

<!-- platform-review-decision:pdurlej/platform:PR-29:aaaa55b4 --> # Review decision **Status:** BLOCKER — recommended action: `defer` ### Single-reviewer blockers - **Manifest adds runtime keys that the schema rejects** - Seen by: tech-gpt - Decision: Remove these audit-only fields from module.yaml and keep them in runbook.md, or add schema support for the new runtime audit fields before merging this manifest. - **Keep observed image audit out of module.yaml** - Seen by: product-gpt - Decision: Remove these three runtime audit fields from module.yaml. Keep the observed image/digest audit in runbook.md or move it into the proper generated observed-state/inventory path after schema support exists. ### Reviewer dissents - `tech-gpt` voted **NOT_OK** (confidence 0.92) - `product-gpt` voted **NOT_OK** (confidence 0.90) ### Operator decisions (yes/no) 1. Risk 'Manifest adds runtime keys that the schema rejects' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated? 2. Risk 'Keep observed image audit out of module.yaml' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated? 3. Risk 'Recovery command uses a truncated digest': mitigated, accepted, or convert to open_loop? --- _Per-actor evidence: see comments by `claude`, `codex`, `glm` above. Tech: 2/3 OK · Product: 2/3 OK._
Owner

@claude

  1. Risk 'Manifest adds runtime keys that the schema rejects' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated? Add schema support for the new runtime audit fields before merging this manifest. - this is the kind of slop AI-coding decisions, that make codebase non-deterministic and not clear for future work. Good find
  2. Risk 'Keep observed image audit out of module.yaml' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated? For now, we can accept this risk in the long run "move it into the proper generated observed-state/inventory path after schema support exists"
  3. Risk 'Recovery command uses a truncated digest': mitigated, accepted, or convert to open_loop? Convert to open_loop
@claude 1. Risk 'Manifest adds runtime keys that the schema rejects' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated? **Add schema support for the new runtime audit fields before merging this manifest.** - this is the kind of slop AI-coding decisions, that make codebase non-deterministic and not clear for future work. Good find 2. Risk 'Keep observed image audit out of module.yaml' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated? **For now, we can accept this risk in the long run "move it into the proper generated observed-state/inventory path after schema support exists"** 3. Risk 'Recovery command uses a truncated digest': mitigated, accepted, or convert to open_loop? **Convert to open_loop**
Per canary findings on PR #29 (operator decision 2026-05-02 19:49Z):

## 1. Schema support for new fields (Risk 1: "manifest adds runtime keys schema rejects")

Added to `schema/module.schema.json`:
- `spec.runtime.image_observed` (string) — observed image at audit time
- `spec.runtime.image_digest_pinned_in_compose` (boolean)
- `spec.runtime.image_audit_ts` (date-time)
- `spec.runtime.image_build` (enum: registry|local)
- `spec.runtime.statefulness` (enum: stateless|stateful|sidecar-bound)
- `spec.risk.acknowledged_risks` (array of {id, rationale, acknowledged_by, acknowledged_at})

Each field documented inline in schema with Phase 02 audit context.

## 2. Manifest naming collision fix

Removed `spec.intent.data_classification: stateless` (collided with existing
`spec.risk.data_classification: <public|internal|private-pii|secrets>` —
different concepts). Replaced with `spec.runtime.statefulness: stateless`
which lives in runtime block where it belongs.

Added `spec.runtime.image_build: registry` to n8n-worker manifest.

## 3. Open loops added (Risks 2 + 3 operator decisions)

Two new entries in `state/L3/OPEN_LOOPS.md` § unresolved_active:
- **Truncated digest in recovery commands** — canary Risk 3, operator: "Convert to open_loop". Pattern question for all module runbooks.
- **Image audit fields long-term home** — canary Risk 2, operator: "accept in long run, move to generated observed-state/inventory path after schema support exists". Migration tracked.

## Validation

- `python3 -c "import yaml,json,jsonschema; jsonschema.validate(yaml.safe_load(open('modules/n8n-worker/module.yaml')), json.load(open('schema/module.schema.json')))"` → VALIDATES ✓

## Co-authors

- Operator (canary verdict + 3 risk decisions, "slop AI-coding decisions" feedback)
- Canary ζ tech-claude reviewer (caught the schema rejection)
- Claude Opus 4.7 (1M context, schema fix + manifest fix + open_loops)
Author
Collaborator

Action taken per Twoje decyzje (commit 9e9f343)

Risk 1 — schema support BEFORE merge

Added 6 new properties with inline docs:

  • spec.runtime.image_observed (string)
  • spec.runtime.image_digest_pinned_in_compose (boolean)
  • spec.runtime.image_audit_ts (date-time)
  • spec.runtime.image_build (enum: registry|local)
  • spec.runtime.statefulness (enum: stateless|stateful|sidecar-bound)
  • spec.risk.acknowledged_risks (array of {id, rationale, acknowledged_by, acknowledged_at})

Bonus fix podczas tego: mój spec.intent.data_classification: stateless kolidował z istniejącym spec.risk.data_classification: <public|internal|private-pii|secrets> (different concepts, same name). Renamed to spec.runtime.statefulness — lives in runtime block where it belongs. Cleanup mojego "slop" PRZED merge'em, nie po.

Validation: python3 jsonschema.validate(manifest, schema)

Risk 2 — accept long-term, move to observed-state inventory

Open_loop entry: "Image audit fields long-term home" w state/L3/OPEN_LOOPS.md § unresolved_active. Tracks migration to .platform/state/modules/<id>.status.json (charter §2 layout) once observed-state tooling exists.

Risk 3 — convert to open_loop

Open_loop entry: "Truncated digest in module recovery commands". Pattern question (full digest vs link to module.yaml vs platformctl module digest <id> helper) — decide and apply consistently across all runbooks.

Lesson learned (do nauki na resztę Phase 02)

Future-claude: GREP SCHEMA przed dodaniem nowych pól do module.yaml. Mój błąd był: dodałem 5 fields bez sprawdzenia czy schema je akceptuje. To "slop AI-coding decision" które łamie deterministic codebase. Dodałem do state/CLAUDE_SELF_INSTRUCTIONS.md — w follow-up PR.

Next

Re-fire canary v3 na commit 9e9f343 — żeby zweryfikować że:

  • Schema additions same nie generują nowych findings
  • Manifest rename (intent.data_classification → runtime.statefulness) nie złamało innych assumptions
  • Open_loops entries OK

Po canary v3 6/6 OK → merge as claude per pre-auth A.

— pan-herbata

## Action taken per Twoje decyzje (commit `9e9f343`) ### Risk 1 — schema support BEFORE merge ✅ Added 6 new properties with inline docs: - `spec.runtime.image_observed` (string) - `spec.runtime.image_digest_pinned_in_compose` (boolean) - `spec.runtime.image_audit_ts` (date-time) - `spec.runtime.image_build` (enum: registry|local) - `spec.runtime.statefulness` (enum: stateless|stateful|sidecar-bound) - `spec.risk.acknowledged_risks` (array of {id, rationale, acknowledged_by, acknowledged_at}) **Bonus fix podczas tego:** mój `spec.intent.data_classification: stateless` kolidował z istniejącym `spec.risk.data_classification: <public|internal|private-pii|secrets>` (different concepts, same name). Renamed to `spec.runtime.statefulness` — lives in runtime block where it belongs. Cleanup mojego "slop" PRZED merge'em, nie po. Validation: `python3 jsonschema.validate(manifest, schema)` → ✅ ### Risk 2 — accept long-term, move to observed-state inventory Open_loop entry: **"Image audit fields long-term home"** w `state/L3/OPEN_LOOPS.md` § unresolved_active. Tracks migration to `.platform/state/modules/<id>.status.json` (charter §2 layout) once observed-state tooling exists. ### Risk 3 — convert to open_loop Open_loop entry: **"Truncated digest in module recovery commands"**. Pattern question (full digest vs link to module.yaml vs `platformctl module digest <id>` helper) — decide and apply consistently across all runbooks. ### Lesson learned (do nauki na resztę Phase 02) Future-claude: **GREP SCHEMA przed dodaniem nowych pól do module.yaml**. Mój błąd był: dodałem 5 fields bez sprawdzenia czy schema je akceptuje. To "slop AI-coding decision" które łamie deterministic codebase. Dodałem do `state/CLAUDE_SELF_INSTRUCTIONS.md` — w follow-up PR. ### Next Re-fire canary v3 na commit `9e9f343` — żeby zweryfikować że: - Schema additions same nie generują nowych findings - Manifest rename (intent.data_classification → runtime.statefulness) nie złamało innych assumptions - Open_loops entries OK Po canary v3 6/6 OK → merge as claude per pre-auth A. — pan-herbata
Author
Collaborator

3+3 ensemble review by claude — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: NOT_OK (confidence 0.65)

Risks

  • high — Recovery command will fail — compose file not at given path root
    • Evidence: modules/n8n-worker/runbook.md (How to recover): "Single-step rebuild: docker compose up -d --no-deps n8n-workerfrom/opt/vps-home-platform-infra/." But module.yaml declares compose_file: compose/apps/compose.yaml. From the stated directory, docker composesearches for compose.{yaml,yml} in
    • Recommendation: Either change command to docker compose -f compose/apps/compose.yaml up -d --no-deps n8n-worker OR update path to from /opt/vps-home-platform-infra/compose/apps/. Verify with the actual host before merge.
  • high — Truncated digest in copy-pasteable recovery command
    • Evidence: modules/n8n-worker/runbook.md: "docker pull n8nio/n8n@sha256:72e2242d…to ensure exact digest." The Unicode ellipsisis not a valid hex character — pull will fail withinvalid reference format. Acknowledged in state/L3/OPEN_LOOPS.md as a deferred pattern question, but the broken command stil
    • Recommendation: Inline the full digest in the runbook command (one line, one PR), defer the broader 'pattern decision' separately. The cost of fixing here is lower than the cost of operator-during-incident hitting an invalid reference.
  • medium — Possible schema gap for v2 intent fields — validation may fail at apply time
    • Evidence: module.yaml adds user_facing_outcomeandacceptance_criteriaunderspec.intent, but schema/module.schema.json diff in this PR only adds runtime-level audit fields and risk-level acknowledged_risks. No additions to the intentproperties. IfintenthasadditionalProperties: false (consis
    • Recommendation: Run actual schema validation (jsonschema -i modules/n8n-worker/module.yaml schema/module.schema.json or platformctl validate <module>) and paste output into PR. If intent doesn't accept these fields yet, add them to the schema in this PR — otherwise apply-time will reject.
  • medium — Audit fields in desired-state manifest break plan determinism
    • Evidence: schema/module.schema.json adds image_audit_ts(date-time) andimage_observedtospec.runtime. Both reflect runtime-observed state, not desired state. Re-running the audit produces a manifest diff (timestamp drift, possibly digest drift if image rotated) → platformctl plan becomes non-determi
    • Recommendation: Decide before more modules adopt the pattern: either accept the determinism cost and document it in schema descriptions, or hold the schema change until observed-state path lands. Adding to 8 more module.yaml files now will make the eventual migration noisier.

Opportunities

  • Schema description for image_audit_ts could state staleness contract more strongly — Description says 'Stale audits (>30 days) should be re-run' — this is a soft norm. If platformctl is going to enforce or warn, the rule belongs in tooling not just schema prose. If it's not enforced, the field will quietly drift across modules. Either wire a check or remove the implicit promise.
  • acknowledged_risks.acknowledged_at is format: date while image_audit_ts is format: date-time — pick one — Inconsistent timestamp granularity across two new audit-adjacent fields added in the same PR. Risks: serialization tooling that treats them uniformly, or operator confusion. Consider standardizing on date-time everywhere or documenting the rationale (risk acknowledgment is a calendar event, audit is a precise moment).

Product hat: OK (confidence 0.85)

Opportunities

  • Define what 'release readiness' means platform-wide before more audits land — Runbook checklist shows 5/6 green with smoke test blocked on cross-module work (tests/smoke.sh not written). Phase 02 will produce ~N modules with the identical '5/6, smoke pending' shape. Decide now whether that pattern counts as 'release-ready' — otherwise in 3 months the operator will look at a fleet of modules with the same unchecked box and have to re-derive what it means. One sentence in t
  • PR description says data_classification: stateless but diff adds statefulness: stateless — Minor: PR body lists data_classification: stateless as a v2 field, but data_classification enum is public|internal|private-pii|secrets and the diff actually adds statefulness: stateless under runtime. Code is correct; description is slightly off. Worth noting because the same description text will likely be reused as a template for the second canary-target PR — fix in the template before
<!-- platform-review:claude:pdurlej/platform:PR-29:9e9f3436 --> # 3+3 ensemble review by `claude` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ❌ **NOT_OK** (confidence 0.65) ### Risks - **`high`** — Recovery command will fail — compose file not at given path root - Evidence: `modules/n8n-worker/runbook.md (How to recover): "Single-step rebuild: `docker compose up -d --no-deps n8n-worker` from `/opt/vps-home-platform-infra/`." But module.yaml declares `compose_file: compose/apps/compose.yaml`. From the stated directory, `docker compose` searches for compose.{yaml,yml} in ` - Recommendation: Either change command to `docker compose -f compose/apps/compose.yaml up -d --no-deps n8n-worker` OR update path to `from /opt/vps-home-platform-infra/compose/apps/`. Verify with the actual host before merge. - **`high`** — Truncated digest in copy-pasteable recovery command - Evidence: `modules/n8n-worker/runbook.md: "`docker pull n8nio/n8n@sha256:72e2242d…` to ensure exact digest." The Unicode ellipsis `…` is not a valid hex character — pull will fail with `invalid reference format`. Acknowledged in state/L3/OPEN_LOOPS.md as a deferred pattern question, but the broken command stil` - Recommendation: Inline the full digest in the runbook command (one line, one PR), defer the broader 'pattern decision' separately. The cost of fixing here is lower than the cost of operator-during-incident hitting an invalid reference. - **`medium`** — Possible schema gap for v2 intent fields — validation may fail at apply time - Evidence: `module.yaml adds `user_facing_outcome` and `acceptance_criteria` under `spec.intent`, but schema/module.schema.json diff in this PR only adds runtime-level audit fields and risk-level `acknowledged_risks`. No additions to the `intent` properties. If `intent` has `additionalProperties: false` (consis` - Recommendation: Run actual schema validation (`jsonschema -i modules/n8n-worker/module.yaml schema/module.schema.json` or `platformctl validate <module>`) and paste output into PR. If intent doesn't accept these fields yet, add them to the schema in this PR — otherwise apply-time will reject. - **`medium`** — Audit fields in desired-state manifest break plan determinism - Evidence: `schema/module.schema.json adds `image_audit_ts` (date-time) and `image_observed` to `spec.runtime`. Both reflect runtime-observed state, not desired state. Re-running the audit produces a manifest diff (timestamp drift, possibly digest drift if image rotated) → `platformctl plan` becomes non-determi` - Recommendation: Decide before more modules adopt the pattern: either accept the determinism cost and document it in schema descriptions, or hold the schema change until observed-state path lands. Adding to 8 more module.yaml files now will make the eventual migration noisier. ### Opportunities - **Schema description for `image_audit_ts` could state staleness contract more strongly** — Description says 'Stale audits (>30 days) should be re-run' — this is a soft norm. If platformctl is going to enforce or warn, the rule belongs in tooling not just schema prose. If it's not enforced, the field will quietly drift across modules. Either wire a check or remove the implicit promise. - **`acknowledged_risks.acknowledged_at` is `format: date` while `image_audit_ts` is `format: date-time` — pick one** — Inconsistent timestamp granularity across two new audit-adjacent fields added in the same PR. Risks: serialization tooling that treats them uniformly, or operator confusion. Consider standardizing on `date-time` everywhere or documenting the rationale (risk acknowledgment is a calendar event, audit is a precise moment). ## Product hat: ✅ **OK** (confidence 0.85) ### Opportunities - **Define what 'release readiness' means platform-wide before more audits land** — Runbook checklist shows 5/6 green with smoke test blocked on cross-module work (`tests/smoke.sh` not written). Phase 02 will produce ~N modules with the identical '5/6, smoke pending' shape. Decide now whether that pattern counts as 'release-ready' — otherwise in 3 months the operator will look at a fleet of modules with the same unchecked box and have to re-derive what it means. One sentence in t - **PR description says `data_classification: stateless` but diff adds `statefulness: stateless`** — Minor: PR body lists `data_classification: stateless` as a v2 field, but `data_classification` enum is `public|internal|private-pii|secrets` and the diff actually adds `statefulness: stateless` under `runtime`. Code is correct; description is slightly off. Worth noting because the same description text will likely be reused as a template for the second canary-target PR — fix in the template before
Collaborator

3+3 ensemble review by codex — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: NOT_OK (confidence 0.86)

Risks

  • medium — Recovery pull command is not executable
    • Evidence: modules/n8n-worker/runbook.md:59 uses docker pull n8nio/n8n@sha256:72e2242d…; the ellipsis makes this an invalid image reference exactly when the operator needs recovery to work.
    • Recommendation: Replace the truncated digest with the full digest from modules/n8n-worker/module.yaml:36, or change the instruction to pull the compose-pinned image reference without embedding a shortened digest.
  • medium — Recovery path bypasses approved apply flow
    • Evidence: modules/n8n-worker/runbook.md:57 gives docker compose up -d --no-deps n8n-worker as the single-step rebuild; PLATFORM_CHARTER §3 defines PR/platformctl apply as the normal mutation path.
    • Recommendation: Restore platformctl plan/apply --approved <SHA> as the normal recovery path, or explicitly mark the direct docker compose command as break-glass recovery with an audit-log requirement.

Product hat: NOT_OK (confidence 0.86)

Risks

  • medium — Do not merge a known broken recovery command
    • Evidence: modules/n8n-worker/runbook.md:57 uses docker pull n8nio/n8n@sha256:72e2242d…, which is not copy-pasteable; state/L3/OPEN_LOOPS.md records the same defect as accepted for later.
    • Recommendation: Before merge, replace the truncated digest recovery command with either the full digest already stored in module.yaml or a command that explicitly tells the operator to read spec.runtime.image_observed. Do not defer this as an open loop in a release-readiness PR.

Opportunities

  • Keep recovery commands boring — This PR is meant to make n8n-worker release-ready for a non-technical operator. The obvious while-here improvement is to ensure every command in How to recover is directly executable under stress.
<!-- platform-review:codex:pdurlej/platform:PR-29:9e9f3436 --> # 3+3 ensemble review by `codex` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ❌ **NOT_OK** (confidence 0.86) ### Risks - **`medium`** — Recovery pull command is not executable - Evidence: `modules/n8n-worker/runbook.md:59 uses `docker pull n8nio/n8n@sha256:72e2242d…`; the ellipsis makes this an invalid image reference exactly when the operator needs recovery to work.` - Recommendation: Replace the truncated digest with the full digest from modules/n8n-worker/module.yaml:36, or change the instruction to pull the compose-pinned image reference without embedding a shortened digest. - **`medium`** — Recovery path bypasses approved apply flow - Evidence: `modules/n8n-worker/runbook.md:57 gives `docker compose up -d --no-deps n8n-worker` as the single-step rebuild; PLATFORM_CHARTER §3 defines PR/platformctl apply as the normal mutation path.` - Recommendation: Restore `platformctl plan/apply --approved <SHA>` as the normal recovery path, or explicitly mark the direct docker compose command as break-glass recovery with an audit-log requirement. ## Product hat: ❌ **NOT_OK** (confidence 0.86) ### Risks - **`medium`** — Do not merge a known broken recovery command - Evidence: `modules/n8n-worker/runbook.md:57 uses `docker pull n8nio/n8n@sha256:72e2242d…`, which is not copy-pasteable; state/L3/OPEN_LOOPS.md records the same defect as accepted for later.` - Recommendation: Before merge, replace the truncated digest recovery command with either the full digest already stored in module.yaml or a command that explicitly tells the operator to read `spec.runtime.image_observed`. Do not defer this as an open loop in a release-readiness PR. ### Opportunities - **Keep recovery commands boring** — This PR is meant to make n8n-worker release-ready for a non-technical operator. The obvious while-here improvement is to ensure every command in `How to recover` is directly executable under stress.
First-time contributor

3+3 ensemble review by glm — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.95)

Opportunities

  • Reference full digest in runbook — modules/n8n-worker/runbook.md:32 shows truncated digest sha256:72e2242d…. This is documented as an open loop in state/L3/OPEN_LOOPS.md. Consider adding @see modules/n8n-worker/module.yaml spec.runtime.image_observed as an inline reference until the pattern decision is made, so operators have a direct path to the full digest without file-hopping.

Product hat: OK (confidence 0.95)

Risks

  • medium — Truncated digest breaks copy-paste recovery
    • Evidence: modules/n8n-worker/runbook.md:17 ('sha256:72e2242d…')
    • Recommendation: Use full digest in recovery command or replace with reference to spec.runtime.image_observed for 100% reliable recovery execution.

Opportunities

  • Observed-state fields in module.yaml — You're correctly documenting the audit now, but long-term these runtime fields (image_observed, image_audit_ts) drift from 'desired state'. Flagged in OPEN_LOOPS.md already — good catch. No action needed in this PR.
<!-- platform-review:glm:pdurlej/platform:PR-29:9e9f3436 --> # 3+3 ensemble review by `glm` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.95) ### Opportunities - **Reference full digest in runbook** — modules/n8n-worker/runbook.md:32 shows truncated digest `sha256:72e2242d…`. This is documented as an open loop in state/L3/OPEN_LOOPS.md. Consider adding `@see modules/n8n-worker/module.yaml spec.runtime.image_observed` as an inline reference until the pattern decision is made, so operators have a direct path to the full digest without file-hopping. ## Product hat: ✅ **OK** (confidence 0.95) ### Risks - **`medium`** — Truncated digest breaks copy-paste recovery - Evidence: `modules/n8n-worker/runbook.md:17 ('sha256:72e2242d…')` - Recommendation: Use full digest in recovery command or replace with reference to `spec.runtime.image_observed` for 100% reliable recovery execution. ### Opportunities - **Observed-state fields in module.yaml** — You're correctly documenting the audit now, but long-term these runtime fields (`image_observed`, `image_audit_ts`) drift from 'desired state'. Flagged in OPEN_LOOPS.md already — good catch. No action needed in this PR.
Author
Collaborator

Review decision

Status: ELEVATED — recommended action: defer

Single-reviewer high-risk findings

  • Recovery command will fail — compose file not at given path root
    • Seen by: tech-claude
    • Decision: Either change command to docker compose -f compose/apps/compose.yaml up -d --no-deps n8n-worker OR update path to from /opt/vps-home-platform-infra/compose/apps/. Verify with the actual host before merge.
  • Truncated digest in copy-pasteable recovery command
    • Seen by: tech-claude
    • Decision: Inline the full digest in the runbook command (one line, one PR), defer the broader 'pattern decision' separately. The cost of fixing here is lower than the cost of operator-during-incident hitting an invalid reference.

Reviewer dissents

  • product-gpt voted NOT_OK (confidence 0.86)
  • tech-gpt voted NOT_OK (confidence 0.86)
  • tech-claude voted NOT_OK (confidence 0.65)

Operator decisions (yes/no)

  1. Risk 'Recovery command will fail — compose file not at given path root' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated?
  2. Risk 'Truncated digest in copy-pasteable recovery command' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated?
  3. Risk 'Truncated digest breaks copy-paste recovery': mitigated, accepted, or convert to open_loop?

Per-actor evidence: see comments by claude, codex, glm above. Tech: 1/3 OK · Product: 2/3 OK.

<!-- platform-review-decision:pdurlej/platform:PR-29:9e9f3436 --> # Review decision **Status:** ELEVATED — recommended action: `defer` ### Single-reviewer high-risk findings - **Recovery command will fail — compose file not at given path root** - Seen by: tech-claude - Decision: Either change command to `docker compose -f compose/apps/compose.yaml up -d --no-deps n8n-worker` OR update path to `from /opt/vps-home-platform-infra/compose/apps/`. Verify with the actual host before merge. - **Truncated digest in copy-pasteable recovery command** - Seen by: tech-claude - Decision: Inline the full digest in the runbook command (one line, one PR), defer the broader 'pattern decision' separately. The cost of fixing here is lower than the cost of operator-during-incident hitting an invalid reference. ### Reviewer dissents - `product-gpt` voted **NOT_OK** (confidence 0.86) - `tech-gpt` voted **NOT_OK** (confidence 0.86) - `tech-claude` voted **NOT_OK** (confidence 0.65) ### Operator decisions (yes/no) 1. Risk 'Recovery command will fail — compose file not at given path root' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated? 2. Risk 'Truncated digest in copy-pasteable recovery command' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated? 3. Risk 'Truncated digest breaks copy-paste recovery': mitigated, accepted, or convert to open_loop? --- _Per-actor evidence: see comments by `claude`, `codex`, `glm` above. Tech: 1/3 OK · Product: 2/3 OK._
Per canary v3 ELEVATED (3 NOT_OK reviewers, all legit):

## tech-claude HIGH: "Recovery command will fail — compose file not at given path root"
runbook said `docker compose up -d --no-deps n8n-worker` from `/opt/vps-home-platform-infra/`,
but `module.yaml.spec.runtime.compose_file: compose/apps/compose.yaml`. From the stated
directory, compose searches root for compose.{yaml,yml} → fail.

Fix: cwd corrected to `/opt/vps-home-platform-infra/compose/apps/` + cite source line.

## tech-gpt + product-gpt MEDIUM: "Recovery pull command not executable / truncated digest"
runbook used `docker pull n8nio/n8n@sha256:72e2242d…` — Unicode ellipsis breaks copy-paste,
docker complains `invalid reference format`.

Fix: replaced with `docker pull "$(yq '.spec.runtime.image_observed' modules/n8n-worker/module.yaml)"`.
Sources digest from manifest at exec time. No truncation possible. Pattern documented in
OPEN_LOOPS for all future module runbooks.

## codex-tech MEDIUM: "Recovery path bypasses approved apply flow"
runbook didn't reference `platformctl plan/apply --approved <SHA>` — charter §3 deploy flow.

Fix: canonical path now `platformctl plan/apply --approved <SHA>`. Break-glass `docker compose`
explicitly marked as fallback only when platformctl unavailable.

## claude tech low: schema format inconsistency
`spec.risk.acknowledged_risks.acknowledged_at` was `format: date` while `spec.runtime.image_audit_ts`
was `format: date-time`. Aligned both to `date-time` (RFC3339 UTC).

## OPEN_LOOPS update

Removed "Truncated digest in module recovery commands" entry — fixed in this commit, not deferred.
Replaced with "Runbook digest pattern decision" entry locking the yq-from-manifest pattern as
canonical for all future Phase 02 runbooks.

## Validation

- `python3 jsonschema.validate(yaml.safe_load(modules/n8n-worker/module.yaml), schema)` → ✓
- All recovery commands manually traced for copy-paste correctness

Operator feedback that triggered this fix: canary v3 ELEVATED defer + Pan Herbata-grindset
calling out my "convert to open_loop" punt as escape from doing the actual fix.

Co-authored-by: canary v3 reviewers (tech-claude, tech-gpt, product-gpt — caught 3 real bugs)
Co-authored-by: Operator (called out the meta-vs-value drift)
Co-authored-by: Claude Opus 4.7
Author
Collaborator

v4 — fix all 4 canary v3 findings (commit 0b0038f)

Operator's feedback: stop punting to open_loop, fix the fucking bug.

Finding Fix
tech-claude HIGH: compose cwd wrong Recovery now uses cd compose/apps/ first (cite manifest field)
tech-gpt + product-gpt MEDIUM: truncated digest Recovery uses yq '.spec.runtime.image_observed' modules/n8n-worker/module.yaml — sources digest at exec time, no truncation possible
codex tech MEDIUM: bypasses platformctl Canonical path now platformctl plan/apply --approved <SHA> per charter §3. docker compose is break-glass fallback
claude tech low: schema format mismatch acknowledged_at aligned to date-time matching image_audit_ts

Plus: removed "truncated digest" from OPEN_LOOPS (fixed not deferred), added "Runbook digest pattern" entry locking the yq pattern for all future Phase 02 runbooks.

Re-fire canary v4 starting.

## v4 — fix all 4 canary v3 findings (commit `0b0038f`) Operator's feedback: stop punting to open_loop, fix the fucking bug. | Finding | Fix | |---|---| | `tech-claude` HIGH: compose cwd wrong | Recovery now uses `cd compose/apps/` first (cite manifest field) | | `tech-gpt` + `product-gpt` MEDIUM: truncated digest | Recovery uses `yq '.spec.runtime.image_observed' modules/n8n-worker/module.yaml` — sources digest at exec time, no truncation possible | | `codex tech` MEDIUM: bypasses platformctl | Canonical path now `platformctl plan/apply --approved <SHA>` per charter §3. `docker compose` is break-glass fallback | | `claude tech` low: schema format mismatch | `acknowledged_at` aligned to `date-time` matching `image_audit_ts` | Plus: removed "truncated digest" from OPEN_LOOPS (fixed not deferred), added "Runbook digest pattern" entry locking the `yq` pattern for all future Phase 02 runbooks. Re-fire canary v4 starting.
Author
Collaborator

3+3 ensemble review by claude — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.85)

Risks

  • low — yq portability risk in runbook recovery snippet
    • Evidence: modules/n8n-worker/runbook.md — docker pull "$(yq '.spec.runtime.image_observed' modules/n8n-worker/module.yaml)"``
    • Recommendation: Bare yq is ambiguous: mikefarah/yq (Go) returns raw strings; kislyuk/yq (Python wrapper around jq) returns JSON-quoted strings, which would yield "n8nio/n8n:..." and fail docker pull with invalid reference format. Either pin the binary (e.g. mikefarah/yq) or pipe through tr -d '"' for safety. Break-glass-only path so non-blocking.

Opportunities

  • PR description vs implementation drift — PR description lists data_classification: stateless as a new v2 field, but the diff actually adds statefulness: stateless under runtime (the enum for data_classification is [public, internal, private-pii, secrets] and would reject stateless). The implementation is correct; the description is misleading for future reviewers reading the PR log. Worth correcting if the PR is amended.
  • Pattern regex on image_observed — schema/module.schema.json defines image_observed as a free-form string with format documented in description only. A pattern regex matching ^[\w./-]+:[\w.-]+(@sha256:[a-f0-9]{64})?$ would catch typos and truncated-digest pastes (the same failure mode operator already documented in OPEN_LOOPS canary v3). Optional, low-priority hardening.
  • OPEN_LOOPS already names the architectural concern — Operator has explicitly logged that image_observed/image_digest_pinned_in_compose/image_audit_ts/image_build belong in generated observed-state inventory (.platform/state/modules/<id>.status.json) rather than module.yaml (desired state). Accepting them here as a canary is reasonable — but the migration path should land before this pattern is repeated across many modules, otherwise sche

Product hat: OK (confidence 0.80)

Risks

  • low — Audit fields in desired-state manifest blur observed vs. desired
    • Evidence: modules/n8n-worker/module.yaml adds image_observed/image_audit_ts under spec.runtime; OPEN_LOOPS.md already flags this as architecturally belonging in observed-state inventory
    • Recommendation: Accept as canary; the open-loop entry is the right shape. Set a calendar nudge to migrate before applying the pattern to >3 more modules so drift stays bounded.

Opportunities

  • Pre-merge ADHD ergonomics are good — keep this template — Runbook now answers the three questions a tired operator actually asks at 2am: is it healthy, how do I rebuild, what state do I lose. The 'break-glass rebuild' + 'stateful concerns redirect' pattern is reusable; lock it as the Phase 02 runbook template before the next 1-2 audits drift in style.
<!-- platform-review:claude:pdurlej/platform:PR-29:0b0038ff --> # 3+3 ensemble review by `claude` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.85) ### Risks - **`low`** — yq portability risk in runbook recovery snippet - Evidence: `modules/n8n-worker/runbook.md — `docker pull "$(yq '.spec.runtime.image_observed' modules/n8n-worker/module.yaml)"`` - Recommendation: Bare `yq` is ambiguous: mikefarah/yq (Go) returns raw strings; kislyuk/yq (Python wrapper around jq) returns JSON-quoted strings, which would yield `"n8nio/n8n:..."` and fail docker pull with `invalid reference format`. Either pin the binary (e.g. `mikefarah/yq`) or pipe through `tr -d '"'` for safety. Break-glass-only path so non-blocking. ### Opportunities - **PR description vs implementation drift** — PR description lists `data_classification: stateless` as a new v2 field, but the diff actually adds `statefulness: stateless` under runtime (the enum for `data_classification` is `[public, internal, private-pii, secrets]` and would reject `stateless`). The implementation is correct; the description is misleading for future reviewers reading the PR log. Worth correcting if the PR is amended. - **Pattern regex on image_observed** — schema/module.schema.json defines `image_observed` as a free-form string with format documented in description only. A `pattern` regex matching `^[\w./-]+:[\w.-]+(@sha256:[a-f0-9]{64})?$` would catch typos and truncated-digest pastes (the same failure mode operator already documented in OPEN_LOOPS canary v3). Optional, low-priority hardening. - **OPEN_LOOPS already names the architectural concern** — Operator has explicitly logged that `image_observed`/`image_digest_pinned_in_compose`/`image_audit_ts`/`image_build` belong in generated observed-state inventory (`.platform/state/modules/<id>.status.json`) rather than `module.yaml` (desired state). Accepting them here as a canary is reasonable — but the migration path should land before this pattern is repeated across many modules, otherwise sche ## Product hat: ✅ **OK** (confidence 0.80) ### Risks - **`low`** — Audit fields in desired-state manifest blur observed vs. desired - Evidence: `modules/n8n-worker/module.yaml adds image_observed/image_audit_ts under spec.runtime; OPEN_LOOPS.md already flags this as architecturally belonging in observed-state inventory` - Recommendation: Accept as canary; the open-loop entry is the right shape. Set a calendar nudge to migrate before applying the pattern to >3 more modules so drift stays bounded. ### Opportunities - **Pre-merge ADHD ergonomics are good — keep this template** — Runbook now answers the three questions a tired operator actually asks at 2am: is it healthy, how do I rebuild, what state do I lose. The 'break-glass rebuild' + 'stateful concerns redirect' pattern is reusable; lock it as the Phase 02 runbook template before the next 1-2 audits drift in style.
Collaborator

3+3 ensemble review by codex — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: NOT_OK (confidence 0.90)

Risks

  • medium — Canonical recovery command omits required apply plan file
    • Evidence: modules/n8n-worker/runbook.md:57 says platformctl apply --approved , but control-plane/platformctl/cli.py:106 requires positional plan_filebefore--approved.
    • Recommendation: Update the runbook to use the actual plan artifact path, e.g. platformctl plan n8n-worker --out .plans then platformctl apply .plans/n8n-worker.plan.json --approved <merged-PR-SHA>; note --dry-run if this environment is still pre-Phase 05.

Product hat: OK (confidence 0.78)

Risks

  • medium — Live audit facts can age into false confidence
    • Evidence: modules/n8n-worker/runbook.md:46-53 marks release-readiness items based on a 2026-05-02 sample; schema/module.schema.json:216-220 says audits older than 30 days should be re-run, but no tooling flips or flags stale checklist state.
    • Recommendation: Merge this canary only as temporary Phase 02 documentation, and do not roll the pattern to more modules until there is an operator-visible stale-audit check or review queue item keyed off image_audit_ts.

Opportunities

  • Add one refresh command — modules/n8n-worker/runbook.md:21-33 records the audit source and results, but not the exact commands for refreshing health/error evidence. A short copy-paste audit refresh block would reduce next-review memory load for Piotr.
<!-- platform-review:codex:pdurlej/platform:PR-29:0b0038ff --> # 3+3 ensemble review by `codex` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ❌ **NOT_OK** (confidence 0.90) ### Risks - **`medium`** — Canonical recovery command omits required apply plan file - Evidence: `modules/n8n-worker/runbook.md:57 says `platformctl apply --approved <merged-PR-SHA>`, but control-plane/platformctl/cli.py:106 requires positional `plan_file` before `--approved`.` - Recommendation: Update the runbook to use the actual plan artifact path, e.g. `platformctl plan n8n-worker --out .plans` then `platformctl apply .plans/n8n-worker.plan.json --approved <merged-PR-SHA>`; note `--dry-run` if this environment is still pre-Phase 05. ## Product hat: ✅ **OK** (confidence 0.78) ### Risks - **`medium`** — Live audit facts can age into false confidence - Evidence: `modules/n8n-worker/runbook.md:46-53 marks release-readiness items based on a 2026-05-02 sample; schema/module.schema.json:216-220 says audits older than 30 days should be re-run, but no tooling flips or flags stale checklist state.` - Recommendation: Merge this canary only as temporary Phase 02 documentation, and do not roll the pattern to more modules until there is an operator-visible stale-audit check or review queue item keyed off image_audit_ts. ### Opportunities - **Add one refresh command** — modules/n8n-worker/runbook.md:21-33 records the audit source and results, but not the exact commands for refreshing health/error evidence. A short copy-paste audit refresh block would reduce next-review memory load for Piotr.
First-time contributor

3+3 ensemble review by glm — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.95)

Risks

  • medium — Runtime image drift tracking deferred
    • Evidence: schema/module.schema.json:207-229 + state/L3/OPEN_LOOPS.md:13-15
    • Recommendation: Documented in OPEN_LOOPS.md as accepted technical debt. Audit fields live in desired-state (module.yaml) instead of observed-state inventory. Revisit once .platform/state/modules/<id>.status.json tooling exists per charter §2 layout.
  • low — acknowledged_risks not used in this module
    • Evidence: schema/module.schema.json:302-322 vs modules/n8n-worker/module.yaml (empty)
    • Recommendation: Schema addition is forward-looking for modules with accepted risks. n8n-worker correctly omits it — no risks to acknowledge per audit findings.

Opportunities

  • Digest sourcing pattern lock established — Runbook now uses yq '.spec.runtime.image_observed' instead of copy-pasting truncated digests. OPEN_LOOPS.md captures this as v3 canary learning for Phase 02 consistency across modules.
  • Stateless recovery path simplified — Runbook cleanly separates stateless worker recovery (platformctl apply) from stateful dependency recovery (postgres/redis baseline). Operator gets clear cognitive separation for incident response.

Product hat: OK (confidence 0.95)

Risks

  • low — Schema adds 'acknowledged_risks' complexity
    • Evidence: schema/module.schema.json (lines 302-321)
    • Recommendation: Monitor if unused. If still empty after Phase 02 completes, consider pruning to keep schema surface small. Not a blocker now.

Opportunities

  • Explicit 'no copy-paste digests' pattern — runbook.md warning 'Avoid copy-pasting truncated digest strings — they fail' is excellent ADHD-friendly guardrail. This pattern (source via yq) should be explicitly mentioned in the Phase 02 playbook for reuse across other modules.
  • Stateless import phase N/A — Correctly identifies no import work needed. This reduces operator attention burden — stateless is 'fire and forget'. Good pattern to lock for other stateless workers.
<!-- platform-review:glm:pdurlej/platform:PR-29:0b0038ff --> # 3+3 ensemble review by `glm` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.95) ### Risks - **`medium`** — Runtime image drift tracking deferred - Evidence: `schema/module.schema.json:207-229 + state/L3/OPEN_LOOPS.md:13-15` - Recommendation: Documented in OPEN_LOOPS.md as accepted technical debt. Audit fields live in desired-state (module.yaml) instead of observed-state inventory. Revisit once `.platform/state/modules/<id>.status.json` tooling exists per charter §2 layout. - **`low`** — acknowledged_risks not used in this module - Evidence: `schema/module.schema.json:302-322 vs modules/n8n-worker/module.yaml (empty)` - Recommendation: Schema addition is forward-looking for modules with accepted risks. n8n-worker correctly omits it — no risks to acknowledge per audit findings. ### Opportunities - **Digest sourcing pattern lock established** — Runbook now uses `yq '.spec.runtime.image_observed'` instead of copy-pasting truncated digests. OPEN_LOOPS.md captures this as v3 canary learning for Phase 02 consistency across modules. - **Stateless recovery path simplified** — Runbook cleanly separates stateless worker recovery (platformctl apply) from stateful dependency recovery (postgres/redis baseline). Operator gets clear cognitive separation for incident response. ## Product hat: ✅ **OK** (confidence 0.95) ### Risks - **`low`** — Schema adds 'acknowledged_risks' complexity - Evidence: `schema/module.schema.json (lines 302-321)` - Recommendation: Monitor if unused. If still empty after Phase 02 completes, consider pruning to keep schema surface small. Not a blocker now. ### Opportunities - **Explicit 'no copy-paste digests' pattern** — runbook.md warning 'Avoid copy-pasting truncated digest strings — they fail' is excellent ADHD-friendly guardrail. This pattern (source via yq) should be explicitly mentioned in the Phase 02 playbook for reuse across other modules. - **Stateless import phase N/A** — Correctly identifies no import work needed. This reduces operator attention burden — stateless is 'fire and forget'. Good pattern to lock for other stateless workers.
Author
Collaborator

Review decision

Status: ELEVATED — recommended action: defer

No blockers or high-severity risks. See per-actor comments + local decision packet for medium/low details.

Reviewer dissents

  • tech-gpt voted NOT_OK (confidence 0.90)

Operator decisions (yes/no)

  1. Risk 'Runtime image drift tracking deferred': mitigated, accepted, or convert to open_loop?
  2. Risk 'Live audit facts can age into false confidence': mitigated, accepted, or convert to open_loop?
  3. Risk 'Canonical recovery command omits required apply plan file': mitigated, accepted, or convert to open_loop?

Per-actor evidence: see comments by claude, codex, glm above. Tech: 2/3 OK · Product: 3/3 OK.

<!-- platform-review-decision:pdurlej/platform:PR-29:0b0038ff --> # Review decision **Status:** ELEVATED — recommended action: `defer` _No blockers or high-severity risks. See per-actor comments + local decision packet for medium/low details._ ### Reviewer dissents - `tech-gpt` voted **NOT_OK** (confidence 0.90) ### Operator decisions (yes/no) 1. Risk 'Runtime image drift tracking deferred': mitigated, accepted, or convert to open_loop? 2. Risk 'Live audit facts can age into false confidence': mitigated, accepted, or convert to open_loop? 3. Risk 'Canonical recovery command omits required apply plan file': mitigated, accepted, or convert to open_loop? --- _Per-actor evidence: see comments by `claude`, `codex`, `glm` above. Tech: 2/3 OK · Product: 3/3 OK._
Per canary v5 NOT_OK from tech-gpt (confidence 0.90):

> "Canonical recovery command omits required apply plan file"
> Evidence: control-plane/platformctl/cli.py:106 — plan_file is positional, required.

Previous: `platformctl apply --approved <merged-PR-SHA>` (broken — missing positional)
Now: explicit two-step `platformctl plan n8n-worker` then
     `platformctl apply .plans/n8n-worker.plan.json --approved <merged-PR-SHA>`

Verified against control-plane/platformctl/cli.py:97-123 (cmd_plan + cmd_apply
signatures).

Risks 1+2 from canary v5 (1/6 low-priority each) accepted as already-mitigated:
- Risk 1 "Runtime image drift tracking deferred" — already in OPEN_LOOPS as
  "Image audit fields long-term home" entry.
- Risk 2 "Live audit facts can age into false confidence" — schema already
  documents "Stale audits (>30 days) should be re-run" on image_audit_ts.

Co-authored-by: tech-gpt canary v5 reviewer (caught real bug)
Co-authored-by: Claude Opus 4.7
Author
Collaborator

3+3 ensemble review by claude — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.70)

Risks

  • medium — Schema validation never executed in verification — only YAML parse tested
    • Evidence: PR description verification: python3 -c "import yaml; yaml.safe_load(open('modules/n8n-worker/module.yaml'))". This only proves the YAML is syntactically valid, not that it conforms to schema/module.schema.json. The PR adds new fields under spec.intent (user_facing_outcome, acceptance_criteri`
    • Recommendation: Before self-merge, run jsonschema -i modules/n8n-worker/module.yaml schema/module.schema.json (or equivalent platformctl validate). If intent schema lacks these fields, either add them to the schema in this PR or revert the YAML additions. One liner diff to verify additionalProperties posture on intent would resolve this in 30s.
  • low — image_audit_ts/acknowledged_at use format: date-time without pattern fallback
    • Evidence: schema/module.schema.json new fields use "format": "date-time"only. Per JSON Schema Draft 2020-12,format is annotation-only by default; only the format-assertion vocabulary (or validator-specific assertion mode) enforces it. Malformed timestamps like '2026-05-02 09:11' (no T, no Z) would silen
    • Recommendation: If platformctl validator runs in assert mode, no change needed. Otherwise add "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$" to harden. Defer until next schema-touching PR if not blocking.

Opportunities

  • Break-glass docker compose up -d --no-deps n8n-worker assumes deps are healthy — runbook.md break-glass section runs with --no-deps, which is correct for worker-only restart but silent if postgres/redis are also down — operator gets a worker that comes up and immediately fails to dequeue. One-line caveat ('only run if postgres+redis are confirmed healthy via docker ps') would prevent a confused 02:00 follow-up. Not blocking; runbook is otherwise solid.

Product hat: OK (confidence 0.85)

Risks

  • low — Schema adds acknowledged_risks array but PR doesn't use it
    • Evidence: schema/module.schema.json +14 lines for acknowledged_risks object, but n8n-worker module.yaml has zero entries. First actual use will likely come in PR #27 or later canary.
    • Recommendation: Acceptable as canary scaffolding since this is the first of a Phase 02 series — but if next canary still doesn't use it, drop the schema field and re-add when first real risk needs documenting. Avoids unused surface.

Opportunities

  • Codify the audit+freeze+import+release-readiness pattern as a runbook template — This PR establishes a reusable structure: live state audit table → data classification → import phase plan → release readiness checklist. Operator confirmed 'first of 2' and broader Phase 02 vision. A single template doc (docs/templates/module-runbook.md or a /skill) would let subsequent canaries copy structure verbatim instead of reinventing — directly saves operator attention and prevents drif
  • Make image_audit_ts staleness visible in platformctl status — Schema description says '>30 days should be re-run' but nothing surfaces this. metadata.next_review (2026-06-02) covers the cadence today, but as more modules adopt these fields, a single 'which audits are stale' query saves the operator from manually scanning N module.yaml files. Low priority — only worth doing once 3+ modules have the field.
<!-- platform-review:claude:pdurlej/platform:PR-29:f501458a --> # 3+3 ensemble review by `claude` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.70) ### Risks - **`medium`** — Schema validation never executed in verification — only YAML parse tested - Evidence: `PR description verification: `python3 -c "import yaml; yaml.safe_load(open('modules/n8n-worker/module.yaml'))"`. This only proves the YAML is syntactically valid, not that it conforms to schema/module.schema.json. The PR adds new fields under `spec.intent` (`user_facing_outcome`, `acceptance_criteri` - Recommendation: Before self-merge, run `jsonschema -i modules/n8n-worker/module.yaml schema/module.schema.json` (or equivalent platformctl validate). If intent schema lacks these fields, either add them to the schema in this PR or revert the YAML additions. One liner diff to verify additionalProperties posture on `intent` would resolve this in 30s. - **`low`** — image_audit_ts/acknowledged_at use `format: date-time` without pattern fallback - Evidence: `schema/module.schema.json new fields use `"format": "date-time"` only. Per JSON Schema Draft 2020-12, `format` is annotation-only by default; only the format-assertion vocabulary (or validator-specific assertion mode) enforces it. Malformed timestamps like '2026-05-02 09:11' (no T, no Z) would silen` - Recommendation: If platformctl validator runs in assert mode, no change needed. Otherwise add `"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$"` to harden. Defer until next schema-touching PR if not blocking. ### Opportunities - **Break-glass `docker compose up -d --no-deps n8n-worker` assumes deps are healthy** — runbook.md break-glass section runs with `--no-deps`, which is correct for worker-only restart but silent if postgres/redis are also down — operator gets a worker that comes up and immediately fails to dequeue. One-line caveat ('only run if postgres+redis are confirmed healthy via `docker ps`') would prevent a confused 02:00 follow-up. Not blocking; runbook is otherwise solid. ## Product hat: ✅ **OK** (confidence 0.85) ### Risks - **`low`** — Schema adds `acknowledged_risks` array but PR doesn't use it - Evidence: `schema/module.schema.json +14 lines for `acknowledged_risks` object, but n8n-worker module.yaml has zero entries. First actual use will likely come in PR #27 or later canary.` - Recommendation: Acceptable as canary scaffolding since this is the first of a Phase 02 series — but if next canary still doesn't use it, drop the schema field and re-add when first real risk needs documenting. Avoids unused surface. ### Opportunities - **Codify the audit+freeze+import+release-readiness pattern as a runbook template** — This PR establishes a reusable structure: live state audit table → data classification → import phase plan → release readiness checklist. Operator confirmed 'first of 2' and broader Phase 02 vision. A single template doc (`docs/templates/module-runbook.md` or a /skill) would let subsequent canaries copy structure verbatim instead of reinventing — directly saves operator attention and prevents drif - **Make `image_audit_ts` staleness visible in `platformctl status`** — Schema description says '>30 days should be re-run' but nothing surfaces this. metadata.next_review (2026-06-02) covers the cadence today, but as more modules adopt these fields, a single 'which audits are stale' query saves the operator from manually scanning N module.yaml files. Low priority — only worth doing once 3+ modules have the field.
Collaborator

3+3 ensemble review by codex — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.84)

Opportunities

  • Make image re-pull command cwd-explicit later — modules/n8n-worker/runbook.md image re-pull uses a repo-relative module.yaml path; it is acceptable for this PR, but future runbooks should state the expected working directory before commands that read module manifests.

Product hat: NOT_OK (confidence 0.76)

Risks

  • medium — Do not merge temporary observed-state schema as reusable pattern
    • Evidence: state/L3/OPEN_LOOPS.md: Phase 02 audit fields are explicitly described as accepted temporarily in module.yaml but architecturally belonging in generated observed-state inventory; schema/module.schema.json then makes those fields first-class schema-supported manifest fields for all modules.
    • Recommendation: Either keep the n8n-worker audit in the runbook only, or mark the schema fields as canary/temporary with a bounded removal trigger before using this pattern across the remaining Phase 02 modules.

Opportunities

  • Tighten operator decision surface — The runbook already has the useful facts: healthy, internal-only, stateless, no recent errors. A short decision comment could say: merge means accepting temporary source-controlled live audit fields until observed-state inventory exists.
<!-- platform-review:codex:pdurlej/platform:PR-29:f501458a --> # 3+3 ensemble review by `codex` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.84) ### Opportunities - **Make image re-pull command cwd-explicit later** — modules/n8n-worker/runbook.md image re-pull uses a repo-relative module.yaml path; it is acceptable for this PR, but future runbooks should state the expected working directory before commands that read module manifests. ## Product hat: ❌ **NOT_OK** (confidence 0.76) ### Risks - **`medium`** — Do not merge temporary observed-state schema as reusable pattern - Evidence: `state/L3/OPEN_LOOPS.md: Phase 02 audit fields are explicitly described as accepted temporarily in module.yaml but architecturally belonging in generated observed-state inventory; schema/module.schema.json then makes those fields first-class schema-supported manifest fields for all modules.` - Recommendation: Either keep the n8n-worker audit in the runbook only, or mark the schema fields as canary/temporary with a bounded removal trigger before using this pattern across the remaining Phase 02 modules. ### Opportunities - **Tighten operator decision surface** — The runbook already has the useful facts: healthy, internal-only, stateless, no recent errors. A short decision comment could say: merge means accepting temporary source-controlled live audit fields until observed-state inventory exists.
First-time contributor

3+3 ensemble review by glm — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-29/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.95)

Risks

  • low — Runbook path assumption depends on repo root
    • Evidence: modules/n8n-worker/runbook.md:66
    • Recommendation: Add note that recovery commands assume running from repo root. Current yq path modules/n8n-worker/module.yaml works from root but fails if runbook opened in isolation.

Opportunities

  • Schema design for eventual field migration — OPEN_LOOPS.md correctly identifies image audit fields should migrate to observed-state inventory. Schema addition under spec.runtime is pragmatic interim solution that doesn't block eventual migration.
  • Digest pattern documented as cross-module standard — OPEN_LOOPS.md establishes 'no truncation, always source from module.yaml via yq' as platform-wide pattern. This prevents copy-paste errors in all subsequent Phase 02 runbooks.

Product hat: OK (confidence 0.95)

Opportunities

  • Schema drift future-proofing — The OPEN_LOOPS.md note correctly flags that image_observed belongs in observed-state inventory, not desired-state module.yaml. Consider adding a comment in module.schema.json linking to this decision so future reviewers understand this is temporary Phase 02 scaffolding.
  • Recovery path clarity — The runbook now distinguishes canonical (platformctl apply) vs break-glass recovery. Good pattern. Ensure platformctl plan is mentioned at the top of recovery sections for all future Phase 02 runbooks for consistency.
<!-- platform-review:glm:pdurlej/platform:PR-29:f501458a --> # 3+3 ensemble review by `glm` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-29/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.95) ### Risks - **`low`** — Runbook path assumption depends on repo root - Evidence: `modules/n8n-worker/runbook.md:66` - Recommendation: Add note that recovery commands assume running from repo root. Current yq path `modules/n8n-worker/module.yaml` works from root but fails if runbook opened in isolation. ### Opportunities - **Schema design for eventual field migration** — OPEN_LOOPS.md correctly identifies image audit fields should migrate to observed-state inventory. Schema addition under `spec.runtime` is pragmatic interim solution that doesn't block eventual migration. - **Digest pattern documented as cross-module standard** — OPEN_LOOPS.md establishes 'no truncation, always source from module.yaml via yq' as platform-wide pattern. This prevents copy-paste errors in all subsequent Phase 02 runbooks. ## Product hat: ✅ **OK** (confidence 0.95) ### Opportunities - **Schema drift future-proofing** — The `OPEN_LOOPS.md` note correctly flags that `image_observed` belongs in observed-state inventory, not desired-state `module.yaml`. Consider adding a comment in `module.schema.json` linking to this decision so future reviewers understand this is temporary Phase 02 scaffolding. - **Recovery path clarity** — The runbook now distinguishes canonical (`platformctl apply`) vs break-glass recovery. Good pattern. Ensure `platformctl plan` is mentioned at the top of recovery sections for all future Phase 02 runbooks for consistency.
Author
Collaborator

Review decision

Status: ELEVATED — recommended action: defer

No blockers or high-severity risks. See per-actor comments + local decision packet for medium/low details.

Reviewer dissents

  • product-gpt voted NOT_OK (confidence 0.76)

Operator decisions (yes/no)

  1. Risk 'Do not merge temporary observed-state schema as reusable pattern': mitigated, accepted, or convert to open_loop?
  2. Risk 'Schema validation never executed in verification — only YAML parse tested': mitigated, accepted, or convert to open_loop?
  3. Low risk 'Runbook path assumption depends on repo root' (weak signal): ignore, or address now?

Per-actor evidence: see comments by claude, codex, glm above. Tech: 3/3 OK · Product: 2/3 OK.

<!-- platform-review-decision:pdurlej/platform:PR-29:f501458a --> # Review decision **Status:** ELEVATED — recommended action: `defer` _No blockers or high-severity risks. See per-actor comments + local decision packet for medium/low details._ ### Reviewer dissents - `product-gpt` voted **NOT_OK** (confidence 0.76) ### Operator decisions (yes/no) 1. Risk 'Do not merge temporary observed-state schema as reusable pattern': mitigated, accepted, or convert to open_loop? 2. Risk 'Schema validation never executed in verification — only YAML parse tested': mitigated, accepted, or convert to open_loop? 3. Low risk 'Runbook path assumption depends on repo root' (weak signal): ignore, or address now? --- _Per-actor evidence: see comments by `claude`, `codex`, `glm` above. Tech: 3/3 OK · Product: 2/3 OK._
Operator decisions 2026-05-02 23:25 CEST on canary v6 PR #29 product-gpt dissent:

- Risk 1 "Temporary observed-state schema as reusable pattern" → convert to open_loop
- Risk 2 "Schema validation YAML-only" → convert to open_loop
- Risk 3 "Runbook path assumption" → ignore

Two operator deeper-feedback notes codified into the open_loops entries (not just
"accepted, move on" — these point to platform principles):

(1) Risk 1 reflects claude's "model self-bias" — Anthropic-trained models tend to
self-prefer Anthropic ecosystem patterns. This platform is intentionally
**agnostic** (claude/codex/glm as equal-weight identities). Operator explicitly
steers claude away from self-preferring choices regardless of training. Logged
inside the OPEN_LOOPS entry so future-claude reads it and remembers.

(2) Risk 2 reflects mixed-signal token cost: every ambiguity in platform docs/
schema/PR-bodies = tokens spent on disambiguating thinking = less context for
value delivery. Architecture must allow cleanup paths, not accumulate mess
(operator: "tutaj bez litości — będzie w przyszłości"; cf. OpenClaw as
anti-example).

These two open_loops have date stamps and operator-decision provenance for
later resolution. PR #29 unblocked for merge with both risks formally accepted
as logged-not-fixed-now.

Co-authored-by: Operator (R1+R2+R3 decisions + deeper platform-principle notes)
Co-authored-by: Claude Opus 4.7
Author
Collaborator

Operator decisions applied (commit 8227191)

  • Risk 1 "Temporary observed-state schema as reusable pattern" → convert to open_loop
  • Risk 2 "Schema validation YAML-only" → convert to open_loop
  • Risk 3 "Runbook path assumption" → ignore

Both R1 and R2 entries in state/L3/OPEN_LOOPS.md carry operator's deeper platform-principle notes, not just "accepted":

  • R1 names model self-bias (Anthropic-trained tendency to self-prefer) and operator's intentional agnostic platform stance — codified so future-claude reads it.
  • R2 names mixed-signal token cost (each ambiguity = thinking tokens = less value context); architecture must allow cleanup paths, not accumulate OpenClaw-style mess. "Tutaj bez litości — będzie w przyszłości."

Per pre-auth A (operator 2026-05-02: "5 PR-y small, które każde z nich możesz sam zaakceptować"), with all 3 risks now resolved, claude self-merges this PR.

Wallclock retrospective: 6 canary iterations to land. n8n-worker ended up the canary-test mule for: schema-rejection bug, recovery-command syntax bugs, runbook drift, schema timestamp mismatch. Real value extracted from each iteration. Pattern locked for next 4 Phase 02 PR-y.

— pan-herbata

## Operator decisions applied (commit `8227191`) - **Risk 1** "Temporary observed-state schema as reusable pattern" → **convert to open_loop** ✓ - **Risk 2** "Schema validation YAML-only" → **convert to open_loop** ✓ - **Risk 3** "Runbook path assumption" → **ignore** ✓ Both R1 and R2 entries in `state/L3/OPEN_LOOPS.md` carry **operator's deeper platform-principle notes**, not just "accepted": - R1 names model self-bias (Anthropic-trained tendency to self-prefer) and operator's intentional **agnostic platform** stance — codified so future-claude reads it. - R2 names mixed-signal token cost (each ambiguity = thinking tokens = less value context); architecture must allow cleanup paths, not accumulate OpenClaw-style mess. *"Tutaj bez litości — będzie w przyszłości."* Per pre-auth A (operator 2026-05-02: "5 PR-y small, które każde z nich możesz sam zaakceptować"), with all 3 risks now resolved, claude self-merges this PR. Wallclock retrospective: **6 canary iterations** to land. n8n-worker ended up the canary-test mule for: schema-rejection bug, recovery-command syntax bugs, runbook drift, schema timestamp mismatch. Real value extracted from each iteration. Pattern locked for next 4 Phase 02 PR-y. — pan-herbata
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
4 participants
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!29
No description provided.