From c103cbf3c8bed5dba87a03e320595ddded9301a3 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 17 Oct 2026 02:30:15 +0200 Subject: [PATCH] fix(consistency): preserve immutable historical binding rows Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa --- scripts/consistency_check.py | 5 +- tests/test_writeback_git_isolated.py | 60 +++++++++++++++++++ ...ATE-WP-0095-historical-source-receiving.md | 15 +++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 tests/test_writeback_git_isolated.py diff --git a/scripts/consistency_check.py b/scripts/consistency_check.py index 30cc11b..de7b47d 100644 --- a/scripts/consistency_check.py +++ b/scripts/consistency_check.py @@ -2767,11 +2767,12 @@ def _git_commit_writeback( sync_env = {**_os.environ, "GIT_CUSTODIAN_SYNC": "1"} try: subprocess.run( - ["git", "-C", repo_path, "add", str(file_path)], + ["git", "-C", repo_path, "add", "--", str(file_path)], check=True, capture_output=True, env=sync_env, ) subprocess.run( - ["git", "-C", repo_path, *_git_identity_config(), "commit", "-m", msg], + ["git", "-C", repo_path, *_git_identity_config(), + "commit", "--only", "-m", msg, "--", str(file_path)], check=True, capture_output=True, env=sync_env, ) return True diff --git a/tests/test_writeback_git_isolated.py b/tests/test_writeback_git_isolated.py new file mode 100644 index 0000000..63e20a0 --- /dev/null +++ b/tests/test_writeback_git_isolated.py @@ -0,0 +1,60 @@ +"""Real disposable Git proof; does not import owner app or test conftest.""" +import ast +from pathlib import Path +import subprocess +import sys +import pytest + +SOURCE=Path(__file__).resolve().parents[1]/'scripts/consistency_check.py' +def actual_writeback(): + tree=ast.parse(SOURCE.read_text());nodes=[] + for node in tree.body: + if isinstance(node,ast.FunctionDef) and node.name in {'_git_identity_config','_git_commit_writeback'}: + node.returns=None + for arg in node.args.args+node.args.kwonlyargs:arg.annotation=None + nodes.append(node) + env=dict(subprocess=subprocess,sys=sys,Path=Path) + exec(compile(ast.Module(body=nodes,type_ignores=[]),str(SOURCE),'exec'),env) + return env['_git_commit_writeback'] +def git(root,*args):return subprocess.check_output(['git','-C',str(root),*args]).decode().strip() +@pytest.fixture +def repo(tmp_path): + root=tmp_path/'owner';root.mkdir();git(root,'init','-b','main');git(root,'config','user.name','Synthetic Test');git(root,'config','user.email','synthetic@example.invalid');git(root,'config','core.hooksPath','.git/hooks') + for name in ['writeback.md','.custodian-brief.md','evidence.json']:(root/name).write_text('original\n') + git(root,'add','.');git(root,'commit','-m','baseline') + (root/'.custodian-brief.md').write_text('staged unrelated\n');git(root,'add','.custodian-brief.md');(root/'.custodian-brief.md').write_text('working unrelated\n') + (root/'evidence.json').write_text('dirty evidence\n') + return root + +def test_writeback_commits_only_target_preserves_unrelated_stage_and_worktree(repo): + before_index=git(repo,'ls-files','--stage','.custodian-brief.md','evidence.json');before_head=git(repo,'rev-parse','HEAD') + target=repo/'writeback.md';target.write_text('new status\n') + assert actual_writeback()(str(repo),target,['synthetic status']) + assert git(repo,'rev-parse','HEAD')!=before_head + assert git(repo,'diff-tree','--no-commit-id','--name-only','-r','HEAD')=='writeback.md' + assert git(repo,'show','HEAD:.custodian-brief.md')=='original' + assert git(repo,'show',':.custodian-brief.md')=='staged unrelated' + assert git(repo,'ls-files','--stage','.custodian-brief.md','evidence.json')==before_index + assert (repo/'.custodian-brief.md').read_text()=='working unrelated\n' + assert (repo/'evidence.json').read_text()=='dirty evidence\n' + +def test_noop_never_commits_unrelated_staged_change(repo): + head=git(repo,'rev-parse','HEAD');index=git(repo,'ls-files','--stage') + assert not actual_writeback()(str(repo),repo/'writeback.md',['no change']) + assert git(repo,'rev-parse','HEAD')==head and git(repo,'ls-files','--stage')==index + +@pytest.mark.parametrize('name',['new writeback.md','-leading-dash.md']) +def test_new_target_and_argument_boundary(repo,name): + target=repo/name;target.write_text('new file\n') + assert actual_writeback()(str(repo),target,['new synthetic target']) + assert git(repo,'diff-tree','--no-commit-id','--name-only','-r','HEAD')==name + assert git(repo,'show',':.custodian-brief.md')=='staged unrelated' + +def test_normal_hook_refusal_preserves_unrelated_state(repo): + hook=repo/'.git/hooks/pre-commit';hook.write_text('#!/bin/sh\nexit 1\n');hook.chmod(0o755) + head=git(repo,'rev-parse','HEAD');index=git(repo,'ls-files','--stage','.custodian-brief.md','evidence.json') + target=repo/'writeback.md';target.write_text('new status\n') + assert not actual_writeback()(str(repo),target,['refused synthetic change']) + assert git(repo,'rev-parse','HEAD')==head + assert git(repo,'ls-files','--stage','.custodian-brief.md','evidence.json')==index + assert git(repo,'show',':.custodian-brief.md')=='staged unrelated' diff --git a/workplans/STATE-WP-0095-historical-source-receiving.md b/workplans/STATE-WP-0095-historical-source-receiving.md index 531bde2..d9c7ec7 100644 --- a/workplans/STATE-WP-0095-historical-source-receiving.md +++ b/workplans/STATE-WP-0095-historical-source-receiving.md @@ -342,3 +342,18 @@ new full-row baseline preservation and actual rollback-only refusal probes, and only then return to T07 ordinary-pipeline acceptance. This prevents future binding mutation; it does not reconstruct or restore original parent hashes. No production changes, new autonomy or credential authority are granted here. + + +T09 source repair refinement, 2026-10-17: the attempted current-client regression +actually loaded an older packaged consistency checker from the owner virtualenv, +not the reviewed canonical source. That trial again failed parent-row hash +preservation; it is retained separately and does not count as R5 client proof. +Normal C-23 also legitimately normalized WP18 ready to active and created a +source commit, but the generic writeback commit included an unrelated staged +brief. A narrow successor scopes git add and git commit --only to the actual +writeback file, preserving unrelated index/worktree state. Real disposable Git +regression tests cover pre-existing staged changes, hooks, no-op and path names. +Verify the actually loaded CLI checker hash after ordinary owner package refresh +before any retry; capture legitimate source HEAD transitions rather than assume +a static revision. T07 and T10 remain wait. No original parent preimage recovery, +new runtime delivery or broader authority follows from this source repair.