Repository navigation
feat: Phase 4 — Apply (idempotent CRUD) + destroy + guardrails - #15
Merged
Merged
Conversation
…eA/B, nameTranslated) Live-verified against eqrm.church.tools: relationship-type uses degreeNameA/ degreeNameB (my provisional degreeForward/degreeReverse did not exist and would have PUT phantom keys). Added nameTranslated to age-group/target-group/group-role to match the real payloads. Locked with tests built from the live responses.
Live write-test against eqrm-dev surfaced this: POST /campuses requires `shorty` (1–10 chars); `shortName` is a vestigial, usually-null sibling. The generic executor sends managedFields as the create body, so campus create failed with a 400 until the model used `shorty`. Switched campus managedFields + deriveKey to `shorty` and updated the coupled tests + docs.
…efully apply: a resource that vanished from ChurchTools but remains in state was replanned as a create; executePlan POSTed a fresh copy, then upsert threw on the stale same-key entry (old id) and never recorded the new one — so state was never updated and every re-run leaked another duplicate. A create now drops the stale entry before upsert, so the new id takes over the key. destroy: the DELETE loop was unguarded — a mid-list failure threw a raw error after earlier targets were already deleted and persisted, with no resume guidance. Wrap each DELETE and stop cleanly, mirroring apply's crash-safe report (state is saved per target, so re-running with the remaining targets resumes).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements Phase 4 — Apply (idempotent CRUD in dependency order) + guardrails (closes #6). Makes the Phase 3 plan real, safely.
Command surface
ct apply— builds the same plan asct plan, shows it, backs up, confirms, then executes creates + updates only in dependency order, saving state after each action (crash-safe / resumable). Flags:-c/--config,-s/--state,--backup-dir,-y/--auto-approve.ct destroy --target <key…>— explicit targets only (repeatable / comma-separated); no target ⇒ hard error. Reverse-dependency order, backup, typed confirmation, then delete + prune state.--forceskips the typed prompt (preventDestroystill enforced).Guardrails (issue #6)
confirm(apply) /confirmTyped(destroy)backups/ct-backup-<ts>.jsonassertNotPeopledenylist on every writefetchWithRetry(429 retried; 5xx/network never blindly re-sent for writes)saveStateafter every create/update/edge/deleteapplynever deletes — dropped resources surface act destroynoticepreventDestroyconfig lifecycle flag + explicit--target+ typed confirmType scope
campus,group,group-type+ write specs forage-group,target-group,relationship-type,group-role, plus group hierarchy edges (reconciled viaPUT/DELETE /groups/{id}/parents/{parentId}). Groupparentsare a set-field diffed live, never stored in state (no false drift).New modules
engine/build.ts(sharedbuildPlan),engine/execute.ts(field-agnosticexecutePlan),engine/guard.ts(assertNotPeople),engine/backup.ts,ui/prompt.ts;commands/apply.ts,commands/destroy.ts; registry gainscollectionPath+updateMethod.✅ Live-verified end-to-end (eqrm-dev, CT 3.134.1)
The full write path was exercised against a real instance, not just unit tests:
--force; resource stays intactsortKey), soapplyupdates don't clobber untouched dataTwo field-mapping bugs that only surface against a live API were found and fixed here (couldn't be caught read-only):
shorty(1–10 chars, required on create), notshortName(a vestigial null sibling) —POST /campuses400'd until fixed.degreeNameA/degreeNameB, not the provisionally-guesseddegreeForward/degreeReverse.Both field sets are now locked with tests built from the live payloads.
Testing
133 tests pass, incl. an adopt→modify→apply→re-plan-no-drift integration test, executor unit tests (create/update/hierarchy/skip-delete/stop-on-error), guard denylist, backup, prompts, and destroy protection/ordering.
tsc/eslint/buildclean.Design doc:
docs/superpowers/specs/2026-07-07-phase-4-apply-destroy-design.md.https://claude.ai/code/session_017tFJu7SrS5uLdit5FtXiwS