Skip to content
Source
Sign inSign up

linkOrigin.ts

60 lines2.4 KB

1import {
2 linkOriginIsFenced,
3 OrchestratorMcpFailure,
4 type OrchestrationV2LinkOrigin,
5 type OrchestrationV2ThreadShell,
6} from "@t3tools/contracts";
7import * as Effect from "effect/Effect";
8​
9import type { Caller } from "./threadAccess.ts";
10​
11/**
12 * Below full access, work a link starts here (all of it carries that session in `linkOrigin`)
13 * may change only work from the same link: not the user's threads, projects or settings, nor
14 * this environment's links. Reads are not fenced. It is a routing rule for T3's tools; agents
15 * still run as this environment's user, so a full-access link is not fenced at all.
16 */
17​
18/** The link a caller acts for: its own session, or the link that started its thread. */
19export const callerLinkOrigin = (
20 caller: Pick<Caller, "scope" | "caller">,
21): OrchestrationV2LinkOrigin | undefined =>
22 caller.caller?.linkOrigin ??
23 (caller.scope.client?.linked === true
24 ? {
25 sessionId: caller.scope.client.sessionId,
26 label: caller.scope.client.label,
27 access: caller.scope.client.access,
28 }
29 : undefined);
30​
31/** The link that fences a caller, if any: one paired below full access. */
32const fencingLinkOrigin = (caller: Pick<Caller, "scope" | "caller">) => {
33 const origin = callerLinkOrigin(caller);
34 return origin !== undefined && linkOriginIsFenced(origin) ? origin : undefined;
35};
36​
37const outsideLink = (origin: OrchestrationV2LinkOrigin, message: string) =>
38 new OrchestratorMcpFailure({
39 code: "capability_denied",
40 message: `This work was started from ${origin.label} through a linked environment, so ${message} A link paired with full access is not limited this way.`,
41 });
42​
43/** A thread a fenced caller may change: one its own link started. */
44export const assertSameLink = (
45 caller: Pick<Caller, "scope" | "caller">,
46 target: Pick<OrchestrationV2ThreadShell, "id" | "linkOrigin">,
47) => {
48 const origin = fencingLinkOrigin(caller);
49 return origin === undefined || target.linkOrigin?.sessionId === origin.sessionId
50 ? Effect.void
51 : Effect.fail(outsideLink(origin, `it may change only threads that link started.`));
52};
53​
54/** Fenced work never changes what the environment already has: projects, settings, scheduled runs. */
55export const assertNotLinked = (caller: Pick<Caller, "scope" | "caller">, what: string) => {
56 const origin = fencingLinkOrigin(caller);
57 return origin === undefined
58 ? Effect.void
59 : Effect.fail(outsideLink(origin, `it cannot ${what}.`));
60};