Skip to content

Feat/merge review staleness - #259

Open
AlexAxthelm wants to merge 3 commits into
mainfrom
feat/merge-review-staleness
Open

Feat/merge review staleness#259
AlexAxthelm wants to merge 3 commits into
mainfrom
feat/merge-review-staleness

Conversation

@AlexAxthelm

@AlexAxthelm AlexAxthelm commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Extends #258 to address the merge-review dead end: when two pending merge
candidates share a source resource, approving one merges that resource away
(its original becomes an all-null shell), leaving the other candidate
functionally invalid. This surfaces that state — hiding the approve/deny
controls and telling the reviewer which resource was merged into which.

Added as a separate PR (stack) primarily because this one has some choices around the ergonomics of merge review that we may want to discuss more without blocking #258


The primary problem I have with the current flow is around the null-shell behavior. I think the best option if we have candidates:

A + B -> D
A + C // Now Invalid

Is that A+C is marked as invalid/outdated, and the merge candidate is taken out of the review queue (like "denied", terminal state, can't be suggested again, but is a 3rd state), and we run the linker on D in hopes that it picks up a new candidate for C (this should probably be part of the approval, or even the resource creation, process - putting the new resource in the queue to look for matches), so that we would get something like:

A + B -> D
A + C ----
D + C -> E

STIT-418 (part 2 of 2) — resolve repointed resources in the Merge Review pane

Stacked on #258 (base branch feat/redirect-detail); review/merge part 1
first, then this retargets to main.

When an overlapping candidate is approved, a member of another pending candidate
gets merged away. Previously that candidate's Approve was guaranteed to 400 with
a message interpolating repr() on ORM objects (Repointed: [<...object at 0x...>]), and the only exit was Deny — recording a judgment no one made.

  • API: merge-candidate views carry repointed_resources (which members moved
    and their terminal resource), computed for PENDING candidates via a single
    batched root lookup. The approve-time guard now names the terminal resource
    ("resource 102 is now resource 301") on both the merge-candidate and
    write-path guards.
  • Frontend: the review pane shows a warning banner naming each move (linked),
    hides Approve/Deny when a member has moved, and annotates moved members with
    "(now 301)".

Also includes a small perf commit dropping redundant queries on the resolve
paths (get_resolved builds the entity from the row it already loaded; create
skips an always-empty staleness lookup).

The retire/requeue/SUPERSEDED-status flow is intentionally deferred until the
stale-candidate dead end actually blocks a reviewer.

Tests

Backend: origin-column batch mapping, N+1 batch guard, PENDING-only rule, the
cross-candidate case end-to-end (approve 400s naming both ids, no object at 0x, stays PENDING), and the apply_resource_merge guard case. Frontend: stale
banner + hidden controls, facts annotation, compat guard, and a pure-function
suite for the staleness util.

AI assistance

Implemented with Claude Code. Verified: API pytest + frontend vitest green.

When an overlapping merge candidate is approved, a member of another pending
candidate gets merged away (repointed). Previously that candidate's Approve was
guaranteed to fail with a message interpolating repr() on ORM objects
("Repointed: [<...object at 0x...>]") — no id, no pointer to where the resource
went — and the only exit was Deny, recording a review judgment no one made.

Surface the move instead: resolve repointed member ids to their terminal
resource and show the curator where each one went. Completes AC 3 of STIT-418

Backend:
- ResourceModel._parent_tree_cte now emits (origin_id, id) so a batch keeps
  each input mapped to its own root instead of returning an unkeyed set of
  roots; add root_id_by_resource_id(), a batched dict-returning resolver.
  get_root/_root_select are unchanged (documented single-id).
- Add RepointedResourceView and a repointed_resources list on MergeCandidateView
  (plain field, not computed_field, so the detail view's model_dump rebuild
  keeps it). Populated only for PENDING candidates via _resolve_candidates,
  which does a single root lookup over the union of member ids (the queue is
  unpaginated, so per-candidate resolution would be an N+1).
- Replace the repr() merge-guard message with a shared repointed_merge_error()
  that names each id and its terminal target ("resource 102 is now resource
  301"), wired into both the merge-candidate and write-path guards.

Frontend:
- New utils/mergeCandidateStaleness.js: the single reader of repointed_resources,
  degrading to "not stale" when the field is absent.
- Merge review pane shows a warning banner naming each move (both ids linked),
  hides the decision controls when stale (Approve would fail; Deny would record
  a false judgment), and annotates a moved source resource as "(now 301)".

Tests:
- Backend: origin-column batch mapping, the N+1 batch guard, the PENDING-only
  rule, the cross-candidate stale case end-to-end (queue + detail report the
  move; re-approve 400s naming both ids with no "object at 0x", stays PENDING),
  and fill the previously-empty apply_resource_merge guard stub.
- Frontend: stale banner + hidden controls, the facts annotation, a compat guard
  (payload without the field renders as before), and a pure-function suite for
  the staleness util.

The retire/requeue/SUPERSEDED-status flow is intentionally deferred (see PLAN.md)
until the stale-candidate dead end actually blocks a reviewer, keeping this PR
purely additive and rollback-safe.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@github-actions

Copy link
Copy Markdown

CD summary 3a61211

Frontend: https://witty-mushroom-017a3dc1e-259.westus2.1.azurestaticapps.net

Deployments (4)
service url fqdn
api open pr-0259-api.purplegrass-c07d0a94.westus2.azurecontainerapps.io
entity-linkage open pr-0259-el.purplegrass-c07d0a94.westus2.azurecontainerapps.io
frontend https://witty-mushroom-017a3dc1e-259.westus2.1.azurestaticapps.net
stitch-llm open pr-0259-llm.purplegrass-c07d0a94.westus2.azurecontainerapps.io
Database (1)
db_name postgres_host postgres_port postgres_db
pr_0259 stitch-dev.postgres.database.azure.com 5432 pr_0259
Jobs (2)
job image postgres_db api_url auth_mode
db-migrations ghcr.io/rmi/stitch-api:pr-0259@sha256:6d9b4dc7ec697ec57a548f3ddd3785d3c8498e3b2b830fb8015757143d3e61a0 pr_0259
seed ghcr.io/rmi/stitch-seed:pr-0259@sha256:8132cd8b82c8eb988cbc9abec651f74940391fa52a7eb37efce3450b80df67c9 https://pr-0259-api.purplegrass-c07d0a94.westus2.azurecontainerapps.io/api/v1 stitch-client-bearer-token
Images (4)
build_time commit_time git_sha image image_digest
2026-08-28T16:21:11Z 2026-08-28T16:20:54Z f7f223f ghcr.io/rmi/stitch-api:pr-0259 ghcr.io/rmi/stitch-api:pr-0259@sha256:6d9b4dc7ec697ec57a548f3ddd3785d3c8498e3b2b830fb8015757143d3e61a0
2026-08-28T16:21:11Z 2026-08-28T16:20:54Z f7f223f ghcr.io/rmi/stitch-entity-linkage:pr-0259 ghcr.io/rmi/stitch-entity-linkage:pr-0259@sha256:84e4db2403c9d16e701df08e14ce3bc7a1bc24b06d2da8aacba6c08f7ad6b18a
2026-08-28T16:21:11Z 2026-08-28T16:20:54Z f7f223f ghcr.io/rmi/stitch-seed:pr-0259 ghcr.io/rmi/stitch-seed:pr-0259@sha256:8132cd8b82c8eb988cbc9abec651f74940391fa52a7eb37efce3450b80df67c9
2026-08-28T16:21:11Z 2026-08-28T16:20:54Z f7f223f ghcr.io/rmi/stitch-stitch-llm:pr-0259 ghcr.io/rmi/stitch-stitch-llm:pr-0259@sha256:f79d040427b9b0af22516f2c48b95aa61ca64b00986c24f513479b1d1e3fe9f7

…(STIT-418)

- get_resolved loads the resource row once and, for the common non-repointed
  case, builds the entity from it directly instead of going through
  resolve_root_id + get() (which re-fetched the same id). A repointed id still
  resolves via resolve_root_id (which maps a broken chain to 404) and loads the
  terminal resource.
- create_merge_candidate returns the view with an empty staleness resolution
  instead of running the root-lookup CTE: its members are validated as
  unrepointed immediately above, so the lookup could only ever return empty.
  (approve/deny short-circuit on their terminal status.)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@AlexAxthelm
AlexAxthelm requested review from jdhoffa and mbarlow12 and removed request for mbarlow12 August 28, 2026 16:55
@AlexAxthelm AlexAxthelm self-assigned this Aug 28, 2026
@AlexAxthelm
AlexAxthelm marked this pull request as ready for review August 28, 2026 16:56
@AlexAxthelm
AlexAxthelm deployed to development August 28, 2026 17:01 — with GitHub Actions Active
@AlexAxthelm
AlexAxthelm deployed to development August 28, 2026 17:01 — with GitHub Actions Active
@AlexAxthelm
AlexAxthelm deployed to development August 28, 2026 17:01 — with GitHub Actions Active
@AlexAxthelm
AlexAxthelm deployed to development August 28, 2026 17:02 — with GitHub Actions Active
@AlexAxthelm
AlexAxthelm deployed to development August 28, 2026 17:02 — with GitHub Actions Active
@AlexAxthelm
AlexAxthelm deployed to development August 28, 2026 17:03 — with GitHub Actions Active
@AlexAxthelm
AlexAxthelm deployed to development August 28, 2026 17:03 — with GitHub Actions Active
@AlexAxthelm
AlexAxthelm deployed to development August 28, 2026 17:04 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

CD summary 35f76e7

Frontend: https://witty-mushroom-017a3dc1e-259.westus2.1.azurestaticapps.net

Deployments (4)
service url fqdn
api open pr-0259-api.purplegrass-c07d0a94.westus2.azurecontainerapps.io
entity-linkage open pr-0259-el.purplegrass-c07d0a94.westus2.azurecontainerapps.io
frontend https://witty-mushroom-017a3dc1e-259.westus2.1.azurestaticapps.net
stitch-llm open pr-0259-llm.purplegrass-c07d0a94.westus2.azurecontainerapps.io
Database (1)
db_name postgres_host postgres_port postgres_db
pr_0259 stitch-dev.postgres.database.azure.com 5432 pr_0259
Jobs (1)
job image postgres_db
db-migrations ghcr.io/rmi/stitch-api:pr-0259@sha256:cfa9e21f3f572cbb4c2726f226abdc53ed9a0d13e4879762c29efde8f59c96a7 pr_0259
Images (4)
build_time commit_time git_sha image image_digest
2026-08-28T17:01:20Z 2026-08-28T17:01:00Z 388fa91 ghcr.io/rmi/stitch-api:pr-0259 ghcr.io/rmi/stitch-api:pr-0259@sha256:cfa9e21f3f572cbb4c2726f226abdc53ed9a0d13e4879762c29efde8f59c96a7
2026-08-28T17:01:19Z 2026-08-28T17:01:00Z 388fa91 ghcr.io/rmi/stitch-entity-linkage:pr-0259 ghcr.io/rmi/stitch-entity-linkage:pr-0259@sha256:af706928bf511c0f0437fec2afcd36cbe3f1d0f3a0b9a0ab768f5c51781874dd
2026-08-28T17:01:19Z 2026-08-28T17:01:00Z 388fa91 ghcr.io/rmi/stitch-seed:pr-0259 ghcr.io/rmi/stitch-seed:pr-0259@sha256:85bb8793994aee34409b738bcab02b65f107239866e332cb4044c6a71268b371
2026-08-28T17:01:23Z 2026-08-28T17:01:00Z 388fa91 ghcr.io/rmi/stitch-stitch-llm:pr-0259 ghcr.io/rmi/stitch-stitch-llm:pr-0259@sha256:ee03a011a1403b4a1a97c1dfc33eca2169a5b44ba810d02d0e08b04e96308546

Base automatically changed from feat/redirect-detail to main September 3, 2026 15:05
Copilot AI lite review requested due to automatic review settings September 4, 2026 13:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

get_resolved still re-queries the same resource on the repointed path via resolve_root_id, which is an avoidable extra round-trip and undermines the intended perf improvement.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR extends the merge-review workflow to detect and surface “stale” merge candidates caused by overlapping candidates where one approval repoints a resource that another pending candidate still references. It adds an explicit repointed_resources signal to merge-candidate API views, blocks re-approval with clearer id-based error messages, and updates the Merge Review UI to warn reviewers and hide actions when a candidate has become invalid.

Changes:

  • Backend: add repointed_resources to merge-candidate views (computed in a batched root lookup for PENDING candidates) and improve merge guards to return id-based “resource X is now resource Y” errors.
  • Frontend: add a staleness utility and update the Merge Review pane to show a warning banner, annotate moved members, and hide Approve/Deny controls for stale candidates.
  • Tests: add/extend unit + integration tests covering batch resolution, PENDING-only behavior, overlapping-candidate staleness, and merge guard messaging.
File summaries
File Description
deployments/stitch-frontend/src/utils/mergeCandidateStaleness.js Adds a single utility to interpret repointed_resources into {isStale, moves}.
deployments/stitch-frontend/src/utils/mergeCandidateStaleness.test.js Adds a pure-function test suite for the staleness utility.
deployments/stitch-frontend/src/pages/MergeCandidateReviewPage.jsx Renders stale-candidate banner, annotates moved members, and hides decision controls when stale.
deployments/stitch-frontend/src/pages/MergeCandidateReviewPage.test.jsx Adds UI tests for stale banner, hidden controls, and facts annotations.
deployments/api/src/stitch/api/entities.py Introduces RepointedResourceView and adds repointed_resources to MergeCandidateView.
deployments/api/src/stitch/api/db/og_field_resource_actions.py Improves repointed-merge error messaging and optimizes get_resolved read path.
deployments/api/src/stitch/api/db/model/resource.py Adds batched root_id_by_resource_id and augments the recursive CTE to preserve origin-to-root mapping.
deployments/api/src/stitch/api/db/merge_candidate_actions.py Computes and propagates repointed_resources for candidates (batched for list endpoint; resolved for detail/actions).
deployments/api/tests/test_merge_candidate_actions.py Updates unit tests and adds tests for batched resolution and terminal-status skip logic.
deployments/api/tests/routers/test_merge_candidates_integration.py Adds end-to-end integration test for overlapping-approval staleness and id-based 400 messaging.
deployments/api/tests/db/test_resource_actions.py Adds integration tests for root_id_by_resource_id mapping correctness and empty-input behavior.
deployments/api/tests/db/actions/test_merge.py Adds guard test ensuring apply-merge rejects repointed inputs with id-based message (no repr() leakage).
Review details
  • Files reviewed: 12/12 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +196 to +198
if model.repointed_id is not None:
root_id = await resolve_root_id(session, id)
return await get(session, root_id, licensed_sources=licensed_sources)
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

CD summary 83e3315

Frontend: https://witty-mushroom-017a3dc1e-259.westus2.1.azurestaticapps.net

Deployments (4)
service url fqdn
api open pr-0259-api.purplegrass-c07d0a94.westus2.azurecontainerapps.io
entity-linkage open pr-0259-el.purplegrass-c07d0a94.westus2.azurecontainerapps.io
frontend https://witty-mushroom-017a3dc1e-259.westus2.1.azurestaticapps.net
stitch-llm open pr-0259-llm.purplegrass-c07d0a94.westus2.azurecontainerapps.io
Database (1)
db_name postgres_host postgres_port postgres_db
pr_0259 stitch-dev.postgres.database.azure.com 5432 pr_0259
Jobs (1)
job image postgres_db
db-migrations ghcr.io/rmi/stitch-api:pr-0259@sha256:4e84bbebc59552192978814d3b27c0a0d624161bd705680d12160d1dc256d94c pr_0259
Images (4)
build_time commit_time git_sha image image_digest
2026-09-04T13:17:33Z 2026-09-04T13:17:13Z bdde2c3 ghcr.io/rmi/stitch-api:pr-0259 ghcr.io/rmi/stitch-api:pr-0259@sha256:4e84bbebc59552192978814d3b27c0a0d624161bd705680d12160d1dc256d94c
2026-09-04T13:17:31Z 2026-09-04T13:17:13Z bdde2c3 ghcr.io/rmi/stitch-entity-linkage:pr-0259 ghcr.io/rmi/stitch-entity-linkage:pr-0259@sha256:34bc444baa61d180271843bd4cfa2fac766784ec93f05bf8bf287bcb058c948e
2026-09-04T13:17:34Z 2026-09-04T13:17:13Z bdde2c3 ghcr.io/rmi/stitch-seed:pr-0259 ghcr.io/rmi/stitch-seed:pr-0259@sha256:7576c09da4861ffe7feb11e8c18c37680f96fdf40b804f413967a6ae80e9a1ec
2026-09-04T13:17:30Z 2026-09-04T13:17:13Z bdde2c3 ghcr.io/rmi/stitch-stitch-llm:pr-0259 ghcr.io/rmi/stitch-stitch-llm:pr-0259@sha256:6bf247b1041f1047502a58f198faa2706cbb02e9ac92ac14e25514b3dd003cb0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants