32ab839289e6efffa5cbfe978af30178b53d63ab
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
03b434a0b6 |
fix(guard): derive base ref from configured upstream, not the remote-HEAD cache
Remediates the REQUEST_CHANGES verdict (review 658) at head |
||
|
|
2d5d5c9d17 |
fix(guard): derive cross-repository target base ref
Cross-repository mutation gating assumed the tracking base ref was
prgs/master, and parity reporting independently assumed origin/master.
A namespace bound to any other repository -- for example remote MDCPS on
integration branch dev -- could not prove base equivalence, so every
gated mutation failed closed with no reachable remedy. The two modules
also disagreed with each other, so at most one could be right for any
given repository.
Derive the target instead of assuming it. canonical_repository_root
already discovered the correct remote while resolving repository
identity and then discarded its name; it now returns that name with its
exact configured case preserved, and resolve_target_base_ref() builds
refs/remotes/<remote>/<branch> from it. The integration branch comes
from refs/remotes/<remote>/HEAD -- git's own record of the remote's
default branch -- so no new configuration field is required. Only when
a remote publishes no such default does it fall back to exactly one
present integration-branch candidate.
Resolution fails closed with a machine-checkable reason_code when
identity is unprovable, when distinct remotes claim different
repositories, when several candidate branches exist with no recorded
default, or when no candidate exists. It never invents a branch, writes
a ref, or falls back to another repository's base.
Both the mutation guard and the parity report now consume that one
resolved target, so they cannot disagree again. Root-checkout
contamination names the ref it actually compared rather than a literal
prgs/master the target repository may not have.
Fixes an observable defect in this repository: refs/remotes/origin/master
survives as an orphan ref from a removed remote, so parity reported the
target stale against a dead commit while reporting its identity as
underivable.
PRGS behaviour is unchanged -- prgs/master still resolves via the
recorded remote HEAD to the same SHA, and an explicit remote_refs
override keeps the historical probe path verbatim.
Tests: 24 new hermetic regression tests covering PRGS prgs/master,
MDCPS/dev, no origin remote, exact remote-name case, equal/behind/
divergent targets, missing remote or ref, ambiguous remote and branch
resolution, gate/report agreement, and every affected production
caller. Full suite 6191 passed / 28 failed, byte-identical failure set
to the baseline at
|
||
|
|
57a52b1a99 |
fix(parity): hermetic live-remote master reads under pytest (Closes #610)
Remediate PR #788 review F1/F2: suite-wide hermetic mode prevents git ls-remote from running in tests so feature worktrees no longer flip legacy runtime-context assertions to live_stale, and unit tests stay offline. Module flag survives patch.dict(clear=True); env override still wins. Co-Authored-By: Grok 4.5 <[email protected]> |
||
|
|
0f19773076 | Merge branch 'master' into feat/issue-610-live-remote-parity | ||
|
|
15a8a76e99 |
fix(mcp): complete canonical-root consumption for cross-repo namespaces (Closes #739)
#706 routed the #274 filesystem guards and the session repository slug through
the configured canonical_repository_root. Three consumption paths were left
deriving from the Gitea-Tools installation checkout.
F1 — gitea_get_runtime_context did not normalize its `remote` argument, while
gitea_whoami did. On a prgs-hosted namespace whose first native call was the
runtime-context path, the 'dadeschools' argument default was pinned: identity
resolved against the wrong host, and because first-bind is first-write-wins a
later correct gitea_whoami(remote="prgs") could not repair the binding. It now
calls _effective_remote before any host lookup, identity resolution, or session
seeding. Explicit remotes pass through untouched and a dadeschools-hosted
profile still resolves to dadeschools.
F2 — _delete_branch_repository_binding_block derived its expected slug from
_workspace_repository_slug, which reads _local_git_remote_url in PROJECT_ROOT
(always Gitea-Tools). For a cross-repository namespace this inverted the guard:
the genuinely bound target was rejected and Gitea-Tools was accepted. The
canonical-root branch already present in _trusted_session_repository is
extracted as _canonical_repository_slug and shared by both call sites. A
configured-but-unresolvable root now fails closed instead of falling back to
the installation identity; unconfigured namespaces keep the install-derived
default unchanged.
F3 — gitea_assess_master_parity measures Gitea-Tools server implementation
parity only. That is intentional and is preserved: startup_head, current_head,
in_parity, stale, and restart_required keep their existing meaning and values,
and only the server dimension gates mutations. Two separately labelled
dimensions are added — server_implementation (installation root and commit) and
target_repository (canonical root, repository slug, checkout head, last-known
remote master head, staleness) — so cross-repository commissioning evidence can
distinguish them. The target assessment makes no network call: the remote side
is read from the existing remote-tracking ref, and an unfetched target is
reported indeterminate rather than guessed at.
Verified intentional and left unchanged: the #274 workspace-membership,
author-mutation-worktree, and branches-only guards already validate against the
configured canonical root; explicit org/repo may confirm but never authorize a
binding; and the four-role repository-specific configuration surface already
passes audit and bind-time validation with a wrong root failing closed.
Tests: 31 new cases in tests/test_cross_repo_canonical_consumption.py using
real git repositories and no network. Full suite 3298 passed / 6 skipped
against a clean-baseline 3267; the 2 failures
(test_issue_702_review_findings_f1_f6, test_reconciler_supersession_close)
reproduce identically on unmodified master
|
||
|
|
324b4b3e93 |
feat: make master-parity live-remote aware so a stale daemon fails closed (Closes #610)
Master-parity previously compared only the daemon's startup commit against the local on-disk HEAD. When the checkout was not pulled, parity reported green even though the live remote master had advanced, so a stale daemon could claim a mutation-safe result while running outdated capability gates (observed during PR #592 recovery, where the resolver correctly required restart but parity said in_parity=true). Changes: - master_parity_gate.assess_master_parity() gains an optional live_remote_head and reports the three commits distinctly (daemon_start_head, local_head, live_remote_head) plus live_known / live_stale / mutation_safe. A result is mutation_safe only when daemon, local checkout, and live remote all agree. - parity_block_reasons() now blocks mutations on live-staleness too; read-only operations remain unblocked (non-goal: never block diagnostics offline). - New parity_resolver_disagreement(): typed fail-closed blocker naming the capability resolver as authoritative when it requires restart but parity looks locally green. - New read_remote_master_head(): best-effort `git ls-remote` for the live target, cached with a 60s TTL (bounded offline latency, no network probe per gate call); env override GITEA_TEST_LIVE_REMOTE_HEAD keeps tests hermetic. - Server: _current_master_parity() reads the live remote head; gitea_assess_master_parity and runtime_context surface the distinguished SHAs, mutation_safe, and resolver-authoritative guidance. Tests: 13 new cases (live-remote parity, live-stale blocking + typed blocker, remote-head reader + TTL cache, server wiring). Full suite: 2423 passed; the 8 remaining failures (test_config TestAuthIntegration, test_credentials TestGetCredentials) are baseline-proven keychain/env failures identical on the unmodified base. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |
||
|
|
934688a71d |
feat: add master-parity staleness gate for MCP mutations (Closes #420)
The MCP server loads capability-gate code into memory at startup, so a newly merged gate (e.g. the branch-delete capability gate #408/#410) does not take effect until the process restarts. A long-running server could therefore still perform a mutation the updated codebase forbids. Runtime profile/config data is already read live from disk each call (gitea_config.load_config re-reads the JSON), so allowed_operations changes apply without a restart. This closes the remaining gap: *code* parity. - master_parity_gate.py: capture the process's startup commit and, at mutation time, compare it against the on-disk master HEAD; pure, injectable assessment plus block-reasons and a restart-required report. - gitea_mcp_server.py: capture _STARTUP_PARITY at import; _profile_operation_gate fails closed for mutating ops when stale while leaving gitea.read allowed; gitea_get_runtime_context surfaces master_parity; new read-only gitea_assess_master_parity tool reports parity/restart-required. - Escape hatches: GITEA_MCP_DISABLE_PARITY_GATE (ops), GITEA_TEST_CURRENT_HEAD (tests). - tests/test_master_parity_gate.py: 18 cases (pure logic, block/report, read override, and server wiring: reads pass, mutations blocked when stale). Validation: venv/bin/python -m pytest -q -> 1618 passed, 6 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |