| 1874 | }; |
| 1875 | |
| 1876 | export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = readonly []>( |
| 1877 | config: ExecutorConfig<TPlugins>, |
| 1878 | ): Effect.Effect<Executor<TPlugins>, StorageFailure> => |
| 1879 | Effect.gen(function* () { |
| 1880 | const defaultPlugins = (): TPlugins => { |
| 1881 | const empty: readonly AnyPlugin[] = []; |
| 1882 | return empty as TPlugins; |
| 1883 | }; |
| 1884 | const { plugins: userPlugins = defaultPlugins() } = config; |
| 1885 | |
| 1886 | const tenant = String(config.tenant); |
| 1887 | const subject = config.subject != null ? String(config.subject) : null; |
| 1888 | |
| 1889 | const ownerBinding: OwnerBinding = { |
| 1890 | tenant: config.tenant, |
| 1891 | subject: config.subject ?? null, |
| 1892 | }; |
| 1893 | |
| 1894 | const ownedKeys = (owner: Owner): OwnedKeys => { |
| 1895 | if (owner === "org") return { tenant, owner, subject: ORG_SUBJECT }; |
| 1896 | if (subject == null) { |
| 1897 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: programmer error caught and surfaced as StorageError below by callers |
| 1898 | throw new StorageError({ |
| 1899 | message: `Cannot target owner "user": executor has no subject.`, |
| 1900 | cause: undefined, |
| 1901 | }); |
| 1902 | } |
| 1903 | return { tenant, owner, subject }; |
| 1904 | }; |
| 1905 | |
| 1906 | const requireUserSubject = (owner: Owner): Effect.Effect<void, StorageFailure> => |
| 1907 | owner === "user" && subject == null |
| 1908 | ? Effect.fail( |
| 1909 | new StorageError({ |
| 1910 | message: `Cannot target owner "user": executor has no subject.`, |
| 1911 | cause: undefined, |
| 1912 | }), |
| 1913 | ) |
| 1914 | : Effect.void; |
| 1915 | |
| 1916 | // Workspace-settings gate (`ExecutorConfig.orgWrites`). Called at the top |
| 1917 | // of every user-intent workspace-level mutation: with an explicit owner it |
| 1918 | // refuses only `"org"` targets; with no owner it guards a tenant-shared |
| 1919 | // surface outright. Deliberately NOT wired into the storage owner policy — |
| 1920 | // operational org-row writes (token refresh, tool-catalog re-sync) must |
| 1921 | // keep working for a denied member. |
| 1922 | const guardOrgWrite = (owner?: Owner): Effect.Effect<void, OrgWriteDeniedError> => |
| 1923 | Effect.gen(function* () { |
| 1924 | const access = |
| 1925 | config.orgWrites === "request" ? yield* currentOrgWriteAccess : config.orgWrites; |
| 1926 | if (access === "denied" && (owner === undefined || owner === "org")) { |
| 1927 | return yield* new OrgWriteDeniedError(); |
| 1928 | } |
| 1929 | }); |
| 1930 | |
| 1931 | // Built-in core-tools plugin: agent-facing static tools over the v2 surface. |
| 1932 | const plugins: readonly AnyPlugin[] = config.coreTools |
| 1933 | ? ([ |