Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,17 @@ the same `persist_edges_for_post` Keyman ingestion already uses. A person R&R
actor is opportunistically joined to an existing `cataloged_person` row
by name (never originated by R&R itself -- documented gap in the ADR:
`cataloged_person` needs `person_side_code`, which R&R's prompt does
not currently capture).
not currently capture). ADR 0019 stores that resolved catalog id on
`post_summary_role` (`cataloged_team_id` /
`cataloged_corporate_entity_id` / `cataloged_person_id`, ADR 0019 /
0021) so a later read does not rejoin `corporate_entity` by
`entity_name`. Fetch returns the person foreign key as
`catalog_node_id` the same way. Historical backfill leaves a role
unbound when two same-named mentions already exist on the post.
Open a post whose R&R names an organization that shares a display name
with another catalog row: the chip keeps the id persist stored. Click
it to walk that organization, not the homonym. Click a person chip to
walk the stored person even when Keyman was not extracted on that post.

## Phase 12: a real counterparty organization is auto-created, not left permanently unresolved

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.d/0.86.3-role-person-catalog-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
R&R person chips read the stored catalog id. Historical person backfill
leaves homonym mentions unbound. A Pending run detail says reconstruction
has not started yet.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ All notable changes to this project are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.86.3] - 2026-08-16

### Fixed

- R&R person chips now read `cataloged_person_id` from
`post_summary_role` (ADR 0021). Open a post whose R&R names a
cataloged person: the chip is a button even when Keyman extraction
was not run on that post. Click it to walk that person, not a later
same-named row. Historical backfill leaves a role unbound when two
same-named mentions already exist.
Comment thread
cursor[bot] marked this conversation as resolved.
- Request a lineage reconstruction: the opened Pending panel says

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

#148 already landed the Pending reconstruction sentence on #74 39ed6eb. This 0.86.3 bullet double-claims a shipped line after rebase.

Next action: drop this bullet. Keep only the cataloged_person_id / unbound-homonym sentence. ADR 0021 stays on this slice. ADR 0020 is still the next free SQL slot on #74 (retention #154 closed unmerged; do not reuse 0020 here).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Live feat/role-responsibility-agent-ontology is 69c035b / 0.87.0. #148 already pinned Pending copy to registered kinds (Pending TEPP must not say reconstruction). This 0.86.3 bullet double-claims a shipped lineage-only sentence and will conflict on rebase.

Next action: drop this bullet. Keep only the cataloged_person_id / unbound-homonym sentence. ADR 0021 stays on this slice. ADR 0020 on the feature tip is retention purge, not pending copy.

reconstruction has not started yet.

## [0.86.2] - 2026-08-16

### Fixed
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ Opening a cutoff title shows the live post -- compare it with the
cutoff before treating the body as reconstructed evidence (ADR 0016).
`POST /api/analysis-runs` records Pending on an authorized
cutoff capture (ADR 0017) and does not reconstruct lineage.
R&R chips read the catalog id stored on `post_summary_role`
(ADR 0019 / 0021), including `cataloged_person_id`. Do not rejoin
`corporate_entity` by `entity_name`. Historical backfill leaves a
role unbound when two same-named mentions already exist on the post.
48 changes: 31 additions & 17 deletions backend/app/post_summary_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@
NullCorporateHierarchyInferenceClient,
)
from lineageweave.fixtures import fixture_thread_cast
from lineageweave.knowledge_graph import NODE_CORPORATE_ENTITY, NODE_TEAM
from lineageweave.knowledge_graph import (
NODE_CORPORATE_ENTITY,
NODE_PERSON,
NODE_TEAM,
)
from lineageweave.ontology import ontology_annotations
from lineageweave.post_summary import (
ACTOR_TYPE_ORGANIZATION,
Expand All @@ -61,8 +65,8 @@ async def fetch_persisted_summary(
"""Return the stored summary payload, or None when none has been written.

``catalog_node_id`` comes from the role row's catalog foreign keys
(ADR 0019). This function does not join ``corporate_entity`` by
``entity_name``.
(ADR 0019 / 0021). This function does not join ``corporate_entity``
by ``entity_name``. Person chips read ``cataloged_person_id``.
"""
header = await conn.fetchrow(
"select korean_summary from post_summary_result where post_id = $1",
Expand All @@ -79,7 +83,8 @@ async def fetch_persisted_summary(
select role.actor_name, role.responsibility, role.actor_type_code,
role.affiliated_organization_name,
role.cataloged_team_id,
role.cataloged_corporate_entity_id
role.cataloged_corporate_entity_id,
role.cataloged_person_id
from post_summary_role role
where role.post_id = $1
order by role.actor_name
Expand All @@ -96,6 +101,9 @@ async def fetch_persisted_summary(
elif row["cataloged_corporate_entity_id"] is not None:
catalog_node_id = str(row["cataloged_corporate_entity_id"])
catalog_node_type_code = NODE_CORPORATE_ENTITY
elif row["cataloged_person_id"] is not None:
catalog_node_id = str(row["cataloged_person_id"])
catalog_node_type_code = NODE_PERSON
payload_roles.append(
{
"actor_name": row["actor_name"],
Expand Down Expand Up @@ -215,6 +223,7 @@ async def _replace_summary_projection(
for role_index, role in enumerate(summary.roles_and_responsibilities):
cataloged_team_id = None
cataloged_corporate_entity_id = None
cataloged_person_id = None
if role.actor_type_code == ACTOR_TYPE_TEAM:
cataloged_team_id = await upsert_team(
conn,
Expand All @@ -226,19 +235,29 @@ async def _replace_summary_projection(
cataloged_corporate_entity_id = resolved_organization_ids.get(
role_index
)
elif role.actor_type_code == ACTOR_TYPE_PERSON:
person_row = await conn.fetchrow(
"select person_id from cataloged_person "
"where person_name = $1 "
"order by created_at, person_id limit 1",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Persist still first-wins among catalog homonyms (ORDER BY created_at, person_id LIMIT 1). Migration 0021 backfill already leaves the role unbound when HAVING count(*) = 1 fails.

A later click walks the oldest Kim Cheolsu, not the person this post named. That guess is now durable on cataloged_person_id.

Next action: bind by name only when exactly one cataloged_person row matches. If two rows share the name, leave cataloged_person_id null and skip post_summary_person_mention. Do not create a person row (ADR 0009). Change test_same_name_person_roles_bind_the_earliest_catalog_row to expect unbound.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Persist still first-wins among catalog homonyms (ORDER BY created_at, person_id LIMIT 1). Migration 0021 backfill already leaves the role unbound when HAVING count(*) = 1 fails.

A later click walks the oldest Kim Cheolsu, not the person this post named. That guess is now durable on cataloged_person_id.

Next action: bind by name only when exactly one cataloged_person row matches. If two rows share the name, leave cataloged_person_id null and skip post_summary_person_mention. Do not create a person row (ADR 0009). Change test_same_name_person_roles_bind_the_earliest_catalog_row to expect unbound.

role.actor_name,
)
if person_row is not None:
cataloged_person_id = str(person_row["person_id"])
await conn.execute(
"insert into post_summary_role "
Comment thread
cursor[bot] marked this conversation as resolved.
"(post_id, actor_name, responsibility, actor_type_code, "
"affiliated_organization_name, cataloged_team_id, "
"cataloged_corporate_entity_id) values "
"($1, $2, $3, $4, $5, $6, $7)",
"cataloged_corporate_entity_id, cataloged_person_id) values "
"($1, $2, $3, $4, $5, $6, $7, $8)",
post_id,
role.actor_name,
role.responsibility,
role.actor_type_code,
role.affiliated_organization_name,
cataloged_team_id,
cataloged_corporate_entity_id,
cataloged_person_id,
)
if cataloged_team_id is not None:
await conn.execute(
Expand All @@ -255,18 +274,13 @@ async def _replace_summary_projection(
post_id,
cataloged_corporate_entity_id,
)
elif role.actor_type_code == ACTOR_TYPE_PERSON:
person_row = await conn.fetchrow(
"select person_id from cataloged_person where person_name = $1 limit 1",
role.actor_name,
elif cataloged_person_id is not None:
await conn.execute(
"insert into post_summary_person_mention (post_id, person_id) "
"values ($1, $2) on conflict do nothing",
post_id,
cataloged_person_id,
)
if person_row is not None:
await conn.execute(
"insert into post_summary_person_mention (post_id, person_id) "
"values ($1, $2) on conflict do nothing",
post_id,
str(person_row["person_id"]),
)
await persist_edges_for_post(conn, post_id)


Expand Down
31 changes: 31 additions & 0 deletions backend/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,37 @@ def summarize(self, post_title: str, post_body: str) -> PostSummary:
assert role["catalog_node_type_code"] == "node_team"


def test_organization_mentioned_only_on_other_corp_private_post_is_forbidden(
client, demo_analyst_token, seeded_db
) -> None:
"""A private org mention must not open the related walk through the UNION."""

admin_conn = psycopg2.connect(seeded_db["dsn"])
admin_conn.autocommit = True
try:
with admin_conn.cursor() as cur:
cur.execute(
"insert into corporate_entity "
"(corporate_entity_code, entity_name, entity_level_code) "
"values ('HIDDEN-MENTION', 'Hidden Mention Corp', 'company') "
"returning corporate_entity_id"
)
hidden_org_id = str(cur.fetchone()[0])
cur.execute(
"insert into post_organization_mention "
"(post_id, corporate_entity_id) values (%s, %s)",
(seeded_db["other_private_post_id"], hidden_org_id),
)
finally:
admin_conn.close()

response = client.get(
f"/api/corporate-entities/{hidden_org_id}/related",
headers={"Authorization": f"Bearer {demo_analyst_token}"},
)
assert response.status_code == 403


def test_organization_mention_only_posts_appear_in_entity_related(
client, demo_analyst_token, seeded_db
) -> None:
Expand Down
1 change: 1 addition & 0 deletions docker/postgres-init/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ COPY migrations/0016_cross_post_actor_identity.sql /docker-entrypoint-initdb.d/1
COPY migrations/0017_prov_o_standard_relations.sql /docker-entrypoint-initdb.d/18-prov-o-standard-relations.sql
COPY migrations/0018_analysis_run_registry.sql /docker-entrypoint-initdb.d/19-analysis-run-registry.sql
COPY migrations/0019_role_catalog_identity.sql /docker-entrypoint-initdb.d/20-role-catalog-identity.sql
COPY migrations/0021_role_person_catalog_identity.sql /docker-entrypoint-initdb.d/21-role-person-catalog-identity.sql
# Official image already drops to this account at runtime; declare it so
# the Dockerfile itself satisfies DS-0002 (explicit non-root USER).
USER postgres
4 changes: 3 additions & 1 deletion docs/adr/0009-cross-post-actor-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ Depends on [ADR 0006](0006-role-responsibility-agent-ontology.md) and
[ADR 0007](0007-team-actor-type.md) (actor *type*) and
`lineageweave.corporate_hierarchy_resolution` (Bhattacharya & Getoor,
2007, cited there) for the organization-matching this ADR reuses rather
than re-deriving.
than re-deriving. [ADR 0019](0019-role-catalog-identity.md) stores the
resolved catalog id on `post_summary_role` so fetch does not rejoin by
display name.

## References (APA 7th)

Expand Down
2 changes: 2 additions & 0 deletions docs/adr/0018-related-nodes-team-org-walk.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Thread-group run list visibility requires at least one ABAC-visible
organization chip.
- A later public post in a thread group no longer lists a January run
that could not have known that post.
- Catalog ids on those chips come from `post_summary_role` (ADR 0019).
Do not rejoin `corporate_entity` by `entity_name`.

## References

Expand Down
3 changes: 2 additions & 1 deletion docs/adr/0019-role-catalog-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ not.
`post_summary_role` stores the resolved catalog foreign keys
(`cataloged_team_id`, `cataloged_corporate_entity_id`) written during
`persist_post_summary`. `fetch_persisted_summary` reads those columns.
It does not join `corporate_entity` by `entity_name`.
It does not join `corporate_entity` by `entity_name`. Person identity
is ADR 0021 (`cataloged_person_id`).

Migration `0019_role_catalog_identity.sql` backfills existing rows from
a post-scoped mention only when the name match is unique on that post.
Expand Down
52 changes: 52 additions & 0 deletions docs/adr/0021-role-person-catalog-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ADR 0021 — Persist the R&R person catalog identity on the role row

**Decision status:** Accepted
**Date:** 2026-08-16
**Depends on:** ADR 0009 cross-post actor identity; ADR 0019 role catalog
identity

## Context

ADR 0019 stores `cataloged_team_id` and `cataloged_corporate_entity_id`
on `post_summary_role` so fetch does not rejoin by display name. Person
actors were still joined at read time by `person_name`, or dropped from
the payload entirely. Two people can share a display name. A later
Keyman row with the same name then steals the chip, and a person named
only in R&R (Keyman not extracted on that post) has no button.

Fellegi and Sunter (1969) treat a match decision as a binding to one
record, not a later re-search by a non-unique attribute.

## Decision

`post_summary_role` stores `cataloged_person_id` written during
`persist_post_summary`. `fetch_persisted_summary` reads that column into
`catalog_node_id` / `node_person`. Person lookup, when it still resolves
by name, orders by `created_at`, then `person_id`. It still does not
create a new `cataloged_person` row (ADR 0009 gap).

Migration `0021_role_person_catalog_identity.sql` backfills existing
rows from a post-scoped mention only when the name match is unique on
that post (`HAVING count(*) = 1`). Two same-named mentions stay unbound.

At most one of `cataloged_team_id`, `cataloged_corporate_entity_id`, and
`cataloged_person_id` is set, and the set column must match
`actor_type_code`.

## Consequences

- Open a post whose R&R names a cataloged person. The chip is a button
even when Keyman extraction was not run on that post. Click it to walk
that person, not a later same-named row.
- Pre-0021 rows with two same-named mentions stay unbound until an
operator re-persists the summary. Do not guess a UUID at migrate time.

## References

Fellegi, I. P., & Sunter, A. B. (1969). A theory for record linkage.
*Journal of the American Statistical Association, 64*(328), 1183–1210.
https://doi.org/10.1080/01621459.1969.10501049

Bhattacharya, I., & Getoor, L. (2007). Collective entity resolution in
relational data. *ACM Transactions on Knowledge Discovery from Data,
1*(1), Article 5. https://doi.org/10.1145/1217299.1217304
22 changes: 22 additions & 0 deletions docs/doctoring/ROLE_CATALOG_IDENTITY_REFERENCES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# R&R catalog identity — doctoring

These are the standards and papers that ground ADR 0019 and ADR 0021.
Cite them in APA 7th when you extend role identity binding or
related-node authorization.

Bhattacharya, I., & Getoor, L. (2007). Collective entity resolution in
relational data. *ACM Transactions on Knowledge Discovery from Data,
1*(1), Article 5. https://doi.org/10.1145/1217299.1217304

Fellegi, I. P., & Sunter, A. B. (1969). A theory for record linkage.
*Journal of the American Statistical Association, 64*(328), 1183–1210.
https://doi.org/10.1080/01621459.1969.10501049

Hu, V. C., Ferraiolo, D., Kuhn, R., Schnitzer, A., Sandlin, K.,
Miller, R., & Scarfone, K. (2014). *Guide to attribute based access
control (ABAC) definition and considerations* (NIST Special Publication
800-162). National Institute of Standards and Technology.
https://doi.org/10.6028/NIST.SP.800-162

Reynolds, D. (Ed.). (2014). *The organization ontology*. World Wide Web
Consortium. https://www.w3.org/TR/vocab-org/
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "0.86.2",
"version": "0.86.3",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,8 @@
responsibility: "고객 측 수신",
actor_type_code: "prov_person",
affiliated_organization_name: "Northridge Grid",
catalog_node_id: "person-priya",
catalog_node_type_code: "node_person",
},
{
actor_name: "당사",
Expand Down Expand Up @@ -1150,6 +1152,7 @@
expect(screen.getByText("첫 번째 이벤트")).toBeInTheDocument();
expect(screen.getByText(/우리 측 후속/)).toBeInTheDocument();
expect(screen.getByRole("button", { name: "R&R Keyman: Ada West" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "R&R person: Priya Nair" })).toBeInTheDocument();
expect(screen.getByText("당사").closest("li")).toHaveTextContent("Organization");
expect(screen.queryByRole("button", { name: "R&R Keyman: 당사" })).not.toBeInTheDocument();
await waitFor(() => expect(screen.getByText("간접")).toBeInTheDocument());
Expand Down Expand Up @@ -1340,6 +1343,17 @@
);
});

it("opens related nodes from an R&R person catalog id", async () => {
stubBackend();
render(<App />);
await userEvent.click(await screen.findByRole("button", { name: "View post: Public post" }));
await userEvent.click(await screen.findByRole("button", { name: "R&R person: Priya Nair" }));
await waitFor(() => expect(screen.getByText("Related to Priya Nair")).toBeInTheDocument());
expect(screen.getByText("Related to Priya Nair").closest(".related-keymen")).toHaveTextContent(
"Ada West (Our side)",
);
});

it("opens related nodes from an R&R team", async () => {
stubBackend();
render(<App />);
Expand Down Expand Up @@ -1801,7 +1815,7 @@
).toBeInTheDocument();
expect(screen.getByText(/has not started yet/)).toBeInTheDocument();
const postCall = fetchMock.mock.calls.find(
(call) => String(call[0]).endsWith("/api/analysis-runs") && call[1]?.method === "POST",

Check failure on line 1818 in frontend/src/App.test.tsx

View workflow job for this annotation

GitHub Actions / Frontend lint, test, build

src/App.test.tsx > App, authenticated > records a pending lineage run and opens the authorized detail

TestingLibraryElementError: Found multiple elements with the text: Open this run to confirm which posts it will use. Reconstruction has not started yet. Here are the matching elements: Ignored nodes: comments, script, style <p class="post-meta" > Open this run to confirm which posts it will use. Reconstruction has not started yet. </p> Ignored nodes: comments, script, style <p class="post-meta" > Open this run to confirm which posts it will use. Reconstruction has not started yet. </p> (If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)). Ignored nodes: comments, script, style <body> <div> <main> <header class="app-header" > <h1> LineageWeave </h1> <div> <span> demo.analyst </span> <button> Log out </button> </div> </header> <section class="popup-section lineage-home" > <h2> Calendar </h2> <ul class="ticket-list" > <li class="ticket-list-item" > <button aria-label="Open commitment for: Public post" class="post-list-item" > <span class="ticket-title" > Send Northridge Grid the revised quote </span> <span class="post-badge" > Public post </span> <span class="post-badge" > Open </span> <span class="post-badge" > due 2026-01-12 </span> </button> </li> <li class="ticket-list-item" > <button aria-label="Open commitment for: Specification revision requested" class="post-list-item" > <span class="ticket-title" > Send Westfield Power the revised specification </span> <span class="post-badge" > Specification revision requested </span> <span class="post-badge" > Open </span> <span class="post-badge" > due 2026-01-14 </span> </button> </li> </ul> </section> <section class="popup-section lineage-home" > <div class="lineage-home-header" > <h2> Analysis runs </h2> <button aria-label="Request a lineage reconstruction" class="keyman-select" > Request a lineage reconstruction </button> </div> <ul aria-label="Analysis runs" class="ticket-list" > <li class="ticket-list-item" > <button aria-label="Open analysis run: Lineage reconstruction · Succeeded · Demo Corp" class="post-list-item" > <span class="ticket-title" > Lineage reconstruction · Succeeded · Demo Corp </span> <span class="post-badge" > 3 documents </span> </button> </li> <li class="ticket-list-item" > <button aria-label="Open analysis run: TEPP measurement · Failed · Demo Corp" class="post-list-item" > <span class="ticket-title" > TEPP m
);
expect(postCall).toBeDefined();
const body = JSON.parse(String(postCall?.[1]?.body));
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,24 @@ function PostDetailPopup({
const catalogId = rr.catalog_node_id;
const catalogType = rr.catalog_node_type_code;
let actorName: ReactNode = <strong>{rr.actor_name}</strong>;
if (person) {
if (catalogType === NODE_PERSON && catalogId) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keep this NODE_PERSON + stored catalog_node_id branch ahead of keymen.find(person_name) after rebase. Live feature 69c035b still prefers Keyman name-find first, which is the steal this slice closes. Do not let a conflict resolution restore the Keyman-first order.

actorName = (
<button
className="keyman-select"
aria-label={`R&R person: ${rr.actor_name}`}
onClick={() => {
setFocusEntity(null);
setFocusTeam(null);
setFocusPerson({
personId: catalogId,
personName: rr.actor_name,
});
}}
>
<strong>{rr.actor_name}</strong>
</button>
);
} else if (person) {
actorName = (
<button
className="keyman-select"
Expand Down Expand Up @@ -1648,6 +1665,7 @@ function AnalysisRunsPanel({
if (runs === null) return <p>Loading analysis runs...</p>;

const corpusHint = selected ? analysisRunCorpusHint(selected) : null;
const selectedNextAction = selected ? analysisRunNextAction(selected) : null;

return (
<section className="popup-section lineage-home">
Expand Down Expand Up @@ -1704,6 +1722,7 @@ function AnalysisRunsPanel({
{" · "}
Requested {selected.requested_at.slice(0, 10)}
</p>
{selectedNextAction && <p className="post-meta">{selectedNextAction}</p>}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

#148 already writes this detail sentence from kind-specific analysisRunNextAction on #74 39ed6eb. This head is still based on 6f79ecb and still uses the kind-blind Pending reconstruction string. Merging without rebase can put “Reconstruction has not started yet” back on a Pending TEPP panel.

Next action: rebase onto #74 39ed6eb and drop this selectedNextAction hunk. Keep the catalog-first NODE_PERSON chip. Do not open a third person-catalog PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This head still calls kind-blind analysisRunNextAction (every Pending kind returns “Reconstruction has not started yet.”). Live feature 69c035b already has kind-specific Pending copy from #148 and already repeats it on the opened detail. GitHub reports this PR CONFLICTING.

Next action: rebase onto 69c035b and drop this selectedNextAction hunk. Keep the catalog-first NODE_PERSON chip. Merging the kind-blind function would put reconstruction copy back on a Pending TEPP panel.

<AnalysisRunReproducibilityDigests
codeRevisionSha={selected.code_revision_sha}
configurationSha256={selected.configuration_sha256}
Expand Down
Loading
Loading