fix: rank complete allocator inventory and resolve declared dependencies (Closes #758) #761

Merged
sysadmin merged 1 commits from fix/issue-758-allocator-dependency-aware into master 2026-07-19 13:32:18 -05:00
Owner

Closes #758

Head SHA: ad59053cd7a2e7ecec60901666160a73eaa36efa
Base: master @ 854818e65a2741ba0740773fd9593d5e1f3d5751

What changed

Two independent allocator defects, fixed together because either one alone still lets the queue select an ineligible issue.

Defect 1 — candidate truncation before ranking (AC1, AC2, AC3)

_allocator_candidates_from_gitea fetched the complete open inventory through api_get_all, then sliced it with issues[: max(1, int(limit))] before constructing any WorkCandidate. Every status:ready issue scores priority 20, and sort_candidates breaks that tie on lowest number, so the slice — not the ranking — chose the winner. Two callers asking the same question at different limit values received different answers.

Candidate construction now consumes the full listing. limit bounds the reported skip list only; the result carries limit_applies_to, skipped_total, and skipped_report_truncated so a shortened report is never read as full coverage.

A failed listing sets inventory_complete=False and the tool fails closed rather than ranking a partial set.

Defect 2 — dependency state inferred from body substrings (AC4-AC7)

Only blocked on # and downstream of # were recognized. The canonical Depends: #N, #N field matched neither, so dependency_unmet stayed False and dependency-blocked issues were emitted as assignable.

New module allocator_dependencies.py:

  • parse_dependency_refs extracts the canonical declaration into structured references, stopping at the next separator or sibling field, so references belonging to other linkage fields are never captured. The legacy blocked on # marker still parses.
  • resolve_dependency_state resolves each reference against live issue state — open means unmet, closed means met, unavailable or raising means unmet (fail closed).

The complete open listing already proves openness, so no extra call is made for open references; anything absent from it is confirmed by a cached targeted lookup instead of being assumed closed.

Fall-through and policy (AC8, AC10)

The selection loop already advanced past a skipped candidate; with dependency state now resolved correctly that fall-through actually engages, and is covered by a regression. SELECTION_POLICY states the ranking and tie-break explicitly and is reported in the result.

Evidence

Verified against master 854818e65a2741ba0740773fd9593d5e1f3d5751.

The defect reproduces live: a dry run of gitea_allocate_next_work(role=author, apply=false) returned candidate_count: 50 and selected issue #644, whose body declares three dependencies that are all still open. Under this branch that candidate is marked ineligible and allocation continues.

Tests: 35 new cases.

  • tests/test_allocator_dependencies.py (23) — parser forms, malformed input, open/closed/mixed/unavailable resolution, >50-candidate limit-invariant selection, apply-vs-preview parity, and a guard proving a pre-ranking slice would change the winner.
  • tests/test_allocator_inventory_mcp.py (12) — MCP-level gitea_allocate_next_work end to end against a faked Gitea API: 73-candidate inventory, identical winner at limit=1 and limit=300, lookup caching, and fail-closed incomplete inventory.

Full suite: 3672 passed, 2 failed, 6 skipped, 431 subtests passed.

Both failures are pre-existing on master and untouched by this branch — test_issue_702_review_findings_f1_f6.py::TestF1RecoveryBeforeTerminalProbe::test_removed_worktree_recovers_before_probe and test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue. Both were reproduced identically in a detached worktree at unmodified 854818e before this work was committed; neither touches allocator code.

Boundary

No issue number is special-cased (AC14). Issues #642, #633, #634, #605, and #644 were read as evidence only; their scope, labels, and review state are unchanged. #628 keeps ownership of the durable dependency table; this PR corrects the present-day loader and selection path only. Dry-run no-mutation behavior and the lock-before-edit safeguards are unchanged (AC13).

Canonical PR State

  • STATE: ready-for-review
  • WHO_IS_NEXT: reviewer
  • NEXT_ACTION: Independent review of head ad59053cd7 against #758 AC1-AC14
  • NEXT_PROMPT: Review this PR at the pinned head; submit the formal verdict; stop
Closes #758 Head SHA: `ad59053cd7a2e7ecec60901666160a73eaa36efa` Base: `master` @ `854818e65a2741ba0740773fd9593d5e1f3d5751` ## What changed Two independent allocator defects, fixed together because either one alone still lets the queue select an ineligible issue. ### Defect 1 — candidate truncation before ranking (AC1, AC2, AC3) `_allocator_candidates_from_gitea` fetched the complete open inventory through `api_get_all`, then sliced it with `issues[: max(1, int(limit))]` before constructing any `WorkCandidate`. Every `status:ready` issue scores priority 20, and `sort_candidates` breaks that tie on lowest number, so the slice — not the ranking — chose the winner. Two callers asking the same question at different `limit` values received different answers. Candidate construction now consumes the full listing. `limit` bounds the reported skip list only; the result carries `limit_applies_to`, `skipped_total`, and `skipped_report_truncated` so a shortened report is never read as full coverage. A failed listing sets `inventory_complete=False` and the tool fails closed rather than ranking a partial set. ### Defect 2 — dependency state inferred from body substrings (AC4-AC7) Only `blocked on #` and `downstream of #` were recognized. The canonical `Depends: #N, #N` field matched neither, so `dependency_unmet` stayed `False` and dependency-blocked issues were emitted as assignable. New module `allocator_dependencies.py`: * `parse_dependency_refs` extracts the canonical declaration into structured references, stopping at the next separator or sibling field, so references belonging to other linkage fields are never captured. The legacy `blocked on #` marker still parses. * `resolve_dependency_state` resolves each reference against **live issue state** — open means unmet, closed means met, unavailable or raising means unmet (fail closed). The complete open listing already proves openness, so no extra call is made for open references; anything absent from it is confirmed by a cached targeted lookup instead of being assumed closed. ### Fall-through and policy (AC8, AC10) The selection loop already advanced past a skipped candidate; with dependency state now resolved correctly that fall-through actually engages, and is covered by a regression. `SELECTION_POLICY` states the ranking and tie-break explicitly and is reported in the result. ## Evidence Verified against master `854818e65a2741ba0740773fd9593d5e1f3d5751`. The defect reproduces live: a dry run of `gitea_allocate_next_work(role=author, apply=false)` returned `candidate_count: 50` and selected issue #644, whose body declares three dependencies that are all still open. Under this branch that candidate is marked ineligible and allocation continues. **Tests:** 35 new cases. * `tests/test_allocator_dependencies.py` (23) — parser forms, malformed input, open/closed/mixed/unavailable resolution, >50-candidate limit-invariant selection, apply-vs-preview parity, and a guard proving a pre-ranking slice would change the winner. * `tests/test_allocator_inventory_mcp.py` (12) — MCP-level `gitea_allocate_next_work` end to end against a faked Gitea API: 73-candidate inventory, identical winner at `limit=1` and `limit=300`, lookup caching, and fail-closed incomplete inventory. **Full suite:** 3672 passed, 2 failed, 6 skipped, 431 subtests passed. Both failures are pre-existing on master and untouched by this branch — `test_issue_702_review_findings_f1_f6.py::TestF1RecoveryBeforeTerminalProbe::test_removed_worktree_recovers_before_probe` and `test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue`. Both were reproduced identically in a detached worktree at unmodified `854818e` before this work was committed; neither touches allocator code. ## Boundary No issue number is special-cased (AC14). Issues #642, #633, #634, #605, and #644 were read as evidence only; their scope, labels, and review state are unchanged. #628 keeps ownership of the durable dependency table; this PR corrects the present-day loader and selection path only. Dry-run no-mutation behavior and the lock-before-edit safeguards are unchanged (AC13). ## Canonical PR State * STATE: ready-for-review * WHO_IS_NEXT: reviewer * NEXT_ACTION: Independent review of head ad59053cd7a2e7ecec60901666160a73eaa36efa against #758 AC1-AC14 * NEXT_PROMPT: Review this PR at the pinned head; submit the formal verdict; stop
jcwalker3 added 1 commit 2026-07-19 13:13:47 -05:00
The author allocator could select an ineligible issue for two independent
reasons, both fixed here.

Defect 1 — candidate truncation before ranking. The loader fetched the
complete open inventory via api_get_all, then sliced it to `limit` items
before constructing any WorkCandidate. Because every status:ready issue
ties at priority 20 and the tie breaks on lowest number, the slice — not
the ranking — decided the winner, so the same query returned different
answers at different `limit` values. Candidate construction now consumes
the full listing; `limit` bounds the reported skip list only, and any
such truncation is reported explicitly rather than silently.

Defect 2 — dependency state inferred from body substrings. Only
"blocked on #" and "downstream of #" were recognized, so the repository's
canonical `Depends: #N, #N` field never set dependency_unmet and
dependency-blocked issues were emitted as eligible. The new
allocator_dependencies module parses the canonical declaration into
structured references and resolves each against live issue state: open
means unmet, closed means met, and unavailable evidence fails closed. The
complete open listing proves openness without extra calls; anything
absent from it is confirmed by a cached targeted lookup instead of being
assumed closed. The legacy "blocked on #" marker still parses.

A failed listing now marks the inventory incomplete and the tool fails
closed instead of ranking a partial set.

Selection already advanced past a skipped candidate; with dependencies
resolved correctly that fall-through now actually engages, and is covered
by a regression.

Tests: 35 new cases across parser, resolver, loader, and an MCP-level
gitea_allocate_next_work regression, including limit-invariant selection
over a 73-candidate inventory and a guard proving pre-ranking truncation
would change the winner. No issue number is special-cased.
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #761
issue: #758
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 57940-e22d2084639d
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-fix-issue-758-allocator-dependency-aware
phase: claimed
candidate_head: ad59053cd7
target_branch: master
target_branch_sha: 854818e65a
last_activity: 2026-07-19T18:22:02Z
expires_at: 2026-07-19T18:32:02Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #761 issue: #758 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 57940-e22d2084639d worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-fix-issue-758-allocator-dependency-aware phase: claimed candidate_head: ad59053cd7a2e7ecec60901666160a73eaa36efa target_branch: master target_branch_sha: 854818e65a2741ba0740773fd9593d5e1f3d5751 last_activity: 2026-07-19T18:22:02Z expires_at: 2026-07-19T18:32:02Z blocker: none
sysadmin approved these changes 2026-07-19 13:25:39 -05:00
sysadmin left a comment
Owner

Independent review — APPROVE

Pinned head reviewed: ad59053cd7a2e7ecec60901666160a73eaa36efa
Base: master @ 854818e65a2741ba0740773fd9593d5e1f3d5751
Issue: #758
Reviewer: sysadmin / prgs-reviewer (not author jcwalker3)
Lease: session 57940-e22d2084639d, comment 12940
Worktree: branches/review-fix-issue-758-allocator-dependency-aware (detached, clean, HEAD==pin)

Acceptance criteria (AC1–AC14)

AC Verdict Evidence
AC1 Complete inventory ranked PASS loader iterates full lists; limit removed from loader; MCP test builds 73 candidates
AC2 Limit cannot alter winner PASS limit only truncates reported skip list; winner identical at limit 1 and 300
AC3 Incomplete inventory fails closed PASS list failure sets inventory_complete=False and refuses assignment
AC4 Canonical Depends parsed PASS parse_dependency_refs for Depends/Depends on; stops at Related/Parent
AC5 Live dependency evidence PASS open set from listing plus cached targeted lookup
AC6 Open deps ineligible PASS dependency_unmet plus classify_skip plus fall-through tests
AC7 Unavailable fails closed PASS None/raising lookup becomes unavailable then ineligible
AC8 Fall-through PASS blocked first yields next eligible
AC9 Preview/apply parity PASS same loader/rank path; dry/apply select identically
AC10 Explicit ranking PASS SELECTION_POLICY reported; sort_candidates documented
AC11 >50 + limit invariance PASS 100-issue unit and 73-issue MCP inventories
AC12 Multi-dep coverage PASS open/closed/mixed/unavailable plus next-candidate
AC13 Dry-run/lock safeguards PASS apply=false default; gitea.read only; no lock changes
AC14 No issue special-cases PASS no production hard-coding of evidence issue numbers

Safety properties

  • Dependency-blocked issues (evidence shapes like #644/#642/#605 with open Depends) cannot be emitted as eligible while prerequisites remain open.
  • #758 has no Depends declaration and remains legitimately eligible.
  • Tests are non-vacuous against pre-fix truncation and substring-only dependency inference.
  • MCP tests exercise the real allocator path with a faked Gitea API.
  • Live limit cannot forge completeness or dependency satisfaction; errors fail closed to ineligible.
  • No author/reviewer/merger/reconciler permission widening.

Independent validation

  • git diff --check clean; py_compile clean on changed files.
  • Focused tests: 35 passed (test_allocator_dependencies + test_allocator_inventory_mcp).
  • Related allocator/lease/control-plane: 49 passed.
  • Broader allocator/lease/routing/anti-stomp slice: 186 passed.

Baseline failures disposition

Both claimed failures reproduce identically on PR head and detached base 854818e:

  1. test_issue_702_review_findings_f1_f6 removed-worktree recovery (GITEA_ACTIVE_WORKTREE env assertion)
  2. test_reconciler_supersession_close preflight org/repo kwargs mismatch

PR does not touch those paths. Label: pre-merge baseline-proven failure (not attributed to #761).

Review Metadata

  • LLM-Agent-SHA: llm-a7f3c91e2b40
  • LLM-Role: reviewer
  • Authenticated-Gitea-User: sysadmin
  • MCP-Profile: prgs-reviewer
  • Eligibility: passed
  • Workflow: skills/llm-project-workflow/workflows/review-merge-pr.md hash 263d0a6cb8a6 boundary clean

Verdict

APPROVE at head ad59053cd7.

Review and merge are separate roles. This approval is not merge authorization.

Canonical PR State

STATE: ready-to-merge
WHO_IS_NEXT: merger
NEXT_ACTION: Merger session adopts reviewer lease or acquires merger lease, re-validates pinned head ad59053cd7, and merges only with explicit operator authorization
NEXT_PROMPT:

Merge PR #761 (Closes #758) in Scaled-Tech-Consulting/Gitea-Tools using prgs-merger.
Pinned reviewed head: ad59053cd7a2e7ecec60901666160a73eaa36efa
Base at review: 854818e65a2741ba0740773fd9593d5e1f3d5751
Reviewer lease session: 57940-e22d2084639d comment 12940
Do not re-author; do not request changes unless head moved.
Confirm MERGE PR 761 only with explicit operator authorization.

WHAT_HAPPENED: Independent reviewer sysadmin/prgs-reviewer approved PR #761 at pinned head after full AC1-AC14 review and independent tests.
WHY: All acceptance criteria and safety properties for complete inventory ranking and dependency-aware eligibility are satisfied; formal approval submitted via native MCP; merge is a separate role.
ISSUE: 758
HEAD_SHA: ad59053cd7
REVIEW_STATUS: approved / approval_at_current_head
MERGE_READY: true
BLOCKERS: none for review; merge requires separate merger profile and explicit authorization
VALIDATION: focused 35 passed; related 49 passed; broader related slice 186 passed; two full-suite failures baseline-proven at 854818e; git diff --check clean; py_compile clean
NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; profile=prgs-reviewer; identity=sysadmin; session=57940-e22d2084639d; lease_comment=12940; tool=gitea_submit_pr_review
LAST_UPDATED_BY: sysadmin (prgs-reviewer)

## Independent review — APPROVE **Pinned head reviewed:** `ad59053cd7a2e7ecec60901666160a73eaa36efa` **Base:** `master` @ `854818e65a2741ba0740773fd9593d5e1f3d5751` **Issue:** #758 **Reviewer:** sysadmin / prgs-reviewer (not author jcwalker3) **Lease:** session `57940-e22d2084639d`, comment `12940` **Worktree:** `branches/review-fix-issue-758-allocator-dependency-aware` (detached, clean, HEAD==pin) ### Acceptance criteria (AC1–AC14) | AC | Verdict | Evidence | |----|---------|----------| | AC1 Complete inventory ranked | PASS | loader iterates full lists; limit removed from loader; MCP test builds 73 candidates | | AC2 Limit cannot alter winner | PASS | limit only truncates reported skip list; winner identical at limit 1 and 300 | | AC3 Incomplete inventory fails closed | PASS | list failure sets inventory_complete=False and refuses assignment | | AC4 Canonical Depends parsed | PASS | parse_dependency_refs for Depends/Depends on; stops at Related/Parent | | AC5 Live dependency evidence | PASS | open set from listing plus cached targeted lookup | | AC6 Open deps ineligible | PASS | dependency_unmet plus classify_skip plus fall-through tests | | AC7 Unavailable fails closed | PASS | None/raising lookup becomes unavailable then ineligible | | AC8 Fall-through | PASS | blocked first yields next eligible | | AC9 Preview/apply parity | PASS | same loader/rank path; dry/apply select identically | | AC10 Explicit ranking | PASS | SELECTION_POLICY reported; sort_candidates documented | | AC11 >50 + limit invariance | PASS | 100-issue unit and 73-issue MCP inventories | | AC12 Multi-dep coverage | PASS | open/closed/mixed/unavailable plus next-candidate | | AC13 Dry-run/lock safeguards | PASS | apply=false default; gitea.read only; no lock changes | | AC14 No issue special-cases | PASS | no production hard-coding of evidence issue numbers | ### Safety properties - Dependency-blocked issues (evidence shapes like #644/#642/#605 with open Depends) cannot be emitted as eligible while prerequisites remain open. - #758 has no Depends declaration and remains legitimately eligible. - Tests are non-vacuous against pre-fix truncation and substring-only dependency inference. - MCP tests exercise the real allocator path with a faked Gitea API. - Live limit cannot forge completeness or dependency satisfaction; errors fail closed to ineligible. - No author/reviewer/merger/reconciler permission widening. ### Independent validation - git diff --check clean; py_compile clean on changed files. - Focused tests: 35 passed (test_allocator_dependencies + test_allocator_inventory_mcp). - Related allocator/lease/control-plane: 49 passed. - Broader allocator/lease/routing/anti-stomp slice: 186 passed. ### Baseline failures disposition Both claimed failures reproduce identically on PR head and detached base 854818e: 1. test_issue_702_review_findings_f1_f6 removed-worktree recovery (GITEA_ACTIVE_WORKTREE env assertion) 2. test_reconciler_supersession_close preflight org/repo kwargs mismatch PR does not touch those paths. Label: pre-merge baseline-proven failure (not attributed to #761). ### Review Metadata - LLM-Agent-SHA: llm-a7f3c91e2b40 - LLM-Role: reviewer - Authenticated-Gitea-User: sysadmin - MCP-Profile: prgs-reviewer - Eligibility: passed - Workflow: skills/llm-project-workflow/workflows/review-merge-pr.md hash 263d0a6cb8a6 boundary clean ### Verdict APPROVE at head ad59053cd7a2e7ecec60901666160a73eaa36efa. Review and merge are separate roles. This approval is not merge authorization. ## Canonical PR State STATE: ready-to-merge WHO_IS_NEXT: merger NEXT_ACTION: Merger session adopts reviewer lease or acquires merger lease, re-validates pinned head ad59053cd7a2e7ecec60901666160a73eaa36efa, and merges only with explicit operator authorization NEXT_PROMPT: ```text Merge PR #761 (Closes #758) in Scaled-Tech-Consulting/Gitea-Tools using prgs-merger. Pinned reviewed head: ad59053cd7a2e7ecec60901666160a73eaa36efa Base at review: 854818e65a2741ba0740773fd9593d5e1f3d5751 Reviewer lease session: 57940-e22d2084639d comment 12940 Do not re-author; do not request changes unless head moved. Confirm MERGE PR 761 only with explicit operator authorization. ``` WHAT_HAPPENED: Independent reviewer sysadmin/prgs-reviewer approved PR #761 at pinned head after full AC1-AC14 review and independent tests. WHY: All acceptance criteria and safety properties for complete inventory ranking and dependency-aware eligibility are satisfied; formal approval submitted via native MCP; merge is a separate role. ISSUE: 758 HEAD_SHA: ad59053cd7a2e7ecec60901666160a73eaa36efa REVIEW_STATUS: approved / approval_at_current_head MERGE_READY: true BLOCKERS: none for review; merge requires separate merger profile and explicit authorization VALIDATION: focused 35 passed; related 49 passed; broader related slice 186 passed; two full-suite failures baseline-proven at 854818e; git diff --check clean; py_compile clean NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; profile=prgs-reviewer; identity=sysadmin; session=57940-e22d2084639d; lease_comment=12940; tool=gitea_submit_pr_review LAST_UPDATED_BY: sysadmin (prgs-reviewer)
Owner

adopted_at: 2026-07-19T18:31:22Z
adopted_by_identity: sysadmin
adopted_by_profile: prgs-merger
adopted_from_session_id: 57940-e22d2084639d
adopted_from_profile: prgs-reviewer
adopted_from_reviewer_identity: sysadmin
adopted_from_comment_id: 12940
adoption_reason: merger-handoff-approved-head

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #761
issue: #758
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 61137-01d254cefc92
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-issue-758-allocator-dependency-aware
phase: adopted
candidate_head: ad59053cd7
target_branch: master
target_branch_sha: 854818e65a
last_activity: 2026-07-19T18:31:22Z
expires_at: 2026-07-19T18:41:22Z
blocker: none

<!-- mcp-review-lease-adoption:v1 --> adopted_at: 2026-07-19T18:31:22Z adopted_by_identity: sysadmin adopted_by_profile: prgs-merger adopted_from_session_id: 57940-e22d2084639d adopted_from_profile: prgs-reviewer adopted_from_reviewer_identity: sysadmin adopted_from_comment_id: 12940 adoption_reason: merger-handoff-approved-head <!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #761 issue: #758 reviewer_identity: sysadmin profile: prgs-merger session_id: 61137-01d254cefc92 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-issue-758-allocator-dependency-aware phase: adopted candidate_head: ad59053cd7a2e7ecec60901666160a73eaa36efa target_branch: master target_branch_sha: 854818e65a2741ba0740773fd9593d5e1f3d5751 last_activity: 2026-07-19T18:31:22Z expires_at: 2026-07-19T18:41:22Z blocker: none
sysadmin merged commit 8a851eb87e into master 2026-07-19 13:32:18 -05:00
Owner

Stale #332 review-decision lock cleanup (#594)

Status: APPLIED

Manual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.

## Stale #332 review-decision lock cleanup (#594) Status: **APPLIED** - actor: `sysadmin` - profile: `prgs-merger` - timestamp: `2026-07-19T18:32:19.495732+00:00` - last terminal: `approve` on PR #761 - PR state: `closed` (merged=True) - merge_commit_sha: `8a851eb87e63fc236d8897ed67f67cbe295bc3ae` - prior live_mutations_count: `1` - prior profile_identity: `prgs-reviewer` Manual deletion of session-state files is **not** the workflow. This path only clears a lock when the referenced PR is merged/closed.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#761