ledger
1 file changed,
+12
−0
.superpowers/sdd/2026-08-23-idear/progress.md+12 −0
diff --git a/.superpowers/sdd/2026-08-23-idear/progress.md b/.superpowers/sdd/2026-08-23-idear/progress.md| index c7f9816..5c6f62b 100644 |
| --- a/.superpowers/sdd/2026-08-23-idear/progress.md |
| +++ b/.superpowers/sdd/2026-08-23-idear/progress.md |
| @@ -115,3 +115,15 @@ Task 6: fix round 1/5 (2 Important + API addition + 6 Minor addressed; commits f |
| Task 6: minor (deferred): SKILL.md §5's "Four more exported helpers" list partly duplicates fuller explanations already in the file (From at :149, Grantable at :200). Only TokenFrom needed introducing. Closest thing to padding in a 19,564-byte doc. Trim someday. |
| Task 6: complete (commits a909990..09732b8, review clean) |
| ALL SIX TASKS COMPLETE. Fable review running against the finished module. |
| + |
| +## FABLE REVIEW — 8 findings, 5 significant. Verdict: publish with changes. |
| + |
| +Fable Ruling F1 (significant, MUST FIX): reconciliation trusts the token alone under password. addressOf() returns "" for a decimal subject, so the email-match block is SKIPPED and Accept binds the token's role to whatever session presents it. Admission binds the token to the email the account is created with; reconciliation binds it to nothing. The doc claims parity that does not exist. Fix: add an OPTIONAL Config.EmailForSubject resolver — when set, reconciliation requires the match; when nil, the doc must say plainly that token possession alone is trusted. Exploit population is small (orphans only) but real: a revoke-during-signup orphan can redeem a HIGHER-role token meant for someone else. Cost if wrong: one optional config field. |
| +Fable Ruling F2 (significant): acceptByAddress redeems the OLDEST pending invitation by id, ignoring role, and Invite never dedups — so re-inviting at a corrected higher role is silently ignored, or a stale higher invite escalates past the admin's intent. The two identity paths also disagree (password spends whichever token is clicked). Fix: Invite revokes any existing pending invitation for the same address in the SAME transaction. Re-inviting supersedes; that is the only semantics an admin would predict. Cost if wrong: an admin who wanted two live invitations for one address cannot have them, which nobody wants. |
| +Fable Ruling F3 (significant): the library hard-imports rastrillo/password, contradicting the plugin-agnostic story; and the spec's dependency list is wrong in BOTH directions — it claims rastrillo/form (grep proves unused) and omits password. Ruling: FIX THE CLAIM, DEFER THE SPLIT. The coupling is real but not a defect; the documentation is the defect. A pwadmit subpackage would add friction to the common path for a keymail-only app's binary size. Recorded as a v2 consideration. Cost if wrong: a keymail-only app links a package it never calls. |
| +Fable Ruling F4 (significant, MUST FIX a+b): keymail identity hazards — a recycled address takes over the old row's role (Owner included) if offboarding forgot to deactivate; an address change orphans the user and, for the Owner, is unrecoverable through the API; a plugin switch orphans the ENTIRE roster at once. Deferring automated rebind is defensible for v1 ONLY with the escape hatch documented, and §8 currently covers transferring ownership, not rebinding a Subject. Fix: (a) break-glass SQL to rebind subject, verified against a real database, (b) a SKILL warning that offboarding MUST deactivate and that an address change means loss of access. Without both, a keymail team that changes an Owner email bricks its instance with no documented recovery — that crosses from deferred into unusable. |
| +Fable Ruling F5: limiter doc under-weights IPv6 reality — the /64 fold stops ONE host, but a routed /48 or /56 has 16k-65k /64s, enough to fill the 4096-entry table. Document the /48 reality so an operator sizes Max accordingly. |
| +Fable Ruling F6: SKILL.md is 19,564 bytes against the 17 KB bar it cites, and repeats itself — CarryToken-is-mandatory appears three times, the NotFound rule four. Collapse §5/§6/§7 into one traps list that points at code instead of re-deriving it. Target ~14 KB. |
| +Fable Ruling F7: no audit trail for role changes, deactivations or transfers. Out of scope to build; state the absence in the spec's "what this does not claim" so an operator layers it rather than assuming it. |
| +Fable Ruling F8: three minors — addressOf's "@" heuristic needs a SKILL note that a custom Config.Subject must preserve the password=no-@ / keymail=@ shape; field() never calls MultipartForm.RemoveAll; a password orphan claiming an empty roster gets a blank Email. |
| +Remediation split by capability: opus takes F1/F2/F4 (security-critical API + transactional change + break-glass SQL that must actually work); sonnet takes F3-docs/F5/F6/F7/F8 afterwards, sequenced to avoid both editing SKILL.md at once. |