Record and list company business relationships over Context
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_relationshiprecords 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,servicesandother; status iscurrentorformer. - An optional
supersedes_idnames the earlier row this one replaces. For example, when an artist leaves the roster, aformerrow supersedes thecurrentrow, which stays as history.
- Kinds are
list_company_relationshipsreads them back 50 per page. Each item carriessupersedes_idandsuperseded_by, and the page isstate: unavailableafter 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 existingauthorizeContextOwnercheck 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 oneif.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.tsandlib/context/README.md. Resolve by keeping both sides. api#981 adds its own inlineif (action === "read_company_baseline") continue;in the same loop. Whichever merges second should fold that action intoundelegatedContextActionsso there is one exclusion mechanism. - api#1011 (ownership parity), whichever of the two merges second:
oauthContextParity.test.tsassertsregistered.size === contextOperationSchema.options.length + 1and expects a delegated tool for every action. It must subtractundelegatedContextActions.buildContextOperationFixture.tsthrowsNo fixture candidate satisfies fieldfor non-object/literal/enum/array fields. It needs a candidate for the discriminated-unioncounterparty, 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 gitignoredlib/mcp/extension/generated-ui.ts, created bynode scripts/mcp-extension/build.mjs.pnpm exec tsc --noEmit: 198 errors. All of them already exist onorigin/main, and none are in the files this PR touches.pnpm exec eslinton the 12 changed TS files: clean.pnpm exec prettier --checkon those files andlib/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_byand the record call sendsp_supersedes; - both uppercase-subject cases, which were rejected as
Company relationship scope mismatchafter the RPC ran.
All 41 passed after the change.
- happy paths, because items now carry
-
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_idwas checked. With the OAuth exclusion line removed,fullOAuthCatalog.test.tsfailed (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
contexttool'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