Skip to content

resolution/1.0 could not say "I decline to answer" - #21

Merged
chris-dare-dev merged 4 commits into
mainfrom
agent/issue-170-resolution-not-applicable
Sep 4, 2026
Merged

chris-dare-dev merged 4 commits into
mainfrom
agent/issue-170-resolution-not-applicable

Conversation

@chris-dare-dev

Copy link
Copy Markdown
Owner

Upstream half of chris-dare-dev/derived-alg-geo-lean#170 (epic #134), which requires tools/statement_resolve.py to emit not_applicable — never current when documents.arxiv_version == ''. The enum had no such member, so the acceptance criterion was unstatable.

arxiv_version is '' for every row in both live notebooks. Matching an entry's quote_sha256 against corpus bytes and writing current for an entry declaring version: v3 asserts a v3 pin confirmed by bytes of unknown version.

Why not_applicable and not not_run

join.py:79 already says it, for the review axes: the remedies differ. not_run is closed by running the resolver; not_applicable by fixing the corpus. ADR-0005 names the four values (pass/fail/not_run/not_applicable) and arXMCP's CLAUDE.md §4.10 rule 3 binds the same spelling across the seam. The corpus axis is the constant's second user, not a new idea.

Why extended in place rather than resolution/1.1

No producer of resolution/1.0 has ever existed — attest/resolution.json is absent from the topic repo and C-08 reads not_run in the v0.1.0 release notes. There is no file in the world to invalidate. There will be as soon as arXMCP #170 lands, which is why this goes first.

Changes

  • result.resolution gains not_applicable; counts gains its key.
  • New conditional: not_applicable MUST carry a non-empty reason and MUST NOT name a resolved_source_version.
  • coverage() counted resolution != not_run as resolved — that would report the soundness hole as coverage, which is exactly what the reviewed line beside it already refuses to do.

Two things found while here

  • containment-with-similarity.json was in REJECTIONS nowhere. The directory sweep in test_validate.py asserted it fails somehow, which is weaker than it looks — additionalProperties: false rejects a document for a typo, so "the CLI said no" does not establish which rule fired. Registered, plus a test that fails on any fixture bound to no rule.
  • A structural test that counts has one key per resolution enum member, derived from the schema. An enum member with no counts key is a state every total silently swallows.

631 passed, 1 skipped (baseline 623, 1).

`documents.arxiv_version` is `''` for every row in both live notebooks, so a
resolver that matches an entry's `quote_sha256` against corpus bytes and writes
`current` for an entry declaring `version: v3` asserts a v3 pin confirmed by
bytes of unknown version. derived-alg-geo-lean #170 requires the resolver to
emit `not_applicable` there, and the enum had no such member.

The four values this contract judges on are `pass` / `fail` / `not_run` /
`not_applicable` (ADR-0005), and arXMCP's CLAUDE.md 4.10 rule 3 binds the same
spelling across the seam. `join.py` already carries the constant and already
states why it may not be folded into `not_run` -- the remedies differ. The
corpus axis is simply its second user.

Extended in place rather than bumped to `resolution/1.1`: no producer of
`resolution/1.0` has ever existed. `attest/resolution.json` is absent from the
topic repo and C-08 reports `not_run` in the v0.1.0 release notes, so there is
no file in the world to invalidate. There will be after arXMCP #170 lands.

- `result.resolution` gains `not_applicable`; `counts` gains its key.
- A new conditional: `not_applicable` MUST carry a non-empty `reason`, and MUST
  NOT name a `resolved_source_version`. A result that both declines on version
  grounds and names a version is incoherent; and without the reason it is
  indistinguishable from `not_run` at the point of reading.
- `coverage()` counted `resolution != not_run` as resolved, which would have
  reported the soundness hole as coverage -- exactly what the `reviewed` line
  next to it already refuses to do. Now excludes both, as that line does.

Two rejection fixtures, both asserted at the schema and (through
`test_validate.py`'s sweep of the directory) at the CLI.

Also registers `containment-with-similarity.json` in `REJECTIONS`, where it
never was. The directory sweep meant it was asserted to fail *somehow*, which
is a weaker claim than it looks: `additionalProperties: false` rejects a
document for a typo, so "the CLI said no" does not establish that the rule the
fixture was written for is the rule that fired. `REJECTIONS` binds a fixture to
its rule, and a new test now fails on any fixture that is in neither.

And a structural test that `counts` has one key per `resolution` enum member,
derived from the schema rather than listed -- an enum member with no counts key
is a state every total silently swallows.

631 passed, 1 skipped (was 623, 1).
`quote_containment`'s conditional listed `current`/`drifted`/`unresolvable`,
which made a containment match withheld by the version guard unrepresentable.
The resolver would then have had to misreport the rung it actually used --
writing `quote_sha256` for a match it made by containment -- to say the one
thing the guard requires it to say.

An entry found by containment in a corpus that cannot name its arXiv version
is withheld for exactly the reason a byte-equal match is. Which rung found the
quote and whether the version can be confirmed are independent questions, and
the schema now lets a result answer them independently.

The valid fixture carries both combinations, so the accepted pairing is
evidence rather than an absence of a rejection.
`/contract/build/` is in `.gitignore` and has been since packaging landed --
but eleven files under it went in with 6bd4739, the same commit that added the
ignore, and `.gitignore` does not untrack what is already tracked. So a fresh
checkout carried a `build/lib/` holding stale copies of `cli.py`, `lint.py`,
`validate.py` and five schemas, and setuptools' `build_py` copies a source
file into `build/lib` only when it is NEWER -- which on a fresh checkout, with
every file stamped at the same moment, it need not be.

Not hypothetical. It bit this branch: `pip install ./contract` shipped a
`resolution-1.0.schema.json` predating `not_applicable`, so a resolver
producing a valid document was told by its own bundled schema that the
document was invalid. Eight of the eleven tracked files were stale.

CI already installs the package rather than running it from the tree, and the
comment there says why -- that is what catches a schema which failed to ship.
It cannot catch one that shipped as the WRONG BYTES, because from outside
those two look identical. So the new test compares the installed schemas
against the source schemas byte for byte, and says to delete `build/` if they
differ. Byte-identity rather than parsed equality: a schema can differ by a
`$comment` that changes nothing and an enum member that changes everything,
and only one of those survives a structural diff.

It skips when `mfc` resolves to the source tree, which is what happens running
pytest from `contract/`. CI runs it from the repo root against an installed
package, which is the environment the claim is about.
`*.egg-info/` is in `.gitignore` beside `/contract/build/` and was tracked for
the same reason -- committed in the packaging commit, before the ignore could
take effect. Ten files, including a `contract/UNKNOWN.egg-info/` left behind by
a build that ran before the project had a name.

Less dangerous than the stale schemas: nothing reads `SOURCES.txt` to decide
what a contract means. But every `pip install ./contract` rewrites `PKG-INFO`
and `SOURCES.txt` in place, so the tree goes dirty on any build and each run
offers the diff up for staging. The previous commit swept two of them in
exactly that way.

Deleted from the index only; the directories stay on disk and stay ignored.
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.

1 participant