test(dynamic): live-gated user-authored ruleset round-trip pin - #59
Merged
Merged
Conversation
The existing live round-trip test writes back CT's own GET output, so it is drift-free by construction and cannot catch CT silently rewriting a RuleSet-level field (description/shorty/importance/personIdFieldName/ process) on PUT — which would make an affected dynamic group update on every apply forever, since a no-op plan requires deepEqual(desired.dynamic, actual.dynamic). Add a doubly-gated (CT_LIVE=1 + CT_LIVE_WRITE=1 + CT_LIVE_WRITE_HOST) test that PUTs a ruleset authored fresh in the test (not copied from a GET), reads it back, and asserts normalized field-by-field equality with a failure message naming exactly which fields CT rewrote. It also builds a plan from the same desired ruleset and asserts the `dynamic` diff is empty post-PUT, and restores the group's prior ruleset in a finally. Skipped by default; not run against any live instance here. Document how to run it and what to do on failure in tests/fixtures/dynamic/README.md and docs/dynamic-groups.md.
…op assertion The plan-no-op assertion in the #36 ruleset round-trip pin built its synthetic desired object with a hardcoded `dynamic: { status: "manual", ruleset: authored }`, but the test never sets or reads the fixture group's real status — only the ruleset is PUT. Since diffFields compares the whole `dynamic` field as one unit, this false-failed whenever the live group's status wasn't exactly "manual" (the committed fixture shows realistic status is "active"). Fetch the group's actual live status via GET /dynamicgroups/{id}/status and use that in the desired object instead, so the no-op assertion isolates exactly the #36 property (ruleset field rewriting) from status drift. Test-only change; still fully skipped by default (CT_LIVE/CT_LIVE_WRITE gated).
This was referenced Jul 9, 2026
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.
Summary
Part of #36.
normalizeRuleset(src/engine/dynamic.ts) only strips the two read-onlytimestamp keys and normalizes the
querysubtree. A no-op plan requiresdeepEqual(desired.dynamic, actual.dynamic), which silently assumes everyother RuleSet-level field (
description,shorty,importance,personIdFieldName,process) round-trips byte-for-byte through ChurchTools.The existing live round-trip test (
tests/dynamic.integration.test.ts) writesback CT's OWN GET output, so it is drift-free by construction and cannot
observe CT rewriting one of those fields on PUT — which, if it happens, makes
an affected dynamic group update on every apply forever.
This PR adds a new, doubly-gated live test that:
description,shorty,importance, …) to a designated dev dynamic group — never avalue copied from a prior GET, so a server-side rewrite is actually
observable.
failure, the message names exactly which RuleSet-level field(s) CT
rewrote, so it tells you directly what to add to
normalizeRuleset.dynamicfield diffs to no-op — the actual property bug: dynamic ruleset round-trip assumption unverified — CT-recomputed fields would cause perpetual diffs #36 protects, notjust raw-object equality.
finally, so the dev instance isn't left mutated.Docs:
tests/fixtures/dynamic/README.mdanddocs/dynamic-groups.mdnowdocument how to run the gated round-trip and what pinned assumption it
protects.
Not run against any live instance in this PR — no credentials were
available or sought in this session.
normalizeRulesetis unchanged; itwill only be extended once this test has actually run against eqrm-dev and
reported which field(s), if any, CT rewrites.
How to run it (on eqrm-dev only)
CT_LIVE_WRITE_HOSTmust exactly match the authenticated host (non-productionconfirmation guard, same pattern as
tests/permission.integration.test.ts).CT_DYNAMIC_FIXTURE_GIDmust point at a disposable dynamic group on adev instance — its ruleset is overwritten and then restored, but a killed
run would leave it holding the test-authored ruleset.
If it fails, extend
normalizeRulesetto drop/canonicalize the namedfield(s) — that is the completion criterion for #36, not this PR (which only
authors the pin; #36 closes after a live run confirms or refutes the
assumption).
Test plan
npm test— full suite green, new test shows as skipped (3 skipped intests/dynamic.integration.test.ts, 5 skipped total across the repo)npm run typecheckcleannpm run lintcleanin this PR)