Skip to content

Let one machine differ from the committed policy without dirtying the tree (#709) - #715

Merged
MongLong0214 merged 1 commit into
mainfrom
feat-709-local-policy-overlay
Aug 17, 2026
Merged

Let one machine differ from the committed policy without dirtying the tree (#709)#715
MongLong0214 merged 1 commit into
mainfrom
feat-709-local-policy-overlay

Conversation

@MongLong0214

Copy link
Copy Markdown
Owner

Closes #709.

.commitlore-policy.json is committed with the repository, which is right for a policy — auto on authorises an agent host to prepare, verify and stage with nobody in the loop, and that applies to everyone who clones. A contributor who needs a different answer had exactly one route: edit the tracked file. Their worktree then stayed modified forever, which is enough to stop a release script that refuses to tag a dirty tree — the failure #709 reports.

Refusing the overlay never prevented the divergence. It converted it into a modified tracked file, which is the worse of the two.

Precedence

.commitlore-policy.local.json     per key, wins
.commitlore-policy.json           repository default
built-in defaults

Per key, not per file: an overlay setting only unattended leaves mode and max_records_per_commit as the repository set them, so a later change to the committed file still applies. commitlore auto on --local / auto off --local write it, and once it exists it is the file commitlore auto writes — existence is the signal, because an auto off that silently created one would stop the tracked file being the answer without anyone choosing that. Nothing writes a .gitignore entry for it.

What pays for the stated precedence

  • The identity hash is taken over the effective policy whenever an overlay is present. A pending transaction stamps it; without this a record prepared under an overlay would carry provenance naming a policy that did not produce it. unattended is an input here though it is not in the defaults digest — M-UX: capture leaves the user's workflow #511's exclusion rests on a file's identity being its own bytes, which an overlay breaks.
  • A repository with no overlay keeps exactly the digest it had. No capture in flight is refused by the upgrade. Adding an overlay does move it, which is correct: that is a policy change.
  • A new policy-overlay doctor check names both files, the value beneath, the value in the overlay and the one in force. ok when they disagree — the operator wrote it on purpose, and a warning that fires forever on a correct machine teaches people to ignore the surface carrying the real ones. warn only when a file cannot be used, since then neither file's values are in force.

Verification

Three mutations, each caught by the test whose name claims it:

mutation fails
overlay merged per file instead of per key 3 cases, incl. leaves the keys it does not set as the repository set them
identity kept on the committed file's bytes all 3 hash cases
effective hash used with no overlay present does not move for a repository that has no overlay + 3 pinned cases in capture-policy.test.ts

209 cases pass across the 8 affected suites; tsc --noEmit clean; build:canonical then artifact:verifyb5e96eb116ac71c43810c409f9864b1bad16e99616b1fe24981ee54d28d5f4fc.

test/init.test.ts fails 6 cases on this machine for an unrelated local reason — two live CommitLore MCP runtimes (v1.0.1 and v1.0.2) answering the runtime-identity probe. Confirmed by stashing the change and re-running on unmodified src/: same 6, same cause.

ADR-0035 records the reversal and what it is conditioned on.

… tree (#709)

`.commitlore-policy.json` is committed, which is right for a policy: `auto on` authorises a host to prepare, verify and stage with nobody in the loop, and that is the repository's decision for everyone who clones. But a contributor who needs a different answer had one route -- edit the tracked file -- and their worktree stayed modified forever. #709 reports the consequence: a release script that refuses to tag a dirty tree stopped working, and the workaround was moving the file out of the repository for the duration of the tag.

So the single-location choice did not prevent the divergence. It converted it into a modified tracked file, which is the worse of the two, and that is what reverses it.

`.commitlore-policy.local.json` now wins per key: an overlay setting only `unattended` leaves `mode` and `max_records_per_commit` as the repository set them, so a later change to the committed file still applies. It may set a value in either direction -- what keeps an unattended record from directing is the drafted stamp and the claim cap in grading, not this switch, and a narrowing-only rule is unenforceable anyway because the contributor who wants more edits the tracked file.

The ambiguity the old choice was avoiding is paid for rather than inherited. The identity hash is taken over the effective policy whenever an overlay is present, so a record prepared under one is stamped with the policy that produced it rather than with bytes that did not; a repository with no overlay keeps exactly the digest it had, so no capture in flight is refused by the upgrade. And a new doctor check names both files, the value beneath, the value in the overlay and the one in force -- ok when they disagree, because that is the feature working, and warn only when a file cannot be used, since then neither file's values are in force.

Limit: unattended is an input to the effective digest but not to the defaults digest -- #511's exclusion rests on a file's identity being its own bytes, which an overlay breaks
Limit: a broken overlay falls back to the built-in defaults, not to the committed file -- layering onto a policy nobody could read states an effective policy no file states
Ruled-out: let an overlay only narrow permissions | it solves the contributor who wants less, and the one who wants more still edits the tracked file, which is the reported failure
Ruled-out: write a .gitignore entry for the overlay | a tool that hides a file on a repository's behalf has decided for the repository what it may not see
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-overlay709
Provenance: authored
Verified: three mutations, each caught by the test whose name claims it -- overlay merged per file instead of per key fails 3 cases including "leaves the keys it does not set as the repository set them"; identity kept on the committed bytes fails all 3 hash cases; the effective hash applied with no overlay present fails "does not move for a repository that has no overlay" and 3 pinned cases in capture-policy.test.ts. 209 cases pass across the 8 affected suites; npx tsc --noEmit clean; build:canonical then artifact:verify -> b5e96eb116ac71c43810c409f9864b1bad16e99616b1fe24981ee54d28d5f4fc. test/init.test.ts fails 6 cases on unmodified src here for a local reason -- two live CommitLore MCP runtimes on this machine -- confirmed by stashing the change and re-running.
CommitLore-Version: 2.0.0
@github-actions

Copy link
Copy Markdown

CommitLore — record lint

Trailers: clean — 1 commit in origin/main..23f6d2998ff12c4974b9cd9757bbe44527d4506a
Active constraints: not read — commitlore: git log --follow accepts exactly one pathspec, so renames are not followed for 30 paths; query one path at a time to follow its rename chain (30 changed paths)

Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR.

@MongLong0214
MongLong0214 merged commit 7e08cbf into main Aug 17, 2026
12 checks passed
@MongLong0214
MongLong0214 deleted the feat-709-local-policy-overlay branch August 17, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A committed .commitlore-policy.json has no local override, so opting in requires permanently dirtying the worktree

1 participant