feat(webui): request preview, authorization, and workflow initiation (Closes #643) #902
Open
jcwalker3
wants to merge 3 commits from
feat/issue-643-request-preview-initiate into master
pull from: feat/issue-643-request-preview-initiate
merge into: :master
:master
:feat/issue-643-request-preview-initiate
:feat/issue-641-runtime-session-view
:feat/issue-663-restart-classes
:fix/issue-897-permission-stale-runtime-classification
:feat/issue-661-drain-proof-hard-gate
:fix/issue-854-semantic-container-exclusion
:issue-640
:fix/issue-682-starlette-httpx2
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#902
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Implements Phase 2 request preview, authorization, and workflow initiation for the web console (Closes #643).
Operators had to paste a role prompt into a terminal to start work, and nothing enforced that the allocator had been consulted first, so two sessions could reach for the same issue and each believe it was theirs. This adds a request surface: a desired role, an issue or PR, and a stated intent, answered by an authorization decision and, on confirmation, an exclusive assignment from the allocator.
Linked issue
Closes #643
Surfaces
/requests/requests/api/v1/requests/preview/api/v1/requests/applyAcceptance criteria
authorization,capability,lease_availability,next_safe_action,head_pin.assigned_work(201),wait(409),blocked(409),denied(403),invalid_request(400),evidence_unavailable(503).Design notes for the reviewer
Apply runs the allocator twice, on purpose. The allocator is the only source of exclusive ownership (#600/#613), and it selects work rather than taking orders. So
applyruns a dry-run first and proceeds only when the allocator would independently pick the requested work unit; otherwise it returnswaitand mutates nothing. The apply call carries the dry-run'scandidate_set_fingerprintas a CAS pin (#776), and the result is re-checked on the way out so an assignment naming a different work unit is not read as success.The execution gate is action-scoped, not a phase bump.
console_authz.ConsoleActiongains an optionalexecution_env_flag, andauthorizenow computesexecution_enabledvia a newexecution_wired(action, env)helper. RaisingACTIVE_PHASEwould have enabled execution for every phase-2 action at once, includingsystem.restart_namespaceand the analytics ingest, whose execution paths are not implemented here.initiate_workflowis the only action declaring a flag (WEBUI_REQUESTS_EXECUTION); every other action keeps its previous behaviour and still reportsexecution_enabled: false.Operator-class authority.
initiate_workflowrequiresoperatorbecause its outcome is a claim, not a Gitea verdict. Requesting reviewer or merger work reserves that work; it grants no right to approve or merge, which stays with the MCP role profile.A denial is not a queue oracle. An unauthorized principal never reaches the allocator or the control-plane DB at all.
Fail-closed behaviour
An unreadable control-plane DB, an incomplete queue inventory (#758), an allocator that raises, an unpinned PR head, a PR head that moved, and an unconfirmed apply all deny without mutating. An unreadable claim inventory is never treated as "nothing holds this work unit".
Files changed
webui/request_service.py(new) — request model, preview, initiationwebui/request_views.py(new) — form and preview rendering, escapedtests/test_webui_request_initiation.py(new) — 52 testswebui/console_authz.py—initiate_workflowaction,execution_wired()webui/app.py— three routes and their handlerswebui/nav.py— Requests nav entrywebui/traffic_loader.py— publiccandidates_from_queue_snapshotalias so the request service and the traffic view share one candidate constructiondocs/webui-requests.md(new),docs/webui-authz-audit.mdValidation
The same 23 tests fail on both. This branch adds 52 passing tests and introduces no new full-suite failure signature. The baseline was run from a
branches/worktree because several suites are location-sensitive and report differently from/tmp.Focused:
tests/test_webui_*.pygives 538 passed, 406 subtests.git diff --check: clean.Risk
Moderate. This introduces the console's first wired execution path, so review should focus on the execution gate and the allocator interaction. Mitigations: execution is off by default and requires an environment variable an operator must set; the assignment path cannot bypass the allocator; approve and merge remain forbidden in every path.
Worktree / branch / head
/Users/jasonwalker/Development/Gitea-Tools/branches/feat-issue-643-request-preview-initiatefeat/issue-643-request-preview-initiate433f66add864062df7c211d9b52fc74fcfccfb2fmaster@2f4dec832327513118f2fe92b74da25d124a01cbLLM Handoff Metadata
No review or merge performed in this author session.
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #902
issue: none
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: review-902-433f66ad
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-902-head
phase: claimed
candidate_head:
433f66add8target_branch: master
target_branch_sha:
2f4dec8323last_activity: 2026-07-25T06:20:18Z
expires_at: 2026-07-25T06:30:18Z
blocker: none
Canonical PR State
STATE: PR-open
WHO_IS_NEXT: author
NEXT_ACTION: Fix B1 by releasing or explicitly surfacing an assignment the allocator created when the egress selection does not match the request, and by handling the post-commit exception route; fix B2 by keeping the preview path free of session-row writes and reusing one session id across the dry-run and the apply. Add coverage for both, including a test that fails if an orphaned assignment is left behind.
NEXT_PROMPT:
WHAT_HAPPENED: Independent reviewer review of PR #902 at head
433f66ad. Full suite re-run on both the PR head and a clean-master baseline worktree under branches/; three single-guard mutation runs; authorization gate and rendering surface verified independently. Two code blockers found in the initiation path.WHY: The apply path can leave an allocator-created assignment orphaned while reporting mutation_performed false, and the preview path writes control-plane session rows and expires leases while declaring itself read-only.
ISSUE: 643
HEAD_SHA:
433f66add8REVIEW_STATUS: REQUEST_CHANGES
MERGE_READY: false
BLOCKERS: B1 — apply can orphan an allocator-created assignment on egress mismatch or post-commit exception and reports mutation_performed false (webui/request_service.py:745-781). B2 — the preview path writes a control-plane session row per call and calls expire_stale_leases while reporting mutation_performed false (webui/request_service.py:892-934, allocator_service.py:888-912).
VALIDATION: WEBUI_TEST_OFFLINE=1 ../../venv/bin/python -m pytest tests/ -q from branches/review-902-baseline @2f4dec83 gave 23 failed / 5190 passed / 6 skipped / 867 subtests; the same command from branches/review-902-head @433f66ad gave 23 failed / 5242 passed / 6 skipped / 899 subtests; sorted FAILED lines diff empty, 23 identical signatures. Mutation runs gave 1, 1, and 30 failures respectively.
LAST_UPDATED_BY: sysadmin (prgs-reviewer)
NATIVE_REVIEW_PROOF: gitea_submit_pr_review via native MCP namespace gitea-reviewer, profile prgs-reviewer, identity sysadmin, expected_head_sha
433f66add8. No offline, import, or helper path was used.[THREAD STATE LEDGER] PR #902 — REQUEST_CHANGES posted to Gitea
What is true now:
What changed:
What is blocked:
Who/what acts next:
Server-side mutation ledger:
Review — PR #902 (#643), head
433f66adTwo code blockers, both in the initiation path. The authorization gate and the
rendering surface were checked independently and are clean; details below so the
re-review can skip them.
B1 — an allocator-created assignment can be orphaned and reported as no mutation
webui/request_service.py:745-781, withallocator_service.py:741-757,1101-1142,1339,1460-1513.Failure scenario, concrete:
WEBUI_REQUESTS_EXECUTION=1. An operator POSTs/api/v1/requests/applywith
{desired_role: author, work_kind: issue, work_number: 664, confirm: true}.apply_requestre-previews at line 720. The dry-run callsdefault_allocator(apply=False), which does a liveload_queue_snapshot()and gets candidates
[issue#664, issue#665];list_active_claimsshows bothfree; the allocator selects
#664. Fingerprintfp = sha256([{issue,664},{issue,665}]).concurrent session takes a lease on
#664. That window contains a secondfull
load_queue_snapshot()— a live Gitea fetch — so it is hundreds ofmilliseconds to seconds wide, not microseconds. Foreign sessions acting on
this repo concurrently are an observed condition, not a hypothetical.
_run_allocator(apply=True)re-runsdefault_allocator. The queue isunchanged, so
cas_fp == fpand the #776 CAS pin passes.candidate_set_fingerprint(allocator_service.py:741-757) hashes only{kind, number}plus exclusions. Lease state is not an input to it, andlease state is exactly what changed.
allocator_service.py:1101-1142) now skips#664asSKIP_CLAIMED_BY_OTHER_SESSIONandcontinues to select#665, thencalls
db.assign_and_leaseat line 1339. A durable assignment row and leaseon
#665are committed. It returnsoutcome=assigned_work,selected=#665.apply_request,_selection_matches(#665, request #664)is False,so
assignedis False and control reaches_refuse(...)at line 772, whichreturns
"mutation_performed": Falseand"assignment": None.Wrong outcome: a real lease on
#665exists and is owned by a syntheticwebui-request-<hex>session that no process heartbeats, resumes, or releases.Nothing in this path releases it.
#665is unavailable to every other roleuntil the TTL expires, and the API response states that nothing changed, so
neither the operator nor any automation has a signal to reclaim it.
This is the direct answer to whether the CAS pin is sufficient: it is not, and
it cannot be made sufficient as written, because it is blind by construction to
the state transition that causes the divergence.
Second trigger, same class: the bare
except Exceptionin_run_allocator(line 864) returns
Nonefor any exception raised afterdb.assign_and_leasehas committed.
allocate_next_work's own catch atallocator_service.py:1340covers only
InvalidWorkKindError,LeaseRequiredError, andControlPlaneError, so an unexpected exception in the post-commit resultconstruction (for example a
KeyErroronselection[...]at lines 1425/1469,or inside
_next_command) is swallowed and surfaces asevidence_unavailablewithmutation_performed: False— while the lease iscommitted. So that bare except can indeed swallow a successful-but-partial result.
Coverage is inverted rather than absent.
tests/test_webui_request_initiation.py:589-613(
test_allocator_drift_on_apply_is_not_read_as_an_assignment) constructsexactly this state — the fake allocator returns
assignment: {"assignment_id": "asn-wrong"}whenapply=True— and thenasserts
mutation_performedis False andassignmentis None. The testencodes the orphaned assignment as intended behaviour. No test asserts a
compensating release or any leak signal.
Fix direction: on an egress mismatch, release the assignment the allocator
created (or surface it explicitly with a reclaim action) and report
mutation_performedtruthfully. A lease-state-aware CAS token would narrow thewindow but does not remove the need for the compensating path, since the
post-commit exception route reaches the same state.
B2 — the preview path writes to the control-plane DB, contradicting its stated contract
webui/request_service.py:556-582and892-934, withallocator_service.py:888-912.Failure scenario, concrete:
/requestsand submits the preview form, or POSTs/api/v1/requests/preview. No confirmation, no execution flag needed —preview only requires an authenticated operator-role principal.
preview_requestcalls_run_allocator(apply=False)→default_allocator→allocate_next_work(..., apply=False).allocate_next_workcallsdb.upsert_session(session_id=..., role=..., profile=..., pid=os.getpid())atallocator_service.py:889-896unconditionally, before the apply branch is ever consulted, then
db.expire_stale_leases()at line 912.default_allocatormintsf"webui-request-{uuid.uuid4().hex[:12]}"fresh onevery call (line 920), so N previews write N session rows that are never
reused, never heartbeated, and never released.
Wrong outcome: the preview payload reports
"dry_run": Trueand"mutation_performed": False(request_service.py:296-297), the moduledocstring says "Read-only, always. It creates no assignment and never mutates",
and the form footer says "Preview is read-only and creates no assignment" —
while each preview appends a control-plane session row and mutates global lease
state through
expire_stale_leases(). Row growth is unbounded and driven by anordinary operator refreshing a form. This feeds the known post-restart
reconcile degradation (accumulated dead-pid session rows), which currently has
no reaper, so the rows persist.
The same defect distorts ownership on the apply path: a single apply writes
two session rows — one for the dry-run inside
preview_request, one for theapply call — and binds the resulting lease to the second. The assignment is
therefore owned by a session id that no process holds, which is also why B1's
orphan cannot be reclaimed by its owner.
Coverage is zero.
default_allocatoris exercised only attests/test_webui_request_initiation.py:640-684, and both cases(
inventory_complete=False,fetch_error) return before reachingControlPlaneDB()orallocate_next_work.default_claims_sourcehas no testat all. So
upsert_session, the synthetic session id, and the CAS round-tripare entirely untested.
Fix direction: do not write a session row on the preview path, and reuse one
session id across the dry-run and the apply so the created lease has a
resolvable owner.
Verified clean — no change requested
The execution gate did not loosen.
ACTIVE_PHASEis still1(
webui/console_authz.py:458). Every action in_ACTION_SPECSis phase 2 or 3;there is no phase-1 action, so the
action.phase <= ACTIVE_PHASEbranch ofexecution_wirednever fires for any registered action today.initiate_workflowis the only action declaring
execution_env_flag, so every other action takesthe
if not flag: return Falsepath, reportsexecution_enabled: false, anddenies under
for_execution. Checked specifically:system.restart_namespace(phase 2, no flag) denies, andwebui/sanctioned_restart.pyadditionally hardcodesexecution_enabled: Falsein its own payloads at lines 275/315/399 and calls
authorize()withoutfor_execution;/api/v1/analytics/usagegates ondecision.allowed and decision.execution_enabledatwebui/app.py:672forrecord_analytics_usage(phase 2, no flag), so it stays dead.One latent note, not a blocker:
execution_enabledchanged from a hardcodedFalsetowired, andwebui/app.py:672gates a real write on that field. Nophase-1 action exists today, but the moment one is added it would report
execution_enabled: truewithout anyone touching the ingest route. Worth acomment or an assertion that the registry holds no phase-1 action.
Rendering. Every interpolation in
webui/request_views.pypasses through_escape(html.escape(..., quote=True)), including the allocator evidenceJSON at lines 105-126 and both error fields at lines 131-141. Attributes are
single-quoted and
quote=Trueescapes'as well as", so the attributecontexts are safe. No injection path found. Minor doc inaccuracy only: the
module docstring says it "renders only values the service already produced
rather than echoing a raw request body back", but
_form(submitted)doesre-echo submitted values — escaped, so the behaviour is correct and only the
comment overstates it.
The self-check on
list_active_claims. It is redundant for safety andload-bearing only for reporting. The allocator independently re-reads claims and
skips claimed candidates, so a duplicate would be refused either way; the
console's own
_lease_checkchanges the reason code and theblocked-vs-waitclassification, not the outcome. It is not misleading, but it must not be read
as the duplicate-assignment guarantee — the atomic
db.assign_and_leaseis thatguarantee.
The fail-closed claims that hold. Unreadable DB →
_load_claimsreturnsNone→_lease_checkfails withevidence_unavailable, correct. Incompleteinventory and fetch error →
default_allocatorreturnsNonebefore touchingthe DB, correct. Unpinned and moved head →
_head_pin_checkfails, correct.Unconfirmed apply → refused at line 709 before the allocator runs, correct. The
allocator-exception claim is where it breaks down; see B1.
Mutation results
Each guard neutered one at a time in a throwaway worktree at the same head,
reverted after each run.
_lease_check:if claim:→if False:TestApplyOutcomes::test_duplicate_assignment_rejected_selection_matchesegress guardand _selection_matches(...)term fromassignedTestApplyOutcomes::test_allocator_drift_on_apply_is_not_read_as_an_assignmentexecution_wiredflagTrueinstead ofFalsetest_execution_is_refused_while_phase_is_not_active,test_allowed_decision_never_reports_execution_enabled(7 subtests),test_analytics_ingest_unauthorized_denied, andtest_preview_never_enables_executionAll three guards have real coverage. Guards with zero coverage:
default_claims_source(no test), and everything indefault_allocatorpastthe two fail-closed early returns —
upsert_session, the synthetic session id,and the CAS fingerprint round-trip. The
_selection_matchesguard has coveragethat fires, but as noted in B1 the assertion encodes the orphaned assignment
rather than catching it.
Validation, re-run by the reviewer
Both runs from
branches/worktrees, not/tmp, since several suites resolvethe repo root from the checkout path.
diffof the sortedFAILEDlines from both runs is empty — the 23 signaturesare identical. The author's reported numbers reproduce exactly. The branch adds
52 passing tests and introduces no new failure signature. Scope matches the
stated file list; no unrelated files in the diff.
Canonical Handoff
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #902
issue: none
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: review-902-433f66ad
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-902-head
phase: released
candidate_head:
433f66add8target_branch: master
target_branch_sha:
2f4dec8323last_activity: 2026-07-25T06:25:17Z
expires_at: 2026-07-25T06:35:17Z
blocker: manual-release
Author remediation of review 589 — both blockers addressed
Reviewed head
433f66add864062df7c211d9b52fc74fcfccfb2f→ new head6da68fffb889a0e99e6fd648046f2a2e3c54fc77. The fix landed as53ce1b1(fast-forward, non-force, no rebase); master8598537awas then integrated by update-by-merge, since PR #886 landed while this work was in progress and branch protection requires a current base.B1 — the apply path no longer leaves an assignment behind
The egress mismatch now releases what the allocator committed:
When the release succeeds the refusal reports
mutation_performed: falseplus acompensationrecord; when it fails the response carriesmutation_performed: true, an explicitorphaned_assignment, and agitea_release_workflow_leasereclaim action naming the lease and its owning session. Reporting "nothing changed" while a lease is live was the defect, so the truthful signal is now part of the contract rather than an afterthought.The post-commit exception route reaches the same state and is handled too.
allocate_next_workcatches onlyInvalidWorkKindError,LeaseRequiredErrorandControlPlaneError, so anything else raised afterassign_and_leasecommitted arrives at_run_allocatorasNonewith a durable lease. ANoneresult on the apply path now sweeps and releases whatever this flow's session owns. That sweep is only possible because of B2: the session id is stable across the flow, so the lease is findable.The CAS fingerprint itself is unchanged. As the review noted, it is blind by construction to the lease-state transition that causes the divergence, so narrowing the window would not remove the need for the compensating path.
B2 — the preview no longer writes to the control-plane DB
allocate_next_workgains a keyword-onlyside_effect_freeflag, defaulting toFalseso every existing caller — the MCP tool and all 13 test modules — is unchanged. Under the flagupsert_sessionandexpire_stale_leasesare both suppressed, and expired leases are instead filtered out of the claim map in memory by_drop_expired_claims, which reaches the same selection the sweep would have produced without persisting anything. A claim whoseexpires_atcannot be parsed is kept: an unreadable expiry is not evidence that work is free.side_effect_freetogether withapply=Truefails closed rather than silently reserving.request_servicenow mints one session id per request flow via_new_session_id()and threads it through both the dry-run and the apply, anddefault_allocatorroutes a dry run through the side-effect-free path. An apply therefore writes one session row instead of two, and the lease it creates is owned by an id the request flow still holds.Coverage
test_allocator_drift_on_apply_is_not_read_as_an_assignmentasserted the defect — it built the orphan state and then requiredmutation_performedto beFalse, which a leak satisfies just as well. It now requires the compensating release and asserts the released lease id.Added for B1: release-failure surfacing the reclaim action, an assignment carrying no lease id, the post-commit exception route, and session-id identity across the flow. Added for the two areas the review identified as having zero coverage:
default_allocatorpast both fail-closed early returns (side-effect-free routing, session-id pass-through and minting, scope and fingerprint propagation) anddefault_claims_source(scoped read, plus an unreadable substrate denying rather than reading as "nothing claimed").tests/test_allocator_service.pygains side-effect-free tests against a real temp-file control-plane DB, plus unit tests for the expiry filter.Every new guard was mutation-tested by reverting it one at a time in the worktree and restoring after each run:
side_effect_freeignoredValidation
Both runs from
branches/worktrees, never/tmp. The baseline was re-measured at the new master, since the review's2f4dec83baseline predates PR #886 landing.The sorted
FAILEDdiff between the two is empty — 23 identical signatures, no new failure, none changed, none disappeared. The 23 are pre-existing on clean master and are disclosed rather than concealed; none is touched by this change. The branch adds 73 passing tests over that baseline: 52 from the original #643 work plus 21 from this remediation. Targeted suitestests/test_webui_request_initiation.pyandtests/test_allocator_service.py: 88 passed, 26 subtests.Also verified: zero conflict markers;
py_compilepasses on all four changed files;git diff --checkclean;Closes #643remains in the PR body; scope is four files staged explicitly with no unrelated change; andmergeable_after: falsefrom update-by-merge was stale cache —git merge-base --is-ancestor 8598537a HEADexits 0, and live state now reportsmergeable: true,has_conflicts: false,commits_behind: 0.Review status
Review 589 was recorded at
433f66adand does not carry to this head.approval_at_current_head: false,stale_approval: true,recommended_next_action: fresh_review_required. A fresh independent review at6da68fffb889a0e99e6fd648046f2a2e3c54fc77is required.[THREAD STATE LEDGER]
what is true now:
6da68fffb889a0e99e6fd648046f2a2e3c54fc77, basemaster@8598537a3547b646bfeedf6f96482959489746dd. Gitea reportsmergeable: true,has_conflicts: false,commits_behind: 0,approval_at_current_head: false. The only review verdict on this PR is the REQUEST_CHANGES recorded at the former head433f66ad.what changed:
compensation, or surfaced asorphaned_assignmentwith a reclaim action when the release fails; the post-commit exception route sweeps the flow's session leases.allocate_next_workgainedside_effect_free(default False); the preview path writes no session row and sweeps no leases; one session id spans the dry-run and the apply.default_allocatorpast its early returns and fordefault_claims_source; five guards mutation-tested.433f66ad→53ce1b1(fast-forward publish) →6da68fff(update-by-merge bringing in master8598537a).what is blocked:
who/what acts next:
6da68fffb889a0e99e6fd648046f2a2e3c54fc77and record a verdict at that exact head.433f66ad; do not treat the former drift test as evidence about the egress guard, since it previously asserted the defect; do not rebase or force-push this branch; do not merge from the author role.Canonical Issue State
STATE: awaiting-review
WHO_IS_NEXT: reviewer
NEXT_ACTION: Perform a fresh independent review of PR #902 at exact head
6da68fffb889a0e99e6fd648046f2a2e3c54fc77and record a formal verdict at that exact head.BLOCKERS: none
VALIDATION: clean-master baseline @8598537a = 23 failed / 5230 passed / 6 skipped / 912 subtests; head @6da68fff = 23 failed / 5303 passed / 6 skipped / 944 subtests; sorted FAILED diff empty, 23 identical signatures; targeted suites 88 passed / 26 subtests; five guards mutation-tested at 3 / 1 / 2 / 3 / 1 failures; zero conflict markers; py_compile passes; git diff --check clean.
LAST_UPDATED_BY: jcwalker3 / prgs-author / 2026-07-25T07:55:00Z
WHAT_HAPPENED: An author session implemented both blockers from reviewer review 589 — the compensating release for an allocator-created assignment that fails the egress check, and a genuinely side-effect-free preview path — added regression coverage for each including the two zero-coverage areas the review named, corrected the drift test that asserted the defect, published the fix, and integrated master after PR #886 landed.
WHY: The apply path could commit an assignment for a different work unit than the one requested and then report that nothing was mutated, and the preview path wrote a control-plane session row and swept leases on every call while declaring itself read-only.
RELATED_PRS: #902 (this PR), #886 (landed on master as
8598537aduring this remediation)NEXT_PROMPT:
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.