Record and list company business relationships over Context

SI
5d ago
+613-2

Summary

Exposes workspace-scoped company business relationships through the shared Context operation, over HTTP POST /api/context and the standard MCP context tool, using two actions:

  • record_company_relationship records an operator-asserted relationship that a saved company subject holds toward the workspace, one of its artists or one of its professionals.
    • Kinds are frontline_roster, catalog_interest, publishing, distribution, management, services and other; status is current or former.
    • An optional supersedes_id names the earlier row this one replaces. For example, when an artist leaves the roster, a former row supersedes the current row, which stays as history.
  • list_company_relationships reads them back 50 per page. Each item carries supersedes_id and superseded_by, and the page is state: unavailable after the company source is withdrawn.

Separate SRP files carry the logic:

  • companyRelationshipSchemas.ts: strict zod input with date/status refinements. Agent-facing descriptions state the relationship direction and scope limits.
  • companyRelationshipTypes.ts: strict response schemas with no ownership, rights or access fields.
  • processCompanyRelationshipOperation.ts: calls the new RPCs after the existing authorizeContextOwner check and rejects malformed or wrong-scope storage output. Echoed company subject IDs are compared case-insensitively. PostgreSQL returns lowercase UUIDs, so a valid uppercase input no longer reports a committed write as a 409.

Shared files get only small additions:

  • processContextOperation.ts: the union spread plus one if.
  • callContextRpc.ts: two allowlist names.
  • contextToolOperations.ts: two entries.
  • lib/context/README.md: one subsection.

Both actions stay out of delegated OAuth, through the new undelegatedContextActions.ts and one continue in registerFullOAuthTools.ts. They reference organization professionals, and the professional roster tools are excluded on the same basis.

Refs recoupable/app#2137 (child of epic recoupable/app#2116).

Merge sequencing

docs recoupable/docs#371 → database recoupable/database#112 (apply the migration first; the API now sends p_supersedes) → api (this PR). No app change. This PR does not depend on any open PR.

Conflicts to resolve at merge time:

  • api#981/#983/#985: small additions in processContextOperation.ts, callContextRpc.ts, contextToolOperations.ts, registerFullOAuthTools.ts, fullOAuthCatalog.test.ts and lib/context/README.md. Resolve by keeping both sides. api#981 adds its own inline if (action === "read_company_baseline") continue; in the same loop. Whichever merges second should fold that action into undelegatedContextActions so there is one exclusion mechanism.
  • api#1011 (ownership parity), whichever of the two merges second:
    • oauthContextParity.test.ts asserts registered.size === contextOperationSchema.options.length + 1 and expects a delegated tool for every action. It must subtract undelegatedContextActions.
    • buildContextOperationFixture.ts throws No fixture candidate satisfies field for non-object/literal/enum/array fields. It needs a candidate for the discriminated-union counterparty, such as { kind: "workspace" }.

Verification

  • Ran (review round, after the last code change):

    • pnpm exec vitest run lib/context/__tests__/companyRelationshipOperations.test.ts lib/context/__tests__/companyRelationshipTransports.test.ts: 2 files, 41 passed.
    • pnpm exec vitest run lib/context (full Context suite): 69 files passed, 8 skipped; 367 tests passed, 8 skipped, 0 failed.
    • pnpm exec vitest run lib/mcp/oauth lib/mcp/tools/context lib/supabase/context_requests: 22 files, 90 passed. This needs the gitignored lib/mcp/extension/generated-ui.ts, created by node scripts/mcp-extension/build.mjs.
    • pnpm exec tsc --noEmit: 198 errors. All of them already exist on origin/main, and none are in the files this PR touches.
    • pnpm exec eslint on the 12 changed TS files: clean. pnpm exec prettier --check on those files and lib/context/README.md: clean.
  • Red → green, review round: the updated tests failed 9 of 41 before the implementation change:

    • happy paths, because items now carry supersedes_id/superseded_by and the record call sends p_supersedes;
    • both uppercase-subject cases, which were rejected as Company relationship scope mismatch after the RPC ran.

    All 41 passed after the change.

  • Red → green, first round: before wiring, the new tests failed 17 of 34. A test for wrong-scope list items failed until each item's company_subject_id was checked. With the OAuth exclusion line removed, fullOAuthCatalog.test.ts failed (86 tools against the expected 84).

  • Not verified:

    • hosted deployment;
    • the hosted or production database (the migration is not applied);
    • the real Supabase RPC round trip (transport tests mock serverClient.rpc);
    • live providers. No paid provider calls were made.

Out of scope / known gaps

  • Relationship direction is fixed: the company holds the relationship toward the counterparty. The reverse direction (the workspace holding an interest in the company) cannot be recorded yet.
  • Counterparties must be reachable in the workspace now. An artist the workspace no longer reaches, or a party known only by name, cannot be recorded until identity resolution (#2119) or roster enrollment (#2136) lands.
  • The standard MCP context tool's long description string is unchanged, to avoid conflicting with api#981, which rewrites that line. The new actions are discoverable through the input schema and its field descriptions.
  • No chat UI. Folding relationships into read_company_baseline (api#981) is a follow-up after both merge.
  • No company-to-company relationships outside a workspace anchor, no rights interests, and no evidence links (api#983).
  • Not available to delegated OAuth connections until the organization-grant audit completes.

🤖 Generated with Claude Code