feat(control-plane): persist allocator dependency edges as durable state (Closes #784) #785

Merged
sysadmin merged 1 commits from feat/issue-784-durable-dependency-edges into master 2026-07-21 17:22:07 -05:00
Owner

Closes #784 (Slice A of umbrella #628, scope item 6). Head 0589ec80698295e8a1e8f18974849000cf1f5861, based on master 300e8acd.

What was wrong

Umbrella #628 scope item 6 requires dependencies to be durable structured state carrying source, target, type, blocking condition, completion condition, current state, and evidence. Nothing stored any of that.

Dependency knowledge existed only as a per-run computation. allocator_dependencies.parse_dependency_refs() re-parsed the Depends: declaration out of every issue body on every allocation; _allocator_candidates_from_gitea() resolved each reference against live issue state; the result collapsed into WorkCandidate.dependency_unmet / dependency_reason, which classify_skip() consumed and discarded. control_plane_db.py had no dependency table at all.

Three consequences:

  1. Nothing could answer "what is waiting on issue N" without re-listing every open issue and re-parsing every body. The reverse edge — the exact query automatic resumption (#628 item 7) needs — did not exist in any form.
  2. Only issue-blocked-by-issue was expressible. The other six relationships #628 enumerates had nowhere to be recorded.
  3. No observation was recorded, so after the fact a transient lookup failure and a real block were indistinguishable, and a blocked/resume decision could not be audited.

What this adds

  • dependency_edges table, schema v4. Creating the table is the v3→v4 migration: additive, idempotent, and it never touches the existing tables. Uniqueness is (scope, source, target, edge_type), so re-observing a relationship updates one row instead of appending duplicates.
  • dependency_graph.py owns the vocabulary — the seven #628 relationship types, the three states, and fail-closed normalization for types, states, and endpoint kinds. An unrecognized value writes nothing rather than landing as unqueryable free text. Evidence is sanitized before storage, so no credential or endpoint URL can be persisted or handed back.
  • Three ControlPlaneDB methodsupsert_dependency_edge, list_dependency_edges, record_dependency_edge_observation. Filtering by target makes reverse lookup a single query. Transitions append to the existing events table rather than a parallel audit table.
  • Allocator ingestion persists what it already resolved. The resolver's met / unmet / unavailable partitions map one-to-one onto stored states, so nothing is re-classified and unavailable evidence is never recorded as met.
  • gitea_list_dependency_edges, read-only, gated on gitea.read, added to the documented inventory the #781 drift guard checks (111 → 112 tools).

What this deliberately does not do

Selection is untouched. classify_skip() still consumes the in-memory dependency_unmet field; the durable store is written alongside it and never read by the selection path. The write is best-effort and reports failures through reasons, so a broken or absent store leaves allocation behaving exactly as before — proven by allocating the same candidate set through a store whose writes all raise, then comparing selection, skip set, and candidate count.

Automatic blocking and resumption (#628 item 7), edge creation from PR/review/merge events, and defect auto-linking (#628 item 9) are later slices. Umbrella #628 is neither claimed nor closed by this work.

Verification

  • 31 new cases in tests/test_issue_784_dependency_edges.py: fresh-schema creation, a real v3→v4 migration with row retention across all seven pre-existing tables, idempotent re-migration, enum rejection writing nothing, upsert idempotence, forward and reverse lookup, scope isolation, transition events carrying prior and new state, redaction proven at rest in the raw column, live allocation-run ingestion, write-failure tolerance, and the tool's permission gate.
  • Full suite in the branch worktree: 4126 passed, 6 skipped, 493 subtests passed, 11 failed.
  • Those 11 failures (test_commit_payloads, test_issue_702_review_findings_f1_f6, test_mcp_server, test_post_merge_moot_lease, test_reconciler_supersession_close) reproduce identically on a clean detached checkout of master at 300e8acd — 11 failed, 254 passed. They are pre-existing and proven by baseline run, not asserted.

Reviewer notes

  • The v3 schema is inlined in the test module so the migration is exercised against a genuine pre-change database rather than a fresh creation dressed up as a migration.
  • events.work_item_id is NULL for edge transitions: an edge endpoint is a Gitea issue/PR that may never have been assigned, so it has no work_items row to reference.
  • _allocator_candidates_from_gitea() gained two optional keyword arguments; the dashboard call site passes no store because a read-only view should not write.
Closes #784 (Slice A of umbrella #628, scope item 6). Head `0589ec80698295e8a1e8f18974849000cf1f5861`, based on master `300e8acd`. ## What was wrong Umbrella #628 scope item 6 requires dependencies to be durable structured state carrying source, target, type, blocking condition, completion condition, current state, and evidence. Nothing stored any of that. Dependency knowledge existed only as a per-run computation. `allocator_dependencies.parse_dependency_refs()` re-parsed the `Depends:` declaration out of every issue body on every allocation; `_allocator_candidates_from_gitea()` resolved each reference against live issue state; the result collapsed into `WorkCandidate.dependency_unmet` / `dependency_reason`, which `classify_skip()` consumed and discarded. `control_plane_db.py` had no dependency table at all. Three consequences: 1. Nothing could answer "what is waiting on issue N" without re-listing every open issue and re-parsing every body. The reverse edge — the exact query automatic resumption (#628 item 7) needs — did not exist in any form. 2. Only issue-blocked-by-issue was expressible. The other six relationships #628 enumerates had nowhere to be recorded. 3. No observation was recorded, so after the fact a transient lookup failure and a real block were indistinguishable, and a blocked/resume decision could not be audited. ## What this adds * **`dependency_edges` table, schema v4.** Creating the table *is* the v3→v4 migration: additive, idempotent, and it never touches the existing tables. Uniqueness is (scope, source, target, edge_type), so re-observing a relationship updates one row instead of appending duplicates. * **`dependency_graph.py`** owns the vocabulary — the seven #628 relationship types, the three states, and fail-closed normalization for types, states, and endpoint kinds. An unrecognized value writes nothing rather than landing as unqueryable free text. Evidence is sanitized before storage, so no credential or endpoint URL can be persisted or handed back. * **Three `ControlPlaneDB` methods** — `upsert_dependency_edge`, `list_dependency_edges`, `record_dependency_edge_observation`. Filtering by target makes reverse lookup a single query. Transitions append to the existing `events` table rather than a parallel audit table. * **Allocator ingestion** persists what it already resolved. The resolver's `met` / `unmet` / `unavailable` partitions map one-to-one onto stored states, so nothing is re-classified and unavailable evidence is never recorded as met. * **`gitea_list_dependency_edges`**, read-only, gated on `gitea.read`, added to the documented inventory the #781 drift guard checks (111 → 112 tools). ## What this deliberately does not do Selection is untouched. `classify_skip()` still consumes the in-memory `dependency_unmet` field; the durable store is written alongside it and never read by the selection path. The write is best-effort and reports failures through `reasons`, so a broken or absent store leaves allocation behaving exactly as before — proven by allocating the same candidate set through a store whose writes all raise, then comparing selection, skip set, and candidate count. Automatic blocking and resumption (#628 item 7), edge creation from PR/review/merge events, and defect auto-linking (#628 item 9) are later slices. Umbrella #628 is neither claimed nor closed by this work. ## Verification * 31 new cases in `tests/test_issue_784_dependency_edges.py`: fresh-schema creation, a real v3→v4 migration with row retention across all seven pre-existing tables, idempotent re-migration, enum rejection writing nothing, upsert idempotence, forward and reverse lookup, scope isolation, transition events carrying prior and new state, redaction proven at rest in the raw column, live allocation-run ingestion, write-failure tolerance, and the tool's permission gate. * Full suite in the branch worktree: **4126 passed**, 6 skipped, 493 subtests passed, 11 failed. * Those 11 failures (`test_commit_payloads`, `test_issue_702_review_findings_f1_f6`, `test_mcp_server`, `test_post_merge_moot_lease`, `test_reconciler_supersession_close`) reproduce **identically** on a clean detached checkout of master at `300e8acd` — 11 failed, 254 passed. They are pre-existing and proven by baseline run, not asserted. ## Reviewer notes * The v3 schema is inlined in the test module so the migration is exercised against a genuine pre-change database rather than a fresh creation dressed up as a migration. * `events.work_item_id` is NULL for edge transitions: an edge endpoint is a Gitea issue/PR that may never have been assigned, so it has no `work_items` row to reference. * `_allocator_candidates_from_gitea()` gained two optional keyword arguments; the dashboard call site passes no store because a read-only view should not write.
jcwalker3 added 1 commit 2026-07-21 17:08:10 -05:00
Umbrella #628 scope item 6 requires dependencies to be durable structured
state carrying source, target, type, blocking condition, completion
condition, current state, and evidence. Nothing stored any of that.

Dependency knowledge existed only as a per-run computation:
allocator_dependencies re-parsed the Depends: declaration out of every
issue body on every allocation, _allocator_candidates_from_gitea resolved
each reference against live issue state, and the result collapsed into two
in-memory WorkCandidate fields that classify_skip consumed and discarded.
Three consequences followed. Nothing could answer "what is waiting on #N"
without re-listing every open issue and re-parsing every body, so the
reverse edge automatic resumption needs did not exist in any form. Only
issue-blocked-by-issue was expressible, leaving the other six #628
relationships with nowhere to live. And no observation was recorded, so a
transient lookup failure and a real block were indistinguishable after the
fact.

Add the store:

- dependency_edges table under schema v4. Creating the table is itself the
  v3 to v4 migration: additive, idempotent, and it never touches the
  existing tables. Uniqueness is (scope, source, target, edge_type), so
  re-observation updates one row rather than appending duplicates.
- dependency_graph.py owns the vocabulary: the seven #628 relationship
  types, the three states, and fail-closed normalization for both plus
  endpoint kinds. An unrecognized value writes nothing rather than landing
  as unqueryable free text. Evidence is sanitized before storage, so no
  credential or endpoint URL can be persisted or read back.
- upsert_dependency_edge, list_dependency_edges, and
  record_dependency_edge_observation on ControlPlaneDB. Filtering by target
  makes reverse lookup a single query. State transitions append to the
  existing events table rather than a parallel audit table.
- The allocator persists what it already resolved. States map one-to-one
  from the resolver's met/unmet/unavailable partitions, so nothing is
  re-classified and unavailable evidence is never recorded as met.
- gitea_list_dependency_edges exposes stored edges read-only, gated on
  gitea.read, and is added to the documented inventory the #781 drift guard
  checks.

Selection is deliberately untouched: classify_skip still consumes the
in-memory dependency_unmet field. The write is best-effort and reports
failures through reasons, so a broken or absent store leaves allocation
behaving exactly as it did before — proven by allocating the same candidate
set through a store whose writes all raise and comparing the selection,
skip set, and candidate count.

Automatic blocking and resumption (#628 item 7), non-issue edge creation,
and defect auto-linking are later slices; this one only makes the graph
durable and queryable.

Tests: 31 new cases covering fresh-schema creation, a real v3-to-v4
migration with row retention, idempotent re-migration, enum rejection,
upsert idempotence, forward and reverse lookup, scope isolation, transition
events, redaction at rest, live allocation-run ingestion, write-failure
tolerance, and the tool's permission gate.

Verification: 4126 passed in the branch worktree. The 11 failures in
test_commit_payloads, test_issue_702_review_findings_f1_f6, test_mcp_server,
test_post_merge_moot_lease, and test_reconciler_supersession_close reproduce
identically on a clean detached checkout of master at 300e8acd, so they are
pre-existing and proven by baseline run, not introduced here.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #785
issue: #784
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 8563-20950baee7f0
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges
phase: claimed
candidate_head: 0589ec8069
target_branch: master
target_branch_sha: 300e8acd13
last_activity: 2026-07-21T22:17:30Z
expires_at: 2026-07-21T22:27:30Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #785 issue: #784 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 8563-20950baee7f0 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges phase: claimed candidate_head: 0589ec80698295e8a1e8f18974849000cf1f5861 target_branch: master target_branch_sha: 300e8acd13cca2482d24dc83c4f24341fe3e127e last_activity: 2026-07-21T22:17:30Z expires_at: 2026-07-21T22:27:30Z blocker: none
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #785
issue: #784
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 8563-20950baee7f0
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges
phase: claimed
candidate_head: 0589ec8069
target_branch: master
target_branch_sha: 300e8acd13
last_activity: 2026-07-21T22:19:30Z
expires_at: 2026-07-21T22:29:30Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #785 issue: #784 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 8563-20950baee7f0 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges phase: claimed candidate_head: 0589ec80698295e8a1e8f18974849000cf1f5861 target_branch: master target_branch_sha: 300e8acd13cca2482d24dc83c4f24341fe3e127e last_activity: 2026-07-21T22:19:30Z expires_at: 2026-07-21T22:29:30Z blocker: none
sysadmin approved these changes 2026-07-21 17:20:53 -05:00
sysadmin left a comment
Owner

Review decision: approve

Independent formal review of PR #785 (Closes issue #784, Slice A of umbrella #628), performed on the reviewer namespace against a detached review worktree pinned to the PR head. The reviewer session edited no file in this branch; every finding comes from reading the diff and executing the suite. The verdict below is an official review mutation recorded live through the reviewer API, not a draft and not a proposal held for later submission.

NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; namespace=gitea-reviewer; profile=prgs-reviewer; identity=sysadmin

Workflow-load helper result: workflow_hash: 263d0a6cb8a6; boundary_status: clean; final_report_schema_hash: b6c65affc336; workflow_source: skills/llm-project-workflow/workflows/review-merge-pr.md; pre_review_command_count: 0

Reviewed head SHA: 0589ec8069
Live head SHA before approval: 0589ec8069
Pushes occurred during validation: no
Prior-landing gate: NOT_LANDED — the head commit exists only on the feature branch; master remains at 300e8acd13 and carries none of these six files' changes, so this is original unlanded work rather than reconciliation of existing history.

Baseline and validation evidence

Pre-merge base commit: 300e8acd13
Tested commit: 0589ec8069
Command: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python -m pytest tests -q -s -p no:randomly
Working directory: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges
Executable path proof: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python (project virtualenv interpreter, invoked by absolute path)
Exit status: 1
Failure signature: 11 failed, 4126 passed, 6 skipped, 493 subtests passed — failures confined to test_commit_payloads (6), test_issue_702_review_findings_f1_f6 (2), test_mcp_server (1), test_post_merge_moot_lease (1), test_reconciler_supersession_close (1)

Baseline execution at the pre-merge base commit, in a clean detached worktree created by this reviewer session:

Baseline worktree path: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-baseline
Pre-merge base commit: 300e8acd13
Command: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python -m pytest tests/test_commit_payloads.py tests/test_issue_702_review_findings_f1_f6.py tests/test_mcp_server.py tests/test_post_merge_moot_lease.py tests/test_reconciler_supersession_close.py -q -s -p no:randomly
Working directory: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-baseline
Executable path proof: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python (same interpreter, absolute path)
Exit status: 1
Failure signature: 11 failed, 254 passed — the same eleven node ids, carrying the identical assertion text "Expected: verify_preflight_purity('prgs', task='reconcile_close_superseded_pr') / Actual: verify_preflight_purity('prgs', task='reconcile_close_superseded_pr', org='Scaled-Tech-Consulting', repo='Gitea-Tools')"

The failure sets at the tested commit and at the pre-merge base commit match in membership and in signature, so this branch introduces no new failing test. The author's numbers were re-executed by this session rather than accepted.

Scope verification against the linked issue

Six files changed, 1475 insertions, 5 deletions. Every change maps to an issue #784 acceptance criterion; nothing unrelated is touched.

  1. Schema (AC1-AC3). control_plane_db.py moves SCHEMA_VERSION 3 to 4 and adds dependency_edges to the shared DDL block. The statement is CREATE TABLE IF NOT EXISTS inside the executescript that already runs on every open, so table creation is itself the migration: additive, idempotent, and structurally incapable of altering the seven pre-existing tables. Proven by test rather than by inspection: the test module inlines the prior v3 schema, seeds work_items, sessions, and events rows, opens the database, then asserts the version advances to 4, the new table appears, and every seeded row survives. Two further opens leave exactly one dependency_edges table.

  2. Vocabulary and fail-closed input handling (AC4, AC5). dependency_graph.py declares all seven relationship types umbrella #628 enumerates plus the three observation states. normalize_edge_type, normalize_edge_state, and normalize_work_kind raise instead of defaulting. The database layer calls all four normalizers before opening a transaction, so a rejected request cannot leave a partial row — confirmed by a case that attempts three invalid writes and then asserts the table is empty.

  3. Uniqueness and lookup (AC6-AC8). The UNIQUE constraint is (remote, org, repo, source_kind, source_number, target_kind, target_number, edge_type), and upsert_dependency_edge selects on exactly that tuple before choosing insert versus update, so re-observation refreshes one row. A dedicated index covers the target direction. The reverse-lookup case returns three waiters spanning two source kinds and two edge types while excluding an edge pointing at a different target. Scope isolation is asserted across repo and remote.

  4. Audit trail (AC9). Transitions append to the existing events table with event_type dependency_edge_state_change and a message carrying prior and new state. An unchanged re-observation emits nothing, which keeps the audit table meaningful. work_item_id is NULL by design because an edge endpoint need never have been assigned; that column is nullable in the pre-existing schema, so no foreign key is violated.

  5. Evidence safety (AC10). sanitize_evidence redacts keys matching a credential pattern and substitutes scheme-qualified URLs, recursing through mappings and sequences under depth and length bounds. The test reads the raw evidence column directly out of SQLite and asserts that neither the synthetic credential nor the scheme substring is present at rest, then asserts the same through the read path.

  6. Allocator ingestion and non-interference (AC11, AC12, AC14). The write sits immediately after the existing resolve step and consumes its met/unmet/unavailable partitions directly, so no dependency is re-classified and unavailable evidence cannot be recorded as satisfied. The candidate fields feeding classify_skip are untouched. Non-interference is demonstrated behaviorally rather than argued: the same candidate set is allocated twice, once through a healthy store and once through a wrapper whose upsert always raises, and selection, skip set, and candidate count compare equal while the failure text surfaces in the reported reasons. A repeated allocation run leaves a single edge row.

  7. Read-only exposure (AC13). gitea_list_dependency_edges gates on gitea.read before any other work, returns edges with read_only true, converts an invalid filter into a fail-closed result rather than an unfiltered set, and performs no write. It is added to docs/mcp-tool-inventory.md, which the issue #781 documentation drift guard compares against the registered tool list; that guard's suite passes at this head.

Findings

No blocking finding. Three non-blocking observations, recorded for the record and not conditions of this decision:

  • The sid computation in gitea_allocate_next_work moved above inventory construction so edge evidence can name the observing session. The value and its later use are unchanged, and the surrounding early-return paths never read sid, so this is a pure reordering.
  • Edge writes now occur on apply=false allocation previews. This does not contradict the documented preview contract, which promises no assignment and no lease; an edge is an observation, not a reservation. The dashboard call site correctly passes no store.
  • The URL substitution in sanitize_evidence keys on a scheme-qualified pattern, so a bare scheme-less hostname would persist. No code path in this change writes a hostname into evidence, so this is a hardening note for whichever later slice ingests provider payloads.

Scope discipline

Selection semantics, ranking, eligibility, and skip classification are unchanged, matching the issue's stated non-goals. Automatic blocking and resumption, non-issue edge creation, and defect auto-linking are correctly deferred to later slices. Umbrella issue #628 is not claimed, modified, or closed by this branch.

Controller Handoff

  • Task: independent formal review of PR #785 at a pinned head
  • Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools
  • Role: reviewer
  • Identity: sysadmin
  • Active profile: prgs-reviewer
  • Runtime context: native MCP reviewer namespace, server implementation in parity at 300e8acd13, mutation gate enforced
  • Selected PR: 785
  • Linked issue: 784
  • Eligibility class: OPEN_PR_FIRST_FORMAL_REVIEW
  • Queue ordering policy: single named PR supplied by the controller handoff; no queue ranking performed in this session
  • Inventory pagination proof: not applicable; no inventory listing was ranked in this session
  • Earlier PRs skipped: none
  • Candidate head SHA: 0589ec8069
  • Reviewed head SHA: 0589ec8069
  • Target branch: master
  • Target branch SHA: 300e8acd13
  • Prior-landing gate: NOT_LANDED; the head commit exists only on the feature branch
  • Author-safety result: PASS; author identity jcwalker3 differs from reviewer identity sysadmin, so no self-review occurred
  • Prior request-changes state: none; this is the first formal review recorded on PR #785
  • Review worktree used: true
  • Review worktree path: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges
  • Review worktree inside branches: true
  • Review worktree HEAD state: detached at 0589ec8069
  • Review worktree dirty before validation: no
  • Review worktree dirty after validation: no
  • Baseline worktree used: true
  • Baseline worktree path: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-baseline
  • Files reviewed: control_plane_db.py, dependency_graph.py, gitea_mcp_server.py, docs/mcp-tool-inventory.md, tests/test_control_plane_db.py, tests/test_issue_784_dependency_edges.py
  • Validation: full suite at the tested commit gave 11 failed and 4126 passed; the five affected files at the pre-merge base commit gave the same 11 node ids with 254 passed, establishing the failures as pre-existing
  • Official validation integrity status: PASS; both runs executed natively in this session through the project virtualenv interpreter by absolute path, with no offline helper and no import of server internals
  • Terminal review mutation: one APPROVE verdict recorded live through gitea_submit_pr_review at the reviewed head SHA; this is an official recorded outcome, not a draft or held decision
  • Review decision: approve
  • Merge preflight: not performed in this session; the merger owns it
  • Merge result: none; no merge was performed by this session
  • Linked issue status: issue #784 carries status:pr-open and stays open until the merge event lands
  • Main checkout branch: master
  • Main checkout dirty state: clean
  • Main checkout updated: no
  • File edits by reviewer: none
  • Worktree/index mutations: two detached worktrees created under branches/ by this session (review-pr-785-dependency-edges at the reviewed head, review-pr-785-baseline at the pre-merge base commit); no index or working-tree content was modified in either
  • Git ref mutations: none on any remote; a fetch updated the local remote-tracking ref for the feature branch, and no branch or tag was created, updated, or deleted
  • MCP/Gitea mutations: one review verdict on PR #785; nothing else
  • Review mutations: one APPROVE verdict at head 0589ec8069
  • Merge mutations: none
  • Cleanup mutations: none
  • External-state mutations: none
  • Read-only diagnostics: gitea_view_pr, gitea_load_review_workflow, git diff and git worktree inspection, two pytest executions
  • Blockers: none
  • Current status: review complete with an approve verdict officially recorded at the reviewed head SHA
  • Next actor: merger
  • Next action: verify the live head still equals 0589ec8069, obtain explicit operator merge authorization, then merge PR #785 through the merger namespace
  • Next prompt: Role MERGER on prgs / Scaled-Tech-Consulting / Gitea-Tools for PR 785 at pinned head 0589ec8069 against base master 300e8acd13. Invoke the gitea-workflow skill first, then gitea_whoami, gitea_assess_master_parity, and gitea_resolve_task_capability immediately before each mutating call. Re-read PR 785 live and confirm the head SHA still equals the pinned SHA; a different head voids this verdict and requires a fresh review. Confirm the recorded verdict is an approval at that same head and that the approving identity differs from the authoring identity. Acquire the merger PR lease. Merge only with explicit operator merge authorization; absent that authorization, stop and report. After the merge event, hand off to the reconciler for label retirement and branch cleanup. Prohibited: merging a changed head, merging without operator authorization, claiming or closing umbrella issue 628, unrelated branch or worktree cleanup.
  • Safe next action: merger verifies the live head still equals 0589ec8069, obtains explicit operator merge authorization, then merges PR #785
  • Safety statement: no merge was performed, no branch was deleted, no issue was closed, umbrella issue #628 was neither claimed nor modified, and no unrelated branch or worktree cleanup was performed
  • Workflow-load helper result: workflow_hash: 263d0a6cb8a6; boundary_status: clean; final_report_schema_hash: b6c65affc336; workflow_source: skills/llm-project-workflow/workflows/review-merge-pr.md

[THREAD STATE LEDGER] PR #785 durable dependency edges — reviewer verdict recorded, merger acts next

What is true now:

  • Server-side decision state: an APPROVE verdict is recorded through the native reviewer API against head 0589ec80698295e8a1e8f18974849000cf1f5861; no merge has been performed and no merge authorization is implied by it
  • Local verdict/state: PR #785 is in open state, mergeable true, base master 300e8acd13, authored by jcwalker3 and reviewed by sysadmin
  • Latest known validation: the tested commit gave 11 failed and 4126 passed; the pre-merge base commit gave the same 11 node ids with 254 passed, so the failing set predates this branch

What changed:

  • This session re-executed the full suite at the reviewed head and the five affected files at the pre-merge base commit in separate worktrees under branches/, rather than accepting the author's numbers
  • Each issue #784 acceptance criterion was mapped to the specific code and test case that satisfies it
  • Three non-blocking observations were recorded: the sid reordering, edge writes during apply=false previews, and scheme-less hostnames in evidence sanitization

What is blocked:

  • Blocker classification: no blocker

Who/what acts next:

  • Next actor: merger
  • Required action: verify the live head still equals 0589ec8069, confirm explicit operator merge authorization, then merge PR #785 through the merger namespace
  • Do not do: do not merge without explicit operator authorization; do not merge a head other than the one this verdict pins; do not claim, modify, or close umbrella issue #628; do not perform unrelated branch or worktree cleanup

Canonical PR State

STATE:
review-complete-approve-recorded

WHO_IS_NEXT:
merger

NEXT_ACTION:
Confirm the live head still matches the pinned SHA and obtain explicit operator merge authorization, then merge PR #785 into master through the merger namespace.

NEXT_PROMPT:

Role: MERGER
Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools
PR: 785 (Closes issue 784, Slice A of umbrella 628)
Pinned head: 0589ec80698295e8a1e8f18974849000cf1f5861
Base: master 300e8acd13cca2482d24dc83c4f24341fe3e127e

Invoke the gitea-workflow skill first. Preflight fail closed on the gitea-merger namespace:
gitea_whoami, gitea_assess_master_parity, then gitea_resolve_task_capability immediately
before each mutating call.

1. Re-read PR 785 live and confirm the head SHA still equals the pinned SHA above. A
   different head voids this review verdict and requires a fresh review.
2. Confirm the recorded verdict is an approval at that same head and that the approving
   identity differs from the authoring identity.
3. Acquire the merger PR lease.
4. Merge only with explicit operator merge authorization. Absent that authorization, stop
   and report; never merge on inference.
5. After the merge event, hand off to the reconciler for label retirement and branch
   cleanup.

Prohibited: merging a changed head; merging without operator authorization; claiming or
closing umbrella issue 628; unrelated branch or worktree cleanup.

WHAT_HAPPENED:
The reviewer session loaded the canonical review workflow, created a detached review worktree pinned to the PR head, read the complete diff across all six changed files, executed the full suite at the tested commit, executed the five affected files at the pre-merge base commit in a separate baseline worktree, and mapped every issue #784 acceptance criterion to the code and test that satisfies it. An APPROVE verdict is recorded at the reviewed head.

WHY:
The change adds the durable dependency-edge state umbrella #628 scope item 6 requires and that no existing component provided. It is correct against its stated acceptance criteria, fails closed at every input boundary, is proven not to alter allocator selection even when the new store raises on every write, and is confined to the files its issue names. The eleven failing tests reproduce identically at the pre-merge base commit, so they are not attributable to this branch.

ISSUE:
784

HEAD_SHA:
0589ec8069

REVIEW_STATUS:
approve verdict recorded at head 0589ec8069 by prgs-reviewer identity sysadmin

MERGE_READY:
yes at head 0589ec8069, contingent on explicit operator merge authorization and on the live head remaining unchanged

BLOCKERS:
none

VALIDATION:

  • Tested commit 0589ec8069: 11 failed, 4126 passed, 6 skipped, 493 subtests passed
  • Pre-merge base commit 300e8acd13 in baseline worktree branches/review-pr-785-baseline: 11 failed, 254 passed across the same five files, identical node ids and identical assertion text
  • 31 new cases in tests/test_issue_784_dependency_edges.py all pass at the tested commit
  • The issue #781 documentation drift guard passes with the new tool documented
  • Live head re-read immediately before this verdict equals the reviewed head SHA; no pushes occurred during validation

LAST_UPDATED_BY:
sysadmin / prgs-reviewer / reviewer session / 2026-07-21

Review decision: approve Independent formal review of PR #785 (Closes issue #784, Slice A of umbrella #628), performed on the reviewer namespace against a detached review worktree pinned to the PR head. The reviewer session edited no file in this branch; every finding comes from reading the diff and executing the suite. The verdict below is an official review mutation recorded live through the reviewer API, not a draft and not a proposal held for later submission. NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; namespace=gitea-reviewer; profile=prgs-reviewer; identity=sysadmin Workflow-load helper result: workflow_hash: 263d0a6cb8a6; boundary_status: clean; final_report_schema_hash: b6c65affc336; workflow_source: skills/llm-project-workflow/workflows/review-merge-pr.md; pre_review_command_count: 0 Reviewed head SHA: 0589ec80698295e8a1e8f18974849000cf1f5861 Live head SHA before approval: 0589ec80698295e8a1e8f18974849000cf1f5861 Pushes occurred during validation: no Prior-landing gate: NOT_LANDED — the head commit exists only on the feature branch; master remains at 300e8acd13cca2482d24dc83c4f24341fe3e127e and carries none of these six files' changes, so this is original unlanded work rather than reconciliation of existing history. ## Baseline and validation evidence Pre-merge base commit: 300e8acd13cca2482d24dc83c4f24341fe3e127e Tested commit: 0589ec80698295e8a1e8f18974849000cf1f5861 Command: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python -m pytest tests -q -s -p no:randomly Working directory: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges Executable path proof: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python (project virtualenv interpreter, invoked by absolute path) Exit status: 1 Failure signature: 11 failed, 4126 passed, 6 skipped, 493 subtests passed — failures confined to test_commit_payloads (6), test_issue_702_review_findings_f1_f6 (2), test_mcp_server (1), test_post_merge_moot_lease (1), test_reconciler_supersession_close (1) Baseline execution at the pre-merge base commit, in a clean detached worktree created by this reviewer session: Baseline worktree path: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-baseline Pre-merge base commit: 300e8acd13cca2482d24dc83c4f24341fe3e127e Command: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python -m pytest tests/test_commit_payloads.py tests/test_issue_702_review_findings_f1_f6.py tests/test_mcp_server.py tests/test_post_merge_moot_lease.py tests/test_reconciler_supersession_close.py -q -s -p no:randomly Working directory: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-baseline Executable path proof: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python (same interpreter, absolute path) Exit status: 1 Failure signature: 11 failed, 254 passed — the same eleven node ids, carrying the identical assertion text "Expected: verify_preflight_purity('prgs', task='reconcile_close_superseded_pr') / Actual: verify_preflight_purity('prgs', task='reconcile_close_superseded_pr', org='Scaled-Tech-Consulting', repo='Gitea-Tools')" The failure sets at the tested commit and at the pre-merge base commit match in membership and in signature, so this branch introduces no new failing test. The author's numbers were re-executed by this session rather than accepted. ## Scope verification against the linked issue Six files changed, 1475 insertions, 5 deletions. Every change maps to an issue #784 acceptance criterion; nothing unrelated is touched. 1. Schema (AC1-AC3). control_plane_db.py moves SCHEMA_VERSION 3 to 4 and adds dependency_edges to the shared DDL block. The statement is CREATE TABLE IF NOT EXISTS inside the executescript that already runs on every open, so table creation is itself the migration: additive, idempotent, and structurally incapable of altering the seven pre-existing tables. Proven by test rather than by inspection: the test module inlines the prior v3 schema, seeds work_items, sessions, and events rows, opens the database, then asserts the version advances to 4, the new table appears, and every seeded row survives. Two further opens leave exactly one dependency_edges table. 2. Vocabulary and fail-closed input handling (AC4, AC5). dependency_graph.py declares all seven relationship types umbrella #628 enumerates plus the three observation states. normalize_edge_type, normalize_edge_state, and normalize_work_kind raise instead of defaulting. The database layer calls all four normalizers before opening a transaction, so a rejected request cannot leave a partial row — confirmed by a case that attempts three invalid writes and then asserts the table is empty. 3. Uniqueness and lookup (AC6-AC8). The UNIQUE constraint is (remote, org, repo, source_kind, source_number, target_kind, target_number, edge_type), and upsert_dependency_edge selects on exactly that tuple before choosing insert versus update, so re-observation refreshes one row. A dedicated index covers the target direction. The reverse-lookup case returns three waiters spanning two source kinds and two edge types while excluding an edge pointing at a different target. Scope isolation is asserted across repo and remote. 4. Audit trail (AC9). Transitions append to the existing events table with event_type dependency_edge_state_change and a message carrying prior and new state. An unchanged re-observation emits nothing, which keeps the audit table meaningful. work_item_id is NULL by design because an edge endpoint need never have been assigned; that column is nullable in the pre-existing schema, so no foreign key is violated. 5. Evidence safety (AC10). sanitize_evidence redacts keys matching a credential pattern and substitutes scheme-qualified URLs, recursing through mappings and sequences under depth and length bounds. The test reads the raw evidence column directly out of SQLite and asserts that neither the synthetic credential nor the scheme substring is present at rest, then asserts the same through the read path. 6. Allocator ingestion and non-interference (AC11, AC12, AC14). The write sits immediately after the existing resolve step and consumes its met/unmet/unavailable partitions directly, so no dependency is re-classified and unavailable evidence cannot be recorded as satisfied. The candidate fields feeding classify_skip are untouched. Non-interference is demonstrated behaviorally rather than argued: the same candidate set is allocated twice, once through a healthy store and once through a wrapper whose upsert always raises, and selection, skip set, and candidate count compare equal while the failure text surfaces in the reported reasons. A repeated allocation run leaves a single edge row. 7. Read-only exposure (AC13). gitea_list_dependency_edges gates on gitea.read before any other work, returns edges with read_only true, converts an invalid filter into a fail-closed result rather than an unfiltered set, and performs no write. It is added to docs/mcp-tool-inventory.md, which the issue #781 documentation drift guard compares against the registered tool list; that guard's suite passes at this head. ## Findings No blocking finding. Three non-blocking observations, recorded for the record and not conditions of this decision: - The sid computation in gitea_allocate_next_work moved above inventory construction so edge evidence can name the observing session. The value and its later use are unchanged, and the surrounding early-return paths never read sid, so this is a pure reordering. - Edge writes now occur on apply=false allocation previews. This does not contradict the documented preview contract, which promises no assignment and no lease; an edge is an observation, not a reservation. The dashboard call site correctly passes no store. - The URL substitution in sanitize_evidence keys on a scheme-qualified pattern, so a bare scheme-less hostname would persist. No code path in this change writes a hostname into evidence, so this is a hardening note for whichever later slice ingests provider payloads. ## Scope discipline Selection semantics, ranking, eligibility, and skip classification are unchanged, matching the issue's stated non-goals. Automatic blocking and resumption, non-issue edge creation, and defect auto-linking are correctly deferred to later slices. Umbrella issue #628 is not claimed, modified, or closed by this branch. ## Controller Handoff - Task: independent formal review of PR #785 at a pinned head - Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools - Role: reviewer - Identity: sysadmin - Active profile: prgs-reviewer - Runtime context: native MCP reviewer namespace, server implementation in parity at 300e8acd13cca2482d24dc83c4f24341fe3e127e, mutation gate enforced - Selected PR: 785 - Linked issue: 784 - Eligibility class: OPEN_PR_FIRST_FORMAL_REVIEW - Queue ordering policy: single named PR supplied by the controller handoff; no queue ranking performed in this session - Inventory pagination proof: not applicable; no inventory listing was ranked in this session - Earlier PRs skipped: none - Candidate head SHA: 0589ec80698295e8a1e8f18974849000cf1f5861 - Reviewed head SHA: 0589ec80698295e8a1e8f18974849000cf1f5861 - Target branch: master - Target branch SHA: 300e8acd13cca2482d24dc83c4f24341fe3e127e - Prior-landing gate: NOT_LANDED; the head commit exists only on the feature branch - Author-safety result: PASS; author identity jcwalker3 differs from reviewer identity sysadmin, so no self-review occurred - Prior request-changes state: none; this is the first formal review recorded on PR #785 - Review worktree used: true - Review worktree path: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges - Review worktree inside branches: true - Review worktree HEAD state: detached at 0589ec80698295e8a1e8f18974849000cf1f5861 - Review worktree dirty before validation: no - Review worktree dirty after validation: no - Baseline worktree used: true - Baseline worktree path: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-baseline - Files reviewed: control_plane_db.py, dependency_graph.py, gitea_mcp_server.py, docs/mcp-tool-inventory.md, tests/test_control_plane_db.py, tests/test_issue_784_dependency_edges.py - Validation: full suite at the tested commit gave 11 failed and 4126 passed; the five affected files at the pre-merge base commit gave the same 11 node ids with 254 passed, establishing the failures as pre-existing - Official validation integrity status: PASS; both runs executed natively in this session through the project virtualenv interpreter by absolute path, with no offline helper and no import of server internals - Terminal review mutation: one APPROVE verdict recorded live through gitea_submit_pr_review at the reviewed head SHA; this is an official recorded outcome, not a draft or held decision - Review decision: approve - Merge preflight: not performed in this session; the merger owns it - Merge result: none; no merge was performed by this session - Linked issue status: issue #784 carries status:pr-open and stays open until the merge event lands - Main checkout branch: master - Main checkout dirty state: clean - Main checkout updated: no - File edits by reviewer: none - Worktree/index mutations: two detached worktrees created under branches/ by this session (review-pr-785-dependency-edges at the reviewed head, review-pr-785-baseline at the pre-merge base commit); no index or working-tree content was modified in either - Git ref mutations: none on any remote; a fetch updated the local remote-tracking ref for the feature branch, and no branch or tag was created, updated, or deleted - MCP/Gitea mutations: one review verdict on PR #785; nothing else - Review mutations: one APPROVE verdict at head 0589ec80698295e8a1e8f18974849000cf1f5861 - Merge mutations: none - Cleanup mutations: none - External-state mutations: none - Read-only diagnostics: gitea_view_pr, gitea_load_review_workflow, git diff and git worktree inspection, two pytest executions - Blockers: none - Current status: review complete with an approve verdict officially recorded at the reviewed head SHA - Next actor: merger - Next action: verify the live head still equals 0589ec80698295e8a1e8f18974849000cf1f5861, obtain explicit operator merge authorization, then merge PR #785 through the merger namespace - Next prompt: Role MERGER on prgs / Scaled-Tech-Consulting / Gitea-Tools for PR 785 at pinned head 0589ec80698295e8a1e8f18974849000cf1f5861 against base master 300e8acd13cca2482d24dc83c4f24341fe3e127e. Invoke the gitea-workflow skill first, then gitea_whoami, gitea_assess_master_parity, and gitea_resolve_task_capability immediately before each mutating call. Re-read PR 785 live and confirm the head SHA still equals the pinned SHA; a different head voids this verdict and requires a fresh review. Confirm the recorded verdict is an approval at that same head and that the approving identity differs from the authoring identity. Acquire the merger PR lease. Merge only with explicit operator merge authorization; absent that authorization, stop and report. After the merge event, hand off to the reconciler for label retirement and branch cleanup. Prohibited: merging a changed head, merging without operator authorization, claiming or closing umbrella issue 628, unrelated branch or worktree cleanup. - Safe next action: merger verifies the live head still equals 0589ec80698295e8a1e8f18974849000cf1f5861, obtains explicit operator merge authorization, then merges PR #785 - Safety statement: no merge was performed, no branch was deleted, no issue was closed, umbrella issue #628 was neither claimed nor modified, and no unrelated branch or worktree cleanup was performed - Workflow-load helper result: workflow_hash: 263d0a6cb8a6; boundary_status: clean; final_report_schema_hash: b6c65affc336; workflow_source: skills/llm-project-workflow/workflows/review-merge-pr.md [THREAD STATE LEDGER] PR #785 durable dependency edges — reviewer verdict recorded, merger acts next What is true now: - Server-side decision state: an APPROVE verdict is recorded through the native reviewer API against head 0589ec80698295e8a1e8f18974849000cf1f5861; no merge has been performed and no merge authorization is implied by it - Local verdict/state: PR #785 is in open state, mergeable true, base master 300e8acd13cca2482d24dc83c4f24341fe3e127e, authored by jcwalker3 and reviewed by sysadmin - Latest known validation: the tested commit gave 11 failed and 4126 passed; the pre-merge base commit gave the same 11 node ids with 254 passed, so the failing set predates this branch What changed: - This session re-executed the full suite at the reviewed head and the five affected files at the pre-merge base commit in separate worktrees under branches/, rather than accepting the author's numbers - Each issue #784 acceptance criterion was mapped to the specific code and test case that satisfies it - Three non-blocking observations were recorded: the sid reordering, edge writes during apply=false previews, and scheme-less hostnames in evidence sanitization What is blocked: - Blocker classification: no blocker Who/what acts next: - Next actor: merger - Required action: verify the live head still equals 0589ec80698295e8a1e8f18974849000cf1f5861, confirm explicit operator merge authorization, then merge PR #785 through the merger namespace - Do not do: do not merge without explicit operator authorization; do not merge a head other than the one this verdict pins; do not claim, modify, or close umbrella issue #628; do not perform unrelated branch or worktree cleanup ## Canonical PR State STATE: review-complete-approve-recorded WHO_IS_NEXT: merger NEXT_ACTION: Confirm the live head still matches the pinned SHA and obtain explicit operator merge authorization, then merge PR #785 into master through the merger namespace. NEXT_PROMPT: ```text Role: MERGER Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools PR: 785 (Closes issue 784, Slice A of umbrella 628) Pinned head: 0589ec80698295e8a1e8f18974849000cf1f5861 Base: master 300e8acd13cca2482d24dc83c4f24341fe3e127e Invoke the gitea-workflow skill first. Preflight fail closed on the gitea-merger namespace: gitea_whoami, gitea_assess_master_parity, then gitea_resolve_task_capability immediately before each mutating call. 1. Re-read PR 785 live and confirm the head SHA still equals the pinned SHA above. A different head voids this review verdict and requires a fresh review. 2. Confirm the recorded verdict is an approval at that same head and that the approving identity differs from the authoring identity. 3. Acquire the merger PR lease. 4. Merge only with explicit operator merge authorization. Absent that authorization, stop and report; never merge on inference. 5. After the merge event, hand off to the reconciler for label retirement and branch cleanup. Prohibited: merging a changed head; merging without operator authorization; claiming or closing umbrella issue 628; unrelated branch or worktree cleanup. ``` WHAT_HAPPENED: The reviewer session loaded the canonical review workflow, created a detached review worktree pinned to the PR head, read the complete diff across all six changed files, executed the full suite at the tested commit, executed the five affected files at the pre-merge base commit in a separate baseline worktree, and mapped every issue #784 acceptance criterion to the code and test that satisfies it. An APPROVE verdict is recorded at the reviewed head. WHY: The change adds the durable dependency-edge state umbrella #628 scope item 6 requires and that no existing component provided. It is correct against its stated acceptance criteria, fails closed at every input boundary, is proven not to alter allocator selection even when the new store raises on every write, and is confined to the files its issue names. The eleven failing tests reproduce identically at the pre-merge base commit, so they are not attributable to this branch. ISSUE: 784 HEAD_SHA: 0589ec80698295e8a1e8f18974849000cf1f5861 REVIEW_STATUS: approve verdict recorded at head 0589ec80698295e8a1e8f18974849000cf1f5861 by prgs-reviewer identity sysadmin MERGE_READY: yes at head 0589ec80698295e8a1e8f18974849000cf1f5861, contingent on explicit operator merge authorization and on the live head remaining unchanged BLOCKERS: none VALIDATION: - Tested commit 0589ec80698295e8a1e8f18974849000cf1f5861: 11 failed, 4126 passed, 6 skipped, 493 subtests passed - Pre-merge base commit 300e8acd13cca2482d24dc83c4f24341fe3e127e in baseline worktree branches/review-pr-785-baseline: 11 failed, 254 passed across the same five files, identical node ids and identical assertion text - 31 new cases in tests/test_issue_784_dependency_edges.py all pass at the tested commit - The issue #781 documentation drift guard passes with the new tool documented - Live head re-read immediately before this verdict equals the reviewed head SHA; no pushes occurred during validation LAST_UPDATED_BY: sysadmin / prgs-reviewer / reviewer session / 2026-07-21
Owner

adopted_at: 2026-07-21T22:21:50Z
adopted_by_identity: sysadmin
adopted_by_profile: prgs-merger
adopted_from_session_id: 8563-20950baee7f0
adopted_from_profile: prgs-reviewer
adopted_from_reviewer_identity: sysadmin
adopted_from_comment_id: 13727
adoption_reason: merger-handoff-approved-head

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #785
issue: #784
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 8570-b92017159069
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges
phase: adopted
candidate_head: 0589ec8069
target_branch: master
target_branch_sha: 300e8acd13
last_activity: 2026-07-21T22:21:50Z
expires_at: 2026-07-21T22:31:50Z
blocker: none

<!-- mcp-review-lease-adoption:v1 --> adopted_at: 2026-07-21T22:21:50Z adopted_by_identity: sysadmin adopted_by_profile: prgs-merger adopted_from_session_id: 8563-20950baee7f0 adopted_from_profile: prgs-reviewer adopted_from_reviewer_identity: sysadmin adopted_from_comment_id: 13727 adoption_reason: merger-handoff-approved-head <!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #785 issue: #784 reviewer_identity: sysadmin profile: prgs-merger session_id: 8570-b92017159069 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-785-dependency-edges phase: adopted candidate_head: 0589ec80698295e8a1e8f18974849000cf1f5861 target_branch: master target_branch_sha: 300e8acd13cca2482d24dc83c4f24341fe3e127e last_activity: 2026-07-21T22:21:50Z expires_at: 2026-07-21T22:31:50Z blocker: none
sysadmin merged commit 7ecf7bf2d6 into master 2026-07-21 17:22:07 -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-21T22:22:10.504248+00:00` - last terminal: `approve` on PR #785 - PR state: `closed` (merged=True) - merge_commit_sha: `7ecf7bf2d666e179c2ecca27b60bb93f7092383f` - 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#785