fix(author bootstrap): restore missing runtime identity and session helpers (Closes #943)
gitea_bootstrap_author_issue_worktree referenced four globals that commit
a942afe (#850) introduced without ever defining:
_active_username 1 reference, 0 definitions
_active_profile_name 1 reference, 0 definitions
_current_session_id 1 reference, 0 definitions
_author_mutation_block 1 reference, 0 definitions
Evaluating the call arguments therefore raised
NameError: name '_active_username' is not defined
before author_issue_bootstrap.bootstrap_author_issue_worktree was entered, so
the capability was unusable for every caller including dry_run=true. The fourth
name, _author_mutation_block, sits on the reviewer-stop refusal path and was
found by the generalised regression test rather than by the original report.
The defect was unreachable until PR #942 (#941) wired the bootstrap scope into
workflow_scope_guard: before that, verify_preflight_purity refused first with
missing_issue_worktree, masking it.
Changes:
* _active_username reads the immutable #714 session context that gitea_whoami
seeds — the identity pin every other mutation gate already consults. An
unbound context returns None so callers fail closed rather than acting as an
unverified actor; a profile's expected_username is never substituted.
* _active_profile_name prefers the live get_profile() and falls back to the
bound session context only when the profile cannot be read.
* _current_session_id mints the same "<profile>-<pid>-<hex>" shape as the three
pre-existing lease call sites, bound once per process so repeated calls
describe one session instead of a fresh owner per call, which would make
lease-ownership comparisons unsatisfiable. None is never memoised.
* _author_mutation_block returns the uniform refusal shape the other author
mutations already return for the same check_author_mutation_after_reviewer_stop
block.
No guard, signature, or permission changes. Wrong-role, wrong-profile,
wrong-identity, stale-runtime, expected-base and workflow-scope enforcement all
still gate the call; the helpers only supply values the service then validates
fail-closed (missing_active_identity / missing_active_profile /
missing_owner_session / stale_concurrency_pin).
Regression: tests/test_issue_943_runtime_context_helpers.py (27 tests). The
generalised test resolves every global the wrapper's body references against
module globals and builtins, so the next missing reference fails too rather
than only the three named here — that test is what found
_author_mutation_block. Coverage also coversdry-run reaching and completing the
service with helper-produced bindings, dry-run leaving no branch, worktree,
assignment or lease, apply reaching its intended transition, each fail-closed
mismatch, expected-base mismatch, and the #941/PR #942 scope wiring.
Pre-fix 20 failed / 13 passed against unmodified aab54d48; post-fix 27 passed.
Targeted bootstrap and guard suites: 245 passed, 59 subtests.
Full suite: 28 failed, 5552 passed, 6 skipped, 1006 subtests — the 28 are the
standing baseline, every one of which also fails on the unmodified base.
Closes #943
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_013ygVZQLbbhJTChuVuLaJWb
This commit is contained in:
@@ -3580,6 +3580,64 @@ def _authenticated_username(host: str):
|
||||
return user
|
||||
|
||||
|
||||
# #943: process-local session identifier. The pre-existing call sites that mint a
|
||||
# session id (workflow dashboard, lease adopt, lease reclaim) all build the same
|
||||
# "<profile>-<pid>-<hex>" shape when the caller supplies none. Binding it once per
|
||||
# process keeps lease-ownership comparisons stable for the life of the session
|
||||
# instead of minting a fresh identifier — and therefore a fresh owner — on every
|
||||
# call. Process-local only, never shared to a file (same rationale as the
|
||||
# immutable session context in session_context_binding).
|
||||
_ACTIVE_SESSION_ID: str | None = None
|
||||
|
||||
|
||||
def _active_username() -> str | None:
|
||||
"""Authenticated identity bound to this session, or None when unbound.
|
||||
|
||||
Reads the immutable #714 session context that ``gitea_whoami`` seeds; it is
|
||||
the authoritative identity pin every other mutation gate already consults.
|
||||
Never re-derives or fabricates an identity: an unbound context returns None
|
||||
so callers fail closed instead of acting as an unverified actor.
|
||||
"""
|
||||
ctx = session_ctx.get_session_context() or {}
|
||||
return ((ctx.get("identity") or "").strip()) or None
|
||||
|
||||
|
||||
def _active_profile_name() -> str | None:
|
||||
"""Active runtime profile name, or None when it cannot be determined.
|
||||
|
||||
The live profile is authoritative (``get_profile``); the bound session
|
||||
context is consulted only when the profile cannot be read, so the reported
|
||||
name always describes the profile actually serving this process.
|
||||
"""
|
||||
try:
|
||||
profile = get_profile() or {}
|
||||
except Exception:
|
||||
profile = {}
|
||||
name = (profile.get("profile_name") or "").strip()
|
||||
if name:
|
||||
return name
|
||||
ctx = session_ctx.get_session_context() or {}
|
||||
return ((ctx.get("profile_name") or "").strip()) or None
|
||||
|
||||
|
||||
def _current_session_id() -> str | None:
|
||||
"""Session identifier for this process, or None when the profile is unknown.
|
||||
|
||||
Uses the same "<profile>-<pid>-<hex>" shape as the existing lease call
|
||||
sites. Bound once per process so repeated calls describe one session; fails
|
||||
soft to None when the profile is undeterminable, letting callers fail closed
|
||||
rather than inventing an owner.
|
||||
"""
|
||||
global _ACTIVE_SESSION_ID
|
||||
if _ACTIVE_SESSION_ID:
|
||||
return _ACTIVE_SESSION_ID
|
||||
profile_name = _active_profile_name()
|
||||
if not profile_name:
|
||||
return None
|
||||
_ACTIVE_SESSION_ID = f"{profile_name}-{os.getpid()}-{uuid.uuid4().hex[:8]}"
|
||||
return _ACTIVE_SESSION_ID
|
||||
|
||||
|
||||
def _authenticated_actor(host: str) -> dict:
|
||||
"""Resolve the authenticated actor's stable identity (#709 F7 review 438).
|
||||
|
||||
@@ -9902,6 +9960,24 @@ def gitea_commit_files(
|
||||
}
|
||||
|
||||
|
||||
def _author_mutation_block(reasons: list[str], **extra) -> dict:
|
||||
"""Uniform fail-closed shape for an author mutation refused after a reviewer stop.
|
||||
|
||||
#943: referenced by ``gitea_bootstrap_author_issue_worktree`` and never
|
||||
defined, so the reviewer-stop refusal path raised ``NameError`` instead of
|
||||
returning its refusal. Mirrors the inline shape the other author mutations
|
||||
return for the same ``check_author_mutation_after_reviewer_stop`` block.
|
||||
"""
|
||||
payload = {
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"outcome": "REFUSED",
|
||||
"reasons": reasons,
|
||||
}
|
||||
payload.update(extra)
|
||||
return payload
|
||||
|
||||
|
||||
def _publication_block(reasons: list[str], **extra) -> dict:
|
||||
"""Uniform fail-closed shape for publication refusals (#812 AC20)."""
|
||||
payload = {
|
||||
|
||||
Reference in New Issue
Block a user