Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5214,6 +5214,28 @@ A bare `Write-Output main$(calc):seed.txt` emits `mainPWNED-EXECUTED:seed.txt`.
**Source:** found 2026-08-06 by a three-pass audit (inventory, adversarial attack, refutation) of the committed gate, prompted by a coordination message from a peer session generalising this gate. That message's own premise was overturned by measurement: the values it named are newline-unreachable here, and the reachable defect was one its proposed fix would not have closed.

## 1082. Rule 3c's deny text for a `--global` or `--system` disarm write names the wrong mechanism, and whether the write takes effect is not knowable from the command
> **THE WORDING IS FIXED 2026-08-27; the banner stays open for the archive pass. THE VERDICT DID NOT
> MOVE, which is the property this item most needed protecting.** Rule 3c had ZERO mentions of
> `--global`/`--system`, so it emitted the shared-config sentence for every scope. It now branches: a
> scoped write is told which file it actually writes, and why it is refused anyway (git falls back to
> that scope when the repository does not set the key). The repository-scope wording is UNCHANGED and
> pinned by its own control, so this did not trade one false sentence for another.
>
> **NOTHING IN THE NEW TEXT REASSURES**, and the tests pin the absence of four phrases rather than the
> presence of one. That is this row's round-4 lesson: segments are judged one at a time and `Write-Deny`
> exits on the first hit, so a reassurance prints over a real disarm when a LATER segment does a local
> write.
>
> **A QUALIFIER THIS ROW DOES NOT HAVE, measured 2026-08-27: whether a `--global` write takes effect
> depends on WHERE IT IS READ FROM, not only on the repository.** From a worktree `core.hooksPath` is
> set at worktree scope -- **75 of 76** `config.worktree` files set it -- so global loses to the more
> specific scope. **From the primary it is unset at every scope** (`--get` exit 1, against a control of
> `core.bare` exit 0), so a `--global` write there WOULD take effect. The row states only the first and
> reads as though it settled the question for the repository. Denying stays correct for both.
>
> **The multi-line residual this row names is NOT fixed here** and still needs its own item: segments
> are split on lines and the first hit exits, so a multi-line command is judged by its first segment.
>

> 🔢 **Re-scored 2026-08-20 -> P3.** Value **4/10** · Difficulty **3/10** · _fill-in_. The wrong sentence is still the only one rule 3c emits, and the rule reaches it for a --global write because the only pre-deny exclusion at :981 is for reads, so a governed cwd resolves and denies with prose that names a mechanism the write does not use. The verdict must stay deny, so the work is a scope-aware wording plus a test that asserts the string rather than the verdict. _(was 4/10 · 3/10.)_
>
Expand Down
51 changes: 46 additions & 5 deletions scripts/hooks/worktree_gate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1417,13 +1417,54 @@ What to do instead:
}
if (-not $govCfg) { continue }

# BACKLOG #1082: SAY WHAT IS TRUE OF THE SCOPE ACTUALLY BEING WRITTEN. A --global or --system
# write does NOT land in this repository's config -- it lands in the per-user or machine-wide
# file -- so the sentence below was false for those scopes. THE VERDICT DOES NOT MOVE: git
# falls back to those scopes when the repository does not set the key, so the write can still
# disarm this checkout by inheritance, and whether it does is NOT knowable from the command.
#
# Measured 2026-08-27 and it depends on WHERE YOU READ FROM, which the row does not say:
# from a worktree core.hooksPath IS set at worktree scope (75 of 76 config.worktree files)
# -> a --global write of that key loses to the more specific scope
# from the primary UNSET at every scope (exit 1; control core.bare exit 0)
# -> a --global write of that key WOULD take effect
# Denying is the correct conservative default for both.
#
# NOTHING HERE MAY REASSURE. A round-4 candidate printed "This does NOT change the shared
# configuration" and three verifiers graded it BLOCKING: segments are judged one at a time and
# Write-Deny exits on the first hit, so on a multi-line command whose LATER segment does a
# local write, that sentence would print over a real disarm.
$scopeFlag = if ($seg.Scan -cmatch '(?:^|\s)--(?<sc>global|system)(?:\s|$)') { $Matches['sc'] } else { $null }
$opening = if ($scopeFlag) {
"BLOCKED: '$badKey' is on the disarm list, and --$scopeFlag writes your " +
$(if ($scopeFlag -eq 'global') { 'per-user' } else { 'machine-wide' }) +
" git configuration rather than this repository's.`n`nGit falls back to that scope when " +
"$($govCfg.Display) does not set the key itself, so the write can still disarm this " +
"checkout's hooks. Which case applies is not knowable from the command alone, so it is refused."
}
else {
"BLOCKED: setting '$badKey' would change the SHARED git configuration of $($govCfg.Display)."
}
# The mechanism paragraph is scope-aware for the same reason the opening is: the shared-.git
# sentence is TRUE of a repository-scope write and FALSE of --global/--system.
$mechanism = if ($scopeFlag) {
"This is refused on the REACH, not on where the file lands: a key inherited from the " +
"$(if ($scopeFlag -eq 'global') { 'per-user' } else { 'machine-wide' }) scope applies to " +
"every repository on this machine that does not override it, including every worktree of " +
"$($govCfg.Display)."
}
else {
"Every worktree of this repository shares one .git directory, so this is not a local " +
"change: it takes effect for all of them at once."
}
Write-Deny -Rule "3c" -Detail "git config $badKey" -Reason @"
BLOCKED: setting '$badKey' would change the SHARED git configuration of $($govCfg.Display).
$opening

$mechanism

Every worktree of this repository shares one .git directory, so this is not a local change: it takes
effect for all of them at once. Repointing core.hooksPath (or aliasing a command, or redirecting
core.worktree) disables the commit-time ledger, claim and secret-leak gates for every session on this
machine, and nothing would report that they had stopped running.
Repointing core.hooksPath (or aliasing a command, or redirecting core.worktree) disables the
commit-time ledger, claim and secret-leak gates, and nothing would report that they had stopped
running.

What to do instead:
* If a commit hook is failing, FIX THE CAUSE -- the hook output names it. Never route around a gate;
Expand Down
95 changes: 95 additions & 0 deletions tests/test_worktree_gate_scope_wording.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 MessageFoundry Organization and contributors
"""BACKLOG #1082: rule 3c's deny text named a mechanism a ``--global``/``--system`` write does not use.

**THE VERDICT WAS ALWAYS CORRECT AND DOES NOT MOVE HERE.** ``git config --global core.hooksPath <dir>``
aimed at a governed repo is refused, and should be: git falls back to the per-user scope when the
repository does not set the key, so the write can still disarm the checkout **by inheritance**. An
earlier filing of this as a FALSE DENY was withdrawn by three independent verifiers who measured the
hook genuinely not firing afterwards. Flipping rule 3c to ALLOW would have been a fail-open.

What was wrong is the SENTENCE. It said the write *"would change the SHARED git configuration of
<repo>"*, and it does not -- it writes ``~/.gitconfig`` or the machine-wide file.

**EVERY TEST HERE ASSERTS THE EMITTED STRING, NOT THE VERDICT.** That is forced, not stylistic: the
verdict is identical before and after, so a row ending in a bare ``assert_denied`` cannot see this
defect at all, and a mutant that reinstated the old sentence would pass such a row unchanged.

**NOTHING IN THE MESSAGE MAY REASSURE.** A round-4 candidate printed *"This does NOT change the shared
configuration"* and three verifiers graded it BLOCKING: ``Get-ScannableSegments`` splits on lines and
``Write-Deny`` exits on the first hit, so on a multi-line command whose LATER segment does a local
write, that reassurance would print over a real disarm. The negative rows below pin its absence.
"""

from __future__ import annotations

from types import SimpleNamespace

import pytest

from tests.test_worktree_gate import assert_denied, run_gate
from tests.test_worktree_gate_control_plane import repo, shell # noqa: F401

SCOPED = [
("global", "git config --global core.hooksPath /dev/null"),
("system", "git config --system core.hooksPath /dev/null"),
]

#: Phrases that must never appear in a scoped deny. The first is the false mechanism this item is
#: about; the rest are the reassurance shapes graded BLOCKING in round 4.
FORBIDDEN_IN_SCOPED = [
"SHARED git configuration",
"does NOT change",
"is harmless",
"safe to run",
]


@pytest.mark.parametrize("scope,command", SCOPED)
def test_a_scoped_write_is_still_denied(repo: SimpleNamespace, scope: str, command: str) -> None: # noqa: F811
"""THE VERDICT ARM. Kept separate from the wording arms so a future change that fixed the sentence
by ALLOWING the write fails here rather than passing quietly."""
assert run_gate(shell(command, cwd=repo.primary), repo.repos) is not None, (
f"--{scope} disarm write was ALLOWED. The verdict must not move: git inherits from that scope "
"when the repository does not set the key, so the write can still disarm this checkout."
)


@pytest.mark.parametrize("scope,command", SCOPED)
def test_a_scoped_deny_does_not_claim_the_repo_config_changed(
repo: SimpleNamespace, # noqa: F811
scope: str,
command: str,
) -> None:
reason = assert_denied(run_gate(shell(command, cwd=repo.primary), repo.repos))
for phrase in FORBIDDEN_IN_SCOPED:
assert phrase not in reason, (
f"--{scope} deny text contains {phrase!r}. Either it names a mechanism this write does not "
"use, or it reassures -- and a reassurance prints over a real disarm when a later segment "
f"of a multi-line command does a local write.\n\n{reason}"
)


@pytest.mark.parametrize("scope,command", SCOPED)
def test_a_scoped_deny_names_the_scope_and_the_inheritance(
repo: SimpleNamespace, # noqa: F811
scope: str,
command: str,
) -> None:
"""Say what IS true: which file is written, and why it is refused anyway."""
reason = assert_denied(run_gate(shell(command, cwd=repo.primary), repo.repos))
assert f"--{scope}" in reason, f"the deny text does not name the scope it refused\n\n{reason}"
assert "inherit" in reason.lower() or "falls back" in reason.lower(), (
f"the deny text does not say WHY a write to another file is refused\n\n{reason}"
)


def test_a_repository_scope_write_still_says_SHARED(repo: SimpleNamespace) -> None: # noqa: F811
"""THE CONTROL. The original sentence is CORRECT for a repository-scope write and must survive
unchanged -- otherwise this fix would have traded one false sentence for another."""
reason = assert_denied(
run_gate(shell("git config core.hooksPath /dev/null", cwd=repo.primary), repo.repos)
)
assert "SHARED git configuration" in reason, (
f"the repository-scope wording was changed; it was already true and should not move\n\n{reason}"
)
1 change: 1 addition & 0 deletions tests/tooling_manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ tests/test_worktree_gate_quote_straddle.py
tests/test_worktree_gate_receipts.py
tests/test_worktree_gate_remedy_families.py
tests/test_worktree_gate_rule_agnostic_coverage.py
tests/test_worktree_gate_scope_wording.py
tests/test_worktree_gate_shell_semantics.py
tests/test_worktree_gate.py
tests/test_worktree_new_cleanup_advice.py
Expand Down
Loading