| 1907 | }; |
| 1908 | |
| 1909 | export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = readonly []>( |
| 1910 | config: ExecutorConfig<TPlugins>, |
| 1911 | ): Effect.Effect<Executor<TPlugins>, StorageFailure> => |
| 1912 | Effect.gen(function* () { |
| 1913 | const defaultPlugins = (): TPlugins => { |
| 1914 | const empty: readonly AnyPlugin[] = []; |
| 1915 | return empty as TPlugins; |
| 1916 | }; |
| 1917 | const { plugins: userPlugins = defaultPlugins() } = config; |
| 1918 | |
| 1919 | const tenant = String(config.tenant); |
| 1920 | const subject = config.subject != null ? String(config.subject) : null; |
| 1921 | |
| 1922 | const ownerBinding: OwnerBinding = { |
| 1923 | tenant: config.tenant, |
| 1924 | subject: config.subject ?? null, |
| 1925 | }; |
| 1926 | |
| 1927 | const ownedKeys = (owner: Owner): OwnedKeys => { |
| 1928 | if (owner === "org") return { tenant, owner, subject: ORG_SUBJECT }; |
| 1929 | if (subject == null) { |
| 1930 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: programmer error caught and surfaced as StorageError below by callers |
| 1931 | throw new StorageError({ |
| 1932 | message: `Cannot target owner "user": executor has no subject.`, |
| 1933 | cause: undefined, |
| 1934 | }); |
| 1935 | } |
| 1936 | return { tenant, owner, subject }; |
| 1937 | }; |
| 1938 | |
| 1939 | const requireUserSubject = (owner: Owner): Effect.Effect<void, StorageFailure> => |
| 1940 | owner === "user" && subject == null |
| 1941 | ? Effect.fail( |
| 1942 | new StorageError({ |
| 1943 | message: `Cannot target owner "user": executor has no subject.`, |
| 1944 | cause: undefined, |
| 1945 | }), |
| 1946 | ) |
| 1947 | : Effect.void; |
| 1948 | |
| 1949 | // Workspace-settings gate (`ExecutorConfig.orgWrites`). Called at the top |
| 1950 | // of every user-intent workspace-level mutation: with an explicit owner it |
| 1951 | // refuses only `"org"` targets; with no owner it guards a tenant-shared |
| 1952 | // surface outright. Deliberately NOT wired into the storage owner policy — |
| 1953 | // operational org-row writes (token refresh, tool-catalog re-sync) must |
| 1954 | // keep working for a denied member. |
| 1955 | const guardOrgWrite = (owner?: Owner): Effect.Effect<void, OrgWriteDeniedError> => |
| 1956 | Effect.gen(function* () { |
| 1957 | const access = |
| 1958 | config.orgWrites === "request" ? yield* currentOrgWriteAccess : config.orgWrites; |
| 1959 | if (access === "denied" && (owner === undefined || owner === "org")) { |
| 1960 | return yield* new OrgWriteDeniedError(); |
| 1961 | } |
| 1962 | }); |
| 1963 | |
| 1964 | // Built-in core-tools plugin: agent-facing static tools over the v2 surface. |
| 1965 | const plugins: readonly AnyPlugin[] = config.coreTools |
| 1966 | ? ([ |