fix(runtime): reject unsupported repository-authority modes (#973 B10)

assess_canonical_repository_root declared a public `mode` keyword defaulting
to "validation" and documented exactly two supported values, but never checked
the argument against an allowlist. Both dispatch points were permissive:

* the configured-root path tested `mode == "derivation"` and routed every other
  value into a catch-all `else`, so an unsupported mode silently received
  validation semantics; and
* the single-repository default path tested `mode == "validation"`, so an
  unsupported mode skipped the identity comparison entirely and was strictly
  weaker than validation, not an alias of it.

Measured at the previous head: mode="invalid_mode" with require_binding=True
and matching expected/observed identities returned proven=True, block=False
with no reasons; on the unconfigured path an unsupported mode returned
proven=True where mode="validation" returned proven=False for identical inputs.
Empty string and None behaved like any other unsupported value, and no
assessment ever emitted a mode-specific rejection.

Add an explicit two-value allowlist (SUPPORTED_MODES) and refuse every other
explicitly supplied value — unknown strings, misspellings, case and whitespace
variants, the empty string, None, and non-strings — as the first act of the
function, before any candidate-root existence check, path or symlink
resolution, git top-level discovery, remote-URL or repository-identity
discovery, and before any expected-versus-observed comparison or
validation/derivation behaviour. The refusal reports proven=False,
block=True, a mode-specific reason naming the offending value, and
reason_code=DENY_UNKNOWN_MODE, following the existing
webui.sanctioned_restart.DENY_UNKNOWN_MODE convention. No repository identity
is resolved through a refused mode: resolved_slug and canonical_repo_root are
both None.

Omission continues to select validation, so the documented default is
unchanged. Unsupported modes are refused rather than normalized onto a
supported mode. No mode is exposed through MCP request parameters, environment
variables, repository configuration, session input, or any public reviewer,
merger, issue, PR or lease API; the only production call sites remain an
omitted mode (validation) and the hardcoded "derivation" literal.

resolve_namespace_mutation_context keeps the install checkout as the canonical
root when an assessment resolves none, so a refused mode cannot bind a root
derived through an undefined mode while roots_aligned and the carried
assessment stay fail-closed.

Regressions in tests/test_issue_973_b10_mode_contract.py cover invalid modes
with missing, matching and conflicting identities, empty string, explicit None,
representative non-strings, misspellings and whitespace variants, omission
defaulting to validation, explicit validation and derivation behaviour, the
single-repository default path differential, rejection ordering (both spied and
mock-free), the absence of any request/environment/configuration injection
surface, and fail-closed reviewer, merger, mutation-context and final
mutation-authorization behaviour through the production paths.

Closes #973

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-29 17:52:32 -04:00
co-authored by Claude Opus 4.8
parent a6c7d1491e
commit 34968475c7
3 changed files with 839 additions and 5 deletions
+11 -1
View File
@@ -249,7 +249,16 @@ def resolve_namespace_mutation_context(
remote=remote,
require_binding=True,
)
canonical_root = crr_assessment["canonical_repo_root"]
# #973 B10: a refused repository-authority mode deliberately resolves no
# canonical root, so downstream guards keep evaluating the install
# checkout rather than an identity derived through an undefined mode.
# ``roots_aligned`` still follows ``proven`` and stays False, and the
# refusal (with its reason_code) rides along in
# ``canonical_root_assessment`` — this is a fail-closed fallback, never a
# normalisation of the mode.
canonical_root = crr_assessment["canonical_repo_root"] or amw.resolve_canonical_repo_root(
process_root, process_root
)
roots_aligned = crr_assessment["proven"]
else:
crr_assessment = {
@@ -260,6 +269,7 @@ def resolve_namespace_mutation_context(
"canonical_repo_root": amw.resolve_canonical_repo_root(process_root, process_root),
"resolved_slug": None,
"source": None,
"reason_code": None,
}
canonical_root = crr_assessment["canonical_repo_root"]
roots_aligned = (canonical_root == process_root)