From df8ffa3934416b4dd1ebb79b92666a577ff5106c Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 6 Aug 2026 13:12:55 +0000 Subject: [PATCH] feat(security): enforce scoped board API keys Add the revision-4 scope contract, safe migration, fresh owner authority checks, default-deny route inventory, audit controls, and focused regression coverage. Co-Authored-By: Paperclip --- cli/src/__tests__/connect.test.ts | 7 + cli/src/__tests__/token.test.ts | 16 +- cli/src/client/board-auth.ts | 31 +- cli/src/commands/client/auth.ts | 18 +- cli/src/commands/client/common.ts | 18 +- cli/src/commands/client/connect.ts | 43 +- cli/src/commands/client/token.ts | 26 +- .../src/board-api-key-scope-migration.test.ts | 98 + packages/db/src/client.test.ts | 22 +- .../migrations/0212_cultured_george_stacy.sql | 54 + .../board_api_key_authorization_events.ts | 37 + packages/db/src/schema/board_api_keys.ts | 11 +- packages/db/src/schema/cli_auth_challenges.ts | 5 +- packages/db/src/schema/index.ts | 1 + .../board-api-key-scope.test.ts.snap | 215 + .../shared/src/board-api-key-scope.test.ts | 101 + packages/shared/src/board-api-key-scope.ts | 213 + packages/shared/src/index.ts | 16 + packages/shared/src/validators/access.ts | 20 +- .../board-key-auth-middleware.test.ts | 194 + server/src/__tests__/cli-auth-routes.test.ts | 23 +- server/src/app.ts | 2 + server/src/middleware/auth.ts | 98 +- server/src/routes/access.ts | 24 +- .../board-key-route-registry.test.ts.snap | 9737 +++++++++++++++++ .../security/board-key-authorization.test.ts | 236 + .../security/board-key-route-registry.test.ts | 105 + .../src/security/board-key-route-registry.ts | 701 ++ server/src/services/board-auth.ts | 120 +- server/src/types/express.d.ts | 6 +- 30 files changed, 12098 insertions(+), 100 deletions(-) create mode 100644 packages/db/src/board-api-key-scope-migration.test.ts create mode 100644 packages/db/src/migrations/0212_cultured_george_stacy.sql create mode 100644 packages/db/src/schema/board_api_key_authorization_events.ts create mode 100644 packages/shared/src/__snapshots__/board-api-key-scope.test.ts.snap create mode 100644 packages/shared/src/board-api-key-scope.test.ts create mode 100644 packages/shared/src/board-api-key-scope.ts create mode 100644 server/src/__tests__/board-key-auth-middleware.test.ts create mode 100644 server/src/security/__snapshots__/board-key-route-registry.test.ts.snap create mode 100644 server/src/security/board-key-authorization.test.ts create mode 100644 server/src/security/board-key-route-registry.test.ts create mode 100644 server/src/security/board-key-route-registry.ts diff --git a/cli/src/__tests__/connect.test.ts b/cli/src/__tests__/connect.test.ts index 01c2388e0b..000e50d030 100644 --- a/cli/src/__tests__/connect.test.ts +++ b/cli/src/__tests__/connect.test.ts @@ -61,6 +61,7 @@ describe("connect command", () => { token: "board-login-token", approvalUrl: `${API_BASE}/cli-auth/challenge-1`, userId: "user-1", + keyId: "board-login-key-1", }); vi.spyOn(console, "log").mockImplementation(() => {}); }); @@ -106,12 +107,16 @@ describe("connect command", () => { contextPath, "--api-base", API_BASE, + "--company-id", + COMPANY_ID, "--json", ], { from: "user" }); expect(loginBoardCli).toHaveBeenCalledWith(expect.objectContaining({ apiBase: API_BASE, requestedAccess: "board", + requestedCompanyId: COMPANY_ID, + scopeConfig: expect.objectContaining({ companyIds: [COMPANY_ID] }), command: "paperclipai connect", })); expect(fetchMock.mock.calls.map((call) => [call[1]?.method ?? "GET", new URL(String(call[0])).pathname])).toEqual([ @@ -170,6 +175,8 @@ describe("connect command", () => { contextPath, "--api-base", API_BASE, + "--company-id", + COMPANY_ID, "--json", ], { from: "user" }); diff --git a/cli/src/__tests__/token.test.ts b/cli/src/__tests__/token.test.ts index 679033e4eb..3abf1eddbb 100644 --- a/cli/src/__tests__/token.test.ts +++ b/cli/src/__tests__/token.test.ts @@ -165,8 +165,14 @@ describe("token commands", () => { expect(fetchMock.mock.calls[0]?.[0]).toBe("http://localhost:3100/api/board-api-keys"); expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({ name: "external-admin", - requestedCompanyId: COMPANY_ID, expiresAt: "2026-06-06T00:00:00.000Z", + scopeConfig: { + version: 1, + kind: "scoped", + companyIds: [COMPANY_ID], + permissions: expect.any(Array), + instanceCapabilities: [], + }, }); expect(JSON.parse(String(log.mock.calls[0]?.[0]))).toMatchObject({ key: { @@ -205,8 +211,14 @@ describe("token commands", () => { expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({ name: "external-admin", - requestedCompanyId: COMPANY_ID, expiresAt: null, + scopeConfig: { + version: 1, + kind: "scoped", + companyIds: [COMPANY_ID], + permissions: expect.any(Array), + instanceCapabilities: [], + }, }); }); diff --git a/cli/src/client/board-auth.ts b/cli/src/client/board-auth.ts index 0f6fe8e85e..5f8e7adbd4 100644 --- a/cli/src/client/board-auth.ts +++ b/cli/src/client/board-auth.ts @@ -2,6 +2,7 @@ import { spawn, type ChildProcess } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import pc from "picocolors"; +import type { BoardApiKeyScopeConfig } from "@paperclipai/shared"; import { buildCliCommandLabel } from "./command-label.js"; import { resolveDefaultCliAuthPath } from "../config/home.js"; @@ -10,6 +11,7 @@ type RequestedAccess = "board" | "instance_admin_required"; interface BoardAuthCredential { apiBase: string; token: string; + keyId?: string | null; createdAt: string; updatedAt: string; userId?: string | null; @@ -43,6 +45,7 @@ interface ChallengeStatusResponse { cancelledAt: string | null; expiresAt: string; approvedByUser: { id: string; name: string; email: string } | null; + boardApiKeyId: string | null; } function defaultBoardAuthStore(): BoardAuthStore { @@ -90,6 +93,7 @@ export function readBoardAuthStore(storePath?: string): BoardAuthStore { normalized[normalizeApiBase(key)] = { apiBase, token, + keyId: toStringOrNull(record.keyId), createdAt, updatedAt, userId: toStringOrNull(record.userId), @@ -116,6 +120,7 @@ export function getStoredBoardCredential(apiBase: string, storePath?: string): B export function setStoredBoardCredential(input: { apiBase: string; token: string; + keyId?: string | null; userId?: string | null; storePath?: string; }): BoardAuthCredential { @@ -126,6 +131,7 @@ export function setStoredBoardCredential(input: { const credential: BoardAuthCredential = { apiBase: normalizedApiBase, token: input.token.trim(), + keyId: input.keyId ?? existing?.keyId ?? null, createdAt: existing?.createdAt ?? now, updatedAt: now, userId: input.userId ?? existing?.userId ?? null, @@ -201,6 +207,7 @@ export async function openUrl(url: string): Promise { export async function loginBoardCli(params: { apiBase: string; requestedAccess: RequestedAccess; + scopeConfig: BoardApiKeyScopeConfig; requestedCompanyId?: string | null; clientName?: string | null; command?: string; @@ -208,7 +215,7 @@ export async function loginBoardCli(params: { print?: boolean; openBrowser?: boolean; publicBaseUrl?: string; -}): Promise<{ token: string; approvalUrl: string; userId?: string | null }> { +}): Promise<{ token: string; approvalUrl: string; userId?: string | null; keyId?: string | null }> { const apiBase = normalizeApiBase(params.apiBase); const createUrl = `${apiBase}/api/cli-auth/challenges`; const command = params.command?.trim() || buildCliCommandLabel(); @@ -220,6 +227,7 @@ export async function loginBoardCli(params: { clientName: params.clientName?.trim() || "paperclipai cli", requestedAccess: params.requestedAccess, requestedCompanyId: params.requestedCompanyId?.trim() || null, + scopeConfig: params.scopeConfig, }), }); @@ -253,24 +261,19 @@ export async function loginBoardCli(params: { ); if (status.status === "approved") { - const me = await requestJson<{ userId: string; user?: { id: string } | null }>( - `${apiBase}/api/cli-auth/me`, - { - headers: { - authorization: `Bearer ${challenge.boardApiToken}`, - }, - }, - ); + const userId = status.approvedByUser?.id ?? null; setStoredBoardCredential({ apiBase, token: challenge.boardApiToken, - userId: me.userId ?? me.user?.id ?? null, + keyId: status.boardApiKeyId, + userId, storePath: params.storePath, }); return { token: challenge.boardApiToken, approvalUrl, - userId: me.userId ?? me.user?.id ?? null, + userId, + keyId: status.boardApiKeyId, }; } @@ -290,13 +293,13 @@ export async function loginBoardCli(params: { export async function revokeStoredBoardCredential(params: { apiBase: string; token: string; + keyId: string; }): Promise { const apiBase = normalizeApiBase(params.apiBase); - await requestJson<{ revoked: boolean }>(`${apiBase}/api/cli-auth/revoke-current`, { - method: "POST", + await requestJson<{ ok: true; keyId: string }>(`${apiBase}/api/board-api-keys/${encodeURIComponent(params.keyId)}`, { + method: "DELETE", headers: { authorization: `Bearer ${params.token}`, }, - body: JSON.stringify({}), }); } diff --git a/cli/src/commands/client/auth.ts b/cli/src/commands/client/auth.ts index 549f119c61..8e2b448c5d 100644 --- a/cli/src/commands/client/auth.ts +++ b/cli/src/commands/client/auth.ts @@ -1,4 +1,5 @@ import type { Command } from "commander"; +import { BOARD_API_KEY_SCOPE_PRESETS } from "@paperclipai/shared"; import { getStoredBoardCredential, loginBoardCli, @@ -36,11 +37,22 @@ export function registerClientAuthCommands(auth: Command): void { .option("--no-browser", "Don't try to open a browser; just print the approval URL") .action(async (opts: AuthLoginOptions) => { try { - const ctx = resolveCommandContext(opts); + const ctx = resolveCommandContext(opts, { requireCompany: true }); + const companyId = ctx.companyId as string; + const preset = opts.instanceAdmin + ? BOARD_API_KEY_SCOPE_PRESETS.full_instance_admin + : BOARD_API_KEY_SCOPE_PRESETS.company_automation; const login = await loginBoardCli({ apiBase: ctx.api.apiBase, requestedAccess: opts.instanceAdmin ? "instance_admin_required" : "board", - requestedCompanyId: ctx.companyId ?? null, + requestedCompanyId: companyId, + scopeConfig: { + version: 1, + kind: "scoped", + companyIds: [companyId], + permissions: [...preset.permissions], + instanceCapabilities: [...preset.instanceCapabilities], + }, command: "paperclipai auth login", openBrowser: opts.browser, }); @@ -74,9 +86,11 @@ export function registerClientAuthCommands(auth: Command): void { } let revoked = false; try { + if (!credential.keyId) throw new Error("Stored credential predates self-revoke metadata"); await revokeStoredBoardCredential({ apiBase: ctx.api.apiBase, token: credential.token, + keyId: credential.keyId, }); revoked = true; } catch { diff --git a/cli/src/commands/client/common.ts b/cli/src/commands/client/common.ts index ad5095e697..d3ca428191 100644 --- a/cli/src/commands/client/common.ts +++ b/cli/src/commands/client/common.ts @@ -1,5 +1,6 @@ import pc from "picocolors"; import type { Command } from "commander"; +import { BOARD_API_KEY_SCOPE_PRESETS } from "@paperclipai/shared"; import { getStoredBoardCredential, loginBoardCli } from "../../client/board-auth.js"; import { buildCliCommandLabel } from "../../client/command-label.js"; import { readConfig } from "../../config/store.js"; @@ -89,10 +90,25 @@ export function resolveCommandContext( if (!shouldRecoverBoardAuth(error)) { return null; } + if (!companyId) { + throw new Error( + "Board authentication requires a company scope. Pass --company-id, set PAPERCLIP_COMPANY_ID, or configure a companyId in the current context.", + ); + } + const preset = requestedAccess === "instance_admin_required" + ? BOARD_API_KEY_SCOPE_PRESETS.full_instance_admin + : BOARD_API_KEY_SCOPE_PRESETS.company_automation; const login = await loginBoardCli({ apiBase, requestedAccess, - requestedCompanyId: companyId ?? null, + requestedCompanyId: companyId, + scopeConfig: { + version: 1, + kind: "scoped", + companyIds: [companyId], + permissions: [...preset.permissions], + instanceCapabilities: [...preset.instanceCapabilities], + }, command: buildCliCommandLabel(), }); return login.token; diff --git a/cli/src/commands/client/connect.ts b/cli/src/commands/client/connect.ts index ec38935916..a16ef6a6cf 100644 --- a/cli/src/commands/client/connect.ts +++ b/cli/src/commands/client/connect.ts @@ -2,7 +2,11 @@ import { Command } from "commander"; import * as p from "@clack/prompts"; import pc from "picocolors"; import type { Agent, Company } from "@paperclipai/shared"; -import { createAgentKeySchema, createBoardApiKeySchema } from "@paperclipai/shared"; +import { + BOARD_API_KEY_SCOPE_PRESETS, + createAgentKeySchema, + createBoardApiKeySchema, +} from "@paperclipai/shared"; import { loginBoardCli } from "../../client/board-auth.js"; import { PaperclipApiClient } from "../../client/http.js"; import { resolveProfile, readContext, setCurrentProfile, upsertProfile } from "../../client/context.js"; @@ -50,6 +54,7 @@ export function registerConnectCommand(program: Command): void { handleCommandError(err); } }), + { includeCompany: true }, ); } @@ -73,10 +78,25 @@ async function connectWizard(opts: ConnectOptions) { console.log(pc.dim(`Checking ${apiBase}/api/health ...`)); await verifyHealth(apiBase); + const scopeCompanyId = opts.companyId?.trim() || resolvedProfile.profile.companyId?.trim(); + if (!scopeCompanyId) { + throw new Error( + "Board authentication requires a company scope. Pass --company-id or configure a companyId in the selected profile.", + ); + } + const loginPreset = BOARD_API_KEY_SCOPE_PRESETS.company_automation; + const boardLogin = await loginBoardCli({ apiBase, requestedAccess: "board", - requestedCompanyId: opts.companyId ?? resolvedProfile.profile.companyId ?? null, + requestedCompanyId: scopeCompanyId, + scopeConfig: { + version: 1, + kind: "scoped", + companyIds: [scopeCompanyId], + permissions: [...loginPreset.permissions], + instanceCapabilities: [], + }, command: "paperclipai connect", }); const boardApi = new PaperclipApiClient({ apiBase, apiKey: boardLogin.token }); @@ -87,18 +107,25 @@ async function connectWizard(opts: ConnectOptions) { const apiKeyEnvVarName = opts.apiKeyEnvVarName?.trim() || "PAPERCLIP_API_KEY"; if (persona === "board") { - const company = await chooseCompany(companies, opts.companyId ?? resolvedProfile.profile.companyId, { - optional: true, + const company = await chooseCompany(companies, scopeCompanyId, { + optional: false, }); + if (!company) throw new Error("Company is required for scoped board profiles"); const tokenName = opts.tokenName?.trim() || `cli-board-${new Date().toISOString()}`; const key = await boardApi.post("/api/board-api-keys", createBoardApiKeySchema.parse({ name: tokenName, - requestedCompanyId: company?.id ?? null, + scopeConfig: { + version: 1, + kind: "scoped", + companyIds: [company.id], + permissions: [...loginPreset.permissions], + instanceCapabilities: [], + }, })); if (!key) throw new Error("Failed to create board token"); upsertProfile(profileName, { apiBase, - companyId: company?.id, + companyId: company.id, persona: "board", agentId: "", agentName: "", @@ -114,9 +141,9 @@ async function connectWizard(opts: ConnectOptions) { profile: profileName, persona: "board", apiBase, - companyId: company?.id ?? null, + companyId: company.id, key: publicKeyResult(key), - exports: buildExports({ apiBase, companyId: company?.id, agentId: undefined, envName: apiKeyEnvVarName, token: key.token }), + exports: buildExports({ apiBase, companyId: company.id, agentId: undefined, envName: apiKeyEnvVarName, token: key.token }), }; } diff --git a/cli/src/commands/client/token.ts b/cli/src/commands/client/token.ts index 92472a3a97..50e3a928b8 100644 --- a/cli/src/commands/client/token.ts +++ b/cli/src/commands/client/token.ts @@ -1,5 +1,10 @@ import { Command } from "commander"; -import { createAgentKeySchema, createBoardApiKeySchema, type Agent } from "@paperclipai/shared"; +import { + BOARD_API_KEY_SCOPE_PRESETS, + createAgentKeySchema, + createBoardApiKeySchema, + type Agent, +} from "@paperclipai/shared"; import { addCommonClientOptions, apiPath, @@ -144,19 +149,26 @@ export function registerTokenCommands(program: Command): void { board .command("create") .description("Create a named board API key") - .option("-C, --company-id ", "Company ID used for audit context") + .requiredOption("-C, --company-id ", "Company ID to pin in the key scope") .option("--name ", "API key label", "cli-board") .option("--expires-at ", "Expiration timestamp") .option("--ttl-days ", "Expiration in days from now") .option("--never-expires", "Create a non-expiring key") .action(async (opts: BoardTokenOptions) => { try { - const ctx = resolveCommandContext(opts); + const ctx = resolveCommandContext(opts, { requireCompany: true }); const expiresAt = resolveBoardKeyExpiresAt(opts); + const preset = BOARD_API_KEY_SCOPE_PRESETS.company_automation; const payload = createBoardApiKeySchema.parse({ name: opts.name, - requestedCompanyId: opts.companyId ?? ctx.companyId ?? null, expiresAt, + scopeConfig: { + version: 1, + kind: "scoped", + companyIds: [ctx.companyId as string], + permissions: [...preset.permissions], + instanceCapabilities: [...preset.instanceCapabilities], + }, }); const key = await ctx.api.post("/api/board-api-keys", payload); if (!key) throw new Error("Failed to create board API key"); @@ -228,17 +240,17 @@ async function resolveAgent(api: { get(path: string): Promise }, co return agent; } -function resolveBoardKeyExpiresAt(opts: BoardTokenOptions): Date | null | undefined { +function resolveBoardKeyExpiresAt(opts: BoardTokenOptions): string | null | undefined { if (opts.neverExpires) return null; if (opts.expiresAt?.trim()) { const date = new Date(opts.expiresAt.trim()); if (!Number.isFinite(date.getTime())) throw new Error(`Invalid --expires-at value: ${opts.expiresAt}`); - return date; + return date.toISOString(); } if (opts.ttlDays?.trim()) { const days = Number(opts.ttlDays); if (!Number.isFinite(days) || days <= 0) throw new Error(`Invalid --ttl-days value: ${opts.ttlDays}`); - return new Date(Date.now() + Math.floor(days * 24 * 60 * 60 * 1000)); + return new Date(Date.now() + Math.floor(days * 24 * 60 * 60 * 1000)).toISOString(); } return undefined; } diff --git a/packages/db/src/board-api-key-scope-migration.test.ts b/packages/db/src/board-api-key-scope-migration.test.ts new file mode 100644 index 0000000000..51dad5670e --- /dev/null +++ b/packages/db/src/board-api-key-scope-migration.test.ts @@ -0,0 +1,98 @@ +import fs from "node:fs"; +import { afterEach, describe, expect, it } from "vitest"; +import postgres from "postgres"; +import { + getEmbeddedPostgresTestSupport, + startEmbeddedPostgresTestDatabase, +} from "./test-embedded-postgres.js"; + +const MIGRATION_FILE = "0212_cultured_george_stacy.sql"; +const cleanups: Array<() => Promise> = []; +const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); +const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; + +afterEach(async () => { + while (cleanups.length > 0) await cleanups.pop()?.(); +}); + +async function migrationStatements() { + const content = await fs.promises.readFile( + new URL(`./migrations/${MIGRATION_FILE}`, import.meta.url), + "utf8", + ); + return content + .split("--> statement-breakpoint") + .map((statement) => statement.trim()) + .filter(Boolean); +} + +describeEmbeddedPostgres("board API key scope migration", () => { + it("marks only null-scope existing rows legacy and enforces scoped creates", async () => { + const database = await startEmbeddedPostgresTestDatabase("paperclip-board-key-scope-"); + cleanups.push(database.cleanup); + const sql = postgres(database.connectionString, { max: 1, onnotice: () => {} }); + + try { + await sql.unsafe(` + DROP TABLE board_api_key_authorization_events CASCADE; + DROP TABLE cli_auth_challenges CASCADE; + DROP TABLE board_api_keys CASCADE; + CREATE TABLE board_api_keys ( + id uuid PRIMARY KEY, + scope_config jsonb + ); + CREATE TABLE cli_auth_challenges (id uuid PRIMARY KEY); + INSERT INTO board_api_keys (id, scope_config) VALUES + ('00000000-0000-4000-8000-000000000001', NULL), + ('00000000-0000-4000-8000-000000000002', '{"version":999,"malformed":true}'::jsonb); + `); + + for (const statement of await migrationStatements()) { + await sql.unsafe(statement); + } + + const rows = await sql.unsafe>(` + SELECT id, scope_config, token_prefix, legacy_unrestricted + FROM board_api_keys + ORDER BY id + `); + expect(rows).toEqual([ + { + id: "00000000-0000-4000-8000-000000000001", + scope_config: null, + token_prefix: null, + legacy_unrestricted: true, + }, + { + id: "00000000-0000-4000-8000-000000000002", + scope_config: { version: 999, malformed: true }, + token_prefix: null, + legacy_unrestricted: false, + }, + ]); + + await expect( + sql.unsafe(`INSERT INTO board_api_keys (id) VALUES ('00000000-0000-4000-8000-000000000003')`), + ).rejects.toThrow(); + await expect( + sql.unsafe(` + INSERT INTO board_api_keys (id, scope_config) + VALUES ('00000000-0000-4000-8000-000000000004', '{"version":1}'::jsonb) + `), + ).resolves.toBeDefined(); + await expect( + sql.unsafe(` + INSERT INTO board_api_keys (id, scope_config, legacy_unrestricted) + VALUES ('00000000-0000-4000-8000-000000000005', '{"version":1}'::jsonb, true) + `), + ).rejects.toThrow(); + } finally { + await sql.end(); + } + }, 20_000); +}); diff --git a/packages/db/src/client.test.ts b/packages/db/src/client.test.ts index 0d2ff2d98a..a0bd9b6175 100644 --- a/packages/db/src/client.test.ts +++ b/packages/db/src/client.test.ts @@ -346,13 +346,27 @@ describeEmbeddedPostgres("applyPendingMigrations", () => { VALUES ('user-1', 'User One', 'user@example.com', true, now(), now()) `); await sql.unsafe(` - INSERT INTO "board_api_keys" ("id", "user_id", "name", "key_hash", "created_at") - VALUES ('00000000-0000-0000-0000-000000000001', 'user-1', 'Key One', 'dup-hash', now()) + INSERT INTO "board_api_keys" ("id", "user_id", "name", "key_hash", "scope_config", "created_at") + VALUES ( + '00000000-0000-0000-0000-000000000001', + 'user-1', + 'Key One', + 'dup-hash', + '{"version":1,"kind":"scoped","companyIds":["11111111-1111-4111-8111-111111111111"],"permissions":["companies:read"],"instanceCapabilities":[]}'::jsonb, + now() + ) `); await expect( sql.unsafe(` - INSERT INTO "board_api_keys" ("id", "user_id", "name", "key_hash", "created_at") - VALUES ('00000000-0000-0000-0000-000000000002', 'user-1', 'Key Two', 'dup-hash', now()) + INSERT INTO "board_api_keys" ("id", "user_id", "name", "key_hash", "scope_config", "created_at") + VALUES ( + '00000000-0000-0000-0000-000000000002', + 'user-1', + 'Key Two', + 'dup-hash', + '{"version":1,"kind":"scoped","companyIds":["11111111-1111-4111-8111-111111111111"],"permissions":["companies:read"],"instanceCapabilities":[]}'::jsonb, + now() + ) `), ).rejects.toThrow(); } finally { diff --git a/packages/db/src/migrations/0212_cultured_george_stacy.sql b/packages/db/src/migrations/0212_cultured_george_stacy.sql new file mode 100644 index 0000000000..303b313b7d --- /dev/null +++ b/packages/db/src/migrations/0212_cultured_george_stacy.sql @@ -0,0 +1,54 @@ +ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "scope_config" jsonb;--> statement-breakpoint +ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "token_prefix" text;--> statement-breakpoint +ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "legacy_unrestricted" boolean DEFAULT false NOT NULL;--> statement-breakpoint + +-- Only rows present before this migration can enter the legacy-unrestricted state. +-- Malformed non-null scope JSON is deliberately preserved so authentication fails closed. +UPDATE "board_api_keys" +SET "legacy_unrestricted" = true +WHERE "scope_config" IS NULL;--> statement-breakpoint + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'board_api_keys_scope_legacy_check' + AND conrelid = 'board_api_keys'::regclass + ) THEN + ALTER TABLE "board_api_keys" + ADD CONSTRAINT "board_api_keys_scope_legacy_check" + CHECK ( + ("legacy_unrestricted" = true AND "scope_config" IS NULL) + OR + ("legacy_unrestricted" = false AND "scope_config" IS NOT NULL) + ); + END IF; +END $$;--> statement-breakpoint + +-- Pending CLI auth challenges created before this migration remain nullable and +-- cannot mint a key; every new challenge must persist a validated scope. +ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "requested_scope_config" jsonb; +--> statement-breakpoint +ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "pending_key_prefix" text; +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "board_api_key_authorization_events" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "board_api_key_id" uuid NOT NULL, + "owner_user_id" text NOT NULL, + "token_prefix" text, + "action" text NOT NULL, + "classification" text NOT NULL, + "authoritative_company_id" uuid, + "authoritative_resource_type" text, + "authoritative_resource_id" text, + "decision" text NOT NULL, + "reason" text NOT NULL, + "request_id" text, + "run_id" uuid, + "details" jsonb DEFAULT '{}'::jsonb NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_key_created_idx" +ON "board_api_key_authorization_events" USING btree ("board_api_key_id", "created_at" DESC);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_company_created_idx" +ON "board_api_key_authorization_events" USING btree ("authoritative_company_id", "created_at" DESC); diff --git a/packages/db/src/schema/board_api_key_authorization_events.ts b/packages/db/src/schema/board_api_key_authorization_events.ts new file mode 100644 index 0000000000..4ea4ce2bb5 --- /dev/null +++ b/packages/db/src/schema/board_api_key_authorization_events.ts @@ -0,0 +1,37 @@ +import { index, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; + +/** + * Instance-level board-key security decisions. Deliberately has no FK to the + * key or owner: audit history must survive key revocation and owner deletion. + * The details object is written only by the central gate from a fixed allowlist. + */ +export const boardApiKeyAuthorizationEvents = pgTable( + "board_api_key_authorization_events", + { + id: uuid("id").primaryKey().defaultRandom(), + boardApiKeyId: uuid("board_api_key_id").notNull(), + ownerUserId: text("owner_user_id").notNull(), + tokenPrefix: text("token_prefix"), + action: text("action").notNull(), + classification: text("classification").notNull(), + authoritativeCompanyId: uuid("authoritative_company_id"), + authoritativeResourceType: text("authoritative_resource_type"), + authoritativeResourceId: text("authoritative_resource_id"), + decision: text("decision").notNull(), + reason: text("reason").notNull(), + requestId: text("request_id"), + runId: uuid("run_id"), + details: jsonb("details").$type>().notNull().default({}), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + keyCreatedIdx: index("board_api_key_authorization_events_key_created_idx").on( + table.boardApiKeyId, + table.createdAt.desc(), + ), + companyCreatedIdx: index("board_api_key_authorization_events_company_created_idx").on( + table.authoritativeCompanyId, + table.createdAt.desc(), + ), + }), +); diff --git a/packages/db/src/schema/board_api_keys.ts b/packages/db/src/schema/board_api_keys.ts index e786760ff3..071d436d9a 100644 --- a/packages/db/src/schema/board_api_keys.ts +++ b/packages/db/src/schema/board_api_keys.ts @@ -1,4 +1,6 @@ -import { pgTable, uuid, text, timestamp, index, uniqueIndex } from "drizzle-orm/pg-core"; +import { sql } from "drizzle-orm"; +import { boolean, check, index, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import type { BoardApiKeyScopeConfig } from "@paperclipai/shared"; import { authUsers } from "./auth.js"; export const boardApiKeys = pgTable( @@ -8,6 +10,9 @@ export const boardApiKeys = pgTable( userId: text("user_id").notNull().references(() => authUsers.id, { onDelete: "cascade" }), name: text("name").notNull(), keyHash: text("key_hash").notNull(), + scopeConfig: jsonb("scope_config").$type(), + tokenPrefix: text("token_prefix"), + legacyUnrestricted: boolean("legacy_unrestricted").notNull().default(false), lastUsedAt: timestamp("last_used_at", { withTimezone: true }), revokedAt: timestamp("revoked_at", { withTimezone: true }), expiresAt: timestamp("expires_at", { withTimezone: true }), @@ -16,5 +21,9 @@ export const boardApiKeys = pgTable( (table) => ({ keyHashIdx: uniqueIndex("board_api_keys_key_hash_idx").on(table.keyHash), userIdx: index("board_api_keys_user_idx").on(table.userId), + scopeLegacyInvariant: check( + "board_api_keys_scope_legacy_check", + sql`(${table.legacyUnrestricted} = true and ${table.scopeConfig} is null) or (${table.legacyUnrestricted} = false and ${table.scopeConfig} is not null)`, + ), }), ); diff --git a/packages/db/src/schema/cli_auth_challenges.ts b/packages/db/src/schema/cli_auth_challenges.ts index 5c6335cab6..c21eef2924 100644 --- a/packages/db/src/schema/cli_auth_challenges.ts +++ b/packages/db/src/schema/cli_auth_challenges.ts @@ -1,4 +1,5 @@ -import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core"; +import { pgTable, uuid, text, timestamp, index, jsonb } from "drizzle-orm/pg-core"; +import type { BoardApiKeyScopeConfig } from "@paperclipai/shared"; import { authUsers } from "./auth.js"; import { companies } from "./companies.js"; import { boardApiKeys } from "./board_api_keys.js"; @@ -12,7 +13,9 @@ export const cliAuthChallenges = pgTable( clientName: text("client_name"), requestedAccess: text("requested_access").notNull().default("board"), requestedCompanyId: uuid("requested_company_id").references(() => companies.id, { onDelete: "set null" }), + requestedScopeConfig: jsonb("requested_scope_config").$type(), pendingKeyHash: text("pending_key_hash").notNull(), + pendingKeyPrefix: text("pending_key_prefix"), pendingKeyName: text("pending_key_name").notNull(), approvedByUserId: text("approved_by_user_id").references(() => authUsers.id, { onDelete: "set null" }), boardApiKeyId: uuid("board_api_key_id").references(() => boardApiKeys.id, { onDelete: "set null" }), diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index c3c393987f..4f5c5e570c 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -10,6 +10,7 @@ export { agents } from "./agents.js"; export { builtInManagedResources } from "./built_in_managed_resources.js"; export { agentMemberships } from "./agent_memberships.js"; export { boardApiKeys } from "./board_api_keys.js"; +export { boardApiKeyAuthorizationEvents } from "./board_api_key_authorization_events.js"; export { cliAuthChallenges } from "./cli_auth_challenges.js"; export { companyMemberships } from "./company_memberships.js"; export { companyUserSidebarPreferences } from "./company_user_sidebar_preferences.js"; diff --git a/packages/shared/src/__snapshots__/board-api-key-scope.test.ts.snap b/packages/shared/src/__snapshots__/board-api-key-scope.test.ts.snap new file mode 100644 index 0000000000..419be2c2d1 --- /dev/null +++ b/packages/shared/src/__snapshots__/board-api-key-scope.test.ts.snap @@ -0,0 +1,215 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`frozen board API key vocabulary and presets > freezes all three ordered preset expansions 1`] = ` +{ + "company_automation": { + "instanceCapabilities": [], + "name": "Company automation", + "permissions": [ + "companies:read", + "agents:read", + "projects:read", + "issues:read", + "goals:read", + "routines:read", + "approvals:read", + "costs:read", + "activity:read", + "artifacts:read", + "workspaces:read", + "skills:read", + "tools:read", + "members:read", + "decisions:read", + "settings:read", + "environments:read", + "pipelines:read", + "search:read", + "runtime:read", + "audit:read", + "companies:write", + "agents:write", + "agents:operate", + "projects:write", + "issues:write", + "issues:control", + "goals:write", + "routines:write", + "routines:run", + "approvals:write", + "costs:write", + "artifacts:write", + "workspaces:manage", + "skills:manage", + "tools:manage", + "secrets:read_metadata", + "secrets:manage", + "decisions:write", + "settings:write", + "environments:manage", + "pipelines:write", + "runtime:manage", + "board_api_keys:revoke_self", + ], + }, + "full_instance_admin": { + "helperText": "Grants instance-global actions; company data still requires pinned companies.", + "instanceCapabilities": [ + "instance_admin", + ], + "name": "Full instance admin", + "permissions": [ + "companies:read", + "companies:write", + "agents:read", + "agents:write", + "agents:operate", + "projects:read", + "projects:write", + "issues:read", + "issues:write", + "issues:control", + "goals:read", + "goals:write", + "routines:read", + "routines:write", + "routines:run", + "approvals:read", + "approvals:write", + "approvals:decide", + "costs:read", + "costs:write", + "activity:read", + "artifacts:read", + "artifacts:write", + "workspaces:read", + "workspaces:manage", + "skills:read", + "skills:manage", + "tools:read", + "tools:manage", + "secrets:read_metadata", + "secrets:manage", + "members:read", + "members:manage", + "decisions:read", + "decisions:write", + "settings:read", + "settings:write", + "environments:read", + "environments:manage", + "pipelines:read", + "pipelines:write", + "search:read", + "runtime:read", + "runtime:manage", + "audit:read", + "instance:read", + "instance:manage", + "companies:create", + "companies:import_export", + "plugins:read", + "plugins:manage", + "adapters:read", + "adapters:manage", + "users:read", + "users:manage", + "catalogs:read", + "catalogs:manage", + "backups:create", + "board_api_keys:revoke_self", + ], + }, + "read_only": { + "instanceCapabilities": [], + "name": "Read-only (company-pinned)", + "permissions": [ + "companies:read", + "agents:read", + "projects:read", + "issues:read", + "goals:read", + "routines:read", + "approvals:read", + "costs:read", + "activity:read", + "artifacts:read", + "workspaces:read", + "skills:read", + "tools:read", + "members:read", + "decisions:read", + "settings:read", + "environments:read", + "pipelines:read", + "search:read", + "runtime:read", + "audit:read", + ], + }, +} +`; + +exports[`frozen board API key vocabulary and presets > freezes the canonical 59 permission keys in numeric order 1`] = ` +[ + "companies:read", + "companies:write", + "agents:read", + "agents:write", + "agents:operate", + "projects:read", + "projects:write", + "issues:read", + "issues:write", + "issues:control", + "goals:read", + "goals:write", + "routines:read", + "routines:write", + "routines:run", + "approvals:read", + "approvals:write", + "approvals:decide", + "costs:read", + "costs:write", + "activity:read", + "artifacts:read", + "artifacts:write", + "workspaces:read", + "workspaces:manage", + "skills:read", + "skills:manage", + "tools:read", + "tools:manage", + "secrets:read_metadata", + "secrets:manage", + "members:read", + "members:manage", + "decisions:read", + "decisions:write", + "settings:read", + "settings:write", + "environments:read", + "environments:manage", + "pipelines:read", + "pipelines:write", + "search:read", + "runtime:read", + "runtime:manage", + "audit:read", + "instance:read", + "instance:manage", + "companies:create", + "companies:import_export", + "plugins:read", + "plugins:manage", + "adapters:read", + "adapters:manage", + "users:read", + "users:manage", + "catalogs:read", + "catalogs:manage", + "backups:create", + "board_api_keys:revoke_self", +] +`; diff --git a/packages/shared/src/board-api-key-scope.test.ts b/packages/shared/src/board-api-key-scope.test.ts new file mode 100644 index 0000000000..251cc9493c --- /dev/null +++ b/packages/shared/src/board-api-key-scope.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from "vitest"; +import { + BOARD_API_KEY_PERMISSION_KEYS, + BOARD_API_KEY_SCOPE_PRESETS, + boardApiKeyScopeConfigSchema, + deriveBoardApiKeyStatus, +} from "./board-api-key-scope.js"; +import { createBoardApiKeySchema } from "./validators/access.js"; + +const COMPANY_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; + +function validScope() { + return { + version: 1 as const, + kind: "scoped" as const, + companyIds: [COMPANY_ID], + permissions: ["companies:read" as const], + instanceCapabilities: [] as const, + }; +} + +describe("boardApiKeyScopeConfigSchema", () => { + it("round trips the strict version-1 shape without normalization", () => { + const input = validScope(); + expect(boardApiKeyScopeConfigSchema.parse(input)).toEqual(input); + }); + + it.each([ + ["unknown object key", { ...validScope(), extra: true }], + ["unsupported version", { ...validScope(), version: 2 }], + ["unsupported kind", { ...validScope(), kind: "legacy" }], + ["empty companies", { ...validScope(), companyIds: [] }], + ["duplicate companies", { ...validScope(), companyIds: [COMPANY_ID, COMPANY_ID] }], + ["noncanonical company UUID", { ...validScope(), companyIds: [COMPANY_ID.toUpperCase()] }], + ["empty permissions", { ...validScope(), permissions: [] }], + ["duplicate permissions", { ...validScope(), permissions: ["companies:read", "companies:read"] }], + ["unknown permission", { ...validScope(), permissions: ["companies:admin"] }], + ["unsupported capability", { ...validScope(), instanceCapabilities: ["root"] }], + ["duplicate capability", { ...validScope(), instanceCapabilities: ["instance_admin", "instance_admin"] }], + ["coerced version", { ...validScope(), version: "1" }], + ["trimmed permission", { ...validScope(), permissions: [" companies:read"] }], + ])("rejects %s", (_label, input) => { + expect(boardApiKeyScopeConfigSchema.safeParse(input).success).toBe(false); + }); + + it("enforces the company and permission cardinality bounds", () => { + const tooManyCompanies = Array.from( + { length: 51 }, + (_, index) => `00000000-0000-4000-8000-${index.toString().padStart(12, "0")}`, + ); + expect(boardApiKeyScopeConfigSchema.safeParse({ ...validScope(), companyIds: tooManyCompanies }).success).toBe(false); + expect(BOARD_API_KEY_PERMISSION_KEYS).toHaveLength(59); + expect(boardApiKeyScopeConfigSchema.parse({ + ...validScope(), + permissions: [...BOARD_API_KEY_PERMISSION_KEYS], + }).permissions).toHaveLength(59); + }); +}); + +describe("frozen board API key vocabulary and presets", () => { + it("freezes the canonical 59 permission keys in numeric order", () => { + expect(BOARD_API_KEY_PERMISSION_KEYS).toMatchSnapshot(); + }); + + it("freezes all three ordered preset expansions", () => { + expect(BOARD_API_KEY_SCOPE_PRESETS).toMatchSnapshot(); + }); +}); + +describe("createBoardApiKeySchema", () => { + it("requires an explicit strict non-null scope", () => { + const valid = { + name: "automation", + expiresAt: "2026-09-05T12:00:00.000Z", + scopeConfig: validScope(), + }; + expect(createBoardApiKeySchema.parse(valid)).toEqual(valid); + expect(createBoardApiKeySchema.safeParse({ name: "automation" }).success).toBe(false); + expect(createBoardApiKeySchema.safeParse({ ...valid, scopeConfig: null }).success).toBe(false); + }); + + it.each([ + ["trimmed name", { name: " automation", scopeConfig: validScope() }], + ["blank name", { name: " ", scopeConfig: validScope() }], + ["coerced expiry", { name: "automation", expiresAt: new Date(), scopeConfig: validScope() }], + ["unknown key", { name: "automation", requestedCompanyId: COMPANY_ID, scopeConfig: validScope() }], + ])("rejects %s", (_label, input) => { + expect(createBoardApiKeySchema.safeParse(input).success).toBe(false); + }); +}); + +describe("deriveBoardApiKeyStatus", () => { + const now = new Date("2026-08-06T12:00:00.000Z"); + + it("derives revoked, expired, finite, and never-expiring states", () => { + expect(deriveBoardApiKeyStatus({ revokedAt: now, expiresAt: null }, now)).toBe("revoked"); + expect(deriveBoardApiKeyStatus({ revokedAt: null, expiresAt: now }, now)).toBe("expired"); + expect(deriveBoardApiKeyStatus({ revokedAt: null, expiresAt: "2026-08-07T12:00:00.000Z" }, now)).toBe("expires"); + expect(deriveBoardApiKeyStatus({ revokedAt: null, expiresAt: null }, now)).toBe("never_expires"); + }); +}); diff --git a/packages/shared/src/board-api-key-scope.ts b/packages/shared/src/board-api-key-scope.ts new file mode 100644 index 0000000000..efdf05557c --- /dev/null +++ b/packages/shared/src/board-api-key-scope.ts @@ -0,0 +1,213 @@ +import { z } from "zod"; + +/** + * Frozen by the PAP-16479 revision-3 security contract. Changes to this list + * or to a preset expansion require a new SecurityEngineer-reviewed revision. + */ +export const BOARD_API_KEY_PERMISSION_KEYS = [ + "companies:read", + "companies:write", + "agents:read", + "agents:write", + "agents:operate", + "projects:read", + "projects:write", + "issues:read", + "issues:write", + "issues:control", + "goals:read", + "goals:write", + "routines:read", + "routines:write", + "routines:run", + "approvals:read", + "approvals:write", + "approvals:decide", + "costs:read", + "costs:write", + "activity:read", + "artifacts:read", + "artifacts:write", + "workspaces:read", + "workspaces:manage", + "skills:read", + "skills:manage", + "tools:read", + "tools:manage", + "secrets:read_metadata", + "secrets:manage", + "members:read", + "members:manage", + "decisions:read", + "decisions:write", + "settings:read", + "settings:write", + "environments:read", + "environments:manage", + "pipelines:read", + "pipelines:write", + "search:read", + "runtime:read", + "runtime:manage", + "audit:read", + "instance:read", + "instance:manage", + "companies:create", + "companies:import_export", + "plugins:read", + "plugins:manage", + "adapters:read", + "adapters:manage", + "users:read", + "users:manage", + "catalogs:read", + "catalogs:manage", + "backups:create", + "board_api_keys:revoke_self", +] as const; + +export type BoardPermissionKey = (typeof BOARD_API_KEY_PERMISSION_KEYS)[number]; + +export const BOARD_API_KEY_INSTANCE_CAPABILITIES = ["instance_admin"] as const; +export type BoardApiKeyInstanceCapability = (typeof BOARD_API_KEY_INSTANCE_CAPABILITIES)[number]; + +const canonicalLowercaseUuidSchema = z + .string() + .uuid() + .refine((value) => value === value.toLowerCase(), "Company IDs must use canonical lowercase UUID text"); + +function uniqueArray(item: T, min: number, max: number) { + return z + .array(item) + .min(min) + .max(max) + .superRefine((values, ctx) => { + const seen = new Set(); + values.forEach((value, index) => { + if (seen.has(value)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Duplicate values are not allowed", + path: [index], + }); + } + seen.add(value); + }); + }); +} + +/** Strict non-null scope accepted for every post-migration key creation. */ +export const boardApiKeyScopeConfigSchema = z + .object({ + version: z.literal(1), + kind: z.literal("scoped"), + companyIds: uniqueArray(canonicalLowercaseUuidSchema, 1, 50), + permissions: uniqueArray(z.enum(BOARD_API_KEY_PERMISSION_KEYS), 1, 59), + instanceCapabilities: uniqueArray(z.enum(BOARD_API_KEY_INSTANCE_CAPABILITIES), 0, 1), + }) + .strict(); + +export type BoardApiKeyScopeConfig = z.infer; + +/** Stored values may be null only when the row is explicitly legacy-unrestricted. */ +export const storedBoardApiKeyScopeConfigSchema = boardApiKeyScopeConfigSchema.nullable(); + +const READ_ONLY_PERMISSIONS = [ + "companies:read", + "agents:read", + "projects:read", + "issues:read", + "goals:read", + "routines:read", + "approvals:read", + "costs:read", + "activity:read", + "artifacts:read", + "workspaces:read", + "skills:read", + "tools:read", + "members:read", + "decisions:read", + "settings:read", + "environments:read", + "pipelines:read", + "search:read", + "runtime:read", + "audit:read", +] as const satisfies readonly BoardPermissionKey[]; + +const COMPANY_AUTOMATION_PERMISSIONS = [ + ...READ_ONLY_PERMISSIONS, + "companies:write", + "agents:write", + "agents:operate", + "projects:write", + "issues:write", + "issues:control", + "goals:write", + "routines:write", + "routines:run", + "approvals:write", + "costs:write", + "artifacts:write", + "workspaces:manage", + "skills:manage", + "tools:manage", + "secrets:read_metadata", + "secrets:manage", + "decisions:write", + "settings:write", + "environments:manage", + "pipelines:write", + "runtime:manage", + "board_api_keys:revoke_self", +] as const satisfies readonly BoardPermissionKey[]; + +export const BOARD_API_KEY_SCOPE_PRESETS = { + read_only: { + name: "Read-only (company-pinned)", + permissions: READ_ONLY_PERMISSIONS, + instanceCapabilities: [] as const, + }, + company_automation: { + name: "Company automation", + permissions: COMPANY_AUTOMATION_PERMISSIONS, + instanceCapabilities: [] as const, + }, + full_instance_admin: { + name: "Full instance admin", + permissions: BOARD_API_KEY_PERMISSION_KEYS, + instanceCapabilities: ["instance_admin"] as const, + helperText: "Grants instance-global actions; company data still requires pinned companies.", + }, +} as const; + +export type BoardApiKeyScopePreset = keyof typeof BOARD_API_KEY_SCOPE_PRESETS; + +export type BoardApiKeyStatus = "active" | "expires" | "never_expires" | "expired" | "revoked"; + +export function deriveBoardApiKeyStatus( + input: { revokedAt: Date | string | null; expiresAt: Date | string | null }, + now: Date = new Date(), +): BoardApiKeyStatus { + if (input.revokedAt) return "revoked"; + if (!input.expiresAt) return "never_expires"; + return new Date(input.expiresAt).getTime() <= now.getTime() ? "expired" : "expires"; +} + +export interface BoardApiKeyResponse { + id: string; + name: string; + tokenPrefix: string | null; + scopeConfig: BoardApiKeyScopeConfig | null; + legacyUnrestricted: boolean; + status: BoardApiKeyStatus; + createdAt: string; + lastUsedAt: string | null; + expiresAt: string | null; + revokedAt: string | null; +} + +export interface CreatedBoardApiKeyResponse extends BoardApiKeyResponse { + token: string; +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 6b17bf735e..b9ab082d66 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -39,6 +39,22 @@ export { type NativeRuntimeMode, type NativeRunTerminalState, } from "./types/native-finalization.js"; +export { + BOARD_API_KEY_PERMISSION_KEYS, + BOARD_API_KEY_INSTANCE_CAPABILITIES, + BOARD_API_KEY_SCOPE_PRESETS, + boardApiKeyScopeConfigSchema, + storedBoardApiKeyScopeConfigSchema, + deriveBoardApiKeyStatus, + type BoardPermissionKey, + type BoardApiKeyInstanceCapability, + type BoardApiKeyScopeConfig, + type BoardApiKeyScopePreset, + type BoardApiKeyStatus, + type BoardApiKeyResponse, + type CreatedBoardApiKeyResponse, +} from "./board-api-key-scope.js"; + export { decisionEffectStalenessSchema, decisionOptionStyleSchema, diff --git a/packages/shared/src/validators/access.ts b/packages/shared/src/validators/access.ts index a5eddd48e5..21001d6b1a 100644 --- a/packages/shared/src/validators/access.ts +++ b/packages/shared/src/validators/access.ts @@ -8,6 +8,7 @@ import { PERMISSION_KEYS, } from "../constants.js"; import { optionalAgentAdapterTypeSchema } from "../adapter-type.js"; +import { boardApiKeyScopeConfigSchema } from "../board-api-key-scope.js"; export const createCompanyInviteSchema = z.object({ allowedJoinTypes: z.enum(INVITE_JOIN_TYPES).default("both"), @@ -75,7 +76,8 @@ export const createCliAuthChallengeSchema = z.object({ clientName: z.string().max(120).optional().nullable(), requestedAccess: boardCliAuthAccessLevelSchema.default("board"), requestedCompanyId: z.string().guid().optional().nullable(), -}); + scopeConfig: boardApiKeyScopeConfigSchema, +}).strict(); export type CreateCliAuthChallenge = z.infer; @@ -85,11 +87,17 @@ export const resolveCliAuthChallengeSchema = z.object({ export type ResolveCliAuthChallenge = z.infer; -export const createBoardApiKeySchema = z.object({ - name: z.string().trim().min(1).max(120).default("paperclipai cli"), - expiresAt: z.coerce.date().optional().nullable(), - requestedCompanyId: z.string().guid().optional().nullable(), -}); +export const createBoardApiKeySchema = z + .object({ + name: z + .string() + .min(1) + .max(120) + .refine((value) => value === value.trim() && value.trim().length > 0, "Name must not have surrounding whitespace"), + expiresAt: z.string().datetime({ offset: true }).optional().nullable(), + scopeConfig: boardApiKeyScopeConfigSchema, + }) + .strict(); export type CreateBoardApiKey = z.infer; diff --git a/server/src/__tests__/board-key-auth-middleware.test.ts b/server/src/__tests__/board-key-auth-middleware.test.ts new file mode 100644 index 0000000000..7413bd02ad --- /dev/null +++ b/server/src/__tests__/board-key-auth-middleware.test.ts @@ -0,0 +1,194 @@ +import { createHash, randomUUID } from "node:crypto"; +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { + authUsers, + boardApiKeyAuthorizationEvents, + boardApiKeys, + companyMemberships, + instanceUserRoles, +} from "@paperclipai/db"; +import { BOARD_API_KEY_SCOPE_PRESETS } from "@paperclipai/shared"; +import { + actorMiddleware, + resetBoardKeyAuthFailureRateLimitForTests, +} from "../middleware/auth.js"; +import { errorHandler } from "../middleware/error-handler.js"; + +const TOKEN = "pcp_board_middleware_valid_token"; + +function createDbState() { + const companyId = randomUUID(); + const ownerId = randomUUID(); + const keyId = randomUUID(); + const state = { + keyExists: true, + ownerExists: true, + membershipActive: true, + malformedScope: false, + revoked: false, + revokeOnTouch: false, + lastUsedAt: null as Date | null, + }; + const audits: Array> = []; + const key = () => ({ + id: keyId, + userId: ownerId, + name: "middleware", + keyHash: createHash("sha256").update(TOKEN).digest("hex"), + tokenPrefix: "pcp_board_middle", + scopeConfig: state.malformedScope + ? { version: 1, kind: "scoped", companyIds: [companyId], permissions: ["not:a:permission"], instanceCapabilities: [] } + : { + version: 1, + kind: "scoped", + companyIds: [companyId], + permissions: [...BOARD_API_KEY_SCOPE_PRESETS.read_only.permissions], + instanceCapabilities: [], + }, + legacyUnrestricted: false, + createdAt: new Date(), + lastUsedAt: state.lastUsedAt, + expiresAt: null, + revokedAt: state.revoked ? new Date() : null, + }); + + const db = { + select: vi.fn(() => ({ + from(table: unknown) { + const rows = table === boardApiKeys + ? (state.keyExists ? [key()] : []) + : table === authUsers + ? (state.ownerExists ? [{ id: ownerId, name: "Owner", email: "owner@example.com" }] : []) + : table === companyMemberships + ? (state.membershipActive ? [{ companyId, membershipRole: "owner", status: "active" }] : []) + : table === instanceUserRoles + ? [] + : []; + return { + where: () => Promise.resolve(rows), + then: (resolve: (value: unknown[]) => unknown) => Promise.resolve(rows).then(resolve), + }; + }, + })), + update: vi.fn(() => ({ + set(values: { lastUsedAt?: Date }) { + return { + where: () => ({ + returning: async () => { + if (state.revoked || state.revokeOnTouch) { + state.revoked = state.revoked || state.revokeOnTouch; + return []; + } + state.lastUsedAt = values.lastUsedAt ?? state.lastUsedAt; + return [{ id: keyId }]; + }, + }), + }; + }, + })), + insert: vi.fn((table: unknown) => ({ + values: async (values: Record) => { + if (table === boardApiKeyAuthorizationEvents) audits.push(values); + return []; + }, + })), + } as any; + + return { db, state, audits, companyId, keyId, ownerId }; +} + +function createApp(db: any, resolveSession = vi.fn(async () => null)) { + const app = express(); + app.use(actorMiddleware(db, { deploymentMode: "authenticated", resolveSession })); + app.get("/actor", (req, res) => res.json(req.actor)); + app.use(errorHandler); + return { app, resolveSession }; +} + +describe("board-key authentication middleware", () => { + beforeEach(() => resetBoardKeyAuthFailureRateLimitForTests()); + + it("builds the board-key principal from a fresh key and live owner authority read", async () => { + const { db, companyId, keyId, ownerId } = createDbState(); + const { app } = createApp(db); + const response = await request(app).get("/actor").set("Authorization", `Bearer ${TOKEN}`); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ + type: "board", + source: "board_key", + keyId, + boardKeyOwnerId: ownerId, + boardKeyPrefix: "pcp_board_middle", + companyIds: [companyId], + }); + expect(response.body).not.toHaveProperty("token"); + }); + + it("never falls back to cookies for an invalid Bearer and returns a generic 401 for malformed stored scope", async () => { + const invalid = createDbState(); + invalid.state.keyExists = false; + const invalidApp = createApp(invalid.db, vi.fn(async () => ({ + session: { id: "session" }, + user: { id: randomUUID() }, + } as any))); + const invalidResponse = await request(invalidApp.app) + .get("/actor") + .set("Authorization", "Bearer pcp_board_unknown"); + expect(invalidResponse.status).toBe(401); + expect(invalidResponse.body).toEqual({ error: "Unauthorized" }); + expect(invalidApp.resolveSession).not.toHaveBeenCalled(); + + const malformed = createDbState(); + malformed.state.malformedScope = true; + const malformedResponse = await request(createApp(malformed.db).app) + .get("/actor") + .set("Authorization", `Bearer ${TOKEN}`); + expect(malformedResponse.status).toBe(401); + expect(malformedResponse.body).toEqual({ error: "Unauthorized" }); + expect(JSON.stringify(malformed.audits)).not.toContain(TOKEN); + expect(JSON.stringify(malformed.audits)).not.toContain("authorization"); + }); + + it("rate-limits repeated failures without logging secret material", async () => { + const { db, state, audits } = createDbState(); + state.keyExists = false; + const { app } = createApp(db); + const statuses: number[] = []; + for (let index = 0; index < 11; index += 1) { + const response = await request(app) + .get("/actor") + .set("Authorization", `Bearer ${TOKEN}bad`); + statuses.push(response.status); + } + expect(statuses.slice(0, 10)).toEqual(Array(10).fill(401)); + expect(statuses[10]).toBe(429); + expect(JSON.stringify(audits)).not.toContain(`${TOKEN}bad`); + }); + + it("does not resurrect last-used state when revocation wins the authentication race", async () => { + const { db, state } = createDbState(); + state.revokeOnTouch = true; + const response = await request(createApp(db).app) + .get("/actor") + .set("Authorization", `Bearer ${TOKEN}`); + + expect(response.status).toBe(401); + expect(state.revoked).toBe(true); + expect(state.lastUsedAt).toBeNull(); + }); + + it("reflects membership removal on the next request without a positive auth cache", async () => { + const { db, state, companyId } = createDbState(); + const { app } = createApp(db); + const first = await request(app).get("/actor").set("Authorization", `Bearer ${TOKEN}`); + expect(first.body.companyIds).toEqual([companyId]); + + state.membershipActive = false; + const second = await request(app).get("/actor").set("Authorization", `Bearer ${TOKEN}`); + expect(second.status).toBe(200); + expect(second.body.companyIds).toEqual([]); + }); +}); diff --git a/server/src/__tests__/cli-auth-routes.test.ts b/server/src/__tests__/cli-auth-routes.test.ts index 391510856b..902b783201 100644 --- a/server/src/__tests__/cli-auth-routes.test.ts +++ b/server/src/__tests__/cli-auth-routes.test.ts @@ -1,6 +1,16 @@ import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { BOARD_API_KEY_SCOPE_PRESETS } from "@paperclipai/shared"; + +const COMPANY_ID = "11111111-1111-4111-8111-111111111111"; +const SCOPE_CONFIG = { + version: 1 as const, + kind: "scoped" as const, + companyIds: [COMPANY_ID], + permissions: [...BOARD_API_KEY_SCOPE_PRESETS.company_automation.permissions], + instanceCapabilities: [] as const, +}; const mockAccessService = vi.hoisted(() => ({ isInstanceAdmin: vi.fn(), @@ -118,6 +128,8 @@ describe.sequential("cli auth routes", () => { command: "paperclipai company import", clientName: "paperclipai cli", requestedAccess: "board", + requestedCompanyId: COMPANY_ID, + scopeConfig: SCOPE_CONFIG, }); expect(res.status, res.text || JSON.stringify(res.body)).toBe(201); @@ -187,6 +199,7 @@ describe.sequential("cli auth routes", () => { cancelledAt: null, expiresAt: "2026-03-23T13:00:00.000Z", approvedByUser: null, + boardApiKeyId: null, }); const app = await createApp({ type: "none", source: "none" }); @@ -315,13 +328,14 @@ describe.sequential("cli auth routes", () => { lastUsedAt: null, revokedAt: null, expiresAt: new Date("2026-06-23T12:00:00.000Z"), + scopeConfig: SCOPE_CONFIG, }); - mockBoardAuthService.resolveBoardActivityCompanyIds.mockResolvedValue(["11111111-1111-4111-8111-111111111111"]); + mockBoardAuthService.resolveBoardActivityCompanyIds.mockResolvedValue([COMPANY_ID]); const app = await createApp({ type: "board", userId: "user-1", - source: "board_key", + source: "session", isInstanceAdmin: false, companyIds: ["11111111-1111-4111-8111-111111111111"], }); @@ -329,8 +343,8 @@ describe.sequential("cli auth routes", () => { .post("/api/board-api-keys") .send({ name: "external-admin", - requestedCompanyId: "11111111-1111-4111-8111-111111111111", expiresAt: "2026-06-23T12:00:00.000Z", + scopeConfig: SCOPE_CONFIG, }); expect(res.status, res.text || JSON.stringify(res.body)).toBe(201); @@ -344,6 +358,7 @@ describe.sequential("cli auth routes", () => { userId: "user-1", name: "external-admin", expiresAt: new Date("2026-06-23T12:00:00.000Z"), + scopeConfig: SCOPE_CONFIG, }); expect(mockLogActivity).toHaveBeenCalledWith( expect.anything(), @@ -382,7 +397,7 @@ describe.sequential("cli auth routes", () => { const app = await createApp({ type: "board", userId: "user-1", - source: "board_key", + source: "session", isInstanceAdmin: false, companyIds: ["company-1"], }); diff --git a/server/src/app.ts b/server/src/app.ts index 003183d8d7..23f3bf3a64 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -173,6 +173,7 @@ import { COMPANY_IMPORT_API_PATH } from "./routes/company-import-paths.js"; import { apiCompression } from "./middleware/api-compression.js"; import { chatWebhookBodyParser } from "./middleware/chat-webhook-body.js"; import { createChatWebhookDiagnostics } from "./services/chat-webhook-diagnostics.js"; +import { boardKeyAuthorizationMiddleware } from "./security/board-key-route-registry.js"; type UiMode = "none" | "static" | "vite-dev"; const FEEDBACK_EXPORT_FLUSH_INTERVAL_MS = 5_000; @@ -563,6 +564,7 @@ export async function createApp( // REPLACES whatever actor the request otherwise resolved to, and only on // the one endpoint it authorizes (see the middleware for the contract). app.use(cloudControlMiddleware()); + app.use(boardKeyAuthorizationMiddleware(db)); app.use("/api/auth", authRoutes(db)); if (opts.betterAuthHandler) { app.all("/api/auth/{*authPath}", opts.betterAuthHandler); diff --git a/server/src/middleware/auth.ts b/server/src/middleware/auth.ts index bbf146c068..ee02ef7407 100644 --- a/server/src/middleware/auth.ts +++ b/server/src/middleware/auth.ts @@ -7,6 +7,7 @@ import { agentApiKeys, agents, authUsers, + boardApiKeyAuthorizationEvents, companies, companyMemberships, heartbeatRuns, @@ -55,7 +56,7 @@ function pruneCloudTenantWriteDebounce( } import { instanceSettingsService } from "../services/instance-settings.js"; import { ensureHumanRoleDefaultGrants } from "../services/principal-access-compatibility.js"; -import { forbidden, unauthorized, unprocessable } from "../errors.js"; +import { forbidden, tooManyRequests, unauthorized, unprocessable } from "../errors.js"; export { isCloudManagedInstance } from "../services/cloud-instance.js"; @@ -63,6 +64,56 @@ function hashToken(token: string) { return createHash("sha256").update(token).digest("hex"); } +const BOARD_KEY_AUTH_FAILURE_WINDOW_MS = 60_000; +const BOARD_KEY_AUTH_FAILURE_LIMIT = 10; +const boardKeyAuthFailures = new Map(); + +function recordBoardKeyAuthFailure(token: string, now = Date.now()) { + const identity = hashToken(token); + const existing = boardKeyAuthFailures.get(identity); + const entry = !existing || existing.resetAt <= now + ? { count: 1, resetAt: now + BOARD_KEY_AUTH_FAILURE_WINDOW_MS } + : { count: existing.count + 1, resetAt: existing.resetAt }; + boardKeyAuthFailures.set(identity, entry); + if (boardKeyAuthFailures.size > 10_000) { + for (const [key, value] of boardKeyAuthFailures) { + if (value.resetAt <= now) boardKeyAuthFailures.delete(key); + } + } + return entry.count > BOARD_KEY_AUTH_FAILURE_LIMIT; +} + +export function resetBoardKeyAuthFailureRateLimitForTests() { + boardKeyAuthFailures.clear(); +} + +async function auditBoardKeyAuthenticationFailure( + db: Db, + req: Request, + input: { + key: { id: string; userId: string; tokenPrefix: string | null } | null; + reason: string; + }, +) { + if (!input.key) return; + try { + await db.insert(boardApiKeyAuthorizationEvents).values({ + boardApiKeyId: input.key.id, + ownerUserId: input.key.userId, + tokenPrefix: input.key.tokenPrefix, + action: "authenticate", + classification: "authentication", + decision: "deny", + reason: input.reason, + requestId: typeof req.id === "string" ? req.id : null, + runId: isUuidLike(req.header("x-paperclip-run-id")) ? req.header("x-paperclip-run-id") : null, + details: {}, + }); + } catch (err) { + logger.warn({ err, boardApiKeyId: input.key.id }, "Failed to audit denied board-key authentication"); + } +} + function normalizeOptionalString(value: string | null | undefined) { return value?.trim() || null; } @@ -304,26 +355,35 @@ export function actorMiddleware(db: Db, opts: ActorMiddlewareOptions): RequestHa return; } - const boardKey = await boardAuth.findBoardApiKeyByToken(token); - if (boardKey) { - const access = await boardAuth.resolveBoardAccess(boardKey.userId); - if (access.user) { - await boardAuth.touchBoardApiKey(boardKey.id); - req.actor = { - type: "board", - userId: boardKey.userId, - userName: access.user?.name ?? null, - userEmail: access.user?.email ?? null, - companyIds: access.companyIds, - memberships: access.memberships, - isInstanceAdmin: access.isInstanceAdmin, - keyId: boardKey.id, - runId: runIdHeader || undefined, - source: "board_key", - }; - next(); + if (token.startsWith("pcp_board_")) { + const authentication = await boardAuth.authenticateBoardApiKey(token); + if (!authentication.ok) { + await auditBoardKeyAuthenticationFailure(db, req, authentication); + next(recordBoardKeyAuthFailure(token) ? tooManyRequests("Too many authentication failures") : unauthorized()); return; } + const { key: boardKey, access, scopeConfig } = authentication; + const effectiveCompanyIds = scopeConfig + ? scopeConfig.companyIds.filter((companyId) => access.companyIds.includes(companyId)) + : access.companyIds; + req.actor = { + type: "board", + userId: boardKey.userId, + userName: access.user.name ?? null, + userEmail: access.user.email ?? null, + companyIds: effectiveCompanyIds, + memberships: access.memberships.filter((membership) => effectiveCompanyIds.includes(membership.companyId)), + isInstanceAdmin: access.isInstanceAdmin, + keyId: boardKey.id, + boardKeyOwnerId: boardKey.userId, + boardKeyScope: scopeConfig, + boardKeyPrefix: boardKey.tokenPrefix, + boardKeyLegacyUnrestricted: boardKey.legacyUnrestricted, + runId: runIdHeader || undefined, + source: "board_key", + }; + next(); + return; } const tokenHash = hashToken(token); diff --git a/server/src/routes/access.ts b/server/src/routes/access.ts index a6052861cc..a266539a7c 100644 --- a/server/src/routes/access.ts +++ b/server/src/routes/access.ts @@ -2912,21 +2912,25 @@ export function accessRoutes( if (req.actor.type !== "board" || !req.actor.userId) { throw unauthorized("Board authentication required"); } + if (req.actor.source === "board_key") { + throw forbidden("Board API keys cannot create board API keys"); + } - if (req.body.requestedCompanyId) { - assertCompanyAccess(req, req.body.requestedCompanyId); + for (const companyId of req.body.scopeConfig.companyIds) { + assertCompanyAccess(req, companyId); } const key = await boardAuth.createNamedBoardApiKey({ userId: req.actor.userId, name: req.body.name, - expiresAt: req.body.expiresAt === undefined ? undefined : req.body.expiresAt, - }); - const companyIds = await boardAuth.resolveBoardActivityCompanyIds({ - userId: req.actor.userId, - requestedCompanyId: req.body.requestedCompanyId ?? null, - boardApiKeyId: key.id, + expiresAt: req.body.expiresAt === undefined + ? undefined + : req.body.expiresAt === null + ? null + : new Date(req.body.expiresAt), + scopeConfig: req.body.scopeConfig, }); + const companyIds = key.scopeConfig.companyIds; for (const companyId of companyIds) { await logActivity(db, { companyId, @@ -2938,7 +2942,9 @@ export function accessRoutes( details: { boardApiKeyId: key.id, name: key.name, - requestedCompanyId: req.body.requestedCompanyId ?? null, + scopeCompanyIds: key.scopeConfig.companyIds, + permissions: key.scopeConfig.permissions, + instanceCapabilities: key.scopeConfig.instanceCapabilities, expiresAt: key.expiresAt?.toISOString() ?? null, }, }); diff --git a/server/src/security/__snapshots__/board-key-route-registry.test.ts.snap b/server/src/security/__snapshots__/board-key-route-registry.test.ts.snap new file mode 100644 index 0000000000..1f479f8e28 --- /dev/null +++ b/server/src/security/__snapshots__/board-key-route-registry.test.ts.snap @@ -0,0 +1,9737 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`board-key route registry > inventories every checked-in route registration with an explicit policy 1`] = ` +[ + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/board-claim/{*path}", + }, + "method": "GET", + "path": "/api/board-claim/11111111-1111-4111-8111-111111111111", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/board-claim/{*path}", + }, + "method": "POST", + "path": "/api/board-claim/11111111-1111-4111-8111-111111111111/claim", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/bootstrap/{*path}", + }, + "method": "POST", + "path": "/api/bootstrap/claim", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cli-auth/{*path}", + }, + "method": "POST", + "path": "/api/cli-auth/challenges", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cli-auth/{*path}", + }, + "method": "GET", + "path": "/api/cli-auth/challenges/11111111-1111-4111-8111-111111111111", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cli-auth/{*path}", + }, + "method": "POST", + "path": "/api/cli-auth/challenges/11111111-1111-4111-8111-111111111111/approve", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cli-auth/{*path}", + }, + "method": "POST", + "path": "/api/cli-auth/challenges/11111111-1111-4111-8111-111111111111/cancel", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cli-auth/{*path}", + }, + "method": "GET", + "path": "/api/cli-auth/me", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/board-api-keys", + }, + "method": "GET", + "path": "/api/board-api-keys", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/board-api-keys", + }, + "method": "POST", + "path": "/api/board-api-keys", + }, + { + "file": "access.ts", + "metadata": { + "action": "board_api_keys:revoke_self", + "classification": "key_self", + "concealment": "not_found", + "method": "DELETE", + "resolver": "key_self", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/board-api-keys/{id}", + }, + "method": "DELETE", + "path": "/api/board-api-keys/11111111-1111-4111-8111-111111111111", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cli-auth/{*path}", + }, + "method": "POST", + "path": "/api/cli-auth/revoke-current", + }, + { + "file": "access.ts", + "metadata": { + "action": "catalogs:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/skills/{*path}", + }, + "method": "GET", + "path": "/api/skills/available", + }, + { + "file": "access.ts", + "metadata": { + "action": "catalogs:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/skills/{*path}", + }, + "method": "GET", + "path": "/api/skills/index", + }, + { + "file": "access.ts", + "metadata": { + "action": "catalogs:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/skills/{*path}", + }, + "method": "GET", + "path": "/api/skills/11111111-1111-4111-8111-111111111111", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/invites", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/openclaw/invite-prompt", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/openclaw/invite-prompt", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/invites/{*path}", + }, + "method": "GET", + "path": "/api/invites/11111111-1111-4111-8111-111111111111", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/invites/{*path}", + }, + "method": "GET", + "path": "/api/invites/11111111-1111-4111-8111-111111111111/logo", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/invites/{*path}", + }, + "method": "GET", + "path": "/api/invites/11111111-1111-4111-8111-111111111111/onboarding", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/invites/{*path}", + }, + "method": "GET", + "path": "/api/invites/11111111-1111-4111-8111-111111111111/onboarding.txt", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/invites/{*path}", + }, + "method": "GET", + "path": "/api/invites/11111111-1111-4111-8111-111111111111/skills/index", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/invites/{*path}", + }, + "method": "GET", + "path": "/api/invites/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/invites/{*path}", + }, + "method": "GET", + "path": "/api/invites/11111111-1111-4111-8111-111111111111/test-resolution", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/invites/{*path}", + }, + "method": "POST", + "path": "/api/invites/11111111-1111-4111-8111-111111111111/accept", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/invites/{*path}", + }, + "method": "POST", + "path": "/api/invites/11111111-1111-4111-8111-111111111111/revoke", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/invites", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/join-requests", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/join-requests/11111111-1111-4111-8111-111111111111/approve", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/join-requests/11111111-1111-4111-8111-111111111111/reject", + }, + { + "file": "access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/join-requests/{*path}", + }, + "method": "POST", + "path": "/api/join-requests/11111111-1111-4111-8111-111111111111/claim-api-key", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/members", + }, + { + "file": "access.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/user-directory", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/members/11111111-1111-4111-8111-111111111111", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/members/11111111-1111-4111-8111-111111111111/role-and-grants", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/members/11111111-1111-4111-8111-111111111111/archive", + }, + { + "file": "access.ts", + "metadata": { + "action": "members:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/members/11111111-1111-4111-8111-111111111111/permissions", + }, + { + "file": "access.ts", + "metadata": { + "action": "users:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/admin/{*path}", + }, + "method": "POST", + "path": "/api/admin/users/11111111-1111-4111-8111-111111111111/promote-instance-admin", + }, + { + "file": "access.ts", + "metadata": { + "action": "users:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/admin/{*path}", + }, + "method": "GET", + "path": "/api/admin/users", + }, + { + "file": "access.ts", + "metadata": { + "action": "users:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/admin/{*path}", + }, + "method": "POST", + "path": "/api/admin/users/11111111-1111-4111-8111-111111111111/demote-instance-admin", + }, + { + "file": "access.ts", + "metadata": { + "action": "users:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/admin/{*path}", + }, + "method": "GET", + "path": "/api/admin/users/11111111-1111-4111-8111-111111111111/company-access", + }, + { + "file": "access.ts", + "metadata": { + "action": "users:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/admin/{*path}", + }, + "method": "PUT", + "path": "/api/admin/users/11111111-1111-4111-8111-111111111111/company-access", + }, + { + "file": "activity.ts", + "metadata": { + "action": "activity:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/activity", + }, + { + "file": "activity.ts", + "metadata": { + "action": "audit:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/audit/agent-actions", + }, + { + "file": "activity.ts", + "metadata": { + "action": "audit:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/audit/agent-actions.csv", + }, + { + "file": "activity.ts", + "metadata": { + "action": "activity:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/activity", + }, + { + "file": "activity.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/activity", + }, + { + "file": "activity.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/runs", + }, + { + "file": "activity.ts", + "metadata": { + "action": "runtime:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "heartbeat_run", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/heartbeat-runs/{id}/{*path}", + }, + "method": "GET", + "path": "/api/heartbeat-runs/11111111-1111-4111-8111-111111111111/issues", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "GET", + "path": "/api/adapters", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "install", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "POST", + "path": "/api/adapters/install", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "GET", + "path": "/api/adapters/11111111-1111-4111-8111-111111111111", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/adapters/11111111-1111-4111-8111-111111111111", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/adapters/11111111-1111-4111-8111-111111111111/override", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/adapters/11111111-1111-4111-8111-111111111111", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "POST", + "path": "/api/adapters/11111111-1111-4111-8111-111111111111/reload", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "POST", + "path": "/api/adapters/11111111-1111-4111-8111-111111111111/reinstall", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "GET", + "path": "/api/adapters/11111111-1111-4111-8111-111111111111/config-schema", + }, + { + "file": "adapters.ts", + "metadata": { + "action": "adapters:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/adapters/{id}/{*path}", + }, + "method": "GET", + "path": "/api/adapters/11111111-1111-4111-8111-111111111111/ui-parser.js", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/adapters/11111111-1111-4111-8111-111111111111/models", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/adapters/11111111-1111-4111-8111-111111111111/models", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/adapters/11111111-1111-4111-8111-111111111111/model-profiles", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/adapters/11111111-1111-4111-8111-111111111111/model-profiles", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/adapters/11111111-1111-4111-8111-111111111111/detect-model", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/adapters/11111111-1111-4111-8111-111111111111/detect-model", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/adapters/11111111-1111-4111-8111-111111111111/test-environment", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/adapters/11111111-1111-4111-8111-111111111111/test-environment", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/skills", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/skills/sync", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/agents", + }, + { + "file": "agents.ts", + "metadata": { + "action": "instance:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "GET", + "path": "/api/instance/scheduler-heartbeats", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/org", + }, + { + "file": "agents.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/org.svg", + }, + { + "file": "agents.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/org.png", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/agent-configurations", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/agent-configurations", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/me", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/me/inbox-lite", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/me/inbox/mine", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/configuration", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/config-revisions", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/config-revisions/11111111-1111-4111-8111-111111111111", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:operate", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/config-revisions/11111111-1111-4111-8111-111111111111/rollback", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/runtime-state", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/task-sessions", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:operate", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/runtime-state/reset-session", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/agent-hires", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/agent-hires", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/agents", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/permissions", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/instructions-path", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/instructions-bundle", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/instructions-bundle", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/instructions-bundle/file", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "PUT", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/instructions-bundle/file", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/instructions-bundle/file", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/agents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:operate", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/pause", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:operate", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/resume", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:operate", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/clear-error", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/approve", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:operate", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/terminate", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/agents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/keys", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/keys", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/keys/11111111-1111-4111-8111-111111111111", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:operate", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/wakeup", + }, + { + "file": "agents.ts", + "metadata": { + "action": "agents:operate", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/heartbeat/invoke", + }, + { + "file": "agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/claude-login", + }, + { + "file": "agents.ts", + "metadata": { + "action": "runtime:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/heartbeat-runs", + }, + { + "file": "agents.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/live-runs", + }, + { + "file": "agents.ts", + "metadata": { + "action": "runtime:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "heartbeat_run", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/heartbeat-runs/{id}/{*path}", + }, + "method": "GET", + "path": "/api/heartbeat-runs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "agents.ts", + "metadata": { + "action": "runtime:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "heartbeat_run", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/heartbeat-runs/{id}/{*path}", + }, + "method": "POST", + "path": "/api/heartbeat-runs/11111111-1111-4111-8111-111111111111/cancel", + }, + { + "file": "agents.ts", + "metadata": { + "action": "runtime:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "heartbeat_run", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/heartbeat-runs/{id}/{*path}", + }, + "method": "POST", + "path": "/api/heartbeat-runs/11111111-1111-4111-8111-111111111111/watchdog-decisions", + }, + { + "file": "agents.ts", + "metadata": { + "action": "runtime:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "heartbeat_run", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/heartbeat-runs/{id}/{*path}", + }, + "method": "GET", + "path": "/api/heartbeat-runs/11111111-1111-4111-8111-111111111111/events", + }, + { + "file": "agents.ts", + "metadata": { + "action": "runtime:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "heartbeat_run", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/heartbeat-runs/{id}/{*path}", + }, + "method": "GET", + "path": "/api/heartbeat-runs/11111111-1111-4111-8111-111111111111/log", + }, + { + "file": "agents.ts", + "metadata": { + "action": "runtime:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "heartbeat_run", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/heartbeat-runs/{id}/{*path}", + }, + "method": "GET", + "path": "/api/heartbeat-runs/11111111-1111-4111-8111-111111111111/workspace-operations", + }, + { + "file": "agents.ts", + "metadata": { + "action": "workspaces:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "workspace_operation", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/workspace-operations/{id}/{*path}", + }, + "method": "GET", + "path": "/api/workspace-operations/11111111-1111-4111-8111-111111111111/log", + }, + { + "file": "agents.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/live-runs", + }, + { + "file": "agents.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/active-run", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/approvals", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "approval", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/approvals/{id}/{*path}", + }, + "method": "GET", + "path": "/api/approvals/11111111-1111-4111-8111-111111111111", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/approvals", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "approval", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/approvals/{id}/{*path}", + }, + "method": "GET", + "path": "/api/approvals/11111111-1111-4111-8111-111111111111/issues", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:decide", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "approval", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/approvals/{id}/{*path}", + }, + "method": "POST", + "path": "/api/approvals/11111111-1111-4111-8111-111111111111/approve", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:decide", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "approval", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/approvals/{id}/{*path}", + }, + "method": "POST", + "path": "/api/approvals/11111111-1111-4111-8111-111111111111/reject", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "approval", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/approvals/{id}/{*path}", + }, + "method": "POST", + "path": "/api/approvals/11111111-1111-4111-8111-111111111111/request-revision", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "approval", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/approvals/{id}/{*path}", + }, + "method": "POST", + "path": "/api/approvals/11111111-1111-4111-8111-111111111111/resubmit", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "approval", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/approvals/{id}/{*path}", + }, + "method": "GET", + "path": "/api/approvals/11111111-1111-4111-8111-111111111111/comments", + }, + { + "file": "approvals.ts", + "metadata": { + "action": "approvals:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "approval", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/approvals/{id}/{*path}", + }, + "method": "POST", + "path": "/api/approvals/11111111-1111-4111-8111-111111111111/comments", + }, + { + "file": "assets.ts", + "metadata": { + "action": "artifacts:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/assets/images", + }, + { + "file": "assets.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/logo", + }, + { + "file": "assets.ts", + "metadata": { + "action": "artifacts:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "asset", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/assets/{id}/{*path}", + }, + "method": "GET", + "path": "/api/assets/11111111-1111-4111-8111-111111111111/content", + }, + { + "file": "attention.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/attention", + }, + { + "file": "auth.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/auth/{*path}", + }, + "method": "GET", + "path": "/api/auth/get-session", + }, + { + "file": "auth.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/auth/{*path}", + }, + "method": "GET", + "path": "/api/auth/profile", + }, + { + "file": "auth.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/auth/{*path}", + }, + "method": "PATCH", + "path": "/api/auth/profile", + }, + { + "file": "board-chat.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/board/{*path}", + }, + "method": "POST", + "path": "/api/board/chat/stream", + }, + { + "file": "built-in-agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/built-in-agents", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/built-in-agents", + }, + { + "file": "built-in-agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/built-in-agents/11111111-1111-4111-8111-111111111111/status", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/built-in-agents/11111111-1111-4111-8111-111111111111/status", + }, + { + "file": "built-in-agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/built-in-agents/11111111-1111-4111-8111-111111111111/reconcile", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/built-in-agents/11111111-1111-4111-8111-111111111111/reconcile", + }, + { + "file": "built-in-agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/built-in-agents/11111111-1111-4111-8111-111111111111/provision", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/built-in-agents/11111111-1111-4111-8111-111111111111/provision", + }, + { + "file": "built-in-agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/built-in-agents/11111111-1111-4111-8111-111111111111/reset", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/built-in-agents/11111111-1111-4111-8111-111111111111/reset", + }, + { + "file": "built-in-agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/built-in-agents/11111111-1111-4111-8111-111111111111/routines/11111111-1111-4111-8111-111111111111/enable", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/built-in-agents/11111111-1111-4111-8111-111111111111/routines/11111111-1111-4111-8111-111111111111/enable", + }, + { + "file": "built-in-agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/built-in-agents/11111111-1111-4111-8111-111111111111/routines/11111111-1111-4111-8111-111111111111/disable", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/built-in-agents/11111111-1111-4111-8111-111111111111/routines/11111111-1111-4111-8111-111111111111/disable", + }, + { + "file": "built-in-agents.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/built-in-agents/11111111-1111-4111-8111-111111111111/routines/11111111-1111-4111-8111-111111111111/run", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/built-in-agents/11111111-1111-4111-8111-111111111111/routines/11111111-1111-4111-8111-111111111111/run", + }, + { + "file": "cases.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/cases", + }, + { + "file": "cases.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/cases", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations/11111111-1111-4111-8111-111111111111", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations/11111111-1111-4111-8111-111111111111/comments", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "PATCH", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations/11111111-1111-4111-8111-111111111111", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "PUT", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/lock", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/unlock", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/revisions/11111111-1111-4111-8111-111111111111/restore", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "DELETE", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/links", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/attachments", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/events", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/revisions", + }, + { + "file": "cases.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/cases", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111", + }, + { + "file": "cases.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "PATCH", + "path": "/api/cases/11111111-1111-4111-8111-111111111111", + }, + { + "file": "cloud.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cloud/{*path}", + }, + "method": "GET", + "path": "/api/cloud/stacks", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:read", + "classification": "company_collection", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies", + }, + "method": "GET", + "path": "/api/companies", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "stats", + "concealment": "forbidden", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/stats", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "issues", + "concealment": "forbidden", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/issues", + }, + { + "file": "companies.ts", + "metadata": { + "action": "artifacts:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/artifacts", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/timeline", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "forbidden", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111", + }, + { + "file": "companies.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/feedback-traces", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/feedback-traces", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:import_export", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/export", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:import_export", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/export/fidelity", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:import_export", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/import/{*path}", + }, + "method": "POST", + "path": "/api/companies/import/preview", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:import_export", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/import/{*path}", + }, + "method": "GET", + "path": "/api/companies/import/jobs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:import_export", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/exports/preview", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:import_export", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/exports", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/imports/preview", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/imports/apply", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:create", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies", + }, + "method": "POST", + "path": "/api/companies", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/branding", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/archive", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skill-policy.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/skill-policy", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skill-policy", + }, + { + "file": "company-skill-policy.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/companies/{id}/skill-policy", + }, + "method": "PUT", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skill-policy", + }, + { + "file": "company-skill-policy.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/companies/{id}/skill-policy", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skill-policy", + }, + { + "file": "company-skill-policy.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/skill-policy/evaluate", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skill-policy/evaluate", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "catalogs:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/skills/{*path}", + }, + "method": "GET", + "path": "/api/skills/catalog", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "catalogs:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/skills/{*path}", + }, + "method": "GET", + "path": "/api/skills/catalog/11111111-1111-4111-8111-111111111111/files", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "catalogs:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/skills/{*path}", + }, + "method": "GET", + "path": "/api/skills/catalog/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/categories", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/fork-precheck", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/versions", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/versions/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/test-inputs", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/test-inputs", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/test-inputs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/test-inputs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/skill-test-run-templates", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skill-test-run-templates", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/skill-test-run-templates", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skill-test-run-templates", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/companies/{id}/skill-test-run-templates/11111111-1111-4111-8111-111111111111", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skill-test-run-templates/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/companies/{id}/skill-test-run-templates/11111111-1111-4111-8111-111111111111", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skill-test-run-templates/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/test-runs", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/test-runs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/test-runs", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/test-runs/11111111-1111-4111-8111-111111111111/cancel", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/test-runs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/versions", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/star", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/star", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/fork", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/rename", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/comments", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/comments", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/comments/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/comments/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/update-status", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/files", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/files", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/files", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/import", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/install-catalog", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/browse-project", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/scan-projects", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/audit", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/install-update", + }, + { + "file": "company-skills.ts", + "metadata": { + "action": "skills:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/skills/11111111-1111-4111-8111-111111111111/reset", + }, + { + "file": "costs.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/cost-events", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/finance-events", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/summary", + }, + { + "file": "costs.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/cost-summary", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/by-agent", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/by-agent-model", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/by-provider", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/by-biller", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/finance-summary", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/finance-by-biller", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/finance-by-kind", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/finance-events", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/window-spend", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/quota-windows", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/budgets/overview", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/budgets/policies", + }, + { + "file": "costs.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/budget-incidents/11111111-1111-4111-8111-111111111111/resolve", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/costs/by-project", + }, + { + "file": "costs.ts", + "metadata": { + "action": "costs:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/budgets", + }, + { + "file": "costs.ts", + "metadata": { + "action": "agents:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "agent", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/agents/11111111-1111-4111-8111-111111111111/budgets", + }, + { + "file": "dashboard.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/dashboard", + }, + { + "file": "dashboard.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/recovery-observability", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-queue-seed-rules", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-queues", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-queues", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-queues/11111111-1111-4111-8111-111111111111", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-queues/11111111-1111-4111-8111-111111111111/items", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-queues/11111111-1111-4111-8111-111111111111/items", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-queues/11111111-1111-4111-8111-111111111111/items/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-triage/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PUT", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PUT", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-triage/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-retention/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-retention/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111/archive", + }, + { + "file": "decision-queues.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-retention/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111/revive", + }, + { + "file": "decision-training.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-training", + }, + { + "file": "decision-training.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-training/preview", + }, + { + "file": "decision-training.ts", + "metadata": { + "action": "decisions:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-training", + }, + { + "file": "decision-training.ts", + "metadata": { + "action": "decisions:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-training/export.jsonl", + }, + { + "file": "decision-training.ts", + "metadata": { + "action": "decisions:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "decision_training", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/decision-training/{id}/{*path}", + }, + "method": "GET", + "path": "/api/decision-training/11111111-1111-4111-8111-111111111111", + }, + { + "file": "decision-training.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "decision_training", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/decision-training/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/decision-training/11111111-1111-4111-8111-111111111111", + }, + { + "file": "decision-training.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "decision_training", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/decision-training/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/decision-training/11111111-1111-4111-8111-111111111111", + }, + { + "file": "decisions.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-archive-proposals", + }, + { + "file": "decisions.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decisions", + }, + { + "file": "decisions.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decision-bundles", + }, + { + "file": "decisions.ts", + "metadata": { + "action": "decisions:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decisions", + }, + { + "file": "decisions.ts", + "metadata": { + "action": "decisions:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/decisions/stats", + }, + { + "file": "decisions.ts", + "metadata": { + "action": "decisions:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "decision", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/decisions/{id}/{*path}", + }, + "method": "GET", + "path": "/api/decisions/11111111-1111-4111-8111-111111111111", + }, + { + "file": "decisions.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "decision", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/decisions/{id}/{*path}", + }, + "method": "POST", + "path": "/api/decisions/11111111-1111-4111-8111-111111111111/decide", + }, + { + "file": "decisions.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "decision", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/decisions/{id}/{*path}", + }, + "method": "POST", + "path": "/api/decisions/11111111-1111-4111-8111-111111111111/dismiss", + }, + { + "file": "decisions.ts", + "metadata": { + "action": "decisions:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "decision", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/decisions/{id}/{*path}", + }, + "method": "POST", + "path": "/api/decisions/11111111-1111-4111-8111-111111111111/cancel", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/environments", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "GET", + "path": "/api/environments/11111111-1111-4111-8111-111111111111/delete-blast-radius", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/environments/capabilities", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "GET", + "path": "/api/environments/11111111-1111-4111-8111-111111111111/custom-image-template", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "POST", + "path": "/api/environments/11111111-1111-4111-8111-111111111111/custom-image-setup-sessions", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environment-custom-image-setup-sessions/{id}/{*path}", + }, + "method": "GET", + "path": "/api/environment-custom-image-setup-sessions/11111111-1111-4111-8111-111111111111", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environment-custom-image-setup-sessions/{id}/{*path}", + }, + "method": "POST", + "path": "/api/environment-custom-image-setup-sessions/11111111-1111-4111-8111-111111111111/terminal-session-token", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environment-custom-image-setup-sessions/{id}/{*path}", + }, + "method": "POST", + "path": "/api/environment-custom-image-setup-sessions/11111111-1111-4111-8111-111111111111/finish", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environment-custom-image-setup-sessions/{id}/{*path}", + }, + "method": "POST", + "path": "/api/environment-custom-image-setup-sessions/11111111-1111-4111-8111-111111111111/cancel", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "POST", + "path": "/api/environments/11111111-1111-4111-8111-111111111111/custom-image-template/rollback", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/environments/11111111-1111-4111-8111-111111111111/custom-image-template", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/environments", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "GET", + "path": "/api/environments/11111111-1111-4111-8111-111111111111", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "GET", + "path": "/api/environments/11111111-1111-4111-8111-111111111111/secret-refs", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "GET", + "path": "/api/environments/11111111-1111-4111-8111-111111111111/leases", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environment-leases/{id}/{*path}", + }, + "method": "GET", + "path": "/api/environment-leases/11111111-1111-4111-8111-111111111111", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/environments/11111111-1111-4111-8111-111111111111", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/environments/11111111-1111-4111-8111-111111111111", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "downstream_company", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/environments/{id}/{*path}", + }, + "method": "POST", + "path": "/api/environments/11111111-1111-4111-8111-111111111111/probe", + }, + { + "file": "environments.ts", + "metadata": { + "action": "environments:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/environments/probe-config", + }, + { + "file": "execution-workspaces.ts", + "metadata": { + "action": "workspaces:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/execution-workspaces", + }, + { + "file": "execution-workspaces.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/workspace-overview", + }, + { + "file": "execution-workspaces.ts", + "metadata": { + "action": "workspaces:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "execution_workspace", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/execution-workspaces/{id}/{*path}", + }, + "method": "GET", + "path": "/api/execution-workspaces/11111111-1111-4111-8111-111111111111", + }, + { + "file": "execution-workspaces.ts", + "metadata": { + "action": "workspaces:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "execution_workspace", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/execution-workspaces/{id}/{*path}", + }, + "method": "GET", + "path": "/api/execution-workspaces/11111111-1111-4111-8111-111111111111/close-readiness", + }, + { + "file": "execution-workspaces.ts", + "metadata": { + "action": "workspaces:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "execution_workspace", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/execution-workspaces/{id}/{*path}", + }, + "method": "GET", + "path": "/api/execution-workspaces/11111111-1111-4111-8111-111111111111/workspace-operations", + }, + { + "file": "execution-workspaces.ts", + "metadata": { + "action": "workspaces:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "execution_workspace", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/execution-workspaces/{id}/{*path}", + }, + "method": "POST", + "path": "/api/execution-workspaces/11111111-1111-4111-8111-111111111111/runtime-services/11111111-1111-4111-8111-111111111111", + }, + { + "file": "execution-workspaces.ts", + "metadata": { + "action": "workspaces:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "execution_workspace", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/execution-workspaces/{id}/{*path}", + }, + "method": "POST", + "path": "/api/execution-workspaces/11111111-1111-4111-8111-111111111111/runtime-commands/11111111-1111-4111-8111-111111111111", + }, + { + "file": "execution-workspaces.ts", + "metadata": { + "action": "workspaces:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "execution_workspace", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/execution-workspaces/{id}/{*path}", + }, + "method": "POST", + "path": "/api/execution-workspaces/11111111-1111-4111-8111-111111111111/reconcile-branch", + }, + { + "file": "execution-workspaces.ts", + "metadata": { + "action": "workspaces:manage", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "execution_workspace", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/execution-workspaces/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/execution-workspaces/11111111-1111-4111-8111-111111111111", + }, + { + "file": "file-resources.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/file-resources/list", + }, + { + "file": "file-resources.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/file-resources/resolve", + }, + { + "file": "file-resources.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/file-resources/content", + }, + { + "file": "folders.ts", + "metadata": { + "action": "settings:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/folders", + }, + { + "file": "folders.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/folders", + }, + { + "file": "folders.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/folders/ensure-my", + }, + { + "file": "folders.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/folders/11111111-1111-4111-8111-111111111111", + }, + { + "file": "folders.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/folders/items/move", + }, + { + "file": "folders.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/folders/11111111-1111-4111-8111-111111111111/move", + }, + { + "file": "folders.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/folders/11111111-1111-4111-8111-111111111111", + }, + { + "file": "goals.ts", + "metadata": { + "action": "goals:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/goals", + }, + { + "file": "goals.ts", + "metadata": { + "action": "goals:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "goal", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/goals/{id}/{*path}", + }, + "method": "GET", + "path": "/api/goals/11111111-1111-4111-8111-111111111111", + }, + { + "file": "goals.ts", + "metadata": { + "action": "goals:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/goals", + }, + { + "file": "goals.ts", + "metadata": { + "action": "goals:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "goal", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/goals/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/goals/11111111-1111-4111-8111-111111111111", + }, + { + "file": "goals.ts", + "metadata": { + "action": "goals:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "goal", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/goals/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/goals/11111111-1111-4111-8111-111111111111", + }, + { + "file": "health.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/health/{*path}", + }, + "method": "POST", + "path": "/api/health/dev-server/restart", + }, + { + "file": "health.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/health/{*path}", + }, + "method": "GET", + "path": "/api/health", + }, + { + "file": "inbox-agent-policy.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/users/me/inbox-agent-policy", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/users/me/inbox-agent-policy", + }, + { + "file": "inbox-agent-policy.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/companies/{id}/users/me/inbox-agent-policy", + }, + "method": "PUT", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/users/me/inbox-agent-policy", + }, + { + "file": "inbox-agent-policy.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/users/11111111-1111-4111-8111-111111111111/inbox-agent-policy", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/users/11111111-1111-4111-8111-111111111111/inbox-agent-policy", + }, + { + "file": "inbox-agent-policy.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/companies/{id}/users/11111111-1111-4111-8111-111111111111/inbox-agent-policy", + }, + "method": "PUT", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/users/11111111-1111-4111-8111-111111111111/inbox-agent-policy", + }, + { + "file": "inbox-dismissals.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/inbox-dismissals", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/inbox-dismissals", + }, + { + "file": "inbox-dismissals.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/inbox-dismissals", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/inbox-dismissals", + }, + { + "file": "inbox-dismissals.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/companies/{id}/inbox-dismissals/11111111-1111-4111-8111-111111111111", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/inbox-dismissals/11111111-1111-4111-8111-111111111111", + }, + { + "file": "instance-database-backups.ts", + "metadata": { + "action": "instance:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "POST", + "path": "/api/instance/database-backups", + }, + { + "file": "instance-settings.ts", + "metadata": { + "action": "instance:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "GET", + "path": "/api/instance/settings", + }, + { + "file": "instance-settings.ts", + "metadata": { + "action": "instance:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "PATCH", + "path": "/api/instance/settings", + }, + { + "file": "instance-settings.ts", + "metadata": { + "action": "instance:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "GET", + "path": "/api/instance/settings/general", + }, + { + "file": "instance-settings.ts", + "metadata": { + "action": "instance:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "PATCH", + "path": "/api/instance/settings/general", + }, + { + "file": "instance-settings.ts", + "metadata": { + "action": "instance:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "GET", + "path": "/api/instance/settings/experimental", + }, + { + "file": "instance-settings.ts", + "metadata": { + "action": "instance:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "PATCH", + "path": "/api/instance/settings/experimental", + }, + { + "file": "instance-settings.ts", + "metadata": { + "action": "instance:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "POST", + "path": "/api/instance/settings/experimental/issue-graph-liveness-auto-recovery/preview", + }, + { + "file": "instance-settings.ts", + "metadata": { + "action": "instance:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/instance/{*path}", + }, + "method": "POST", + "path": "/api/instance/settings/experimental/issue-graph-liveness-auto-recovery/run", + }, + { + "file": "issue-tree-control.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/tree-control/preview", + }, + { + "file": "issue-tree-control.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/tree-holds", + }, + { + "file": "issue-tree-control.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/tree-control/state", + }, + { + "file": "issue-tree-control.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/tree-holds", + }, + { + "file": "issue-tree-control.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/tree-holds/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issue-tree-control.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/tree-holds/11111111-1111-4111-8111-111111111111/release", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/issues?companyId={companyId}", + }, + "method": "GET", + "path": "/api/issues", + }, + { + "file": "issues.ts", + "metadata": { + "action": "search:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/search/extract", + }, + { + "file": "issues.ts", + "metadata": { + "action": "search:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/search", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/issues", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/issues/count", + }, + { + "file": "issues.ts", + "metadata": { + "action": "settings:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/labels", + }, + { + "file": "issues.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/labels", + }, + { + "file": "issues.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "label", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/labels/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/labels/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/heartbeat-context", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/diagnostics/blockers", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/diagnostics/wakes", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/diagnostics/subtree", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/watchdog", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "PUT", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "PUT", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/watchdog", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/watchdog", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/recovery-actions", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/recovery-actions/resolve", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/work-products", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/external-objects", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/external-object-summary", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/issues/external-object-summaries", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/external-objects/refresh", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations/11111111-1111-4111-8111-111111111111/comments", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/annotations/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "PUT", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "PUT", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/lock", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/unlock", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/revisions", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/revisions/11111111-1111-4111-8111-111111111111/restore", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/work-products", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/low-trust/promotions", + }, + { + "file": "issues.ts", + "metadata": { + "action": "artifacts:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "work_product", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/work-products/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/work-products/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "artifacts:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "work_product", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/work-products/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/work-products/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/read", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/read", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/inbox-archive", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/inbox-archive", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/approvals", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/approvals", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/approvals/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/issues", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/children", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/accepted-plan-decompositions", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/accepted-plan-decompositions", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/monitor/check-now", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/scheduled-retry/retry-now", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/stalled-review-decision", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/issues/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/issues/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/checkout", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/release", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:control", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/admin/force-release", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/comments", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/interactions", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/interactions", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/interactions/11111111-1111-4111-8111-111111111111/accept", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/interactions/11111111-1111-4111-8111-111111111111/reject", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/interactions/11111111-1111-4111-8111-111111111111/respond", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/interactions/11111111-1111-4111-8111-111111111111/verdicts", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/interactions/11111111-1111-4111-8111-111111111111/withdraw", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/interactions/11111111-1111-4111-8111-111111111111/cancel", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/comments/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/comments/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/feedback-votes", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/feedback-traces", + }, + { + "file": "issues.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/feedback-traces/{*path}", + }, + "method": "GET", + "path": "/api/feedback-traces/11111111-1111-4111-8111-111111111111", + }, + { + "file": "issues.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/feedback-traces/{*path}", + }, + "method": "GET", + "path": "/api/feedback-traces/11111111-1111-4111-8111-111111111111/bundle", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/comments", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "POST", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/feedback-votes", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "issue", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/issues/{id}/{*path}", + }, + "method": "GET", + "path": "/api/issues/11111111-1111-4111-8111-111111111111/attachments", + }, + { + "file": "issues.ts", + "metadata": { + "action": "issues:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/issues/11111111-1111-4111-8111-111111111111/attachments", + }, + { + "file": "issues.ts", + "metadata": { + "action": "artifacts:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "attachment", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/attachments/{id}/{*path}", + }, + "method": "GET", + "path": "/api/attachments/11111111-1111-4111-8111-111111111111/content", + }, + { + "file": "issues.ts", + "metadata": { + "action": "artifacts:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "attachment", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/attachments/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/attachments/11111111-1111-4111-8111-111111111111", + }, + { + "file": "llms.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/llms/{*path}", + }, + "method": "GET", + "path": "/api/llms/agent-configuration.txt", + }, + { + "file": "llms.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/llms/{*path}", + }, + "method": "GET", + "path": "/api/llms/agent-icons.txt", + }, + { + "file": "llms.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/llms/{*path}", + }, + "method": "GET", + "path": "/api/llms/agent-configuration/11111111-1111-4111-8111-111111111111.txt", + }, + { + "file": "openapi.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/openapi.json/{*path}", + }, + "method": "GET", + "path": "/api/openapi.json", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/pipelines", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/pipelines-attention", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/case-events", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/pipelines", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/review-cases", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/review-cases/bulk", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "GET", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "GET", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/health", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "GET", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/intake-form", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "PATCH", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "POST", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/stages", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "PATCH", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/stages/11111111-1111-4111-8111-111111111111", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "PATCH", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/stages/11111111-1111-4111-8111-111111111111/automation-env", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "DELETE", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/stages/11111111-1111-4111-8111-111111111111", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "PUT", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/transitions", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "GET", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "PUT", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "GET", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/revisions", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "POST", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/documents/11111111-1111-4111-8111-111111111111/revisions/11111111-1111-4111-8111-111111111111/restore", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "POST", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/cases", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "POST", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/cases/batch", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/breakdown", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/pipelines/{*path}", + }, + "method": "GET", + "path": "/api/pipelines/11111111-1111-4111-8111-111111111111/cases", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/children", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/claim", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/release", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/transition", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/suggest-transition", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/resolve-suggestion", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/acknowledge-drift", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/review", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "PUT", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/blockers", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/open-conversation", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/issue-links", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/outputs", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/issue-links", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "DELETE", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/issue-links/11111111-1111-4111-8111-111111111111", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/children/tree", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/rollup", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/context-pack", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "GET", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/automation/retry-plan", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/automation/retry", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/automations/11111111-1111-4111-8111-111111111111/retry", + }, + { + "file": "pipelines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/cases/{*path}", + }, + "method": "POST", + "path": "/api/cases/11111111-1111-4111-8111-111111111111/automation/current-stage/rerun", + }, + { + "file": "plugin-ui-static.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/_plugins/{*path}", + }, + "method": "GET", + "path": "/api/_plugins/11111111-1111-4111-8111-111111111111/ui/*filePath", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "examples", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/examples", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "ui-contributions", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/ui-contributions", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "tools", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/tools", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "POST", + "path": "/api/plugins/tools/execute", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "install", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "POST", + "path": "/api/plugins/install", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/bridge/data", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/bridge/action", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/data/11111111-1111-4111-8111-111111111111", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/actions/11111111-1111-4111-8111-111111111111", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/bridge/stream/11111111-1111-4111-8111-111111111111", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/enable", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/disable", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/health", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/logs", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/upgrade", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/config", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/config", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/config/test", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/jobs", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/jobs/11111111-1111-4111-8111-111111111111/runs", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:manage", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/jobs/11111111-1111-4111-8111-111111111111/trigger", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/webhooks/11111111-1111-4111-8111-111111111111", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/companies/11111111-1111-4111-8111-111111111111/local-folders", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/companies/11111111-1111-4111-8111-111111111111/local-folders/11111111-1111-4111-8111-111111111111/status", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "POST", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/companies/11111111-1111-4111-8111-111111111111/local-folders/11111111-1111-4111-8111-111111111111/validate", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/plugins/{*path}", + }, + "method": "PUT", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/companies/11111111-1111-4111-8111-111111111111/local-folders/11111111-1111-4111-8111-111111111111", + }, + { + "file": "plugins.ts", + "metadata": { + "action": "plugins:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/plugins/{id}/{*path}", + }, + "method": "GET", + "path": "/api/plugins/11111111-1111-4111-8111-111111111111/dashboard", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/projects", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "GET", + "path": "/api/projects/11111111-1111-4111-8111-111111111111", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "GET", + "path": "/api/projects/11111111-1111-4111-8111-111111111111/external-object-summary", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/projects", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/projects/11111111-1111-4111-8111-111111111111", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "GET", + "path": "/api/projects/11111111-1111-4111-8111-111111111111/workspaces", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "POST", + "path": "/api/projects/11111111-1111-4111-8111-111111111111/workspaces", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/projects/11111111-1111-4111-8111-111111111111/workspaces/11111111-1111-4111-8111-111111111111", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "POST", + "path": "/api/projects/11111111-1111-4111-8111-111111111111/workspaces/11111111-1111-4111-8111-111111111111/runtime-services/11111111-1111-4111-8111-111111111111", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "POST", + "path": "/api/projects/11111111-1111-4111-8111-111111111111/workspaces/11111111-1111-4111-8111-111111111111/runtime-commands/11111111-1111-4111-8111-111111111111", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/projects/11111111-1111-4111-8111-111111111111/workspaces/11111111-1111-4111-8111-111111111111", + }, + { + "file": "projects.ts", + "metadata": { + "action": "projects:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "project", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/projects/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/projects/11111111-1111-4111-8111-111111111111", + }, + { + "file": "resource-memberships.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/resource-memberships/me", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/resource-memberships/me", + }, + { + "file": "resource-memberships.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/companies/{id}/resource-memberships/me/projects/11111111-1111-4111-8111-111111111111", + }, + "method": "PUT", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/resource-memberships/me/projects/11111111-1111-4111-8111-111111111111", + }, + { + "file": "resource-memberships.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/companies/{id}/resource-memberships/me/agents/11111111-1111-4111-8111-111111111111", + }, + "method": "PUT", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/resource-memberships/me/agents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "resource-memberships.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/companies/{id}/resource-memberships/me/documents/11111111-1111-4111-8111-111111111111", + }, + "method": "PUT", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/resource-memberships/me/documents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/routines", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/routines", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "GET", + "path": "/api/routines/11111111-1111-4111-8111-111111111111", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "GET", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/revisions", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "GET", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/description/annotations", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "GET", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/description/annotations/11111111-1111-4111-8111-111111111111", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "POST", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/description/annotations", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "POST", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/description/annotations/11111111-1111-4111-8111-111111111111/comments", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/description/annotations/11111111-1111-4111-8111-111111111111", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/routines/11111111-1111-4111-8111-111111111111", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "POST", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/revisions/11111111-1111-4111-8111-111111111111/restore", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "GET", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/runs", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "POST", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/triggers", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "routine_trigger", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routine-triggers/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/routine-triggers/11111111-1111-4111-8111-111111111111", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "routine_trigger", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routine-triggers/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/routine-triggers/11111111-1111-4111-8111-111111111111", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "routine_trigger", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routine-triggers/{id}/{*path}", + }, + "method": "POST", + "path": "/api/routine-triggers/11111111-1111-4111-8111-111111111111/rotate-secret", + }, + { + "file": "routines.ts", + "metadata": { + "action": "routines:run", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "routine", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/routines/{id}/{*path}", + }, + "method": "POST", + "path": "/api/routines/11111111-1111-4111-8111-111111111111/run", + }, + { + "file": "routines.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/routine-triggers/public/{id}/fire", + }, + "method": "POST", + "path": "/api/routine-triggers/public/11111111-1111-4111-8111-111111111111/fire", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/me/secret-proposals", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/me/secret-proposals", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/agents/me/secret-proposals/11111111-1111-4111-8111-111111111111", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secret-proposals", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secret-proposals/11111111-1111-4111-8111-111111111111/approve", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secret-proposals/11111111-1111-4111-8111-111111111111/reject", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "GET", + "path": "/api/agents/me/secrets", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/me/secrets/11111111-1111-4111-8111-111111111111/value", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secret-providers", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secret-providers/health", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:read_metadata", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secret-provider-configs", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secret-provider-configs/discovery/preview", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secret-provider-configs", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:read_metadata", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "secret_provider", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secret-provider-configs/{id}/{*path}", + }, + "method": "GET", + "path": "/api/secret-provider-configs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "secret_provider", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secret-provider-configs/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/secret-provider-configs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "secret_provider", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secret-provider-configs/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/secret-provider-configs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "secret_provider", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secret-provider-configs/{id}/{*path}", + }, + "method": "POST", + "path": "/api/secret-provider-configs/11111111-1111-4111-8111-111111111111/default", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "secret_provider", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secret-provider-configs/{id}/{*path}", + }, + "method": "POST", + "path": "/api/secret-provider-configs/11111111-1111-4111-8111-111111111111/health", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:read_metadata", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secrets", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/user-secret-definitions", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/user-secret-definitions", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/user-secret-definitions/11111111-1111-4111-8111-111111111111", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/user-secret-definitions/11111111-1111-4111-8111-111111111111", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/user-secret-definitions/11111111-1111-4111-8111-111111111111/coverage", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/me/user-secrets", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/me/user-secrets", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/me/user-secrets", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/me/user-secrets", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PATCH", + "resolver": "none", + "routePattern": "/api/companies/{id}/me/user-secrets/11111111-1111-4111-8111-111111111111", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/me/user-secrets/11111111-1111-4111-8111-111111111111", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/me/user-secrets/11111111-1111-4111-8111-111111111111/rotate", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/me/user-secrets/11111111-1111-4111-8111-111111111111/rotate", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "DELETE", + "resolver": "none", + "routePattern": "/api/companies/{id}/me/user-secrets/11111111-1111-4111-8111-111111111111", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/me/user-secrets/11111111-1111-4111-8111-111111111111", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secrets", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secrets/remote-import/preview", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/secrets/remote-import", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "secret", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secrets/{id}/{*path}", + }, + "method": "POST", + "path": "/api/secrets/11111111-1111-4111-8111-111111111111/rotate", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "secret", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secrets/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/secrets/11111111-1111-4111-8111-111111111111", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:read_metadata", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "secret", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secrets/{id}/{*path}", + }, + "method": "GET", + "path": "/api/secrets/11111111-1111-4111-8111-111111111111/usage", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:read_metadata", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "secret", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secrets/{id}/{*path}", + }, + "method": "GET", + "path": "/api/secrets/11111111-1111-4111-8111-111111111111/access-events", + }, + { + "file": "secrets.ts", + "metadata": { + "action": "secrets:manage", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "secret", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/secrets/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/secrets/11111111-1111-4111-8111-111111111111", + }, + { + "file": "sidebar-badges.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/sidebar-badges", + }, + { + "file": "sidebar-preferences.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/sidebar-preferences/{*path}", + }, + "method": "GET", + "path": "/api/sidebar-preferences/me", + }, + { + "file": "sidebar-preferences.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/sidebar-preferences/{*path}", + }, + "method": "PUT", + "path": "/api/sidebar-preferences/me", + }, + { + "file": "sidebar-preferences.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/sidebar-preferences/me", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/sidebar-preferences/me", + }, + { + "file": "sidebar-preferences.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "PUT", + "resolver": "none", + "routePattern": "/api/companies/{id}/sidebar-preferences/me", + }, + "method": "PUT", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/sidebar-preferences/me", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/oauth/authorize", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/oauth/authorize", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/oauth/token", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/oauth/userinfo", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/oauth/revoke", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/services", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/services/start", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/services/stop", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/install-fixtures", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/runs", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/runs", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/runs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/runs/11111111-1111-4111-8111-111111111111", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/runs/11111111-1111-4111-8111-111111111111/steps", + }, + { + "file": "smoke-lab.ts", + "metadata": { + "action": "companies:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/smoke-lab/reset", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/status-cards", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/status-cards", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "GET", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "GET", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111/updates", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "GET", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111/summary-revisions", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "POST", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111/recompile", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "POST", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111/refresh", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "GET", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111/dry-run", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "concealment": "not_found", + "method": "PUT", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "PUT", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111/query", + }, + { + "file": "status-cards.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "concealment": "not_found", + "method": "PUT", + "resolver": "status_card", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/status-cards/{id}/{*path}", + }, + "method": "PUT", + "path": "/api/status-cards/11111111-1111-4111-8111-111111111111/summary", + }, + { + "file": "summary-slots.ts", + "metadata": { + "action": "settings:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/summary-slots/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111", + }, + { + "file": "summary-slots.ts", + "metadata": { + "action": "settings:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/summary-slots/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111/revisions", + }, + { + "file": "summary-slots.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/summary-slots/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111/generate", + }, + { + "file": "summary-slots.ts", + "metadata": { + "action": "settings:write", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PUT", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PUT", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/summary-slots/11111111-1111-4111-8111-111111111111/11111111-1111-4111-8111-111111111111", + }, + { + "file": "teams-catalog.ts", + "metadata": { + "action": "catalogs:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/teams/{*path}", + }, + "method": "GET", + "path": "/api/teams/catalog", + }, + { + "file": "teams-catalog.ts", + "metadata": { + "action": "catalogs:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/teams/{*path}", + }, + "method": "GET", + "path": "/api/teams/catalog/11111111-1111-4111-8111-111111111111/files", + }, + { + "file": "teams-catalog.ts", + "metadata": { + "action": "catalogs:read", + "classification": "instance_global", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/teams/{*path}", + }, + "method": "GET", + "path": "/api/teams/catalog/11111111-1111-4111-8111-111111111111", + }, + { + "file": "teams-catalog.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/teams/catalog/installed", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/teams/catalog/installed", + }, + { + "file": "teams-catalog.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/teams/catalog/11111111-1111-4111-8111-111111111111/preview", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/teams/catalog/11111111-1111-4111-8111-111111111111/preview", + }, + { + "file": "teams-catalog.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/{id}/teams/catalog/11111111-1111-4111-8111-111111111111/install", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/teams/catalog/11111111-1111-4111-8111-111111111111/install", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/me/connections/11111111-1111-4111-8111-111111111111/start-authorization", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/agents/{id}/{*path}", + }, + "method": "POST", + "path": "/api/agents/me/connections/11111111-1111-4111-8111-111111111111/token", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/gallery", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/apps/connect", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/connections/11111111-1111-4111-8111-111111111111/start-authorization", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/tools/oauth/{*path}", + }, + "method": "POST", + "path": "/api/tools/oauth/11111111-1111-4111-8111-111111111111/start", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/tools/oauth/{*path}", + }, + "method": "GET", + "path": "/api/tools/oauth/callback", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/apps/11111111-1111-4111-8111-111111111111/finish", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/examples", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/apps/attention", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/action-requests", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/examples/11111111-1111-4111-8111-111111111111/install", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/examples/11111111-1111-4111-8111-111111111111/smoke", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/applications", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/applications", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "tool_application", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-applications/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/tool-applications/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "tool_application", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-applications/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/tool-applications/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/connections", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/connections", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "GET", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "GET", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/grants", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "POST", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/grants/installations", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/grants/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "GET", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/usage", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "GET", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/installs", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "PUT", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "PUT", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/installs", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "GET", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/test-agents", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "POST", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/test-calls", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "GET", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/test-calls/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "POST", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/health-check", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "POST", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/reconnect", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "POST", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/catalog/refresh", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "GET", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/catalog", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_connection", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-connections/{id}/{*path}", + }, + "method": "GET", + "path": "/api/tool-connections/11111111-1111-4111-8111-111111111111/activity", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/profiles", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_profile", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-profiles/{id}/{*path}", + }, + "method": "GET", + "path": "/api/tool-profiles/11111111-1111-4111-8111-111111111111/new-tools", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/profiles", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/profiles/effective/agents/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "tool_profile", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-profiles/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/tool-profiles/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_profile", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-profiles/{id}/{*path}", + }, + "method": "POST", + "path": "/api/tool-profiles/11111111-1111-4111-8111-111111111111/duplicate", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "tool_profile", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-profiles/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/tool-profiles/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_profile", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-profiles/{id}/{*path}", + }, + "method": "POST", + "path": "/api/tool-profiles/11111111-1111-4111-8111-111111111111/new-tools/review", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_profile", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-profiles/{id}/{*path}", + }, + "method": "POST", + "path": "/api/tool-profiles/11111111-1111-4111-8111-111111111111/entries", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "tool_profile_entry", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-profile-entries/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/tool-profile-entries/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "DELETE", + "resolver": "tool_profile_entry", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-profile-entries/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/tool-profile-entries/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/profiles/11111111-1111-4111-8111-111111111111/bind", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/profiles/11111111-1111-4111-8111-111111111111/unbind", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/runtime-slots", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/runtime-slots/11111111-1111-4111-8111-111111111111/stop", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/runtime-slots/11111111-1111-4111-8111-111111111111/restart", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/runtime-health", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/runs/11111111-1111-4111-8111-111111111111/decisions", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/trust-rules", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/policies", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/policies/reorder", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/policies", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/policies/11111111-1111-4111-8111-111111111111/duplicate", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "PATCH", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "PATCH", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/policies/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "DELETE", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "DELETE", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/policies/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/action-requests/11111111-1111-4111-8111-111111111111/trust-rule", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/trust-rules/11111111-1111-4111-8111-111111111111/revoke", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/stdio-templates", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/stdio-templates", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/stdio-templates/11111111-1111-4111-8111-111111111111/disable", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/mcp/import-json", + }, + { + "file": "tool-access.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/policy/test", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/mcp/{*path}", + }, + "method": "GET", + "path": "/mcp/gateways/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/mcp/{*path}", + }, + "method": "POST", + "path": "/mcp/gateways/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "GET", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/gateways", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "companyId": "11111111-1111-4111-8111-111111111111", + "concealment": "not_found", + "method": "POST", + "resolver": "company", + "routePattern": "/api/companies/{id}/{*path}", + }, + "method": "POST", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/tools/gateways", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "PATCH", + "resolver": "tool_gateway", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "PATCH", + "path": "/api/tool-gateway/gateways/11111111-1111-4111-8111-111111111111", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_gateway", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/gateways/11111111-1111-4111-8111-111111111111/tokens", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_gateway", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/gateway-tokens/11111111-1111-4111-8111-111111111111/revoke", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_gateway", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "GET", + "path": "/api/tool-gateway/gateways/11111111-1111-4111-8111-111111111111/mcp", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_gateway", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/gateways/11111111-1111-4111-8111-111111111111/mcp", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/sessions", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/sessions/11111111-1111-4111-8111-111111111111/revoke", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "GET", + "path": "/api/tool-gateway/tools", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/tools/call", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_gateway", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/action-requests/11111111-1111-4111-8111-111111111111/approve", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_gateway", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/action-requests/11111111-1111-4111-8111-111111111111/decline", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_gateway", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "GET", + "path": "/api/tool-gateway/runtime-slots", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_gateway", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/runtime-slots/11111111-1111-4111-8111-111111111111/stop", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:manage", + "classification": "company", + "concealment": "not_found", + "method": "POST", + "resolver": "tool_gateway", + "resourceId": "11111111-1111-4111-8111-111111111111", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "POST", + "path": "/api/tool-gateway/runtime-slots/11111111-1111-4111-8111-111111111111/restart", + }, + { + "file": "tool-gateway.ts", + "metadata": { + "action": "tools:read", + "classification": "company", + "concealment": "not_found", + "method": "GET", + "resolver": "tool_gateway", + "routePattern": "/api/tool-gateway/{*path}", + }, + "method": "GET", + "path": "/api/tool-gateway/audit", + }, + { + "file": "user-profiles.ts", + "metadata": { + "action": "deny", + "classification": "board_key_denied", + "concealment": "forbidden", + "method": "GET", + "resolver": "none", + "routePattern": "/api/companies/{id}/users/11111111-1111-4111-8111-111111111111/profile", + }, + "method": "GET", + "path": "/api/companies/11111111-1111-4111-8111-111111111111/users/11111111-1111-4111-8111-111111111111/profile", + }, + { + "file": "companies.ts", + "metadata": { + "action": "companies:import_export", + "classification": "instance_global", + "concealment": "forbidden", + "method": "POST", + "resolver": "none", + "routePattern": "/api/companies/import/{*path}", + }, + "method": "POST", + "path": "/api/companies/import", + }, +] +`; diff --git a/server/src/security/board-key-authorization.test.ts b/server/src/security/board-key-authorization.test.ts new file mode 100644 index 0000000000..8becf6a49f --- /dev/null +++ b/server/src/security/board-key-authorization.test.ts @@ -0,0 +1,236 @@ +import { randomUUID } from "node:crypto"; +import type { Request } from "express"; +import { describe, expect, it, vi } from "vitest"; +import { + authUsers, + boardApiKeyAuthorizationEvents, + boardApiKeys, + companyMemberships, + instanceUserRoles, +} from "@paperclipai/db"; +import { BOARD_API_KEY_SCOPE_PRESETS } from "@paperclipai/shared"; +import { HttpError } from "../errors.js"; +import { boardAuthService, hashBearerToken } from "../services/board-auth.js"; +import { authorizeBoardKey, lookupBoardKeyRoute } from "./board-key-route-registry.js"; + +const TOKEN = "pcp_board_authorization_matrix"; + +function scope(companyId: string) { + return { + version: 1 as const, + kind: "scoped" as const, + companyIds: [companyId], + permissions: [...BOARD_API_KEY_SCOPE_PRESETS.full_instance_admin.permissions], + instanceCapabilities: ["instance_admin"] as const, + }; +} + +function createLiveAuthorityDb() { + const companyId = randomUUID(); + const ownerId = randomUUID(); + const keyId = randomUUID(); + const state = { + ownerExists: true, + membershipActive: true, + membershipRole: "owner", + instanceAdmin: true, + revoked: false, + }; + const key = { + id: keyId, + userId: ownerId, + name: "matrix", + keyHash: hashBearerToken(TOKEN), + tokenPrefix: "pcp_board_author", + scopeConfig: scope(companyId), + legacyUnrestricted: false, + createdAt: new Date(), + lastUsedAt: null, + expiresAt: null, + revokedAt: null, + }; + const audit: Array> = []; + + const db = { + select: vi.fn(() => ({ + from(table: unknown) { + const rows = table === boardApiKeys + ? (state.revoked ? [{ ...key, revokedAt: new Date() }] : [key]) + : table === authUsers + ? (state.ownerExists ? [{ id: ownerId, name: "Owner", email: "owner@example.com" }] : []) + : table === companyMemberships + ? (state.membershipActive ? [{ companyId, membershipRole: state.membershipRole, status: "active" }] : []) + : table === instanceUserRoles + ? (state.instanceAdmin ? [{ id: randomUUID() }] : []) + : []; + return { + where: () => Promise.resolve(rows), + then: (resolve: (value: unknown[]) => unknown) => Promise.resolve(rows).then(resolve), + }; + }, + })), + update: vi.fn(() => ({ + set: () => ({ + where: () => ({ + returning: () => Promise.resolve(state.revoked ? [] : [{ id: keyId }]), + then: (resolve: (value: unknown[]) => unknown) => Promise.resolve([]).then(resolve), + }), + }), + })), + insert: vi.fn((table: unknown) => ({ + values: async (values: Record) => { + if (table === boardApiKeyAuthorizationEvents) audit.push(values); + return []; + }, + })), + } as any; + + return { db, state, key, companyId, ownerId, audit }; +} + +function requestFor(authentication: Awaited["authenticateBoardApiKey"]>>) { + if (!authentication.ok) throw new Error("Expected successful board-key authentication"); + const { key, scopeConfig, access } = authentication; + const companyIds = scopeConfig + ? scopeConfig.companyIds.filter((id) => access.companyIds.includes(id)) + : access.companyIds; + return { + id: randomUUID(), + actor: { + type: "board", + source: "board_key", + userId: key.userId, + keyId: key.id, + boardKeyOwnerId: key.userId, + boardKeyPrefix: key.tokenPrefix, + boardKeyScope: scopeConfig, + boardKeyLegacyUnrestricted: key.legacyUnrestricted, + companyIds, + memberships: access.memberships.filter((membership) => companyIds.includes(membership.companyId)), + isInstanceAdmin: access.isInstanceAdmin, + }, + } as unknown as Request; +} + +async function expectDenied(run: Promise, status: number) { + await expect(run).rejects.toMatchObject({ status } satisfies Partial); +} + +describe("board-key effective authority", () => { + it("audits an allow before the protected side effect runs", async () => { + const { db, companyId, audit } = createLiveAuthorityDb(); + const authentication = await boardAuthService(db).authenticateBoardApiKey(TOKEN); + const req = requestFor(authentication); + const metadata = lookupBoardKeyRoute("POST", `/api/companies/${companyId}/issues`); + const sideEffect = vi.fn(); + + await authorizeBoardKey( + db, + req, + metadata.action, + async () => ({ companyId, resourceType: "company", resourceId: companyId }), + metadata, + ); + expect(audit.at(-1)).toMatchObject({ decision: "allow", action: "issues:write" }); + sideEffect(); + expect(sideEffect).toHaveBeenCalledOnce(); + }); + + it("fails closed before side effects when the allow audit cannot be persisted", async () => { + const { db, companyId } = createLiveAuthorityDb(); + const authentication = await boardAuthService(db).authenticateBoardApiKey(TOKEN); + const req = requestFor(authentication); + const metadata = lookupBoardKeyRoute("POST", `/api/companies/${companyId}/issues`); + db.insert = vi.fn(() => ({ values: vi.fn().mockRejectedValue(new Error("audit unavailable")) })); + const sideEffect = vi.fn(); + + try { + await authorizeBoardKey( + db, + req, + metadata.action, + async () => ({ companyId, resourceType: "company", resourceId: companyId }), + metadata, + ); + sideEffect(); + } catch (error) { + expect(error).toMatchObject({ status: 500 }); + } + expect(sideEffect).not.toHaveBeenCalled(); + }); + + it("allows only self-revoke without requiring instance admin or a company pin", async () => { + const { db, key } = createLiveAuthorityDb(); + const authentication = await boardAuthService(db).authenticateBoardApiKey(TOKEN); + const req = requestFor(authentication); + req.actor.isInstanceAdmin = false; + req.actor.companyIds = []; + req.actor.memberships = []; + const ownMetadata = lookupBoardKeyRoute("DELETE", `/api/board-api-keys/${key.id}`); + + await expect(authorizeBoardKey( + db, + req, + ownMetadata.action, + async () => ({ companyId: null, resourceType: "board_api_key", resourceId: key.id }), + ownMetadata, + )).resolves.toBeUndefined(); + + const otherKeyId = randomUUID(); + const otherMetadata = lookupBoardKeyRoute("DELETE", `/api/board-api-keys/${otherKeyId}`); + await expectDenied(authorizeBoardKey( + db, + req, + otherMetadata.action, + async () => ({ companyId: null, resourceType: "board_api_key", resourceId: otherKeyId }), + otherMetadata, + ), 404); + }); + + it("applies owner suspension, removal, demotion, admin loss, and deletion on the next request", async () => { + const { db, state, companyId } = createLiveAuthorityDb(); + const service = boardAuthService(db); + const companyMetadata = lookupBoardKeyRoute("POST", `/api/companies/${companyId}/issues`); + const instanceMetadata = lookupBoardKeyRoute("POST", "/api/instance/settings"); + + state.membershipActive = false; // suspension + let authentication = await service.authenticateBoardApiKey(TOKEN); + await expectDenied(authorizeBoardKey( + db, + requestFor(authentication), + companyMetadata.action, + async () => ({ companyId, resourceType: "company", resourceId: companyId }), + companyMetadata, + ), 404); + + state.membershipActive = false; // removal has the same effective-authority result + authentication = await service.authenticateBoardApiKey(TOKEN); + expect(authentication.ok && authentication.access.companyIds).toEqual([]); + + state.membershipActive = true; + state.membershipRole = "viewer"; // demotion + authentication = await service.authenticateBoardApiKey(TOKEN); + await expectDenied(authorizeBoardKey( + db, + requestFor(authentication), + companyMetadata.action, + async () => ({ companyId, resourceType: "company", resourceId: companyId }), + companyMetadata, + ), 403); + + state.membershipRole = "owner"; + state.instanceAdmin = false; // instance-admin role removed + authentication = await service.authenticateBoardApiKey(TOKEN); + await expectDenied(authorizeBoardKey( + db, + requestFor(authentication), + instanceMetadata.action, + async () => ({ companyId: null, resourceType: "instance", resourceId: null }), + instanceMetadata, + ), 403); + + state.ownerExists = false; // owner deletion rejects authentication itself + authentication = await service.authenticateBoardApiKey(TOKEN); + expect(authentication).toMatchObject({ ok: false, reason: "owner_deleted" }); + }); +}); diff --git a/server/src/security/board-key-route-registry.test.ts b/server/src/security/board-key-route-registry.test.ts new file mode 100644 index 0000000000..ab39c7a65d --- /dev/null +++ b/server/src/security/board-key-route-registry.test.ts @@ -0,0 +1,105 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { BOARD_API_KEY_PERMISSION_KEYS } from "@paperclipai/shared"; +import { lookupBoardKeyRoute } from "./board-key-route-registry.js"; + +const ROUTES_DIR = path.resolve(import.meta.dirname, "../routes"); +const ROUTE_REGISTRATION = /\b(?:router|routes)\.(get|post|put|patch|delete)\s*\(\s*(["'`])([^"'`]+)\2/g; +const ROUTE_CALL = /\b(?:router|routes)\.(?:get|post|put|patch|delete)\s*\(/g; +const SAMPLE_UUID = "11111111-1111-4111-8111-111111111111"; + +type InventoriedRoute = { file: string; method: string; path: string }; + +function mountedPath(file: string, routePath: string) { + const mount = file === "companies.ts" + ? "/api/companies" + : file === "auth.ts" + ? "/api/auth" + : file === "health.ts" + ? "/api/health" + : file === "cloud.ts" + ? "/api/cloud" + : routePath.startsWith("/mcp") + ? "" + : "/api"; + const joined = `${mount}${routePath === "/" ? "" : routePath}` || "/"; + return joined + .replace(/:[A-Za-z][A-Za-z0-9_]*/g, SAMPLE_UUID) + .replace(/\{\*[^}]+\}/g, "inventory-tail"); +} + +function inventoryRoutes() { + const inventory: InventoriedRoute[] = []; + const unsupported: string[] = []; + for (const file of fs.readdirSync(ROUTES_DIR).filter((name) => name.endsWith(".ts") && !name.includes(".test."))) { + const source = fs.readFileSync(path.join(ROUTES_DIR, file), "utf8"); + const calls = source.match(ROUTE_CALL)?.length ?? 0; + let captured = 0; + for (const match of source.matchAll(ROUTE_REGISTRATION)) { + captured += 1; + const routePath = match[3]!; + if (routePath.includes("${")) { + unsupported.push(`${file}: dynamic template route ${routePath}`); + continue; + } + inventory.push({ + file, + method: match[1]!.toUpperCase(), + path: mountedPath(file, routePath), + }); + } + // companies.ts has one frozen constant route: COMPANY_IMPORT_ROUTE_PATH. + const allowedNonLiteralCalls = file === "companies.ts" ? 1 : 0; + if (calls - captured !== allowedNonLiteralCalls) { + unsupported.push(`${file}: ${calls - captured} route registration(s) do not use a literal path`); + } + } + inventory.push({ file: "companies.ts", method: "POST", path: "/api/companies/import" }); + const unique = new Map(); + const duplicates: InventoriedRoute[] = []; + for (const route of inventory) { + const key = `${route.method} ${route.path}`; + if (unique.has(key)) duplicates.push(route); + else unique.set(key, route); + } + return { inventory: [...unique.values()], unsupported, duplicates }; +} + +describe("board-key route registry", () => { + it.each([ + ["GET", "/api/companies/11111111-1111-4111-8111-111111111111/issues", "issues:read", "company"], + ["POST", "/api/issues/11111111-1111-4111-8111-111111111111/checkout", "issues:control", "company"], + ["DELETE", "/api/board-api-keys/11111111-1111-4111-8111-111111111111", "board_api_keys:revoke_self", "key_self"], + ["POST", "/api/board-api-keys", "deny", "board_key_denied"], + ["GET", "/api/cli-auth/me", "deny", "board_key_denied"], + ["GET", "/api/not-yet-registered", "deny", "undeclared"], + ] as const)("classifies %s %s", (method, routePath, action, classification) => { + expect(lookupBoardKeyRoute(method, routePath)).toMatchObject({ action, classification }); + }); + + it("inventories every checked-in route registration with an explicit policy", () => { + const { inventory, unsupported, duplicates } = inventoryRoutes(); + const undeclared = inventory + .map((route) => ({ ...route, metadata: lookupBoardKeyRoute(route.method, route.path) })) + .filter((route) => route.metadata.classification === "undeclared"); + const unsafeDuplicates = duplicates.filter( + (route) => lookupBoardKeyRoute(route.method, route.path).classification !== "board_key_denied", + ); + + expect(inventory.length).toBeGreaterThan(500); + expect(unsupported).toEqual([]); + // cases.ts and pipelines.ts intentionally overlap while both experimental + // surfaces are hard-denied. A duplicate on an allowed route is unsafe. + expect(unsafeDuplicates).toEqual([]); + expect(undeclared).toEqual([]); + expect(inventory.map((route) => ({ + ...route, + metadata: lookupBoardKeyRoute(route.method, route.path), + }))).toMatchSnapshot(); + for (const route of inventory) { + const { action } = lookupBoardKeyRoute(route.method, route.path); + expect(action === "deny" || BOARD_API_KEY_PERMISSION_KEYS.includes(action)).toBe(true); + } + }); +}); diff --git a/server/src/security/board-key-route-registry.ts b/server/src/security/board-key-route-registry.ts new file mode 100644 index 0000000000..80556178fb --- /dev/null +++ b/server/src/security/board-key-route-registry.ts @@ -0,0 +1,701 @@ +import type { Request, RequestHandler } from "express"; +import { eq } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { + agents, + approvals, + assets, + boardApiKeyAuthorizationEvents, + boardApiKeys, + companySecretProviderConfigs, + companySecrets, + companies, + decisionTrainingExamples, + decisions, + executionWorkspaces, + folders, + goals, + heartbeatRuns, + issueAttachments, + issueWorkProducts, + issues, + labels, + projects, + routines, + routineTriggers, + statusCards, + toolActionRequests, + toolApplications, + toolConnections, + toolMcpGateways, + toolMcpGatewayTokens, + toolProfileEntries, + toolProfiles, + toolRuntimeSlots, + workspaceOperations, +} from "@paperclipai/db"; +import { isUuidLike, type BoardPermissionKey } from "@paperclipai/shared"; +import { HttpError, forbidden, notFound } from "../errors.js"; +import { logger } from "../middleware/logger.js"; + +export type BoardKeyRouteClassification = + | "company" + | "company_collection" + | "instance_global" + | "key_self" + | "board_key_denied" + | "undeclared"; + +export type BoardKeyAuthoritativeResolver = + | "none" + | "company" + | "issue" + | "agent" + | "project" + | "goal" + | "routine" + | "approval" + | "execution_workspace" + | "asset" + | "attachment" + | "work_product" + | "heartbeat_run" + | "secret" + | "secret_provider" + | "tool_application" + | "tool_connection" + | "tool_profile" + | "tool_profile_entry" + | "tool_gateway" + | "status_card" + | "label" + | "folder" + | "routine_trigger" + | "decision" + | "decision_training" + | "workspace_operation" + | "key_self" + | "downstream_company"; + +export interface BoardKeyRouteMetadata { + method: string; + routePattern: string; + action: BoardPermissionKey | "deny"; + classification: BoardKeyRouteClassification; + resolver: BoardKeyAuthoritativeResolver; + resourceId?: string; + companyId?: string; + concealment: "forbidden" | "not_found"; +} + +type AuthoritativeResource = { + companyId: string | null; + resourceType: string | null; + resourceId: string | null; +}; + +const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]); +const ID = "{id}"; + +function permissionForMethod( + method: string, + read: BoardPermissionKey, + write: BoardPermissionKey, +): BoardPermissionKey { + return SAFE_METHODS.has(method) ? read : write; +} + +function declared( + method: string, + routePattern: string, + action: BoardPermissionKey | "deny", + classification: BoardKeyRouteClassification, + resolver: BoardKeyAuthoritativeResolver, + extra: Partial> = {}, +): BoardKeyRouteMetadata { + return { + method, + routePattern, + action, + classification, + resolver, + concealment: extra.concealment ?? (resolver === "company" ? "forbidden" : "not_found"), + ...(extra.resourceId ? { resourceId: extra.resourceId } : {}), + ...(extra.companyId ? { companyId: extra.companyId } : {}), + }; +} + +function denied(method: string, routePattern: string): BoardKeyRouteMetadata { + return declared(method, routePattern, "deny", "board_key_denied", "none", { concealment: "forbidden" }); +} + +function normalizePath(rawPath: string) { + const withoutQuery = rawPath.split("?", 1)[0] || "/"; + return withoutQuery.length > 1 ? withoutQuery.replace(/\/+$/, "") : withoutQuery; +} + +function queryCompanyId(rawPath: string) { + try { + const value = new URL(rawPath, "http://paperclip.invalid").searchParams.get("companyId"); + return value && isUuidLike(value) ? value : undefined; + } catch { + return undefined; + } +} + +function companySubresourcePermission(method: string, subresource: string | undefined, tail: string[]) { + const write = !SAFE_METHODS.has(method); + switch (subresource) { + case "agents": + case "org": + return write ? "agents:write" : "agents:read"; + case "projects": return write ? "projects:write" : "projects:read"; + case "issues": return write ? "issues:write" : "issues:read"; + case "goals": return write ? "goals:write" : "goals:read"; + case "routines": return write ? "routines:write" : "routines:read"; + case "approvals": return write ? "approvals:write" : "approvals:read"; + case "costs": + case "finance-events": + case "budgets": return write ? "costs:write" : "costs:read"; + case "activity": return "activity:read"; + case "artifacts": + case "assets": + case "attachments": + case "work-products": return write ? "artifacts:write" : "artifacts:read"; + case "execution-workspaces": + case "project-workspaces": + case "workspaces": return write ? "workspaces:manage" : "workspaces:read"; + case "skills": return write ? "skills:manage" : "skills:read"; + case "tools": + case "tool-applications": + case "tool-connections": + case "tool-profiles": + case "tool-gateway": return write ? "tools:manage" : "tools:read"; + case "secrets": + case "secret-provider-configs": return write ? "secrets:manage" : "secrets:read_metadata"; + case "members": + case "users": + case "invites": + case "join-requests": return write ? "members:manage" : "members:read"; + case "decisions": + case "decision-queues": + case "decision-triage": + case "decision-retention": + case "decision-training": return write ? "decisions:write" : "decisions:read"; + case "labels": + case "folders": + case "summary-slots": + case "status-cards": + case "sidebar-preferences": + case "resource-memberships": return write ? "settings:write" : "settings:read"; + case "environments": return write ? "environments:manage" : "environments:read"; + case "search": return "search:read"; + case "heartbeat-runs": + case "runtime": return write ? "runtime:manage" : "runtime:read"; + case "audit": return "audit:read"; + case "import": + case "export": + case "exports": return "companies:import_export"; + default: + if (tail.some((segment) => segment === "search" || segment === "extract")) return "search:read"; + return write ? "companies:write" : "companies:read"; + } +} + +function issuePermission(method: string, tail: string[]) { + if (tail.some((segment) => [ + "checkout", + "release", + "force-release", + "retry-now", + "check-now", + "tree-control", + "tree-holds", + "watchdog", + "recovery-actions", + "scheduled-retry", + "monitor", + "inbox-archive", + "read", + "low-trust", + ].includes(segment))) return "issues:control" as const; + return permissionForMethod(method, "issues:read", "issues:write"); +} + +function agentPermission(method: string, tail: string[]) { + if (tail.some((segment) => [ + "heartbeat", + "wakeup", + "pause", + "resume", + "terminate", + "clear-error", + "reset-session", + "rollback", + ].includes(segment))) return "agents:operate" as const; + return permissionForMethod(method, "agents:read", "agents:write"); +} + +function isDeniedCompanyRoute(method: string, tail: string[]) { + const joined = tail.join("/"); + if ( + joined.startsWith("adapters/") + || joined === "agent-configurations" + || joined.startsWith("built-in-agents") + || joined === "feedback-traces" + || joined.startsWith("inbox-dismissals") + || joined.startsWith("me/user-secrets") + || joined.startsWith("resource-memberships/me") + || joined === "sidebar-preferences/me" + || joined.startsWith("skill-policy") + || joined.startsWith("teams/catalog") + || joined.startsWith("openclaw/invite-prompt") + || joined === "agent-hires" + ) return true; + if (tail[0] === "users" && (tail.at(-1) === "inbox-agent-policy" || tail.at(-1) === "profile")) return true; + if (tail[0] === "skill-test-run-templates") return true; + return method === "POST" && tail[0] === "skills" && tail.includes("test-runs") && tail.at(-1) === "run"; +} + +function isDeniedAgentRoute(resourceId: string | undefined, tail: string[]) { + if (resourceId === "me") return true; + return tail[0] === "keys" + || (tail[0] === "instructions-bundle" && tail[1] === "file") + || tail[0] === "claude-login"; +} + +function isDeniedPluginRoute(method: string, segments: string[]) { + const tail = segments.slice(3); + if (segments[2] === "tools" && tail[0] === "execute") return true; + if (["actions", "bridge", "data", "webhooks"].includes(tail[0] ?? "")) return true; + if (tail[0] === "companies" && tail[2] === "local-folders") return true; + return method === "GET" && tail[0] === "bridge" && tail[1] === "stream"; +} + +function isDeniedToolGatewayRoute(method: string, segments: string[]) { + const tail = segments.slice(2); + if (tail[0] === "sessions") return true; + if (tail[0] === "tools") return method === "GET" || tail[1] === "call"; + return false; +} + +/** + * Central route registry. It intentionally returns `undeclared` rather than a + * permissive default. The inventory test exercises every checked-in route and + * fails when a new first-class route family is not classified here. + */ +export function lookupBoardKeyRoute(methodInput: string, rawPath: string): BoardKeyRouteMetadata { + const method = methodInput.toUpperCase(); + const path = normalizePath(rawPath); + const segments = path.split("/").filter(Boolean); + + if (segments[0] === "mcp") return denied(method, "/mcp/{*path}"); + if (segments[0] !== "api") { + return declared(method, path, "deny", "undeclared", "none", { concealment: "forbidden" }); + } + + const top = segments[1]; + if (!top) return denied(method, "/api"); + + if (top === "board-api-keys") { + if (method === "DELETE" && segments.length === 3) { + return declared(method, `/api/board-api-keys/${ID}`, "board_api_keys:revoke_self", "key_self", "key_self", { + resourceId: segments[2], + concealment: "not_found", + }); + } + return denied(method, `/api/board-api-keys${segments.length > 2 ? `/${ID}` : ""}`); + } + + if ([ + "auth", + "cli-auth", + "health", + "openapi.json", + "invites", + "join-requests", + "board-claim", + "profile", + "get-session", + "sidebar-preferences", + "llms", + "cloud", + "stacks", + "smoke-lab", + "cases", + "pipelines", + "_plugins", + ].includes(top)) return denied(method, `/api/${top}/{*path}`); + + if (top === "companies") { + const companyId = segments[2]; + if (!companyId) { + if (method === "GET") { + return declared(method, "/api/companies", "companies:read", "company_collection", "none", { concealment: "forbidden" }); + } + if (method === "POST") { + return declared(method, "/api/companies", "companies:create", "instance_global", "none", { concealment: "forbidden" }); + } + return denied(method, "/api/companies"); + } + if (["import", "imports", "export", "exports"].includes(companyId)) { + return declared(method, `/api/companies/${companyId}/{*path}`, "companies:import_export", "instance_global", "none", { + concealment: "forbidden", + }); + } + if (isDeniedCompanyRoute(method, segments.slice(3))) { + return denied(method, `/api/companies/${ID}/${segments.slice(3).join("/")}`); + } + const action = companySubresourcePermission(method, segments[3], segments.slice(4)); + return declared(method, `/api/companies/${ID}/{*path}`, action, "company", "company", { + companyId, + concealment: segments.length > 3 ? "not_found" : "forbidden", + }); + } + + const resourceId = segments[2]; + const tail = segments.slice(3); + const collectionCompanyId = resourceId ? undefined : queryCompanyId(rawPath); + const companyCollectionRoute = ( + action: BoardPermissionKey, + routePattern: string, + ) => declared(method, routePattern, action, "company", "company", { + companyId: collectionCompanyId, + concealment: "not_found", + }); + switch (top) { + case "issues": + if (!resourceId) return companyCollectionRoute(issuePermission(method, tail), "/api/issues?companyId={companyId}"); + return declared(method, `/api/issues/${ID}/{*path}`, issuePermission(method, tail), "company", "issue", { resourceId }); + case "agents": + if (isDeniedAgentRoute(resourceId, tail)) return denied(method, `/api/agents/${ID}/{*path}`); + if (!resourceId) return companyCollectionRoute(agentPermission(method, tail), "/api/agents?companyId={companyId}"); + return declared(method, `/api/agents/${ID}/{*path}`, agentPermission(method, tail), "company", "agent", { resourceId }); + case "projects": + if (!resourceId) return companyCollectionRoute(permissionForMethod(method, "projects:read", "projects:write"), "/api/projects?companyId={companyId}"); + return declared(method, `/api/projects/${ID}/{*path}`, permissionForMethod(method, "projects:read", "projects:write"), "company", "project", { resourceId }); + case "goals": + if (!resourceId) return companyCollectionRoute(permissionForMethod(method, "goals:read", "goals:write"), "/api/goals?companyId={companyId}"); + return declared(method, `/api/goals/${ID}/{*path}`, permissionForMethod(method, "goals:read", "goals:write"), "company", "goal", { resourceId }); + case "routines": { + const action = tail.includes("run") ? "routines:run" : permissionForMethod(method, "routines:read", "routines:write"); + return declared(method, `/api/routines/${ID}/{*path}`, action, "company", "routine", { resourceId }); + } + case "approvals": { + const action = tail.some((segment) => segment === "approve" || segment === "reject") + ? "approvals:decide" + : permissionForMethod(method, "approvals:read", "approvals:write"); + return declared(method, `/api/approvals/${ID}/{*path}`, action, "company", "approval", { resourceId }); + } + case "execution-workspaces": + return declared(method, `/api/execution-workspaces/${ID}/{*path}`, permissionForMethod(method, "workspaces:read", "workspaces:manage"), "company", "execution_workspace", { resourceId }); + case "attachments": + return declared(method, `/api/attachments/${ID}/{*path}`, permissionForMethod(method, "artifacts:read", "artifacts:write"), "company", "attachment", { resourceId }); + case "assets": + return declared(method, `/api/assets/${ID}/{*path}`, permissionForMethod(method, "artifacts:read", "artifacts:write"), "company", "asset", { resourceId }); + case "work-products": + return declared(method, `/api/work-products/${ID}/{*path}`, permissionForMethod(method, "artifacts:read", "artifacts:write"), "company", "work_product", { resourceId }); + case "heartbeat-runs": + return declared(method, `/api/heartbeat-runs/${ID}/{*path}`, permissionForMethod(method, "runtime:read", "runtime:manage"), "company", "heartbeat_run", { resourceId }); + case "environments": + case "environment-leases": + case "environment-custom-image-setup-sessions": + return declared(method, `/api/${top}/${ID}/{*path}`, permissionForMethod(method, "environments:read", "environments:manage"), "company", "downstream_company", { resourceId }); + case "secrets": + return declared(method, `/api/secrets/${ID}/{*path}`, permissionForMethod(method, "secrets:read_metadata", "secrets:manage"), "company", "secret", { resourceId }); + case "secret-provider-configs": + return declared(method, `/api/secret-provider-configs/${ID}/{*path}`, permissionForMethod(method, "secrets:read_metadata", "secrets:manage"), "company", "secret_provider", { resourceId }); + case "tool-connections": + return declared(method, `/api/tool-connections/${ID}/{*path}`, permissionForMethod(method, "tools:read", "tools:manage"), "company", "tool_connection", { resourceId }); + case "tool-profiles": + return declared(method, `/api/tool-profiles/${ID}/{*path}`, permissionForMethod(method, "tools:read", "tools:manage"), "company", "tool_profile", { resourceId }); + case "tool-profile-entries": + return declared(method, `/api/tool-profile-entries/${ID}/{*path}`, permissionForMethod(method, "tools:read", "tools:manage"), "company", "tool_profile_entry", { resourceId }); + case "tool-applications": + return declared(method, `/api/tool-applications/${ID}/{*path}`, permissionForMethod(method, "tools:read", "tools:manage"), "company", "tool_application", { resourceId }); + case "status-cards": + return declared(method, `/api/status-cards/${ID}/{*path}`, permissionForMethod(method, "settings:read", "settings:write"), "company", "status_card", { resourceId }); + case "labels": + return declared(method, `/api/labels/${ID}/{*path}`, permissionForMethod(method, "settings:read", "settings:write"), "company", "label", { resourceId }); + case "folders": + return declared(method, `/api/folders/${ID}/{*path}`, permissionForMethod(method, "settings:read", "settings:write"), "company", "folder", { resourceId }); + case "routine-triggers": + if (resourceId === "public") return denied(method, "/api/routine-triggers/public/{id}/fire"); + return declared(method, `/api/routine-triggers/${ID}/{*path}`, "routines:write", "company", "routine_trigger", { resourceId }); + case "plugins": + if (isDeniedPluginRoute(method, segments)) return denied(method, "/api/plugins/{*path}"); + return declared(method, `/api/plugins/${ID}/{*path}`, permissionForMethod(method, "plugins:read", "plugins:manage"), "instance_global", "none", { resourceId, concealment: "forbidden" }); + case "adapters": + return declared(method, `/api/adapters/${ID}/{*path}`, permissionForMethod(method, "adapters:read", "adapters:manage"), "instance_global", "none", { resourceId, concealment: "forbidden" }); + case "instance": + case "dev-server": + return declared(method, `/api/${top}/{*path}`, permissionForMethod(method, "instance:read", "instance:manage"), "instance_global", "none", { concealment: "forbidden" }); + case "admin": + return declared(method, "/api/admin/{*path}", permissionForMethod(method, "users:read", "users:manage"), "instance_global", "none", { concealment: "forbidden" }); + case "teams": + case "skills": + case "built-in-agents": + return declared(method, `/api/${top}/{*path}`, permissionForMethod(method, "catalogs:read", "catalogs:manage"), "instance_global", "none", { concealment: "forbidden" }); + case "feedback-traces": + return denied(method, "/api/feedback-traces/{*path}"); + case "decision-training": + return declared(method, `/api/decision-training/${ID}/{*path}`, permissionForMethod(method, "decisions:read", "decisions:write"), "company", "decision_training", { resourceId }); + case "decisions": + return declared(method, `/api/decisions/${ID}/{*path}`, permissionForMethod(method, "decisions:read", "decisions:write"), "company", "decision", { resourceId }); + case "workspace-operations": + return declared(method, `/api/workspace-operations/${ID}/{*path}`, permissionForMethod(method, "workspaces:read", "workspaces:manage"), "company", "workspace_operation", { resourceId }); + case "stats": + return declared(method, "/api/stats", "companies:read", "company_collection", "none", { concealment: "forbidden" }); + case "import": + return declared(method, "/api/import/{*path}", "companies:import_export", "instance_global", "none", { concealment: "forbidden" }); + case "board": + case "bootstrap": + return denied(method, `/api/${top}/{*path}`); + case "sidebar-preferences": + return denied(method, "/api/sidebar-preferences/{*path}"); + case "tool-gateway": + if (isDeniedToolGatewayRoute(method, segments)) return denied(method, "/api/tool-gateway/{*path}"); + return declared(method, "/api/tool-gateway/{*path}", permissionForMethod(method, "tools:read", "tools:manage"), "company", "tool_gateway", { resourceId: segments[3] }); + case "tools": + if (segments[2] === "oauth") return denied(method, "/api/tools/oauth/{*path}"); + return declared(method, "/api/tools/{*path}", permissionForMethod(method, "tools:read", "tools:manage"), "company", "downstream_company", { resourceId }); + default: + return declared(method, path, "deny", "undeclared", "none", { concealment: "forbidden" }); + } +} + +async function resolveAuthoritativeResource( + db: Db, + metadata: BoardKeyRouteMetadata, +): Promise { + const id = metadata.resourceId; + const resolveCompanyTableRow = async ( + table: { id: any; companyId: any }, + resourceType: string, + ): Promise => { + if (!id || !isUuidLike(id)) return null; + const row = await db.select({ id: table.id, companyId: table.companyId }).from(table as any) + .where(eq(table.id, id)).then((rows) => rows[0] as { id: string; companyId: string } | undefined); + return row ? { companyId: row.companyId, resourceType, resourceId: row.id } : null; + }; + switch (metadata.resolver) { + case "none": return { companyId: null, resourceType: null, resourceId: null }; + case "company": { + if (!metadata.companyId) return null; + const row = await db.select({ id: companies.id }).from(companies).where(eq(companies.id, metadata.companyId)).then((rows) => rows[0] ?? null); + return row ? { companyId: row.id, resourceType: "company", resourceId: row.id } : null; + } + case "issue": { + if (!id) return null; + const row = await db.select({ id: issues.id, companyId: issues.companyId }).from(issues) + .where(isUuidLike(id) ? eq(issues.id, id) : eq(issues.identifier, id)).then((rows) => rows[0] ?? null); + return row ? { companyId: row.companyId, resourceType: "issue", resourceId: row.id } : null; + } + case "agent": { + if (!id || !isUuidLike(id)) return null; + const row = await db.select({ id: agents.id, companyId: agents.companyId }).from(agents).where(eq(agents.id, id)).then((rows) => rows[0] ?? null); + return row ? { companyId: row.companyId, resourceType: "agent", resourceId: row.id } : null; + } + case "project": { + if (!id || !isUuidLike(id)) return null; + const row = await db.select({ id: projects.id, companyId: projects.companyId }).from(projects).where(eq(projects.id, id)).then((rows) => rows[0] ?? null); + return row ? { companyId: row.companyId, resourceType: "project", resourceId: row.id } : null; + } + case "goal": { + if (!id || !isUuidLike(id)) return null; + const row = await db.select({ id: goals.id, companyId: goals.companyId }).from(goals).where(eq(goals.id, id)).then((rows) => rows[0] ?? null); + return row ? { companyId: row.companyId, resourceType: "goal", resourceId: row.id } : null; + } + case "routine": { + if (!id || !isUuidLike(id)) return null; + const row = await db.select({ id: routines.id, companyId: routines.companyId }).from(routines).where(eq(routines.id, id)).then((rows) => rows[0] ?? null); + return row ? { companyId: row.companyId, resourceType: "routine", resourceId: row.id } : null; + } + case "approval": { + if (!id || !isUuidLike(id)) return null; + const row = await db.select({ id: approvals.id, companyId: approvals.companyId }).from(approvals).where(eq(approvals.id, id)).then((rows) => rows[0] ?? null); + return row ? { companyId: row.companyId, resourceType: "approval", resourceId: row.id } : null; + } + case "execution_workspace": { + if (!id || !isUuidLike(id)) return null; + const row = await db.select({ id: executionWorkspaces.id, companyId: executionWorkspaces.companyId }).from(executionWorkspaces).where(eq(executionWorkspaces.id, id)).then((rows) => rows[0] ?? null); + return row ? { companyId: row.companyId, resourceType: "execution_workspace", resourceId: row.id } : null; + } + case "asset": return resolveCompanyTableRow(assets, "asset"); + case "attachment": return resolveCompanyTableRow(issueAttachments, "attachment"); + case "work_product": return resolveCompanyTableRow(issueWorkProducts, "work_product"); + case "heartbeat_run": return resolveCompanyTableRow(heartbeatRuns, "heartbeat_run"); + case "secret": return resolveCompanyTableRow(companySecrets, "secret"); + case "secret_provider": return resolveCompanyTableRow(companySecretProviderConfigs, "secret_provider_config"); + case "tool_application": return resolveCompanyTableRow(toolApplications, "tool_application"); + case "tool_connection": return resolveCompanyTableRow(toolConnections, "tool_connection"); + case "tool_profile": return resolveCompanyTableRow(toolProfiles, "tool_profile"); + case "tool_profile_entry": return resolveCompanyTableRow(toolProfileEntries, "tool_profile_entry"); + case "tool_gateway": { + for (const [table, resourceType] of [ + [toolMcpGateways, "tool_gateway"], + [toolMcpGatewayTokens, "tool_gateway_token"], + [toolActionRequests, "tool_action_request"], + [toolRuntimeSlots, "tool_runtime_slot"], + ] as const) { + const row = await resolveCompanyTableRow(table, resourceType); + if (row) return row; + } + return null; + } + case "status_card": return resolveCompanyTableRow(statusCards, "status_card"); + case "label": return resolveCompanyTableRow(labels, "label"); + case "folder": return resolveCompanyTableRow(folders, "folder"); + case "routine_trigger": return resolveCompanyTableRow(routineTriggers, "routine_trigger"); + case "decision": return resolveCompanyTableRow(decisions, "decision"); + case "decision_training": return resolveCompanyTableRow(decisionTrainingExamples, "decision_training"); + case "workspace_operation": return resolveCompanyTableRow(workspaceOperations, "workspace_operation"); + case "key_self": { + if (!id || !isUuidLike(id)) return null; + const row = await db.select({ id: boardApiKeys.id, userId: boardApiKeys.userId }).from(boardApiKeys).where(eq(boardApiKeys.id, id)).then((rows) => rows[0] ?? null); + return row ? { companyId: null, resourceType: "board_api_key", resourceId: row.id } : null; + } + case "downstream_company": + return { companyId: null, resourceType: null, resourceId: id ?? null }; + } +} + +function isWriteAction(action: BoardPermissionKey) { + return /:(?:write|manage|control|operate|run|decide|create|import_export)$/.test(action); +} + +async function auditDecision( + db: Db, + req: Request, + metadata: BoardKeyRouteMetadata, + resource: AuthoritativeResource | null, + decision: "allow" | "deny", + reason: string, +) { + if (req.actor.source !== "board_key" || !req.actor.keyId || !req.actor.boardKeyOwnerId) return; + await db.insert(boardApiKeyAuthorizationEvents).values({ + boardApiKeyId: req.actor.keyId, + ownerUserId: req.actor.boardKeyOwnerId, + tokenPrefix: req.actor.boardKeyPrefix ?? null, + action: metadata.action, + classification: metadata.classification, + authoritativeCompanyId: resource?.companyId ?? null, + authoritativeResourceType: resource?.resourceType ?? null, + authoritativeResourceId: resource?.resourceId ?? null, + decision, + reason, + requestId: typeof req.id === "string" ? req.id : null, + runId: isUuidLike(req.actor.runId) ? req.actor.runId : null, + details: {}, + }); +} + +async function denyBoardKey( + db: Db, + req: Request, + metadata: BoardKeyRouteMetadata, + resource: AuthoritativeResource | null, + reason: string, + conceal = metadata.concealment, +): Promise { + try { + await auditDecision(db, req, metadata, resource, "deny", reason); + } catch (err) { + logger.warn({ err, boardApiKeyId: req.actor.keyId, action: metadata.action }, "Failed to audit denied board-key authorization"); + } + if (conceal === "not_found") throw notFound(); + throw forbidden(); +} + +export async function authorizeBoardKey( + db: Db, + req: Request, + action: BoardPermissionKey | "deny", + authoritativeResourceResolver: () => Promise, + metadata: BoardKeyRouteMetadata, +) { + if (req.actor.source !== "board_key") return; + if (metadata.classification === "undeclared") { + await denyBoardKey(db, req, metadata, null, "route_undeclared", "forbidden"); + } + if (metadata.classification === "board_key_denied" || action === "deny") { + await denyBoardKey(db, req, metadata, null, "route_denied", "forbidden"); + } + + const resource = await authoritativeResourceResolver(); + if (!resource) await denyBoardKey(db, req, metadata, null, "resource_not_found", "not_found"); + + const scope = req.actor.boardKeyScope; + const legacy = req.actor.boardKeyLegacyUnrestricted === true; + const permissions = new Set(scope?.permissions ?? []); + + if (!legacy && !permissions.has(action as BoardPermissionKey)) { + await denyBoardKey(db, req, metadata, resource, "permission_missing", "forbidden"); + } + + if (metadata.classification === "key_self") { + if (resource?.resourceId !== req.actor.keyId) { + await denyBoardKey(db, req, metadata, resource, "key_self_mismatch", "not_found"); + } + const row = await db.select({ userId: boardApiKeys.userId }).from(boardApiKeys) + .where(eq(boardApiKeys.id, req.actor.keyId!)).then((rows) => rows[0] ?? null); + if (!row || row.userId !== req.actor.boardKeyOwnerId) { + await denyBoardKey(db, req, metadata, resource, "key_owner_mismatch", "not_found"); + } + } else if (metadata.classification === "instance_global") { + if (!legacy && !scope?.instanceCapabilities.includes("instance_admin")) { + await denyBoardKey(db, req, metadata, resource, "instance_capability_missing", "forbidden"); + } + if (!req.actor.isInstanceAdmin) { + await denyBoardKey(db, req, metadata, resource, "owner_instance_admin_missing", "forbidden"); + } + } else if (metadata.classification === "company_collection") { + if ((req.actor.companyIds ?? []).length === 0) { + await denyBoardKey(db, req, metadata, resource, "owner_company_membership_missing", "forbidden"); + } + } else if (metadata.resolver === "downstream_company") { + // A family may be inventoried before it has a safe generic resolver. It is + // available to migrated legacy keys through existing route-level checks, + // but scoped keys fail closed until an authoritative resolver is added. + if (!legacy) await denyBoardKey(db, req, metadata, resource, "authoritative_resolver_unavailable", "not_found"); + } else if (resource?.companyId) { + const membership = req.actor.memberships?.find( + (item) => item.companyId === resource.companyId && item.status === "active", + ); + if (!membership || !(req.actor.companyIds ?? []).includes(resource.companyId)) { + await denyBoardKey(db, req, metadata, resource, "company_scope_mismatch", "not_found"); + } + if (isWriteAction(action as BoardPermissionKey) && membership?.membershipRole === "viewer") { + await denyBoardKey(db, req, metadata, resource, "owner_role_read_only", "forbidden"); + } + } + + try { + await auditDecision(db, req, metadata, resource, "allow", "authorized"); + } catch (err) { + logger.error({ err, boardApiKeyId: req.actor.keyId, action }, "Board-key authorization audit failed closed"); + throw new HttpError(500, "Internal server error"); + } +} + +export function boardKeyAuthorizationMiddleware(db: Db): RequestHandler { + return async (req, _res, next) => { + if (req.actor.source !== "board_key") { + next(); + return; + } + const metadata = lookupBoardKeyRoute(req.method, req.originalUrl); + try { + await authorizeBoardKey( + db, + req, + metadata.action, + () => resolveAuthoritativeResource(db, metadata), + metadata, + ); + next(); + } catch (err) { + next(err); + } + }; +} diff --git a/server/src/services/board-auth.ts b/server/src/services/board-auth.ts index 7478ae1af8..fca95bd16a 100644 --- a/server/src/services/board-auth.ts +++ b/server/src/services/board-auth.ts @@ -1,6 +1,11 @@ import { createHash, randomBytes, timingSafeEqual } from "node:crypto"; import { and, eq, gt, isNull, or, sql } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; +import { + boardApiKeyScopeConfigSchema, + deriveBoardApiKeyStatus, + type BoardApiKeyScopeConfig, +} from "@paperclipai/shared"; import { authUsers, boardApiKeys, @@ -32,6 +37,10 @@ export function createBoardApiToken() { return `pcp_board_${randomBytes(24).toString("hex")}`; } +export function boardApiKeyTokenPrefix(token: string) { + return token.slice(0, "pcp_board_".length + 12); +} + export function createCliAuthSecret() { return `pcp_cli_auth_${randomBytes(24).toString("hex")}`; } @@ -52,7 +61,10 @@ function challengeStatusForRow(row: typeof cliAuthChallenges.$inferSelect): CliA } export function boardAuthService(db: Db) { - const touchedBoardApiKeys = new Map | null }>(); + const touchedBoardApiKeys = new Map< + string, + { completedAt: number | null; inFlight: Promise<{ id: string } | null> | null } + >(); function pruneTouchedBoardApiKeys(nowMs: number) { for (const [id, entry] of touchedBoardApiKeys) { @@ -149,35 +161,43 @@ export function boardAuthService(db: Db) { async function findBoardApiKeyByToken(token: string) { const tokenHash = hashBearerToken(token); - const now = new Date(); return db .select() .from(boardApiKeys) - .where( - and( - eq(boardApiKeys.keyHash, tokenHash), - isNull(boardApiKeys.revokedAt), - ), - ) - .then((rows) => rows.find((row) => !row.expiresAt || row.expiresAt.getTime() > now.getTime()) ?? null); + .where(eq(boardApiKeys.keyHash, tokenHash)) + .then((rows) => rows[0] ?? null); } - async function touchBoardApiKey(id: string) { - const nowMs = Date.now(); + async function touchBoardApiKey(id: string, now: Date = new Date()) { + const nowMs = now.getTime(); pruneTouchedBoardApiKeys(nowMs); const cached = touchedBoardApiKeys.get(id); if (cached?.inFlight) return cached.inFlight; - if (cached?.completedAt !== null && cached?.completedAt !== undefined - && cached.completedAt > nowMs - BOARD_API_KEY_TOUCH_DEBOUNCE_MS) return; + if ( + cached?.completedAt !== null + && cached?.completedAt !== undefined + && cached.completedAt > nowMs - BOARD_API_KEY_TOUCH_DEBOUNCE_MS + ) { + return { id }; + } const inFlight = db .update(boardApiKeys) - .set({ lastUsedAt: new Date() }) - .where(eq(boardApiKeys.id, id)) - .then(() => { + .set({ lastUsedAt: now }) + .where(and( + eq(boardApiKeys.id, id), + isNull(boardApiKeys.revokedAt), + or(isNull(boardApiKeys.expiresAt), gt(boardApiKeys.expiresAt, now)), + )) + .returning({ id: boardApiKeys.id }) + .then((rows) => { + const touched = rows[0] ?? null; touchedBoardApiKeys.delete(id); - touchedBoardApiKeys.set(id, { completedAt: Date.now(), inFlight: null }); - pruneTouchedBoardApiKeys(Date.now()); + if (touched) { + touchedBoardApiKeys.set(id, { completedAt: Date.now(), inFlight: null }); + pruneTouchedBoardApiKeys(Date.now()); + } + return touched; }) .catch((error) => { if (touchedBoardApiKeys.get(id)?.inFlight === inFlight) touchedBoardApiKeys.delete(id); @@ -187,11 +207,41 @@ export function boardAuthService(db: Db) { return inFlight; } + async function authenticateBoardApiKey(token: string) { + const key = await findBoardApiKeyByToken(token); + if (!key) return { ok: false as const, reason: "unknown_key" as const, key: null }; + if (key.revokedAt) return { ok: false as const, reason: "revoked" as const, key }; + if (key.expiresAt && key.expiresAt.getTime() <= Date.now()) { + return { ok: false as const, reason: "expired" as const, key }; + } + + let scopeConfig: BoardApiKeyScopeConfig | null; + if (key.legacyUnrestricted === true && key.scopeConfig === null) { + scopeConfig = null; + } else if (key.legacyUnrestricted === false && key.scopeConfig !== null) { + const parsed = boardApiKeyScopeConfigSchema.safeParse(key.scopeConfig); + if (!parsed.success) return { ok: false as const, reason: "malformed_scope" as const, key }; + scopeConfig = parsed.data; + } else { + return { ok: false as const, reason: "malformed_scope" as const, key }; + } + + const access = await resolveBoardAccess(key.userId); + if (!access.user) return { ok: false as const, reason: "owner_deleted" as const, key }; + + // This conditional update is the final authentication commit point. A + // concurrent revocation/expiry cannot be overwritten or authenticated. + const touched = await touchBoardApiKey(key.id); + if (!touched) return { ok: false as const, reason: "revoked" as const, key }; + + return { ok: true as const, key, scopeConfig, access }; + } + async function revokeBoardApiKey(id: string) { const now = new Date(); return db .update(boardApiKeys) - .set({ revokedAt: now, lastUsedAt: now }) + .set({ revokedAt: now }) .where(and(eq(boardApiKeys.id, id), isNull(boardApiKeys.revokedAt))) .returning() .then((rows) => rows[0] ?? null); @@ -201,14 +251,18 @@ export function boardAuthService(db: Db) { userId: string; name: string; expiresAt?: Date | null; + scopeConfig: BoardApiKeyScopeConfig; }) { const token = createBoardApiToken(); + const scopeConfig = boardApiKeyScopeConfigSchema.parse(input.scopeConfig); const created = await db .insert(boardApiKeys) .values({ userId: input.userId, - name: input.name.trim(), + name: input.name, keyHash: hashBearerToken(token), + tokenPrefix: boardApiKeyTokenPrefix(token), + scopeConfig, expiresAt: input.expiresAt === undefined ? boardApiKeyExpiresAt() : input.expiresAt, }) .returning() @@ -218,6 +272,10 @@ export function boardAuthService(db: Db) { id: created.id, name: created.name, token, + tokenPrefix: created.tokenPrefix, + scopeConfig, + legacyUnrestricted: false, + status: deriveBoardApiKeyStatus(created), createdAt: created.createdAt, lastUsedAt: created.lastUsedAt, revokedAt: created.revokedAt, @@ -244,6 +302,9 @@ export function boardAuthService(db: Db) { .select({ id: boardApiKeys.id, name: boardApiKeys.name, + tokenPrefix: boardApiKeys.tokenPrefix, + scopeConfig: boardApiKeys.scopeConfig, + legacyUnrestricted: boardApiKeys.legacyUnrestricted, createdAt: boardApiKeys.createdAt, lastUsedAt: boardApiKeys.lastUsedAt, revokedAt: boardApiKeys.revokedAt, @@ -251,7 +312,8 @@ export function boardAuthService(db: Db) { }) .from(boardApiKeys) .where(and(...conditions)) - .orderBy(sql`${boardApiKeys.createdAt} desc`); + .orderBy(sql`${boardApiKeys.createdAt} desc`) + .then((rows) => rows.map((row) => ({ ...row, status: deriveBoardApiKeyStatus(row) }))); } async function getBoardApiKeyForUser(keyId: string, userId: string) { @@ -260,6 +322,9 @@ export function boardAuthService(db: Db) { id: boardApiKeys.id, userId: boardApiKeys.userId, name: boardApiKeys.name, + tokenPrefix: boardApiKeys.tokenPrefix, + scopeConfig: boardApiKeys.scopeConfig, + legacyUnrestricted: boardApiKeys.legacyUnrestricted, createdAt: boardApiKeys.createdAt, lastUsedAt: boardApiKeys.lastUsedAt, revokedAt: boardApiKeys.revokedAt, @@ -275,9 +340,11 @@ export function boardAuthService(db: Db) { clientName?: string | null; requestedAccess: "board" | "instance_admin_required"; requestedCompanyId?: string | null; + scopeConfig: BoardApiKeyScopeConfig; }) { const challengeSecret = createCliAuthSecret(); const pendingBoardToken = createBoardApiToken(); + const scopeConfig = boardApiKeyScopeConfigSchema.parse(input.scopeConfig); const expiresAt = cliAuthChallengeExpiresAt(); const labelBase = input.clientName?.trim() || "paperclipai cli"; const pendingKeyName = @@ -293,7 +360,9 @@ export function boardAuthService(db: Db) { clientName: input.clientName?.trim() || null, requestedAccess: input.requestedAccess, requestedCompanyId: input.requestedCompanyId?.trim() || null, + requestedScopeConfig: scopeConfig, pendingKeyHash: hashBearerToken(pendingBoardToken), + pendingKeyPrefix: boardApiKeyTokenPrefix(pendingBoardToken), pendingKeyName, expiresAt, }) @@ -354,6 +423,7 @@ export function boardAuthService(db: Db) { approvedAt: challenge.approvedAt?.toISOString() ?? null, cancelledAt: challenge.cancelledAt?.toISOString() ?? null, expiresAt: challenge.expiresAt.toISOString(), + boardApiKeyId: challenge.boardApiKeyId ?? null, approvedByUser: approvedBy ? { id: approvedBy.id, @@ -388,6 +458,11 @@ export function boardAuthService(db: Db) { throw forbidden("Instance admin required"); } + const requestedScope = boardApiKeyScopeConfigSchema.safeParse(challenge.requestedScopeConfig); + if (!requestedScope.success || !challenge.pendingKeyPrefix) { + throw conflict("CLI auth challenge must be recreated with an explicit board-key scope"); + } + let boardKeyId = challenge.boardApiKeyId; if (!boardKeyId) { const createdKey = await tx @@ -396,6 +471,8 @@ export function boardAuthService(db: Db) { userId, name: challenge.pendingKeyName, keyHash: challenge.pendingKeyHash, + tokenPrefix: challenge.pendingKeyPrefix, + scopeConfig: requestedScope.data, expiresAt: boardApiKeyExpiresAt(), }) .returning() @@ -455,6 +532,7 @@ export function boardAuthService(db: Db) { return { resolveBoardAccess, + authenticateBoardApiKey, findBoardApiKeyByToken, touchBoardApiKey, revokeBoardApiKey, diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts index 33c09c1604..1d2cf6c76e 100644 --- a/server/src/types/express.d.ts +++ b/server/src/types/express.d.ts @@ -1,6 +1,6 @@ export {}; -import type { AgentApiKeyScope } from "@paperclipai/shared"; +import type { AgentApiKeyScope, BoardApiKeyScopeConfig } from "@paperclipai/shared"; declare global { namespace Express { @@ -27,6 +27,10 @@ declare global { isInstanceAdmin?: boolean; keyId?: string; keyScope?: AgentApiKeyScope; + boardKeyScope?: BoardApiKeyScopeConfig | null; + boardKeyOwnerId?: string; + boardKeyPrefix?: string | null; + boardKeyLegacyUnrestricted?: boolean; runId?: string; onBehalfOfUserId?: string | null; identityContextId?: string | null;