F-003's field report — the same repository, path, record and moment returning directive on one route and claim on another — reproduces from current main with a single build. Two runtimes are not required.
Reproduced
A repository in signature mode, with the signing key in the allowlist:
commitlore.requireSignedDirective=true
commitlore.trustedAuthor=Router <router@test.invalid>
commitlore.trustedSigner=F64FCAD1D09B89E137C25C6BA5ED7A1AF64EE2EF
git log -1 --format='%G?' reports G and %GF reports that same fingerprint.
1) index built while the keyring was unreachable => claim
2) keyring restored, same index => claim
3) same moment, --no-index => directive
Steps 2 and 3 are the same repository, the same record, the same moment and the same binary. They disagree.
Deleting the index and rebuilding it with the keyring reachable gives directive on every subsequent run, which confirms the cached value is what decides.
The mechanism
src/core/index-db.ts stores signatureStatus from %G? alongside the record (:699, :715, :847, :862), and src/core/grade.ts:806 refuses directive when it is not G.
But %G? is not a property of the repository. It is the verdict of the process that ran git log, and it depends on which keys that process could reach — the keyring, GNUPGHOME, whether gpg is installed, whether the key has since been imported. The same commit yields G in one environment and something else in another, with no change to the repository at all.
ADR-0003 states the index is a derived, discardable cache of what git already holds. A cached %G? breaks that: it is not derivable from the repository, and nothing invalidates it when the environment that produced it changes. The record's authority then depends on the conditions under which the cache happened to be warmed.
Why this matters for #631 and F-001
The field report was attributed to a stale runtime, and #644 makes surfaces converge on one runtime identity. That is necessary and not sufficient. This divergence needs only one build: any surface reading through the index disagrees with any surface that bypasses it, whenever the cache was warmed without the keys. Closing #631 on runtime convergence alone would leave the reproduction above intact.
Suggested acceptance
- a signature status is not served from the cache as if it were a fact about the repository — either it is recomputed where it is used, or the cached entry records the verifying environment and is invalidated when that changes
- a fixture that warms the index without the keys and then reads with them present grades identically on the index and
--no-index routes
- the negative control: restore the caching and show the two routes disagreeing again, so the fix cannot be a test that never exercised it
Note on scope
This is confined to signature mode, which is opt-in (commitlore.requireSignedDirective=true). Author-string mode does not read %G? and is unaffected — measured: with signature mode off, both routes grade directive.
F-003's field report — the same repository, path, record and moment returning
directiveon one route andclaimon another — reproduces from currentmainwith a single build. Two runtimes are not required.Reproduced
A repository in signature mode, with the signing key in the allowlist:
git log -1 --format='%G?'reportsGand%GFreports that same fingerprint.Steps 2 and 3 are the same repository, the same record, the same moment and the same binary. They disagree.
Deleting the index and rebuilding it with the keyring reachable gives
directiveon every subsequent run, which confirms the cached value is what decides.The mechanism
src/core/index-db.tsstoressignatureStatusfrom%G?alongside the record (:699,:715,:847,:862), andsrc/core/grade.ts:806refusesdirectivewhen it is notG.But
%G?is not a property of the repository. It is the verdict of the process that rangit log, and it depends on which keys that process could reach — the keyring,GNUPGHOME, whethergpgis installed, whether the key has since been imported. The same commit yieldsGin one environment and something else in another, with no change to the repository at all.ADR-0003 states the index is a derived, discardable cache of what git already holds. A cached
%G?breaks that: it is not derivable from the repository, and nothing invalidates it when the environment that produced it changes. The record's authority then depends on the conditions under which the cache happened to be warmed.Why this matters for #631 and F-001
The field report was attributed to a stale runtime, and #644 makes surfaces converge on one runtime identity. That is necessary and not sufficient. This divergence needs only one build: any surface reading through the index disagrees with any surface that bypasses it, whenever the cache was warmed without the keys. Closing #631 on runtime convergence alone would leave the reproduction above intact.
Suggested acceptance
--no-indexroutesNote on scope
This is confined to signature mode, which is opt-in (
commitlore.requireSignedDirective=true). Author-string mode does not read%G?and is unaffected — measured: with signature mode off, both routes gradedirective.