diff --git a/docs/superpowers/notes/2026-09-26-etappe-1a-rulings.md b/docs/superpowers/notes/2026-09-26-etappe-1a-rulings.md new file mode 100644 index 0000000..246713d --- /dev/null +++ b/docs/superpowers/notes/2026-09-26-etappe-1a-rulings.md @@ -0,0 +1,35 @@ +# Etappe 1a – Entscheidungen (Rulings) und offene Kleinigkeiten + +Aus dem SDD-Ledger übernommen, 2026-09-26. + + - Ruling P1 (B1): T7 PostGuard::postType() return type `: object` (WP_Post_Type) instead of `object|WP_Error` (compile error); errors via exceptions/other method as plan logic allows — cost if wrong: none. + - Ruling P2 (B2): T1 adds `` to phpcs.xml.dist (WordPress.Security rule) — our exceptions are internal and become JSON WP_Error messages, never HTML output — cost if wrong: an exception message echoed into HTML unescaped (we never do this). + - Ruling P3 (B3): T7 FieldValidator::checkScalar and T15 SettingsWhitelist::validate — parenthesize nested ternaries / extract helper so PHPCompatibility passes — cost: none. + - Ruling P4 (R1): T14 MenuWriter must accept the exact tree shape lit/menu-get returns: for object-linked items (page/post/category/term) a `url` field is ignored (not rejected); add a unit test for the round trip menu-get → menu-set-items — cost: none. + - Ruling P5 (R2): spec principle 7 (never clobber parallel edits) binds: lit/content-update REQUIRES `rev` (from lit/content-get) for all content; mismatch → lit_rev_mismatch with hint to re-read. T6 instructions/T9 accordingly — cost if wrong: one extra read per update. + - Ruling P6 (R3): all-or-nothing (spec §8.2): T9 validates everything before writing; if writeExtras() fails after insert (create) → delete the just-created draft (force, it's our own new draft); after update → restore the pre-update revision via RevisionPoint; return the error — cost: none. + - Ruling P7 (R4): T11 check wp_trash_post($copyId) result; if it fails, return ok with a warning "Kopie konnte nicht in den Papierkorb gelegt werden" — cost: none. + - Ruling P8 (C4): T16 moves shared smoke helpers (fail/pass/mcp/cleanup/token) into tests/Integration/lib.sh sourced by both mcp-smoke.sh and wp-basis-smoke.sh — cost: none. + - Task 1: minor (deferred): Errors::make silent fallback on json encode failure w/o log; $details may overwrite reserved keys + - Task 2: minor (deferred): WP_Application_Passwords client-name branch untested; clientName not length-capped (sanitize when logging!); duplicated try/catch block in AbilityRegistrar + - Task 3: minor (deferred): insert format array not asserted in test; object_id/ability not truncated + - Task 4: minor (deferred): sampling ORDER BY ID vs index; array_merge numeric meta keys + - Task 5: minor (deferred): podFor call outside try/catch in write(); get_fields() null tolerance + - Task 6: Ruling P9: lit/content-types must only list/describe post types the user can edit (post type cap->edit_posts) — carried into Task 8 dispatch (small change in ContentTypesAbility) — cost if wrong: none (schema only) + - Task 6: minor (deferred): instructions "Inhaltstypen" line uncapped/untested length + - Task 7: minor (deferred): multi-value scalar fields validated as scalar; TermAssigner::assign trusts resolve(); PostGuard cap fallback to raw cap string + - Ruling P10: content rev must detect parallel edits of title/excerpt/content/status too, not only post_content: add Rev::ofPost(WP_Post) = Rev::of(post_content|post_title|post_excerpt|post_status|post_modified_gmt) used by PostPresenter (content-get) and content-update check/return (T9); Rev::of(string) stays for Divi (Etappe 2) — cost if wrong: occasional extra rev_mismatch → re-read + - Task 9: Ruling P11: rev does not cover field/term-only changes by others — accepted limitation (fields rarely edited in parallel; WP does not bump post_modified on meta change) — cost if wrong: a parallel field edit could be overwritten (revision/log still allow undo) + - Task 9: minor (deferred): assign() midway cleanup failure not reported; WP_Error after rowTouched relies on template pre-validation; Pods snapshot no "absent"; rollback via kses may alter admin markup + - Task 10: minor (deferred): permission()/execute() double load (repo-wide pattern) + - Task 11: Ruling P12: replace-with-copy loses original meta/terms (revision covers only title/content/excerpt) → before the swap, clone the original into a trashed backup post (PostCloner), return backup_id; if the backup can't be created → abort with lit_write_failed; with EMPTY_TRASH_DAYS=0 we already refuse — cost if wrong: one extra trashed post per swap + - Task 11: FINAL-FIX (important-ish, carry to final wave): PostCloner::ownMeta must check $wpdb->last_error and throw — a failed SELECT currently looks like "no meta" and replace would wipe original meta; also add create_posts guard before backup; include backup_id in WriteFailed details + - Task 11: minor (deferred): raw inserts skip meta hooks (intended); meta key with backslash edge case + - Task 14: Ruling P13: custom menu links may be /path, #anchor, http(s), mailto, tel (brief) — my 'http(s) only' was shorthand; javascript:/data: etc. must be rejected — cost if wrong: none + - Task 14: Ruling P14: private targets always refused; draft/pending/future only with allow_unpublished + correct warning; non-public taxonomy terms refused; status gate only for newly linked items (existing ones round-trip) — cost if wrong: Claude must set a flag for draft-based site setup + - Task 14: minor (deferred): description should state status gate applies only to new links; raw status string in message; no depth limit + - Task 16: minor (deferred): content-types guide "Jede Änderung liefert revision_id" also near duplicate (null there) + - Final review 1a: ready with fixes. Ruling P15: fix wave = I1 (PostCloner last_error, create_posts guard, backup_id in WriteFailed), I2 (settings isStored via sanitize_option), I3 (guard against future→publish on writes with missed schedule), I4 (edit_post_meta per key), I5 (guides/hints: codes without lit_ prefix, indirect tools via execute-ability as lit/, revision_id claim), M1 (settings FollowUp/WriteFailed/partial log; menu partial log), M2 (checkRelation read_post), M3 (PostGuard refuses trashed by default), M4 (status tools WriteFailed on throwing hook), M6 (permalink regex), M7 (ChangeLog truncate object_id/ability), Task14 description, consolidation: PostChangeResult helper, Rev::matches, ChangeLog::recordPartial. + - Ruling P16: rev naming — `rev` always means the post-level Rev::ofPost for post tools; Etappe 2 Divi tree lock will use a distinct field name (e.g. `tree_rev`) — spec §5.3 to be amended in Etappe 2 plan — cost if wrong: rename later + - Ruling P17: deferred to 1b: menu rev (M5), lit_mcp_guides filter, M8 warnings (front page trash, menu unassign confirm, private schedule note, divi hint) + - Final: parked (Ruling P18): missedSchedule 60s window & zero GMT date fallback; write_failed wording mentions backup — deferred to 1b minor fixes — cost if wrong: seconds-early publish on edits within last minute before schedule