Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
19 KiB
Etappe 3b – Entscheidungen (Rulings), Befunde und offene Punkte
Aus dem SDD-Ledger übernommen, 2026-09-27. Security-Audit: 2026-09-27-etappe-3b-security-audit.md. Abnahme: 2026-09-27-etappe-3b-abnahme.md.
Offen für spätere Etappen
- Etappe U: Revisionen von Theme-Builder-Layouts und Bibliothekseinträgen über MCP wiederherstellen (jetzt nur restore_url zum WordPress-Revisionsbildschirm, Audit L3); Oracle über Vorfilter/next_cursor von replace-text (Q13); Race beim Anhängen von Vorlagen; try/catch um Divi-Aufrufe; tree_rev-Race; DNS-Pinning; Build-Hygiene.
- Etappe 4: Presets können Lottie-Quelle/Interactions tragen – Preset-Schreiben braucht dieselben Builder-Sperren.
- Bewusst belassen: {{field:}} im Body-Layout warnt nur (Anzeige-Parität mit Divi, Audit I1); Bestätigung ist nicht an den Probelauf gebunden (wie 3a, Audit I3); gemeinsamer confirm-Helfer verworfen (Q17 M4).
Ledger
Spec: docs/superpowers/specs/2026-09-25-wp-lit-mcp-design.md; decisions: docs/superpowers/notes/2026-09-27-etappe-3-entscheidungen-entwurf.md (§5 user answers binding, F1: TB templates disabled by default + explicit activation tool); rulings 1a/1b/2a/2b/3a in docs/superpowers/notes/.
Branch: etappe-3 (base 0edb474; 3a merged to main 4f05388), in place. Pods 3.3.9.2 activated locally by user permission (2026-09-27), no pods defined yet.
Process rules carried over: opus reviewers for security-relevant tasks; controller verifies live with benign probes if reviewer agents stop; smoke guard; commits by explicit path; final review + security audit + one fix wave (breaker after round 2).
Pre-flight rulings
Pre-flight (opus): 1 Critical, 6 Important, 16 Minor, 7 Info; all anchors match once; full tables in preflight.md (B1–B23).
- Ruling Q1 (B1, Critical): 3b fixtures use their own pod
lit_fixture_person(„[Fixture] Personen“) that no smoke deletes; wp-basis-smoke's shared lit_person stays as is — cost if wrong: none - Ruling Q2 (B2): extract GlobalEdits' backup + confirm-merge into a shared helper; live TB layout edits reuse it (backup when revisions off, P9 refusals) — cost if wrong: none
- Ruling Q3 (B3): lit/tb-assign leaves omitted exclude_from unchanged; dry run shows both lists (before/after) — cost if wrong: none
- Ruling Q4 (B4): TemplateBuildTest uses assertPartials — cost if wrong: none
- Ruling Q5 (B5, USER-APPROVED 2026-09-27): Task 1 Step 8 may enable the fixture template briefly, conditions limited to exactly one „[Fixture]“ person, disabled again in the same step (try/finally), verified off afterwards; Step 6 does not change Divi's post-type setting — cost if wrong: momentary visible change on one fixture URL
- Ruling Q6 (B6): tb-smoke creates/deletes its own temporary pod, never touches lit_person or lit_fixture_person — cost if wrong: none
- Ruling Q7 (B7): UsageMatcher::referencesAny() refuses any global reference in every spelling Divi renders; used by lit/library-make-global and lit/library-save scope global (no self-reference, no nested refs in shortcode text) — cost if wrong: none
- Ruling Q8 (B8–B21 minors): adopt as proposed in preflight.md (template live = attached+enabled regardless of post status; layouts must be published before reattach/activate; tb-list hides IDs of templates the user can't edit; test/count/doc fixes; smoke library-id check via jq) — cost if wrong: small
- Ruling Q9 (B22 / plan open point): live-visible TB edits use dry run + confirm (with Q2 backup), not refusal — matches F2 — cost if wrong: none
- Ruling Q10 (B23 / plan open point): author conditions require list_users — only admins hold Divi's theme_builder right by default — cost if wrong: editors with theme_builder can't set author conditions
Progress
- Task 1: dispatched (opus, BASE 0edb474; Q1 own pod, Q5 user-approved brief activation)
- Task 1: done (c615522; fixtures: pod lit_fixture_person 2381, persons 2385/2386, body layout 2391, template 2394 disabled, global header 1061; brief activation only for Erika, off again; live site unchanged). Finding 10: stale TB editor save detaches our template silently + marks unused (7-day trash) → confirms plan mitigation (detached/reattach). Finding 12: Divi dialog writes Pods fields as post_meta_key + select_meta_key "custom_meta_" (empty meta_key); plugin writes custom_meta_; choice shows stored value; relation shows raw ID
- Ruling Q11: Task 11 (Pods field check) and any usage/field detection recognize both forms (custom_meta_ and post_meta_key/select_meta_key); S29/S30 meta-key rules already cover both on write — cost if wrong: missed warnings
- Ruling Q12: Task 1 (fixtures/test data only) verified by controller from report + commit instead of a separate review; fixtures are re-checked by later task reviews — cost if wrong: none
- Task 1: complete (0edb474..c615522)
- Tasks 2–3: dispatched (opus, BASE
c615522, one commit each) - Tasks 2–3: implementer done (
06f1379P10 PostIdParser + type-exact post_link_url;c7ce924P13 revealsIds + replace-text skipped_nodes ID-oracle fix; 1923 tests); concern: replace-text prefilter/next_cursor partial oracle → U (Ruling Q13) - Tasks 2–3: review (opus): both Approved, live-verified (P10 nothing widened; P13 no ID in any path, digit oracle fixed, nur lesen reveals). Important (pre-existing): {{link:}} type oracle ("Unbekannter Name post_link_url_et_pb_layout" vs "gibt es nicht")
- Ruling Q14: WpLinkTargets treats et_pb_layout (and any post type without a Divi post_link_url option, incl. internal _-types) as not found → uniform message; minors: merge stacked docblocks WordPressIntegration.php:52-56, DiviTextSearch withoutLibraryIds doc (hides more for undecodable opaque openers) — fix round 1 now — cost if wrong: none
- Ruling Q13 (confirmed): replace-text prefilter/next_cursor oracle deferred to U, documented as parity with WordPress' own content search
- Tasks 2–3: fix round 1 (Q14; 82ef69a; 1926 tests): WpLinkTargets linkable = Divi's et_builder_get_public_post_types() minus attachment/_-types/et_pb_layout; identical answers library vs missing ID (test); DiviTextSearch strips IDs only in global nodes — verified by controller against report/tests
- Tasks 2, 3: complete (c615522..82ef69a)
- Task 4: dispatched (opus, BASE 82ef69a; wp_die guard, ThemeBuilderAccess, Q8 minors)
- Task 4: implementer done (d04a508; 1958 tests); helpers for T7/T9/T10: ThemeBuilderAccess::canEditTemplate(), layoutsRefusal(); attach race → U
- Task 4: review (opus): Approved; wp_die guard live-verified (CLI/AJAX/JSON, nested, no output/status leak, handlers restored, nothing written); ThemeBuilderAccess = Divi's save gate; P12 no oracle; isLive matches Divi
- Ruling Q15: ThemeBuilderAccess::canUse additionally requires edit_theme_options (Divi's TB admin page gate) — never laxer than Divi's UI — cost if wrong: roles with theme_builder but without edit_theme_options can't use TB tools
- Ruling Q16 (T4 minors): T10 must be able to deactivate an attached+enabled template that sits in the trash (raw lookup for the off-switch, fail-safe); docblock note on third-party wp_die inside hooks; canEditTemplate docblock (IDs from Divi's list); dedupe GlobalUsages::THEME_BUILDER_TYPES/DiviSaver cache clear/compat row → final wave
- Task 4: fix round 1 (Q15 + docblocks) dispatched
- Task 4: fix round 1 (Q15; 4ff1c1e; 1959 tests) — verified by controller
- Task 4: complete (82ef69a..4ff1c1e)
- Task 5: dispatched (opus, BASE 4ff1c1e; Q2 shared backup/confirm helper, Q9, Q8)
- Task 5: implementer done (b1dd35a; 1980 tests; smoke ok); VisibleEdits shared helper (GlobalEdits/ThemeBuilderEdits/DiviWriter); TB layouts get Divi flags (as Divi writes); T11 adds ?DynamicFields after VisibleEdits param
- Task 5: review (opus): Approved; live: 9 write paths on live layout → confirm_required/merged confirm/replace-text refused, confirmed save with revision, backup when revisions off; access P12 no oracle; post-content body-only on all paths; GlobalEdits byte-identical after Q2
- Ruling Q17 (T5 minors): M1 GlobalUsages P8a TB rows reuse ThemeBuilderAccess::canUse (not only theme_builder) → final wave (security, must); M2 confirm description mentions live TB layouts → T16; M3 library-path P9/P14 wording test → final wave; M4 optional VisibleEdits::confirmed → final wave
- Task 5: complete (4ff1c1e..b1dd35a)
- Tasks 6–7: dispatched (opus, BASE
b1dd35a, one commit each; Q10, Q8, group wiring) - Tasks 6–7: implementer done (
ac69fdfTemplateConditions: 34 Divi IDs, 27 round-trip live, assign_term for terms, list_users for authors, uniform missing/unreadable;54922a9tb-list + group live: nur lesen blocks TB writes, disabled → not_found; 1998 tests) - Tasks 6–7: review (opus): both Approved; live: 34 Divi condition IDs covered by construction, parsing safe, uniform missing/unreadable, tb-list leaks nothing, detached shown, group matrix verified, never enabled a template
- Ruling Q18: ThemeBuilderAccess::template() answers not_found (identical to missing) for templates the user can't edit (canEditTemplate), consistent with tb-list (Q8/B11) — implement in T8 (first caller) incl. test; minors → final wave: DETACHED_LIMIT applied after edit filter + truncation note, invalid id message, role-slug regex doc
- Tasks 6, 7: complete (b1dd35a..54922a9)
- Task 8: dispatched (opus, BASE 54922a9; F1, Q4, Q8, Q18, rollback reuse)
- Task 8: implementer done (42b9b38; 2020 tests; live create: disabled+attached, tb-list shows, fixture page byte-identical, cleanup complete); controller deleted 3 leftover change-log rows of the live check (1248–1250)
- Task 8: review (opus): never-enabled verified on all paths incl. forced read-back; rollback per step live; unsaved-editor detection = Divi's hasDraft; rights live. Important: post inserted by Divi before it fails (throw inside store_template/insert_layout after wp_insert_post) not tracked → orphan published et_template (disabled/detached, no visible effect); minors: orphan layout_type term relationships after delete, partial() names deleted IDs, refuse() loses details, default template editability
- Ruling Q19: while guarded() runs, record IDs of posts inserted of the expected types (wp_insert_post action) and hand them over (exception subclass carrying IDs) so TemplateBuild rolls them back; undo() also removes layout_type term relationships; partial() lists only IDs whose delete failed; refuse() keeps all failure details; defaultTemplate() respects canEditTemplate — fix round 1 — cost if wrong: none
- Task 8: fix round 1 (Q19; 2827fd4; 2030 tests; live wp_die-in-hook on template and layout save → write_failed, nothing left) — verified by controller; residual (throw between DB insert and first cache clean) documented, accepted
- Task 8: complete (54922a9..2827fd4)
- Tasks 9–10: dispatched (opus, BASE
2827fd4, one commit each; Q3, Q9, Q8, Q16, Q18) - Tasks 9–10: implementer done (
0975b2ftb-assign;64fc439tb-activate; 2056 tests; live on temp draft only, visible site unchanged, cleanup verified) - Ruling Q20: the off-switch (tb-activate enabled:false) is exempt from the unsaved-editor refusal (E7) — reducing visibility must always be possible; result warns that saving an open Theme Builder editor may restore its own state — cost if wrong: a stale editor may re-enable (warned)
- Tasks 9–10: review (opus): spec ✅ both; live: no path makes a template visible except confirmed activate (reattach disables first), Q3/Q9 dry runs, Q16 trash off-switch no oracle, rights, change log; visible site unchanged at every step; changes requested: overlap lists leak uneditable live templates (Q8/B11), Q20 not yet implemented
- Ruling Q21: one shared ThemeBuilderAccess::editableLiveTemplates() used by tb-list, tb-assign and tb-activate dry runs; uneditable live templates only as a count („weitere aktive Vorlagen ohne Berechtigung“); Q20 implemented (off-switch skips draft refusal + warning); minors: dedupe overlaps vs possible_overlaps, reattach records previous enabled state in previous/log, stricter exclude_from null handling, live from $after->isLive() — fix round 1
- Tasks 9–10: first fix-round implementer stopped by user mid-way; user said 'weiter' → fresh opus implementer continues from uncommitted partial work (Q20, Q21, minors)
- Tasks 9–10: fix round 1 (Q20, Q21, minors; a31d434; 2068 tests); tb-list hidden_live count; re-review running
- Tasks 9–10: re-review clean (Q21 no leak, counts only; Q20 live with a real Divi draft; minors fixed)
- Ruling Q22 (must, final wave): ThemeBuilderEdits::showing()/listing() use editableLiveTemplates() (count for hidden) — same leak class as Q21, in layout-edit confirm details — cost if wrong: none
- Tasks 9, 10: complete (2827fd4..a31d434)
- Task 11: dispatched (opus, BASE a31d434; Q11 both forms, Q21 pattern)
- Task 11: implementer done (6be2e65; 2084 tests; live read-only on 2391); DynamicFields::metaKeyOf reads both forms (Q11), choice/relation notes, editable templates only, dry run shows field warnings
- Ruling Q23: the plan's review checkpoint after Task 11 is covered by per-task reviews + the final whole-branch review (every task so far reviewed with live checks) — no separate checkpoint review — cost if wrong: late discovery of cross-task drift (final review)
- Task 11: review (sonnet): Approved; metaKeyOf matches Divi's DynamicContentOptionPostMetaKey branch for branch, live fixture 2391 unchanged, no leak, named-arg wiring correct
- Task 11: complete (a31d434..6be2e65)
- Tasks 12–13: dispatched (opus, BASE
6be2e65, one commit each; bounded, no leaks, cheap write-time warning) - Tasks 12–13: implementer done (
f82964ffind-similar: caps 200 posts/200 matches/5000 nodes, batch loads;4f70886write-time warning: ≤200 library sections cached per request; 2102 tests; smoke ok) - Tasks 12–13: review (sonnet): both Approved; live find-similar as admin + temp author: no oracle (P12 forbidden for foreign reference), pagination exact, excluded types uniform; minors → final wave: excluded-type message names "" type; multi-type own-posts restriction documented
- Tasks 12, 13: complete (6be2e65..4f70886)
- Tasks 14–15: dispatched (opus, BASE
4f70886, one commit each; Q7 referencesAny, reuse library-save/GlobalInstance/VisibleEdits, per-page results) - Tasks 14–15: implementer done (
44c1804make-global + NewLibraryEntry shared + referencesAny;e733065replace identical occurrences: pre-check all targets, per-page commit+revision, stop at first failure with changed/failed/unchanged lists; 2160 tests; smokes ok) - Ruling Q24: make-global refuses Theme Builder layouts also as SOURCE (stricter than brief; keeps TB edits on the TB tools) — cost if wrong: a TB section can't be made global directly (copy to a page first)
- Tasks 14–15: review (opus, SEC): T14 Approved (referencesAny 27 spellings live, rights, rollback); T15 Important: Fingerprint::body ignores text parts / core-block inner HTML → "identical" target with different visible content replaced (live, silent content swap); minors: revision note on page-N failure, "pages and posts only" wording vs Divi-enabled types; Low: Divi re-runs do_blocks in shortcode module/Woo checkout text → escaped global-layout inside JSON value expands (document with P25)
- Ruling Q25: Fingerprint content hash includes non-whitespace text parts and full bytes of opaque blocks (structure hash unchanged); plans() additionally compares normalized serialized bytes of the node; regression tests for both variants; find-similar "identical" uses the same fix; failed-page message keeps "Revision X bleibt erhalten"; scope check matches wording (only page/post types, or wording = Divi-enabled types) — decide pages+posts only; Low item documented with P25 — fix round 1 — cost if wrong: none
- Tasks 14–15: fix round 1 (Q25; 4e95983; 2170 tests; smokes ok; live: altered copies no longer identical, forced confirm refused, pages unchanged) — verified by controller against the reviewer's repro scenario
- Tasks 14, 15: complete (4f70886..4e95983)
- Tasks 16 (docs, opus) and 17 (tb-smoke + acceptance, opus) dispatched in parallel, BASE
4e95983(disjoint files) - Task 16: implementer done (3db96a3; 2173 tests; v0.5.0); docs honest about open Q22/Q17-M1 (final wave)
- Task 16: review approved (spec ✅, quality approved; 2173 tests re-run green; only cosmetic line-wrap nit) Task 16: complete
- Task 17: implementer done (0b7a1c2; 2173 tests; all 7 smokes green); deviations: enabled template on temp draft page via preview, temp pod never enabled, read-only → not found. Review dispatched (sonnet)
- Task 17: review approved (spec ✅, quality approved; smoke re-run live green, no leftovers, TB baseline 1065=0 1066=0 2394=0) Ruling: Claude-Session trailer after Co-Authored-By is correct — the session's current attribution instruction requires both lines; common.md predates it — cost if wrong: cosmetic Task 17: complete
- Final whole-branch review + security audit dispatched in parallel (opus), range 0edb474..0b7a1c2
- Final review done: 0 critical, 3 important (I-1 Q22, I-2 Q17M1 + TB-ID leak to library-only editors, I-3 Q17M3), 12 minor; report final-review.md. Ruling: drop Q17 M4 (shared confirm helper) — 13 call sites project-wide, out of scope — cost: minor duplication stays. Waiting for security audit before fix wave.
- Security audit done: 0 C/H/M, 4 Low (L1=Q17M1, L2=Q22, L3 TB/library revisions not restorable via MCP, L4 reattach accepts TB-library templates), 3 Info; report security-audit.md Ruling: fix wave covers I-1..I-3, L1..L4, all final-review minors except Q17 M4 — both reports agree, all small — cost if wrong: extra churn Ruling: L3 gets the minimal fix (restore_url to WP revision screen + corrected docs), not MCP restore for TB/library types — the full route needs new access wiring, belongs to Etappe U — cost: restore stays a manual step in wp-admin Ruling: I1 stays a warning (display parity, preflight ruling), I2 documented in guide, I3 unchanged like 3a — cost: none beyond 3a status quo
- Final fix wave dispatched (opus), BASE
0b7a1c2 - Fix wave done:
7590da443dc3fb79f2fdbdcae2d4e64d32b5290a9de7bbe1e14bc9546288acf50cfa42beb8706; 2199 tests, lint clean, tb/library/write smokes green, TB baseline off Ruling: accept stricter I-2 (read-only TB group also blocks global edits reaching TB layouts) — matches the guide and group semantics — cost: a global edit may be blocked where the owner expected it to pass; one-line revert noted in fixwave-report Ruling: accept M-9 forbidden for accounts without any TB rights (tool-level, ID-independent, same as tb-list) — no existence oracle since answer does not depend on the ID; Q18 not_found stays for per-template refusals — cost: answer-code change for clients - Scoped re-review dispatched (opus), range 0b7a1c2..beb8706
- Re-review: Approved (all in-scope findings addressed; N-1 minor docblock, N-2/N-3 info). Ruling: fix N-1, partial-write restore_url (completes L3) and make-global backup hint now via resumed fix-wave agent; N-2 as guide sentence; N-3 left — all tiny, closes L3 cleanly — cost: one more small commit set