Skip to content

fix: destroy 404 wedge, adopt group-role DSL collision, dynamic normalizer + demote convergence - #37

Merged
2000game merged 3 commits into
mainfrom
fix/destroy-adopt-dynamic-32-31-34
Jul 9, 2026
Merged

2000game merged 3 commits into
mainfrom
fix/destroy-adopt-dynamic-32-31-34

Conversation

@2000game

@2000game 2000game commented Jul 9, 2026

Copy link
Copy Markdown
Member

Fixes three verified bugs from the 2026-07-08 codebase review.

#32 — destroy wedges permanently on already-deleted targets

The delete loop treated a 404 like any error: printed a message falsely claiming the target was removed from state, returned before the state deletion, and skipped all remaining targets. Now a 404 is success-with-note (entry dropped from state, saved, loop continues); non-404 errors still stop with an accurate "state saved up to this point" message. Loop extracted to a testable runDeleteLoop.

#31 — ct adopt group-role emits undeclarable config

configSnippet emitted groupRole(...), which the permissions feature took over. The master-data resource is now declarable as roleDefinition(...); the registry gained a dslName field (default camelCase) so snippets always emit the real DSL function name. New round-trip property test: every registry type's adopt snippet must load cleanly through loadConfig.

#34 — dynamic normalizer corrupts leading-zero strings; status: "none" never converges

  • Coercion in the query subtree is now restricted to canonical integer strings within Number.isSafeInteger — '01067' and >2^53 digit strings pass through untouched; "5" vs 5 still diffs equal.
  • Demote convergence: when desired status === "none", the desired side folds to the same {status:"none", ruleset:{}} sentinel as the actual side — a demoted group with its authored ruleset kept (per the documented contract) is now a no-op on re-plan. The demote ruleset DELETE tolerates 404.

Verification: 231 passed / 4 skipped, typecheck + lint clean.

Closes #32. Closes #31. Closes #34.

2000game added 3 commits July 9, 2026 07:27
#32)

The delete loop's catch had no 404 special-case: an already-deleted target
hit the catch, returned before the state deletion (falsely claiming it was
removed), and skipped all remaining targets — wedging every re-run.

Treat CtApiError 404 as success-with-note: drop the state entry, save, and
continue to the next target (mirrors the backup loop). Non-404 errors still
stop with state saved up to that point. Extracted the loop into an exported
runDeleteLoop for direct testing.
configSnippet camelCased group-role to groupRole(...), but the DSL's groupRole
is now the permission function definePermission("group_role") — pasting an
adopted role broke every subsequent plan. ConfigContext had no resource entry
for the group-role type at all.

Add a dslName field to the registry (default: camelCase of the type); the
group-role entry emits roleDefinition(...). ConfigContext now declares
roleDefinition -> define("group-role"), leaving the permission groupRole /
groupTypeRole functions untouched. Adds a round-trip property test over all
RESOURCES: every adoptable type's snippet loads cleanly through loadConfig.
…one demote (#34)

a) coerceScalars parseInt-ed any /^-?\d+$/ string in the query subtree, so a
zip like '01067' became 1067 and >2^53 digit strings lost precision — written
back on any dynamic change, silently breaking JSONLogic string compares.
Restrict coercion to canonical integer strings (/^(-?[1-9]\d*|0)$/) within
Number.MAX_SAFE_INTEGER; a canonical 5 vs "5" pair still diffs equal.

b) status:'none' demote never converged: actual side is the sentinel
{status:'none',ruleset:{}} but desired folded the user's full ruleset — a
perpetual update. Fold the desired side to the same sentinel when status is
'none' (docs still say keep the dynamic block). Also tolerate a 404 on the
demote ruleset DELETE (never-dynamic / already-demoted group).
@2000game
2000game merged commit ee95aee into main Jul 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant