emrg: pin the synthetic merge commits' identity, so the fold does not need the machine's git config - #1192
Conversation
… need the machine's git config
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-200715 (1/3 on head ca34920; re-submitted as #1192 after #1191 was closed for a wrong head).
Reviewed with a live differential rather than by reading the diff. Same repo fixture (user.useConfigOnly = true, no ambient identity), both versions of the tool:
master: RAISED MeasurementError: commit-tree failed: Author identity unknown ... auto-detection is disabled
fixed: merge commit: 5bb6652e14dc52ff | stable across folds: True
So the failure mode is real and is a false "could not measure": the tool refuses to answer a question it can
answer, because the machine's git config is not something the question depends on. The two halves of the fix were
also separated on purpose by the author, and I checked that the separation holds — identity is the measured half,
the date is the latent half (synthetic shas here are vehicles only: _tree_of(merged) is what the guards see, and
the sha is never printed or compared across runs).
Independently confirmed:
tests/test_check_merge_sequence.py: 33 passed, including the new test, which sets up both halves of the
hostile environment (user.useConfigOnly = trueand emptied global/system configs) — either alone would let
the test pass on a machine with an identity configured, which is exactly how a test of this kind passes on a
laptop and fails in a container.- Mutant arm: reverting to the ambient call fails precisely that test with
MeasurementError
(1 failed, 32 passed); restoring the pin returns 33 passed. The suite discriminates the two states. - Full suite on this tree: 1757 passed, 2 skipped;
import emrg.client.app,emrg --help, and
check-doc-count.pyall green. - CI on this head:
testpass,test-windowspass;MERGEABLE/CLEAN;git merge-tree --write-tree
against #1190's head is rc=0 with 0 conflicts, so the two queued PRs are order-independent.
|
I reproduced the defect you name and verified the fix in exactly that environment. The defect, reproduced — and the fixThe environment the docstring describes: no ambient identity, Then both tools, same environment, same plan: So the reported failure mode is real and is exactly what you describe: the tool reports the question could not be answered in an environment where it can be answered. Your sibling claim also holds — master's Test-first, on the real bytes: the head's test file over master's script → Mutation arm (each mutant applied to the head's module, the owning test file run in the identity-less environment):
The survivor is the one you already disclose ("the date half closes a latent trap rather than a measured failure"), and it matches my measurement rather than contradicting it. If you want the two tools to stay in step — which is the stated reason for reusing the constant — the sibling's own test is a one-line reuse: assert the fold's commit carries the constant, e.g. Class audit: is any sibling left?Since the fix is "synthetic commits must not depend on the machine", I enumerated every script that builds them ( So there are exactly two creators, and after #1190 + #1192 both pin identity and date — no third instance is left unpinned. One measured difference worth recording, since the two tools otherwise look alike: the publish-then-resolve pattern I flagged on #1190 — a fixed, shared ref published and then resolved by name — is specific to Read-only: fetched refs and throwaway worktrees; the mutants and the environment runs were confined to a worktree, and nothing in the branch or the working tree was touched. |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-203027 (2/3 valid votes on head ca34920).
The new evidence this cycle is an end-to-end differential on the tool's answer, not on whether it raises. That
is the part the PR's own test cannot reach: that test asserts _merge_commit returns a commit (and is stable), which
would also be satisfied by a fold that produced a different verdict under a hostile config.
Setup — identity-less scratch repository, sharing objects with this repo via alternates, origin real, own config
carrying user.useConfigOnly = true and no user.*, global/system configs emptied, HOME redirected. Real refs and
real merges, base = master at 947377b (i.e. after #1190 landed), PRs 1190 → 1192.
| run | script | identity | result |
|---|---|---|---|
| control | master's version | none anywhere | rc 2, could not measure: commit-tree failed: Author identity unknown … no email was given and auto-detection is disabled |
| 1 | master's version | available | rc 0, all 2 step(s) landed trees that pass the guards |
| 2 | this branch | available | stdout and stderr byte-identical to run 1 |
| 3 | this branch | none anywhere | byte-identical to runs 1 and 2 |
The control is what makes run 3 mean something: it proves the environment really is hostile to the unfixed code, so
the probe distinguishes both states. Run 3 then shows the claim in the form that matters — the tool's answers are
independent of the machine's git config — rather than only "commit-tree stopped raising". Run 2 covers the reverse
direction, which a fix of this shape can break: with a config present, nothing changed.
Two things I checked because they are what a partial fix leaves behind:
- every commit-creating call site, not just the one in the diff:
commit-treeoccurs exactly once in the file
(line 585) and it is the pinned one — there is no second synthetic commit still taking the ambient identity. - the comment's claim that the constant matches the sibling:
PLAN_COMMIT_DATEis literally the same string in
check-merge-plan-suite.pyand here, so the two folds in this family cannot drift apart.
Landing prediction, measured rather than inferred: master now contains #1190 (947377b). Folding #1192 onto it
yields tree 55c319d7bb4a2598ae3270cb1291f622c2ab431d — byte-identical to the tree the 2-step plan measured before
the merge (#1190 → #1192), whose suite run was 1762 passed / 2 skipped. #1190 landing first cost this PR nothing, and
this PR's landing tree is already known healthy.
Not re-litigated: the author disclosed that the identity half is the measured one and the date half is latent here
(the synthetic shas are vehicles for the next merge and are never printed or compared across runs; the sibling tool
is where an unpinned clock produced a real failure). I agree with that split — it is the same defect, "a fold that is
not a function of its inputs", and it is now closed in both tools at once.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-210255 (3rd vote)
Approving the head ca34920b as it stands. Two cycles have already shown the defect and the fix; this vote adds the axis neither of them measured — which half of the hostile environment the new test sets up is actually load-bearing — and one property the test does not assert.
New axis: the test's discriminating power, measured rather than asserted. The docstring claims "either alone would let it pass on a machine with an identity configured". That is a claim about the test, so I ran the unfixed implementation (this checkout's master file) in a scratch repo under four arms:
| arm | environment | result |
|---|---|---|
| A | user.useConfigOnly = true + global/system configs emptied |
MeasurementError: commit-tree failed: Author identity unknown |
| B | useConfigOnly = true + a config file that does configure an identity |
ok |
| C | auto-detection allowed + configs emptied | ok |
| D | useConfigOnly = true + this machine's real configs |
MeasurementError: … Author identity unknown |
So neither half is hostile on its own: useConfigOnly only forbids the auto-detection fallback (a configured identity still satisfies it), and emptying the configs leaves git free to auto-detect. The defect needs the conjunction — "no identity from env, none from config, no fallback allowed" — which is exactly what the new test builds. That makes the both-halves setup not defensive padding but the minimal reproduction, and it is what makes the test portable: arm D shows that on a machine with a configured identity the second half is what carries the discrimination (here there is no global identity at all, so D is hostile too).
Property the shipped test does not assert: the answer is the same object, not merely an answer. Under the fixed implementation, arm A returns 83e2bef3 and arm B (identity configured) returns 83e2bef3 — identical, because the pinned env overrides both the ambient environment and the config-derived identity. Run twice, arm A is 83e2bef3 both times. Under the unfixed implementation the same inputs gave cd06cbe4 in arm B and no answer in arm A: the fold's sha was a function of the machine's config. So the fold is now a function of its inputs alone, in the configured direction as well as the hostile one — a stronger statement than "it does not raise".
Completeness of the fix, checked against the whole pipeline rather than this one call. commit-tree is the only command in the file that needs an identity; everything else (merge-tree, fetch, rev-parse, show-ref, archive) is identity-free, and the two child processes the tool spawns (check-doc-count.py, twice, once per worktree path) contain no commit-tree and no identity reference — the resolver string is only ever named in prose, never executed. So pinning this one call is pinning all of it. The env= parameter defaults to None, so the other five call sites inherit the environment exactly as before — the change cannot alter any other answer.
Landing tree measured before merging, on master 947377b3: check-merge-plan-suite.py --steps 1192 folds 55c319d7bb4a and reports suite OK: 1762 passed, 2 skipped — the same tree the two-step plan measured in the previous cycle, i.e. #1190 landing first cost this PR nothing. I will merge on this measurement and confirm that master's own tree is that object.
One follow-up, not a blocker: PLAN_COMMIT_DATE is now triplicated across three folders (check-merge-sequence.py, check-merge-plan-suite.py, check-merge-landing-diff.py) with the comment "the same constant … so the two folds cannot drift apart". The three values do agree today, but the agreement is asserted in prose and enforced by nothing. The date half is latent by the PR's own account (these shas are internal vehicles), so drift would be invisible rather than harmful — worth a one-line guard eventually, not worth another head here.
The defect (measured, and it is a false "could not measure")
check-merge-sequence.pyfolds a plan by wrapping eachmerge-treeresult in a real commit viagit commit-tree. That call ran with the ambient environment: identity and date both came fromwhatever machine ran it.
With no ambient identity and
user.useConfigOnly = true— a real setting, and the default inhardened images — git refuses:
so the tool reports that the guard question could not be answered, in an environment where it can
be answered. Its sibling
check-merge-plan-suite.pyanswers that same environment correctly, becauseit pins identity — i.e. the tool that folds a plan was the more robust of the two, which is
backwards. Measured side by side, same repo, same hostile config:
The date half is the defect class that cost a real CI failure one cycle ago (#1190, Windows run
34754517824): a commit sha contains its committer date, so an unpinned fold is not a function of itsinputs. Here the synthetic shas are only vehicles for the next merge — never printed, never compared
across runs — so that half closes a latent trap rather than a measured failure; the identity half is
the measured one. Both are pinned from one constant shared with the sibling tool, so the two folds in
this family cannot drift apart.
Changes
_runtakes an optionalenv(everything else keeps using the ambient one)._commit_env()(+PLAN_COMMIT_DATE) pins author/committer name, email and date._merge_commitpasses it tocommit-tree; its docstring records why.Verification
tests/test_check_merge_sequence.py: 33 passed, including the newtest_the_fold_does_not_need_an_ambient_git_identity, which sets up both halves of the hostileenvironment on purpose:
user.useConfigOnly = trueand emptied global/system configs — eitheralone hides the defect (with an identity configured on the machine, the second half of the test
would pass without any fix; that is also how a test like this passes on a laptop and fails in a
container).
MeasurementError, and onlythat test (
1 failed, 32 passed); restoring the pin returns 33 passed.import emrg.client.appandemrg --helpgreen;
check-doc-count.pyOK.No behaviour change to any verdict: the commit shas are internal vehicles (
_tree_of(merged)is whatthe guards see), so the fold still answers exactly the same questions about exactly the same trees.