Persist allocator dependency edges as durable control-plane state #784

Closed
opened 2026-07-21 16:53:47 -05:00 by jcwalker3 · 4 comments
Owner

Parent: #628 (Slice A) · Related: #758, #613, #600, #601

Problem

Umbrella #628 scope item 6 requires dependencies to be represented as durable structured state, with each edge carrying source, target, type, blocking condition, completion condition, current state, and evidence. The repository does not have that store.

Today the allocator derives dependency status entirely in-memory, once per allocation run:

  • allocator_dependencies.parse_dependency_refs() re-parses the Depends: declaration out of each issue body on every call;
  • _allocator_candidates_from_gitea() in gitea_mcp_server.py resolves each parsed reference against live issue state and collapses the result into two ephemeral WorkCandidate fields, dependency_unmet and dependency_reason;
  • allocator_service.classify_skip() consumes those fields and then discards them.

Nothing is written down. control_plane_db.py (SCHEMA_VERSION 3) has tables for sessions, work_items, leases, assignments, terminal_locks, events, and incident_links — and no table for dependency relationships.

Impact

  1. No queryable graph. Nothing can answer "what blocks issue X" or "what is waiting on issue Y" without re-listing every open issue and re-parsing every body. Automatic resumption (#628 scope item 7) needs the reverse edge — waiters keyed by target — which does not exist in any form.
  2. Only one edge kind is expressible. #628 enumerates seven relationship types (issue blocked by issue, PR waiting for requested changes, merge waiting for approval, reconciliation waiting for merge, deployment waiting for infrastructure, controller acceptance waiting for validation, task waiting for tooling-defect fix). Issue-body text can only express the first. The other six have nowhere to be recorded.
  3. No evidence trail. When an allocation run skips a candidate for unmet dependencies, the observation vanishes. There is no record of what was observed, when, or from what live state — so a later blocked/resume decision cannot be audited, and a transient lookup failure is indistinguishable from a real block after the fact.
  4. Recomputation cost and drift. Every run performs targeted per-reference issue lookups (_issue_state) with only a per-run cache. Two roles allocating seconds apart can reach different conclusions with no durable record of either.

Scope (this slice)

Persistence and read access only. This slice records the graph; it does not change what the allocator selects.

1. Schema

Add a dependency_edges table to control_plane_db.py under an additive, idempotent migration to SCHEMA_VERSION 4. Existing databases must migrate in place without data loss and without requiring a rebuild.

Columns must cover the #628 edge contract:

  • identity: edge_id, scope (remote, org, repo);
  • source_kind / source_number (the waiting work unit);
  • target_kind / target_number (the depended-upon unit);
  • edge_type (validated enum);
  • blocking_condition and completion_condition (text);
  • state (validated enum);
  • evidence (structured JSON: observed live state, source of observation, observing session/role);
  • created_at, updated_at, last_observed_at.

Uniqueness is on (scope, source, target, edge_type) so re-observation updates one row instead of appending duplicates.

2. Edge vocabulary module

Add dependency_graph.py owning the edge_type and state enums and their validation. Both fail closed: an unrecognized type or state raises rather than being stored as free text. Edge types cover the seven #628 relationships; states cover at minimum unmet, met, and unavailable — matching the existing three-way outcome of resolve_dependency_state(), where unavailable evidence is not treated as satisfied.

3. ControlPlaneDB API

  • upsert_dependency_edge(...) — insert or update one edge by its uniqueness key, refreshing state, evidence, and last_observed_at.
  • list_dependency_edges(...) — filter by scope, source, target, edge type, and/or state. The target filter is what makes reverse lookup ("what waits on #N") a single query.
  • record_dependency_edge_observation(...) — update state plus evidence for an existing edge, appending an events row so the transition is auditable alongside existing control-plane events.

4. Allocator ingestion

Where _allocator_candidates_from_gitea() already resolves declared references against live issue state, additionally persist each resolved reference as an edge: edge_type = issue-blocked-by-issue, state from the existing resolution outcome, evidence recording the observed live state and the observing session. Persistence is best-effort and must never fail an allocation run: a write error is reported in reasons and allocation continues on the in-memory values exactly as it does today.

5. Read-only exposure

Add gitea_list_dependency_edges as a read-only MCP tool gated on gitea.read, returning stored edges for a scope with the same filters as the DB API. No mutation tool is added in this slice.

Non-goals

  • Do not change allocator selection, ranking, eligibility, or skip semantics. classify_skip() keeps consuming the in-memory dependency_unmet field; the durable store is written alongside it, not read by it.
  • Do not implement automatic blocking, requeueing, or resumption (#628 scope item 7) — that consumes this store in a later slice.
  • Do not create edges from PR, review, merge, or reconciliation events yet; only the vocabulary must accommodate them.
  • Do not implement automatic tooling-defect linking (#628 scope item 9).
  • Do not mirror edges into Gitea comments, and do not change the Depends: declaration syntax or allocator_dependencies.py parsing behavior.
  • Do not claim, modify, or close umbrella #628.

Acceptance criteria

  1. control_plane_db.py reports SCHEMA_VERSION 4 and creates dependency_edges on a fresh database.
  2. A database created at SCHEMA_VERSION 3 migrates in place to 4, gains the new table, and retains all pre-existing rows in sessions, work_items, leases, assignments, terminal_locks, events, and incident_links.
  3. Migration is idempotent: running it twice produces no error and no duplicate schema objects.
  4. dependency_graph.py exposes edge-type and state enums covering the seven #628 relationship types and the unmet/met/unavailable states.
  5. An unrecognized edge_type or state is rejected fail-closed at the API boundary and no row is written.
  6. upsert_dependency_edge twice for the same (scope, source, target, edge_type) yields exactly one row with refreshed state, evidence, and last_observed_at.
  7. list_dependency_edges filtered by target returns every edge whose target is that work unit, across differing sources and edge types.
  8. list_dependency_edges scopes strictly by remote/org/repo: an edge in one repository is never returned for another.
  9. A state transition through record_dependency_edge_observation writes a corresponding events row carrying the prior and new state.
  10. Evidence is stored structurally and round-trips through read; no credential, token, or endpoint URL is ever persisted in the evidence payload.
  11. After a live allocation run over issues declaring dependencies, the store holds one edge per declared reference with the state matching that run's resolution outcome.
  12. An injected dependency-store write failure during an allocation run does not fail the run: the selection result is unchanged and the failure surfaces in reasons.
  13. gitea_list_dependency_edges returns stored edges for a scope, fails closed without gitea.read, and performs no mutation.
  14. Allocator selection output for an unchanged candidate set is byte-identical before and after this change, apart from added dependency-store reporting fields.
  15. Tests cover: fresh-schema creation; v3-to-v4 migration with data retention; idempotent re-migration; enum validation rejection; upsert idempotence; forward and reverse lookup; scope isolation; transition event emission; allocation-run ingestion; write-failure tolerance; and the read-only tool's permission gate.

Implementation investigation

  • control_plane_db.pySCHEMA_VERSION at line 32, DDL block from line 49, existing migration helpers _migrate_lease_lifecycle_columns and _migrate_session_ownership_columns establish the additive-migration pattern to follow.
  • allocator_dependencies.pyresolve_dependency_state() already returns met / unmet / unavailable tuples; these map directly onto the stored state without new classification logic.
  • gitea_mcp_server.py_allocator_candidates_from_gitea() is the single ingestion point where references are resolved against live state; it already holds the resolution result needed for the edge write.
  • allocator_service.pyWorkCandidate.dependency_unmet / dependency_reason and classify_skip() remain the selection path and are untouched.
  • Reuse the existing events table for transitions rather than adding a parallel audit table.

Safety and architectural constraints

  • Fail closed on invalid edge type or state; never coerce unknown values into a default.
  • Unavailable evidence is never recorded as met.
  • Dependency persistence must not become a new failure mode for allocation: writes are best-effort and non-blocking.
  • No secrets, tokens, or endpoint URLs in stored evidence or tool output.
  • The control-plane DB coordinates; Gitea remains the durable record of issue and PR truth. This store holds observations about relationships, not a replacement for issue history.
Parent: #628 (Slice A) · Related: #758, #613, #600, #601 ## Problem Umbrella #628 scope item 6 requires dependencies to be represented as **durable structured state**, with each edge carrying source, target, type, blocking condition, completion condition, current state, and evidence. The repository does not have that store. Today the allocator derives dependency status entirely in-memory, once per allocation run: * `allocator_dependencies.parse_dependency_refs()` re-parses the `Depends:` declaration out of each issue body on every call; * `_allocator_candidates_from_gitea()` in `gitea_mcp_server.py` resolves each parsed reference against live issue state and collapses the result into two ephemeral `WorkCandidate` fields, `dependency_unmet` and `dependency_reason`; * `allocator_service.classify_skip()` consumes those fields and then discards them. Nothing is written down. `control_plane_db.py` (SCHEMA_VERSION 3) has tables for `sessions`, `work_items`, `leases`, `assignments`, `terminal_locks`, `events`, and `incident_links` — and no table for dependency relationships. ## Impact 1. **No queryable graph.** Nothing can answer "what blocks issue X" or "what is waiting on issue Y" without re-listing every open issue and re-parsing every body. Automatic resumption (#628 scope item 7) needs the reverse edge — waiters keyed by target — which does not exist in any form. 2. **Only one edge kind is expressible.** #628 enumerates seven relationship types (issue blocked by issue, PR waiting for requested changes, merge waiting for approval, reconciliation waiting for merge, deployment waiting for infrastructure, controller acceptance waiting for validation, task waiting for tooling-defect fix). Issue-body text can only express the first. The other six have nowhere to be recorded. 3. **No evidence trail.** When an allocation run skips a candidate for unmet dependencies, the observation vanishes. There is no record of what was observed, when, or from what live state — so a later blocked/resume decision cannot be audited, and a transient lookup failure is indistinguishable from a real block after the fact. 4. **Recomputation cost and drift.** Every run performs targeted per-reference issue lookups (`_issue_state`) with only a per-run cache. Two roles allocating seconds apart can reach different conclusions with no durable record of either. ## Scope (this slice) Persistence and read access only. This slice records the graph; it does not change what the allocator selects. ### 1. Schema Add a `dependency_edges` table to `control_plane_db.py` under an additive, idempotent migration to SCHEMA_VERSION 4. Existing databases must migrate in place without data loss and without requiring a rebuild. Columns must cover the #628 edge contract: * identity: `edge_id`, scope (`remote`, `org`, `repo`); * `source_kind` / `source_number` (the waiting work unit); * `target_kind` / `target_number` (the depended-upon unit); * `edge_type` (validated enum); * `blocking_condition` and `completion_condition` (text); * `state` (validated enum); * `evidence` (structured JSON: observed live state, source of observation, observing session/role); * `created_at`, `updated_at`, `last_observed_at`. Uniqueness is on (scope, source, target, edge_type) so re-observation updates one row instead of appending duplicates. ### 2. Edge vocabulary module Add `dependency_graph.py` owning the `edge_type` and `state` enums and their validation. Both fail closed: an unrecognized type or state raises rather than being stored as free text. Edge types cover the seven #628 relationships; states cover at minimum `unmet`, `met`, and `unavailable` — matching the existing three-way outcome of `resolve_dependency_state()`, where unavailable evidence is not treated as satisfied. ### 3. ControlPlaneDB API * `upsert_dependency_edge(...)` — insert or update one edge by its uniqueness key, refreshing state, evidence, and `last_observed_at`. * `list_dependency_edges(...)` — filter by scope, source, target, edge type, and/or state. The target filter is what makes reverse lookup ("what waits on #N") a single query. * `record_dependency_edge_observation(...)` — update state plus evidence for an existing edge, appending an `events` row so the transition is auditable alongside existing control-plane events. ### 4. Allocator ingestion Where `_allocator_candidates_from_gitea()` already resolves declared references against live issue state, additionally persist each resolved reference as an edge: `edge_type` = issue-blocked-by-issue, `state` from the existing resolution outcome, `evidence` recording the observed live state and the observing session. Persistence is best-effort and must never fail an allocation run: a write error is reported in `reasons` and allocation continues on the in-memory values exactly as it does today. ### 5. Read-only exposure Add `gitea_list_dependency_edges` as a read-only MCP tool gated on `gitea.read`, returning stored edges for a scope with the same filters as the DB API. No mutation tool is added in this slice. ## Non-goals * Do not change allocator selection, ranking, eligibility, or skip semantics. `classify_skip()` keeps consuming the in-memory `dependency_unmet` field; the durable store is written alongside it, not read by it. * Do not implement automatic blocking, requeueing, or resumption (#628 scope item 7) — that consumes this store in a later slice. * Do not create edges from PR, review, merge, or reconciliation events yet; only the vocabulary must accommodate them. * Do not implement automatic tooling-defect linking (#628 scope item 9). * Do not mirror edges into Gitea comments, and do not change the `Depends:` declaration syntax or `allocator_dependencies.py` parsing behavior. * Do not claim, modify, or close umbrella #628. ## Acceptance criteria 1. `control_plane_db.py` reports SCHEMA_VERSION 4 and creates `dependency_edges` on a fresh database. 2. A database created at SCHEMA_VERSION 3 migrates in place to 4, gains the new table, and retains all pre-existing rows in `sessions`, `work_items`, `leases`, `assignments`, `terminal_locks`, `events`, and `incident_links`. 3. Migration is idempotent: running it twice produces no error and no duplicate schema objects. 4. `dependency_graph.py` exposes edge-type and state enums covering the seven #628 relationship types and the unmet/met/unavailable states. 5. An unrecognized `edge_type` or `state` is rejected fail-closed at the API boundary and no row is written. 6. `upsert_dependency_edge` twice for the same (scope, source, target, edge_type) yields exactly one row with refreshed state, evidence, and `last_observed_at`. 7. `list_dependency_edges` filtered by target returns every edge whose target is that work unit, across differing sources and edge types. 8. `list_dependency_edges` scopes strictly by remote/org/repo: an edge in one repository is never returned for another. 9. A state transition through `record_dependency_edge_observation` writes a corresponding `events` row carrying the prior and new state. 10. Evidence is stored structurally and round-trips through read; no credential, token, or endpoint URL is ever persisted in the evidence payload. 11. After a live allocation run over issues declaring dependencies, the store holds one edge per declared reference with the state matching that run's resolution outcome. 12. An injected dependency-store write failure during an allocation run does not fail the run: the selection result is unchanged and the failure surfaces in `reasons`. 13. `gitea_list_dependency_edges` returns stored edges for a scope, fails closed without `gitea.read`, and performs no mutation. 14. Allocator selection output for an unchanged candidate set is byte-identical before and after this change, apart from added dependency-store reporting fields. 15. Tests cover: fresh-schema creation; v3-to-v4 migration with data retention; idempotent re-migration; enum validation rejection; upsert idempotence; forward and reverse lookup; scope isolation; transition event emission; allocation-run ingestion; write-failure tolerance; and the read-only tool's permission gate. ## Implementation investigation * `control_plane_db.py` — `SCHEMA_VERSION` at line 32, DDL block from line 49, existing migration helpers `_migrate_lease_lifecycle_columns` and `_migrate_session_ownership_columns` establish the additive-migration pattern to follow. * `allocator_dependencies.py` — `resolve_dependency_state()` already returns `met` / `unmet` / `unavailable` tuples; these map directly onto the stored state without new classification logic. * `gitea_mcp_server.py` — `_allocator_candidates_from_gitea()` is the single ingestion point where references are resolved against live state; it already holds the resolution result needed for the edge write. * `allocator_service.py` — `WorkCandidate.dependency_unmet` / `dependency_reason` and `classify_skip()` remain the selection path and are untouched. * Reuse the existing `events` table for transitions rather than adding a parallel audit table. ## Safety and architectural constraints * Fail closed on invalid edge type or state; never coerce unknown values into a default. * Unavailable evidence is never recorded as met. * Dependency persistence must not become a new failure mode for allocation: writes are best-effort and non-blocking. * No secrets, tokens, or endpoint URLs in stored evidence or tool output. * The control-plane DB coordinates; Gitea remains the durable record of issue and PR truth. This store holds observations about relationships, not a replacement for issue history.
jcwalker3 added status:in-progress and removed status:ready labels 2026-07-21 16:54:57 -05:00
Author
Owner

Issue claim heartbeat

<!-- gitea-issue-claim-heartbeat:v1 --> **Issue claim heartbeat** - kind: claim - issue: #784 - branch: feat/issue-784-durable-dependency-edges - phase: claimed - profile: prgs-author - pr: none - blocker: none - next_action: create worktree and begin implementation
jcwalker3 added status:pr-open and removed status:in-progress labels 2026-07-21 17:08:11 -05:00
Author
Owner

[THREAD STATE LEDGER] Issue #784 durable dependency edges — author implementation complete, awaiting independent reviewer

What is true now:

  • Server-side decision state: no review verdict has been recorded via the review API for PR #785; no server-side review or merge state exists yet
  • Local verdict/state: implementation is committed and published at head 0589ec80698295e8a1e8f18974849000cf1f5861; PR #785 is in open state against base master 300e8acd13
  • Latest known validation: full suite in the branch worktree gave 4126 passed, 6 skipped, 493 subtests passed, 11 failed; the same 11 failures reproduce identically on a clean detached checkout of master at 300e8acd (11 failed, 254 passed), so they predate this branch

What changed:

  • Added dependency_edges table under control-plane schema v4; the table creation is itself the v3-to-v4 migration (additive, idempotent, existing tables untouched)
  • Added dependency_graph.py owning the seven umbrella #628 relationship types, the three observation states, fail-closed normalization, and evidence sanitization
  • Added ControlPlaneDB.upsert_dependency_edge, list_dependency_edges, record_dependency_edge_observation; state transitions append to the existing events table
  • Allocator now persists each dependency it already resolved; the write is best-effort and surfaces failures through reasons
  • Added read-only tool gitea_list_dependency_edges plus its entry in docs/mcp-tool-inventory.md (111 to 112 tools) for the #781 drift guard
  • Added tests/test_issue_784_dependency_edges.py with 31 cases; updated the schema-version assertion in tests/test_control_plane_db.py from 3 to 4

What is blocked:

  • Blocker classification: no blocker
  • Selection behavior is intentionally unchanged: classify_skip still consumes the in-memory dependency_unmet field, and a store whose writes all raise produces an identical selection, skip set, and candidate count

Who/what acts next:

  • Next actor: reviewer
  • Required action: acquire a reviewer PR lease on PR #785 pinned at head 0589ec8069, load the review workflow, and record a formal review verdict through the native reviewer namespace
  • Do not do: do not review or merge from this author session; do not claim, modify, or close umbrella issue #628; do not extend scope into automatic blocking/resumption (#628 item 7), non-issue edge creation, or defect auto-linking (#628 item 9); do not perform unrelated branch or worktree cleanup

Canonical Issue State

STATE:
implementation-complete-awaiting-review

WHO_IS_NEXT:
reviewer

NEXT_ACTION:
Perform an independent formal review of PR #785 at head 0589ec8069 and record the verdict through the reviewer namespace.

NEXT_PROMPT:

Role: REVIEWER
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. Fail closed on preflight: gitea_whoami on the
gitea-reviewer namespace, gitea_assess_master_parity, gitea_load_review_workflow, then
gitea_resolve_task_capability immediately before each reviewer mutation.

Scope to verify:
1. dependency_edges table and the v3-to-v4 migration are additive and idempotent, and no
   pre-existing table loses rows.
2. dependency_graph.py rejects unknown edge types, states, and endpoint kinds fail-closed,
   and evidence sanitization removes credentials and endpoint URLs before storage.
3. Uniqueness on (scope, source, target, edge_type) makes re-observation update one row.
4. Reverse lookup by target returns every waiter across sources and edge types.
5. Allocator selection is unchanged: confirm classify_skip still consumes the in-memory
   dependency_unmet field and that a failing store leaves selection, skip set, and
   candidate count identical.
6. gitea_list_dependency_edges is read-only, gated on gitea.read, and documented in
   docs/mcp-tool-inventory.md as the #781 drift guard requires.

Verification already claimed by the author, to be checked not assumed: 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.

Prohibited: approving your own authored work; merging in the reviewer session; claiming or
closing umbrella issue 628; unrelated branch or worktree cleanup.

WHAT_HAPPENED:
Allocator dry-run re-confirmed umbrella #628 as the highest-priority author candidate (candidate_count 67, fingerprint 0f9f5e98cb75cf4acc5807062f0bc898738137769c259fba8ed505cdc01517fe). Rather than claiming the umbrella, Slice A was cut as child issue #784 covering umbrella scope item 6 only. Issue #784 was locked to branch feat/issue-784-durable-dependency-edges with an isolated worktree, implemented, and published as PR #785.

WHY:
Dependency knowledge existed only as a per-run parse of issue-body Depends declarations, collapsed into two in-memory allocator fields and then discarded. Nothing could answer which work units wait on a given target, six of the seven relationship types umbrella #628 enumerates had nowhere to be recorded, and no observation evidence survived a run. Automatic resumption depends on that reverse edge existing as durable state.

RELATED_PRS:

BLOCKERS:
none

VALIDATION:

  • 31 new cases in tests/test_issue_784_dependency_edges.py covering fresh-schema creation, v3-to-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 in the raw stored column, live allocation-run ingestion, write-failure tolerance, and the read-only tool permission gate
  • branch worktree suite: 4126 passed, 6 skipped, 493 subtests passed, 11 failed
  • baseline detached checkout of master at 300e8acd: 11 failed, 254 passed across the same five files, proving those failures predate this branch
  • no review verdict recorded; no merge performed

LAST_UPDATED_BY:
jcwalker3 / prgs-author / author session / 2026-07-21

[THREAD STATE LEDGER] Issue #784 durable dependency edges — author implementation complete, awaiting independent reviewer What is true now: - Server-side decision state: no review verdict has been recorded via the review API for PR #785; no server-side review or merge state exists yet - Local verdict/state: implementation is committed and published at head 0589ec80698295e8a1e8f18974849000cf1f5861; PR #785 is in open state against base master 300e8acd13cca2482d24dc83c4f24341fe3e127e - Latest known validation: full suite in the branch worktree gave 4126 passed, 6 skipped, 493 subtests passed, 11 failed; the same 11 failures reproduce identically on a clean detached checkout of master at 300e8acd (11 failed, 254 passed), so they predate this branch What changed: - Added dependency_edges table under control-plane schema v4; the table creation is itself the v3-to-v4 migration (additive, idempotent, existing tables untouched) - Added dependency_graph.py owning the seven umbrella #628 relationship types, the three observation states, fail-closed normalization, and evidence sanitization - Added ControlPlaneDB.upsert_dependency_edge, list_dependency_edges, record_dependency_edge_observation; state transitions append to the existing events table - Allocator now persists each dependency it already resolved; the write is best-effort and surfaces failures through reasons - Added read-only tool gitea_list_dependency_edges plus its entry in docs/mcp-tool-inventory.md (111 to 112 tools) for the #781 drift guard - Added tests/test_issue_784_dependency_edges.py with 31 cases; updated the schema-version assertion in tests/test_control_plane_db.py from 3 to 4 What is blocked: - Blocker classification: no blocker - Selection behavior is intentionally unchanged: classify_skip still consumes the in-memory dependency_unmet field, and a store whose writes all raise produces an identical selection, skip set, and candidate count Who/what acts next: - Next actor: reviewer - Required action: acquire a reviewer PR lease on PR #785 pinned at head 0589ec80698295e8a1e8f18974849000cf1f5861, load the review workflow, and record a formal review verdict through the native reviewer namespace - Do not do: do not review or merge from this author session; do not claim, modify, or close umbrella issue #628; do not extend scope into automatic blocking/resumption (#628 item 7), non-issue edge creation, or defect auto-linking (#628 item 9); do not perform unrelated branch or worktree cleanup ## Canonical Issue State STATE: implementation-complete-awaiting-review WHO_IS_NEXT: reviewer NEXT_ACTION: Perform an independent formal review of PR #785 at head 0589ec80698295e8a1e8f18974849000cf1f5861 and record the verdict through the reviewer namespace. NEXT_PROMPT: ```text Role: REVIEWER 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. Fail closed on preflight: gitea_whoami on the gitea-reviewer namespace, gitea_assess_master_parity, gitea_load_review_workflow, then gitea_resolve_task_capability immediately before each reviewer mutation. Scope to verify: 1. dependency_edges table and the v3-to-v4 migration are additive and idempotent, and no pre-existing table loses rows. 2. dependency_graph.py rejects unknown edge types, states, and endpoint kinds fail-closed, and evidence sanitization removes credentials and endpoint URLs before storage. 3. Uniqueness on (scope, source, target, edge_type) makes re-observation update one row. 4. Reverse lookup by target returns every waiter across sources and edge types. 5. Allocator selection is unchanged: confirm classify_skip still consumes the in-memory dependency_unmet field and that a failing store leaves selection, skip set, and candidate count identical. 6. gitea_list_dependency_edges is read-only, gated on gitea.read, and documented in docs/mcp-tool-inventory.md as the #781 drift guard requires. Verification already claimed by the author, to be checked not assumed: 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. Prohibited: approving your own authored work; merging in the reviewer session; claiming or closing umbrella issue 628; unrelated branch or worktree cleanup. ``` WHAT_HAPPENED: Allocator dry-run re-confirmed umbrella #628 as the highest-priority author candidate (candidate_count 67, fingerprint 0f9f5e98cb75cf4acc5807062f0bc898738137769c259fba8ed505cdc01517fe). Rather than claiming the umbrella, Slice A was cut as child issue #784 covering umbrella scope item 6 only. Issue #784 was locked to branch feat/issue-784-durable-dependency-edges with an isolated worktree, implemented, and published as PR #785. WHY: Dependency knowledge existed only as a per-run parse of issue-body Depends declarations, collapsed into two in-memory allocator fields and then discarded. Nothing could answer which work units wait on a given target, six of the seven relationship types umbrella #628 enumerates had nowhere to be recorded, and no observation evidence survived a run. Automatic resumption depends on that reverse edge existing as durable state. RELATED_PRS: - PR #785 (this slice, in open state at head 0589ec80698295e8a1e8f18974849000cf1f5861) BLOCKERS: none VALIDATION: - 31 new cases in tests/test_issue_784_dependency_edges.py covering fresh-schema creation, v3-to-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 in the raw stored column, live allocation-run ingestion, write-failure tolerance, and the read-only tool permission gate - branch worktree suite: 4126 passed, 6 skipped, 493 subtests passed, 11 failed - baseline detached checkout of master at 300e8acd: 11 failed, 254 passed across the same five files, proving those failures predate this branch - no review verdict recorded; no merge performed LAST_UPDATED_BY: jcwalker3 / prgs-author / author session / 2026-07-21
sysadmin removed the status:pr-open label 2026-07-21 17:22:10 -05:00
Owner

[THREAD STATE LEDGER] Issue #784 reconciliation — post-merge cleanup tail finalized

What is true now:

  • Server-side decision state: this reconciliation changed no issue or PR decision state; issue #784 stays in closed state and PR #785 stays in closed state carrying merge commit 7ecf7bf2d6.
  • Local verdict/state: audit and cleanup only. The canonical checkout sits on master at 7ecf7bf2d6 with a clean working tree, in exact parity with prgs/master and with the MCP runtime startup head.
  • Latest known validation: repository-wide terminal-label hygiene over 426 issues reports residual_count 0 for status:pr-open with open_pr_count 0.

What changed:

  • Remote source branch feat/issue-784-durable-dependency-edges at head 0589ec8069 was deleted through the sanctioned reconciler path; branch-scoped post-delete readback returned verified_absent true.
  • Post-merge moot reviewer lease assessment on PR #785 returned lease_moot false with no active reviewer lease remaining, so no lease marker comment was warranted.
  • No other repository state was touched by this reconciliation.

What is blocked:

  • Blocker classification: no blocker

Who/what acts next:

  • Next actor: controller
  • Required action: continue the supervised loop by selecting the next eligible work item through gitea_allocate_next_work for the author role.
  • Do not do: do not reopen issue #784; do not claim or close umbrella #628 as single-cycle work; do not recreate the deleted source branch.

Canonical Issue State

STATE:
reconciled-complete

WHO_IS_NEXT:
controller

NEXT_ACTION:
Run the canonical allocator to select the next eligible work item; nothing further remains on issue #784.

NEXT_PROMPT:

Continue the supervised Gitea-Tools loop in Scaled-Tech-Consulting/Gitea-Tools at master 7ecf7bf2d666e179c2ecca27b60bb93f7092383f. Issue #784 needs no further work and its source branch is gone. Call gitea_allocate_next_work with role=author, exclude umbrella #628, and drive the selected work item through the full author, reviewer, merger, and reconciler lifecycle.

WHAT_HAPPENED:
Slice A of umbrella #628 shipped as issue #784 and PR #785, landing on master as merge commit 7ecf7bf2d6 from head 0589ec8069. The reconciler tail of that cycle could not run at the time because the MCP runtime had gone stale against the new master. After the canonical checkout advanced and all four namespaces restarted, this reconciliation completed the outstanding cleanup: the remote source branch was deleted and verified absent, the post-merge lease assessment confirmed nothing remained to neutralise, and terminal-label hygiene was re-verified repository-wide.

WHY:
The cycle's post-merge cleanup was deferred, not skipped. Leaving the source branch present and the audit trail unwritten would misrepresent the lifecycle as incomplete and leave a stale remote ref behind.

RELATED_PRS:
PR #785 — source branch feat/issue-784-durable-dependency-edges, head 0589ec8069, merge commit 7ecf7bf2d6. Umbrella #628 stays in open state and retains its remaining slices.

BLOCKERS:
None. Blocker classification: no blocker.

VALIDATION:
gitea_cleanup_merged_pr_branch returned success true, performed true, verified_absent true with readback status not_found. gitea_cleanup_post_merge_moot_lease in read-only mode returned pr_state closed with no active reviewer lease. gitea_assess_terminal_label_hygiene over 426 issues returned clean true with residual_count 0. gitea_assess_master_parity reports in_parity true at 7ecf7bf2d6.

LAST_UPDATED_BY:
prgs-reconciler (sysadmin) via the gitea-reconciler MCP namespace

[THREAD STATE LEDGER] Issue #784 reconciliation — post-merge cleanup tail finalized What is true now: - Server-side decision state: this reconciliation changed no issue or PR decision state; issue #784 stays in closed state and PR #785 stays in closed state carrying merge commit 7ecf7bf2d666e179c2ecca27b60bb93f7092383f. - Local verdict/state: audit and cleanup only. The canonical checkout sits on master at 7ecf7bf2d666e179c2ecca27b60bb93f7092383f with a clean working tree, in exact parity with prgs/master and with the MCP runtime startup head. - Latest known validation: repository-wide terminal-label hygiene over 426 issues reports residual_count 0 for status:pr-open with open_pr_count 0. What changed: - Remote source branch feat/issue-784-durable-dependency-edges at head 0589ec80698295e8a1e8f18974849000cf1f5861 was deleted through the sanctioned reconciler path; branch-scoped post-delete readback returned verified_absent true. - Post-merge moot reviewer lease assessment on PR #785 returned lease_moot false with no active reviewer lease remaining, so no lease marker comment was warranted. - No other repository state was touched by this reconciliation. What is blocked: - Blocker classification: no blocker Who/what acts next: - Next actor: controller - Required action: continue the supervised loop by selecting the next eligible work item through gitea_allocate_next_work for the author role. - Do not do: do not reopen issue #784; do not claim or close umbrella #628 as single-cycle work; do not recreate the deleted source branch. ## Canonical Issue State STATE: reconciled-complete WHO_IS_NEXT: controller NEXT_ACTION: Run the canonical allocator to select the next eligible work item; nothing further remains on issue #784. NEXT_PROMPT: ```text Continue the supervised Gitea-Tools loop in Scaled-Tech-Consulting/Gitea-Tools at master 7ecf7bf2d666e179c2ecca27b60bb93f7092383f. Issue #784 needs no further work and its source branch is gone. Call gitea_allocate_next_work with role=author, exclude umbrella #628, and drive the selected work item through the full author, reviewer, merger, and reconciler lifecycle. ``` WHAT_HAPPENED: Slice A of umbrella #628 shipped as issue #784 and PR #785, landing on master as merge commit 7ecf7bf2d666e179c2ecca27b60bb93f7092383f from head 0589ec80698295e8a1e8f18974849000cf1f5861. The reconciler tail of that cycle could not run at the time because the MCP runtime had gone stale against the new master. After the canonical checkout advanced and all four namespaces restarted, this reconciliation completed the outstanding cleanup: the remote source branch was deleted and verified absent, the post-merge lease assessment confirmed nothing remained to neutralise, and terminal-label hygiene was re-verified repository-wide. WHY: The cycle's post-merge cleanup was deferred, not skipped. Leaving the source branch present and the audit trail unwritten would misrepresent the lifecycle as incomplete and leave a stale remote ref behind. RELATED_PRS: PR #785 — source branch feat/issue-784-durable-dependency-edges, head 0589ec80698295e8a1e8f18974849000cf1f5861, merge commit 7ecf7bf2d666e179c2ecca27b60bb93f7092383f. Umbrella #628 stays in open state and retains its remaining slices. BLOCKERS: None. Blocker classification: no blocker. VALIDATION: gitea_cleanup_merged_pr_branch returned success true, performed true, verified_absent true with readback status not_found. gitea_cleanup_post_merge_moot_lease in read-only mode returned pr_state closed with no active reviewer lease. gitea_assess_terminal_label_hygiene over 426 issues returned clean true with residual_count 0. gitea_assess_master_parity reports in_parity true at 7ecf7bf2d666. LAST_UPDATED_BY: prgs-reconciler (sysadmin) via the gitea-reconciler MCP namespace
Owner

[THREAD STATE LEDGER] Issue #784 reconciler tail — post-merge cleanup performed

What is true now:

  • Server-side decision state: server-side state changed — the PR #783 source branch was deleted and verified absent; no server-side state changed on PR #785, whose source branch was already absent before this pass.
  • Local verdict/state: the reconciler tail for the #784 / PR #785 cycle now has no outstanding items; control-plane active leases 0.
  • Latest known validation: gitea_assess_master_parity reports in_parity at 7ecf7bf2d666; gitea_assess_terminal_label_hygiene reports clean across 426 issues with open_pr_count 0 and residual_count 0.

What changed:

  • Remote branch fix/issue-781-mcp-edit-issue-tool (PR #783 head a002864a) deleted through gitea_cleanup_merged_pr_branch, confirmed by branch-scoped post-delete readback (verified_absent: true).
  • Local worktree branches/fix-issue-781-mcp-edit-issue-tool removed — clean tree, head a002864a already an ancestor of master — to clear the active_branch_ownership / worktree_binding guard that fail-closed the delete.
  • Remote branch feat/issue-784-durable-dependency-edges confirmed already absent on the server; the local remote-tracking ref was stale and has been pruned.

What is blocked:

  • Blocker classification: no blocker

Who/what acts next:

  • Next actor: controller
  • Required action: open the next supervised cycle on the next eligible work item; no reconciliation remains outstanding for #784.
  • Do not do: do not re-run branch cleanup for PR #783 or PR #785, and do not re-open issue #784.

Canonical Issue State

STATE:
reconciled-complete

WHO_IS_NEXT:
controller

NEXT_ACTION:
Open the next supervised cycle on the next eligible issue; no reconciliation remains outstanding for #784.

NEXT_PROMPT:

Start the next supervised end-to-end loop cycle on the next eligible issue in Scaled-Tech-Consulting/Gitea-Tools. Master is at 7ecf7bf2d666e179c2ecca27b60bb93f7092383f and the MCP namespaces report in_parity at that commit. The #784 / PR #785 reconciler tail is finished: both cycle source branches are absent from the remote, control-plane active leases are 0, and repo-wide residual status:pr-open is 0.

WHAT_HAPPENED:
The #784 / PR #785 cycle stranded its reconciler tail when the MCP daemons went stale at the merge (startup_head 300e8acd against current_head 7ecf7bf2). After the namespaces restarted at 7ecf7bf2d666, the tail ran to completion: the PR #783 source branch was deleted and verified absent, the PR #785 source branch was confirmed already absent server-side, stale local tracking refs were pruned, and terminal label hygiene was re-verified clean.

WHY:
The cleanup could not run in the authoring session because both gates named in the cycle notes were live at the time — the stale-runtime gate (#420) fail-closed every mutation, and the branch-ownership guard protected the PR #783 branch while its worktree binding was still active. Both conditions have since cleared.

RELATED_PRS:
PR #785 — merge commit 7ecf7bf2d666e179c2ecca27b60bb93f7092383f. PR #783 — merge commit 300e8acd13cca2482d24dc83c4f24341fe3e127e.

BLOCKERS:
none

VALIDATION:
gitea_assess_master_parityin_parity: true at 7ecf7bf2d666. gitea_cleanup_merged_pr_branch (PR #783) → performed: true, verified_absent: true, readback not_found. git ls-remote --heads → neither cycle source branch present. gitea_assess_terminal_label_hygieneclean: true, 426 issues checked, 0 residual. gitea_list_workflow_leases → 0 active.

LAST_UPDATED_BY:
prgs-reconciler

[THREAD STATE LEDGER] Issue #784 reconciler tail — post-merge cleanup performed What is true now: - Server-side decision state: server-side state changed — the PR #783 source branch was deleted and verified absent; no server-side state changed on PR #785, whose source branch was already absent before this pass. - Local verdict/state: the reconciler tail for the #784 / PR #785 cycle now has no outstanding items; control-plane active leases 0. - Latest known validation: `gitea_assess_master_parity` reports `in_parity` at `7ecf7bf2d666`; `gitea_assess_terminal_label_hygiene` reports `clean` across 426 issues with `open_pr_count` 0 and `residual_count` 0. What changed: - Remote branch `fix/issue-781-mcp-edit-issue-tool` (PR #783 head `a002864a`) deleted through `gitea_cleanup_merged_pr_branch`, confirmed by branch-scoped post-delete readback (`verified_absent: true`). - Local worktree `branches/fix-issue-781-mcp-edit-issue-tool` removed — clean tree, head `a002864a` already an ancestor of master — to clear the `active_branch_ownership` / `worktree_binding` guard that fail-closed the delete. - Remote branch `feat/issue-784-durable-dependency-edges` confirmed already absent on the server; the local remote-tracking ref was stale and has been pruned. What is blocked: - Blocker classification: no blocker Who/what acts next: - Next actor: controller - Required action: open the next supervised cycle on the next eligible work item; no reconciliation remains outstanding for #784. - Do not do: do not re-run branch cleanup for PR #783 or PR #785, and do not re-open issue #784. ## Canonical Issue State STATE: reconciled-complete WHO_IS_NEXT: controller NEXT_ACTION: Open the next supervised cycle on the next eligible issue; no reconciliation remains outstanding for #784. NEXT_PROMPT: ```text Start the next supervised end-to-end loop cycle on the next eligible issue in Scaled-Tech-Consulting/Gitea-Tools. Master is at 7ecf7bf2d666e179c2ecca27b60bb93f7092383f and the MCP namespaces report in_parity at that commit. The #784 / PR #785 reconciler tail is finished: both cycle source branches are absent from the remote, control-plane active leases are 0, and repo-wide residual status:pr-open is 0. ``` WHAT_HAPPENED: The #784 / PR #785 cycle stranded its reconciler tail when the MCP daemons went stale at the merge (startup_head `300e8acd` against current_head `7ecf7bf2`). After the namespaces restarted at `7ecf7bf2d666`, the tail ran to completion: the PR #783 source branch was deleted and verified absent, the PR #785 source branch was confirmed already absent server-side, stale local tracking refs were pruned, and terminal label hygiene was re-verified clean. WHY: The cleanup could not run in the authoring session because both gates named in the cycle notes were live at the time — the stale-runtime gate (#420) fail-closed every mutation, and the branch-ownership guard protected the PR #783 branch while its worktree binding was still active. Both conditions have since cleared. RELATED_PRS: PR #785 — merge commit `7ecf7bf2d666e179c2ecca27b60bb93f7092383f`. PR #783 — merge commit `300e8acd13cca2482d24dc83c4f24341fe3e127e`. BLOCKERS: none VALIDATION: `gitea_assess_master_parity` → `in_parity: true` at `7ecf7bf2d666`. `gitea_cleanup_merged_pr_branch` (PR #783) → `performed: true`, `verified_absent: true`, readback `not_found`. `git ls-remote --heads` → neither cycle source branch present. `gitea_assess_terminal_label_hygiene` → `clean: true`, 426 issues checked, 0 residual. `gitea_list_workflow_leases` → 0 active. LAST_UPDATED_BY: prgs-reconciler
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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