Skip to content

fix(architecture): enforce Managed System compatibility for canonical entity links - #425

Open
hjung3113 wants to merge 2 commits into
developfrom
fix/388-cross-ms-entity-links
Open

fix(architecture): enforce Managed System compatibility for canonical entity links#425
hjung3113 wants to merge 2 commits into
developfrom
fix/388-cross-ms-entity-links

Conversation

@hjung3113

Copy link
Copy Markdown
Owner

Summary

  • assertLinkManagedSystemCompatibility (entity-links/service.ts) is the single create-time gate rejecting cross-MS entity links, matching the validation order already documented in 06-entity-linking-contract.md.
  • Wired into generic POST /entity-links, Finding link-evidence, and the domain paths that insert links directly: createFindingFromVoc, createFindingFromCluster, linkExistingFinding.
  • Two tests that relied on cross-MS POST succeeding as an unrelated hidden-stub fixture now seed the row directly instead.
  • New AC-named tests assert DB row/audit counts on rejection, not just HTTP status.
  • DB-constraint question investigated and answered: core.entity_links is polymorphic, so application enforcement is authoritative (documented in the contract + module AGENTS.md, which also had its stale tuple count fixed 10→14).

Test plan

Closes #388

🤖 Generated with Claude Code

https://claude.ai/code/session_01H5bvJ64nqUZWP8bDkpqJGk

… entity links

Adds assertLinkManagedSystemCompatibility as the single create-time gate
rejecting cross-MS entity links (validation.failed/managed_system_mismatch)
before any capability check, insert, or audit row — the ordering matches
docs/implementation/06-entity-linking-contract.md's already-documented
validation order. Wired into createLink (generic POST /entity-links, Finding
link-evidence) plus the domain paths that insert links directly:
findings.createFindingFromVoc and voc-clusters.createFindingFromCluster/
linkExistingFinding.

Reconciles the two integration tests that relied on cross-MS creation
succeeding as an unrelated hidden-stub-visibility fixture: they now seed the
cross-MS row directly instead of via POST. Adds AC-named rejection tests for
the generic path, all registered same-MS pairs (regression), and the
Finding/VOC-Cluster source-derived paths, each asserting on DB row/audit
counts rather than just HTTP status.

DB-level enforcement was investigated and rejected: core.entity_links is
polymorphic (source_type/target_type pick the owning table), so a CHECK/FK
can't express the compatibility rule without per-type triggers duplicating
application authorization in SQL — documented in the contract and this
module's AGENTS.md, which also had its registeredEntityLinkPairs count fixed
(stale at 10, actually 14).

Verified end-to-end against a throwaway Postgres: full backend integration
suite is 1269/1270 green, the one failure is the pre-existing #419
(list-vocs tab=high, unrelated, confirmed via git stash).

Closes #388.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5bvJ64nqUZWP8bDkpqJGk

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f9edac25c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +219 to +222
* Create-time Managed System compatibility gate (#388). Both endpoints of an
* entity link must resolve to the same Managed System; a mismatch is rejected
* before any capability check or insert. This is the single authoritative seam
* — the polymorphic entity_links table cannot express it as a CHECK/FK.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate inherited evidence before task conversion

When a pre-existing cross-MS VOC→Finding evidence link is converted through a Finding's Task Request, preserveSourceLinks in tasks/service.ts:139-165 copies it into a new VOC→Task link without resolving the VOC's Managed System or invoking this gate. Such rows can exist because cross-MS creation was previously allowed and this commit deliberately retains them for reads, so Task conversion can continue creating the canonical cross-MS links this change is intended to prohibit. Validate or reconcile inherited evidence before inserting the propagated link.

AGENTS.md reference: apps/backend/src/modules/entity-links/AGENTS.md:L13-L14

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

맞는 지적입니다. tasks/service.ts preserveSourceLinks에 assertLinkManagedSystemCompatibility 호출 추가(41b6a39) — legacy cross-MS finding→task_request, voc→finding evidence 링크 전파 전에 검증. 스로어웨이 DB로 tasks suite 42/42 확인.

throw new HttpError('not_found.record', 'entity link endpoint not found');
}

assertLinkManagedSystemCompatibility(sourceRow.managed_system_id, targetRow.managed_system_id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Authorize endpoints before exposing MS mismatch

On POST /findings/:id/link-evidence, a Developer who can manage a Finding in MS A but cannot read a supplied VOC in MS B now receives 422 managed_system_mismatch, while a missing or same-MS unreadable VOC receives the non-disclosing 404 from findings/service.ts:859-863. Because this check runs before sourceProvider.canRead, it exposes the existence and cross-MS placement of a hidden VOC; run compatibility validation only after endpoint authorization succeeds, while still keeping it before persistence.

AGENTS.md reference: apps/backend/src/modules/entity-links/AGENTS.md:L3-L6

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

맞는 지적입니다. 41b6a39에서 MS-compat 체크를 sourceProvider.canRead/targetProvider.canRead(canCreateTarget) 통과 후, persist 직전으로 이동했습니다. findings.linkEvidence의 permission.denied→404 매핑이 다시 정상 작동합니다(unreadable+cross-MS VOC → 여전히 404). evidence-highlights 테스트도 그 케이스를 명시적으로 커버하도록 갱신.

);
// The seeded cross-MS row from the first test still exists; the HTTP
// create path must not have added or audited a second one.
expect(persisted.rows[0]?.links).toBe('1');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Seed the cross-MS row within this test

When this AC is run through Vitest's supported narrow filtering, the earlier test that seeds the cross-MS row is skipped, so links is 0 and this assertion fails despite correct production behavior. The test is therefore order-dependent; create its prerequisite row in this test or compare counts before and after the request instead.

AGENTS.md reference: apps/backend/AGENTS.md:L38-L38

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

맞는 지적입니다. 41b6a39에서 절대 카운트 대신 before/after delta로 변경했습니다. 이 테스트만 단독 필터링해서(다른 seeding 테스트 스킵된 상태로) green 확인했습니다 — order-independence 검증 완료.

Addresses 3 automated review findings on PR #425:

1. assertLinkManagedSystemCompatibility ran before endpoint authorization in
   createLink, so an actor without read access to a source VOC could learn
   it exists and is cross-MS via 422 instead of the disclosure-safe 404
   findings.linkEvidence maps permission.denied to. Moved the check to run
   after both source/target authorization succeed, before persist.

2. preserveSourceLinks (tasks/service.ts) propagated legacy pre-#388
   finding->task_request and voc->finding evidence links into new
   finding->task and voc->task links during Task Request conversion without
   checking Managed System compatibility, defeating the new gate for
   grandfathered cross-MS data. Now asserts compatibility before each
   propagated insert, consistent with every other write path in this PR.

3 AC-388-4 in link-existing-finding.integration.test.ts asserted an absolute
   row count that only held because an earlier test in the same file had
   already seeded a cross-MS fixture row as a side effect — order-dependent,
   fails under narrow test filtering. Switched to a before/after row-count
   delta, verified independently by running the test alone.

Verified against a throwaway Postgres: entity-links/findings/voc-clusters/
tasks suites all green, AC-388-4 passes in isolation (confirms order-
independence), full backend suite 1269/1270 (only pre-existing #419 fails,
unrelated). typecheck/boundaries/biome clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5bvJ64nqUZWP8bDkpqJGk
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.

1 participant