Repository navigation
WP09: ChangeRule workflow, Rulebook.Edit change-set engine and the ChangeRule action - #76
Merged
Merged
Conversation
The Change Rule workflow (#11) and later the dashboard write path (WP15) need one function that applies a change set to overrides.json, all or nothing, and lands it. Rulebook.Edit reads and writes overrides.json in the template layout (byte-identical round trip), sets entries in place by order-insensitive selector equality and removes them with the existing entries named on a miss, validates a change set against the settings and the catalog, and plans it on a candidate copy so the repository itself is never written. The plan has one row per matching endpoint with provenance and the no-op rule of D48 (no endpoint and not the file changes); the rendering covers the table, the pull request body and the job summary with the effective diff; Publish-RulebookChange pushes change-rule/<id>/<timestamp> or a direct commit with the fallback of D47. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ChangeRule.ps1 builds a one-item change set from the form, plans it, and only then checks the token, so an invalid id or a no-op answers without the secret; a real change exchanges and masks the token and lands per commitOptions.createPullRequest (D47). The template ChangeRule.yaml has the five inputs, no directCommit input, a read-only workflow token and choice lists laid out so the update's rewrite (D30) reproduces it with the shipped settings. The fixture workflows rename the input id to ruleId. The CI job changerule-action proves the token failure, the no-op and the invalid id on a copy of valid-minimal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…write Rulebook.Edit.Tests covers the overrides I/O, set, replace and remove, every change set check, the acceptance criteria of #11 on valid-minimal and the publish against a bare repository. ChangeRule.Action.Tests covers action.yaml, the template workflow with its settings step run in-process, and the entry script including a bare remote. The Update suite checks the rewrite of the shipped ChangeRule.yaml; the Template suite counts 12 hand-written files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
docs/reference/change-mechanics.md is the contract of the change set, the replace and no-op rules, the plan, the table and the landing, with a placeholder for the live run. ADR D47 (ChangeRule follows commitOptions.createPullRequest, no directCommit input) and D48 (no-op changes are reported and never written). ARCHITECTURE 7.5 links the contract and gains section 5.8, the naming, template content, effective diff and update references, README, template README and CONTRIBUTING name the action, the module, the 44 template files and the CI job. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ries, guards Arthur decided on 2026-10-08 that an empty justification keeps the entry's text; only a non-empty one replaces it. A new entry that changes no endpoint (it repeats what the base or a broader entry gives) is now a no-op and is not written, as #11 asks. Set keeps one entry per selector set when a hand edit left duplicates. The justification and the note are written on one line in the body and the summary, and a remove-only change has no justification paragraph. An empty levels or stages input reaches the validation finding instead of a binding error, an unreadable candidate is a validation failure, nothing to commit after the plan is reported as a no-op, and a missing checkout head warns that the base-move guard is off. The action no longer gets GITHUB_TOKEN. The CI no-op step passes no justification again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tries The job summary of a change with an empty effective diff (a justification-only edit, a direct commit that changes no endpoint) lost its tables because an if expression unrolled the empty diff to null; it is wrapped now. Collapsing duplicate entries of the effective entry is its own outcome, deduplicated, with the note "duplicate entries removed" instead of "justification updated". A dead new entry is dropped before overrides.json is written also when another item of the set changes something, and the others are recomputed. The no-op notice is worded from the rows, so a more specific entry that keeps another action is named; a justification given for a dead entry says it was not stored. Selectors accept an exactly empty value (validation finding) and keep a repeated slug once; a no-op outputs no changed endpoints. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…state rows Collapsing duplicate entries kept the first position, which could flip the precedence on a specificity tie (the later entry wins); the survivor now takes the position of the last duplicate, the effective one. The rows of a dead new entry are computed against the final state, after the entry is dropped, so they show what really decides each endpoint, and its sentence is worded from the rows when a more specific entry keeps another action. The overrides reader accepts comments and trailing commas as ConvertFrom-Json does. ADR 0048 names the deduplicated outcome and the two-pass drop, and the action suite asserts the summary of an empty effective diff. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The E2E on Arthurvdv/rulebook-e2e-changerule ran steps 1 to 11 of the WP09 plan against the branch code: the invalid id, the token guard, AC1 to AC6, the justification-only edit, the remove miss, the direct commit and its fallback under a ruleset, and the choice-list rewrite after adding a level. The table names the runs, pull requests and observed outcomes, including the refused push showing as a log warning rather than an annotation and the new level landing in settings order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Oct 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
modules/Rulebook.Edit(the change-set engine of D32):overrides.jsonI/O in the template layout (byte-identical round trip; comments and trailing commas read, a date-like justification stays text),Set-RulebookOverride/Remove-RulebookOverride(one entry per selector set, replaced in place by order-insensitive selector equality; duplicates collapse into the effective entry at its position; a remove miss lists the id's entries),Test-RulebookChangeSet,Invoke-RulebookChangeSet(all or nothing on a candidate copy, never the repository; one row per matching endpoint with provenance; the no-op rule of D48, dead new entries dropped also in multi-item sets), the title, table, pull request body and job summary (the git-based effective diff in the summary only), andPublish-RulebookChange(change-rule/<ruleId>/<yyMMddHHmmss>, base-moved guard, direct commit with the pull-request fallback).actions/ChangeRule: a one-item client of the engine. The token guard runs after the local plan, so an invalid id or a no-op needs no secret; the token is exchanged and masked before any request. Outputsresult(pull-request, direct-commit, no-op),noop,changedEndpoints,pullRequestUrl,branch,failure.template/.github/workflows/ChangeRule.yaml: inputsruleId,action(incl. Remove),levels,stages,justification; nodirectCommitinput, landing followscommitOptions.createPullRequest(D47);contents: read; choice lists rewritten by the update (D30), byte-identical with the shipped settings. Template now 44 files, 12 hand-written. The fixture workflows renameidtoruleId.changerule-actionon a copy ofvalid-minimal: token failure, no-op, invalid id, and a guard on the three outcomes.Rulebook.Edit.Tests.ps1(61),ChangeRule.Action.Tests.ps1(44), the shipped-workflow rewrite case in the Update suite, the template file list.docs/reference/change-mechanics.md(the live-run section follows the E2E), ADRs D47 and D48, ARCHITECTURE 7.5/7.6 and the new 5.8, naming, template content, effective diff, update mechanics, README, template README, CONTRIBUTING (incl. the ruleset JSON withchangerule-action).Decisions
commitOptions.createPullRequeston every run and has nodirectCommitinput; a refused direct push falls back to the pull request.update.check) as Let update.check in the settings turn the Validate update check off (#67) #74.Verification: Pester 1601 tests (1600 passed, 1 skipped: the Actions-only smoke test), PSScriptAnalyzer clean, Test-Rulebook V1 to V14 clean, Build-Template -WhatIf current.
Review: three Sonnet rounds at effort high. Round 1: two must-fix (dead new entries were written; the empty-justification rule never ran because a
[string]parameter turns$nullinto'') plus the empty-selector binding error, single-line justification in body and summary, theno-changesresult mapped to no-op, the base-guard warning, an unreadable candidate as a validation failure, one entry per selector set, and noGITHUB_TOKENfor the action. Round 2: one must-fix (the summary lost its tables for an empty effective diff, anifexpression unrolled@()to$null) plusdeduplicatedas its own outcome, the row-based no-op notice, dead entries dropped in multi-item sets, emptychangedEndpointsfor a no-op, repeated slugs kept once, and the "given but not stored" justification line. Round 3: one must-fix (collapsing duplicates kept the first position and could flip precedence on a specificity tie; the survivor now takes the effective, last position) plus rows of dead entries computed against the final state, a row-aware no-op sentence, the summary assertion at action level, ADR 0048 wording, and comments and trailing commas inoverrides.json. Not applied: the settings step hard-fails on an invalidghTokenWorkflowSecretNamebefore the action runs (the same as the Update and Scan workflows; consistency wins); the concurrency queue replacing a waiting run (documented in change-mechanics section 9); the Validate default flip (intended, #74); a tag asref_nameon a direct commit (the form is dispatched from a branch); the multi-item no-op notice naming the first item only (WP15); the quadratic body rebuild, the duplicated secret-name regex and other performance notes.Live run: all eleven E2E steps and the refused-push fallback observed as designed on Arthurvdv/rulebook-e2e-changerule; the table with run ids and PR links is in
docs/reference/change-mechanics.mdsection 11. Close-out with the acceptance-criteria evidence and the deviations: #11 (comment)User docs: ALCops/rulebook#6 (merge after this pull request).
Spin-off: #75
Closes #11
🤖 Generated with Claude Code