{"openapi":"3.0.0","paths":{"/v1/features":{"get":{"operationId":"FeatureFlagsController_getFeatures","summary":"Get public feature flags","description":"Returns the current feature flag configuration. Used by the frontend to conditionally render features.","parameters":[],"responses":{"200":{"description":"Feature flags retrieved successfully"}},"tags":["Feature Flags"]}},"/v1/cost/usage":{"get":{"operationId":"CostController_getUsage","summary":"Get current daily token and search budget usage (requires cost.read.all)","parameters":[],"responses":{"200":{"description":"Cost usage snapshot"},"403":{"description":"Missing cost.read.all permission"}},"tags":["Cost"],"security":[{"bearer":[]}]}},"/v1/auth/google/init":{"get":{"operationId":"AuthController_getGoogleAuthUrl","summary":"Initialize Google OAuth","description":"Returns the Google OAuth URL to initiate authentication","parameters":[{"name":"callback_url","required":true,"in":"query","description":"Frontend callback URL","schema":{"example":"http://localhost:3000/auth/success","type":"string"}}],"responses":{"200":{"description":"Google OAuth URL generated"}},"tags":["Authentication"]}},"/v1/auth/google/callback":{"get":{"operationId":"AuthController_googleCallback","summary":"Handle Google OAuth callback","description":"Processes callback from Google and redirects to frontend","parameters":[{"name":"code","required":true,"in":"query","description":"Authorization code from Google","schema":{"type":"string"}},{"name":"state","required":true,"in":"query","description":"Frontend callback URL","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to frontend with result"}},"tags":["Authentication"]},"post":{"operationId":"AuthController_googleCallbackPost","summary":"Handle Google OAuth callback (POST)","description":"Alternative POST endpoint for callback processing","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"code":{"type":"string"},"callback_url":{"type":"string"}}}}}},"responses":{"200":{"description":"Authentication successful"}},"tags":["Authentication"]}},"/v1/auth/microsoft/init":{"get":{"operationId":"AuthController_getMicrosoftAuthUrl","summary":"Initialize Microsoft OAuth","description":"Returns the Microsoft OAuth URL to initiate authentication","parameters":[{"name":"callback_url","required":true,"in":"query","description":"Frontend callback URL","schema":{"example":"http://localhost:3000/auth/success","type":"string"}}],"responses":{"200":{"description":"Microsoft OAuth URL generated"}},"tags":["Authentication"]}},"/v1/auth/microsoft/callback":{"get":{"operationId":"AuthController_microsoftCallback","summary":"Handle Microsoft OAuth callback","description":"Processes callback from Microsoft and redirects to frontend","parameters":[{"name":"code","required":true,"in":"query","description":"Authorization code from Microsoft","schema":{"type":"string"}},{"name":"state","required":true,"in":"query","description":"Frontend callback URL","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to frontend with result"}},"tags":["Authentication"]},"post":{"operationId":"AuthController_microsoftCallbackPost","summary":"Handle Microsoft OAuth callback (POST)","description":"Alternative POST endpoint for Microsoft callback processing","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"code":{"type":"string"},"callback_url":{"type":"string"}}}}}},"responses":{"200":{"description":"Authentication successful"}},"tags":["Authentication"]}},"/v1/auth/email/init":{"post":{"operationId":"AuthController_requestMagicLink","summary":"Request magic link","description":"Sends a magic link to the specified email","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestMagicLinkDto"}}}},"responses":{"201":{"description":"Magic link sent"},"400":{"description":"Invalid email format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"429":{"description":"Rate limit: max 5 magic links per minute","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"500":{"description":"Magic link auth method disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Authentication"]}},"/v1/auth/email/callback":{"post":{"operationId":"AuthController_verifyMagicLink","summary":"Verify magic link","description":"Verifies the token and sets session cookie","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyMagicLinkDto"}}}},"responses":{"200":{"description":"Verification successful"},"400":{"description":"Invalid or expired magic link token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"429":{"description":"Rate limit: max 5 verifications per minute","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Authentication"]}},"/v1/auth/refresh":{"post":{"operationId":"AuthController_refreshToken","summary":"Story 9.2 AC3 — Refresh session token (rotation)","description":"Aceita session token expirado em body, valida UserSession.valid + signature, issue novo token + invalida antigo. FE chama quando recebe errorCode JWT_EXPIRED em outro endpoint.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshTokenDto"}}}},"responses":{"200":{"description":"Novo session token + TTL"},"401":{"description":"Refresh token invalido (revoked, expired, signature)"},"429":{"description":"Rate limit: max 5 refreshes per minute"}},"tags":["Authentication"]}},"/v1/auth/dev-login":{"get":{"operationId":"AuthController_devLogin","summary":"[DEV ONLY] Auto-login by email","description":"Creates a session for the given email without OAuth. Only works in LOCAL/DEBUG.","parameters":[{"name":"email","required":false,"in":"query","description":"Email to login as","schema":{}}],"responses":{"302":{"description":"Redirects to frontend with session cookie"}},"tags":["Authentication"]}},"/v1/auth/logout":{"post":{"operationId":"AuthController_logout","summary":"Logout","description":"Invalidates the current session and clears auth cookie","parameters":[],"responses":{"201":{"description":"Logout successful"}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/v1/auth/api-token":{"get":{"operationId":"AuthController_getApiToken","summary":"Get API token","description":"Returns the current session token for use in external tools (Postman, curl, scripts)","parameters":[],"responses":{"200":{"description":"Token retrieved successfully"}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/v1/auth/me":{"get":{"operationId":"AuthController_me","summary":"Get current user","description":"Returns authenticated user information including workspaces and isDooorAdmin","parameters":[],"responses":{"200":{"description":"User profile retrieved"}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/v1/auth/sso-exchange":{"post":{"operationId":"SsoExchangeController_ssoExchange","summary":"Exchange a DooorOS-issued JWT for a Spectra session cookie","description":"Cross-App SSO Contract DooorOS (Story 9.3). Validates the external token via JWKS, issues `csn_session`, and returns a relative `redirectTo` for the frontend deep-link.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoExchangeDto"}}}},"responses":{"200":{"description":"Session cookie issued; payload includes `redirectTo`."},"401":{"description":"External token invalid, expired or IdP unreachable."}},"tags":["Authentication"]}},"/v1/users":{"get":{"operationId":"UsersController_getUser","summary":"Get current user information","description":"Retrieves complete information about the currently authenticated user, including workspace memberships and organization details.","parameters":[{"name":"Authorization","in":"header","description":"Bearer token for Google authentication","required":true,"schema":{"type":"string","example":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}}],"responses":{"200":{"description":"User information retrieved successfully","schema":{"example":{"id":"123e4567-e89b-12d3-a456-426614174000","googleId":"1234567890123456789","email":"user@example.com","name":"John Doe","image":"https://storage.googleapis.com/bucket/profile-images/user-id.jpg","createdAt":"2023-01-01T00:00:00.000Z","updatedAt":"2023-01-02T00:00:00.000Z","workspaces":[{"id":"456e7890-e89b-12d3-a456-426614174001","name":"My Workspace","description":"Main workspace for team collaboration","role":"ADMIN","organization":{"id":"789e0123-e89b-12d3-a456-426614174002","name":"My Organization","description":"Organization for managing workspaces"}}]}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"401":{"description":"Authentication failed - invalid or missing token","content":{"application/json":{"schema":{"example":{"statusCode":401,"message":"Invalid Google auth token","error":"Unauthorized"}}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"example":{"statusCode":404,"message":"User not found","error":"Not Found"}}}}}},"tags":["Users"],"security":[{"bearer":[]}]}},"/v1/users/profile-image":{"post":{"operationId":"UsersController_uploadProfileImage","summary":"Upload profile image","description":"Uploads a profile image for the currently authenticated user. Accepts JPEG, PNG, and WebP images up to 5MB.","parameters":[{"name":"Authorization","in":"header","description":"Bearer token for Google authentication","required":true,"schema":{"type":"string","example":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","format":"binary","description":"Profile image file (JPEG, PNG, or WebP, max 5MB)"}}}}}},"responses":{"200":{"description":"Profile image uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDTO"}}}},"400":{"description":"Invalid file type or size","content":{"application/json":{"schema":{"examples":{"noFile":{"summary":"No file provided","value":{"statusCode":400,"message":"No file provided","error":"Bad Request"}},"invalidType":{"summary":"Invalid file type","value":{"statusCode":400,"message":"Invalid file type. Only JPEG, PNG, and WebP images are allowed.","error":"Bad Request"}},"fileTooLarge":{"summary":"File too large","value":{"statusCode":400,"message":"File size exceeds 5MB limit.","error":"Bad Request"}}}}}}},"401":{"description":"Authentication failed - invalid or missing token","content":{"application/json":{"schema":{"example":{"statusCode":401,"message":"Invalid Google auth token","error":"Unauthorized"}}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"example":{"statusCode":404,"message":"User not found","error":"Not Found"}}}}}},"tags":["Users"],"security":[{"bearer":[]}]}},"/v1/users/settings":{"put":{"operationId":"UsersSettingsController_updateUserSettings","summary":"Update user information","description":"Updates the current user information such as email address. Returns the updated user information.","parameters":[{"name":"Authorization","in":"header","description":"Bearer token for Google authentication","required":true,"schema":{"type":"string","example":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}}],"requestBody":{"required":true,"description":"User information to update","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserSettingsDTO"},"examples":{"updateEmail":{"summary":"Update email address","description":"Example updating user email address","value":{"email":"newemail@example.com"}}}}}},"responses":{"200":{"description":"User settings updated successfully","schema":{"example":{"user":{"id":"123e4567-e89b-12d3-a456-426614174000","googleId":"1234567890123456789","email":"user@example.com","name":"John Doe","image":"https://storage.googleapis.com/bucket/profile-images/user-id.jpg","createdAt":"2023-01-01T00:00:00.000Z","updatedAt":"2023-01-02T00:00:00.000Z","workspaces":[{"id":"456e7890-e89b-12d3-a456-426614174001","name":"My Workspace","description":"Main workspace for team collaboration","role":"ADMIN","organization":{"id":"789e0123-e89b-12d3-a456-426614174002","name":"My Organization","description":"Organization for managing workspaces"}}]},"message":"User settings updated successfully"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserSettingsResponseDTO"}}}},"400":{"description":"Invalid request data or nickname already in use","content":{"application/json":{"schema":{"examples":{"emailInUse":{"summary":"Email already in use","value":{"statusCode":400,"message":"Email already in use","error":"Bad Request"}},"invalidData":{"summary":"Invalid request data","value":{"statusCode":400,"message":["email must be a valid email address"],"error":"Bad Request"}}}}}}},"401":{"description":"Authentication failed - invalid or missing token","content":{"application/json":{"schema":{"example":{"statusCode":401,"message":"Invalid Google auth token","error":"Unauthorized"}}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"example":{"statusCode":404,"message":"User not found","error":"Not Found"}}}}}},"tags":["Users"],"security":[{"bearer":[]}]}},"/v1/user-preferences/{key}":{"get":{"operationId":"UserPreferenceController_findByKey","summary":"Get user preference by key","description":"Retrieves a specific user preference by its key. Returns null if not found.","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}},{"name":"chave","required":true,"in":"path","description":"Preference key identifier","schema":{"example":"requisicoes.visibleColumns"}},{"name":"Authorization","in":"header","description":"Bearer token for authentication","required":true,"schema":{"type":"string","example":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}}],"responses":{"200":{"description":"Preference retrieved successfully or null if not found","content":{"application/json":{"schema":{"example":{"id":"clxxxxxxxxxxxxxxxxxxxxxxxx","userId":"clxxxxxxxxxxxxxxxxxxxxxxxx","chave":"requisicoes.visibleColumns","valor":{"visibleColumns":["reqId","status"]},"createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z"}}}}},"401":{"description":"Authentication failed - invalid or missing token"}},"tags":["User Preferences"],"security":[{"bearer":[]}]},"put":{"operationId":"UserPreferenceController_upsert","summary":"Upsert user preference","description":"Creates or updates a user preference for the given key.","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}},{"name":"chave","required":true,"in":"path","description":"Preference key identifier","schema":{"example":"requisicoes.visibleColumns"}},{"name":"Authorization","in":"header","description":"Bearer token for authentication","required":true,"schema":{"type":"string","example":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertUserPreferenceDto"}}}},"responses":{"200":{"description":"Preference upserted successfully","content":{"application/json":{"schema":{"example":{"id":"clxxxxxxxxxxxxxxxxxxxxxxxx","userId":"clxxxxxxxxxxxxxxxxxxxxxxxx","chave":"requisicoes.visibleColumns","valor":{"visibleColumns":["reqId","status"]},"createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z"}}}}},"401":{"description":"Authentication failed - invalid or missing token"}},"tags":["User Preferences"],"security":[{"bearer":[]}]},"delete":{"operationId":"UserPreferenceController_delete","summary":"Delete user preference","description":"Deletes a specific user preference by its key.","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}},{"name":"chave","required":true,"in":"path","description":"Preference key identifier","schema":{"example":"requisicoes.visibleColumns"}},{"name":"Authorization","in":"header","description":"Bearer token for authentication","required":true,"schema":{"type":"string","example":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}}],"responses":{"204":{"description":"Preference deleted successfully"},"401":{"description":"Authentication failed - invalid or missing token"}},"tags":["User Preferences"],"security":[{"bearer":[]}]}},"/v1/organizations":{"post":{"operationId":"OrganizationController_create","summary":"Create a new organization (Dooor platform admin only)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrganizationDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationResponseDto"}}}},"403":{"description":"Dooor admin only"}},"tags":["Organizations"],"security":[{"bearer":[]}]},"get":{"operationId":"OrganizationController_findAll","summary":"Get all organizations where the user is a member","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationResponseDto"}}}}}},"tags":["Organizations"],"security":[{"bearer":[]}]}},"/v1/organizations/{id}":{"get":{"operationId":"OrganizationController_findOne","summary":"Get organization by ID with workspaces","parameters":[{"name":"id","required":true,"in":"path","description":"Organization ID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationWithWorkspacesResponseDto"}}}}},"tags":["Organizations"],"security":[{"bearer":[]}]},"patch":{"operationId":"OrganizationController_update","summary":"Update organization","parameters":[{"name":"id","required":true,"in":"path","description":"Organization ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrganizationDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationResponseDto"}}}}},"tags":["Organizations"],"security":[{"bearer":[]}]},"delete":{"operationId":"OrganizationController_remove","summary":"Delete organization","parameters":[{"name":"id","required":true,"in":"path","description":"Organization ID","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["Organizations"],"security":[{"bearer":[]}]}},"/v1/organizations/{id}/stats":{"get":{"operationId":"OrganizationController_getStats","summary":"Get organization statistics","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"workspacesCount":{"type":"number"},"totalMembersCount":{"type":"number"}}}}}}},"tags":["Organizations"],"security":[{"bearer":[]}]}},"/v1/organizations/{orgId}/members":{"get":{"operationId":"OrganizationMembersController_list","summary":"List members of an organization (UserOrgs)","parameters":[{"name":"orgId","required":true,"in":"path","description":"Organization ID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationMemberResponseDto"}}}}}},"tags":["Organization Members"],"security":[{"access-token":[]}]}},"/v1/organizations/{orgId}/members/{membershipId}":{"get":{"operationId":"OrganizationMembersController_get","summary":"Get a member by membership ID","parameters":[{"name":"orgId","required":true,"in":"path","description":"Organization ID","schema":{"type":"string"}},{"name":"membershipId","required":true,"in":"path","description":"Membership ID (UserOrg.id)","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationMemberResponseDto"}}}}},"tags":["Organization Members"],"security":[{"access-token":[]}]},"patch":{"operationId":"OrganizationMembersController_update","summary":"Update tags / permissions / roles of a member","parameters":[{"name":"orgId","required":true,"in":"path","description":"Organization ID","schema":{"type":"string"}},{"name":"membershipId","required":true,"in":"path","description":"Membership ID (UserOrg.id)","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrganizationMemberDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationMemberResponseDto"}}}}},"tags":["Organization Members"],"security":[{"access-token":[]}]},"delete":{"operationId":"OrganizationMembersController_remove","summary":"Remove a member from the organization","parameters":[{"name":"orgId","required":true,"in":"path","description":"Organization ID","schema":{"type":"string"}},{"name":"membershipId","required":true,"in":"path","description":"Membership ID (UserOrg.id)","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["Organization Members"],"security":[{"access-token":[]}]}},"/v1/workspaces":{"get":{"operationId":"WorkspaceController_getWorkspaces","summary":"Get all workspaces for a user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceResponseDto"}}}}}},"tags":["Workspaces"],"security":[{"bearer":[]}]},"post":{"operationId":"WorkspaceController_create","summary":"Create a new workspace","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkspaceDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceResponseDto"}}}}},"tags":["Workspaces"],"security":[{"bearer":[]}]}},"/v1/workspaces/organization/{organizationId}":{"get":{"operationId":"WorkspaceController_findAllByOrganization","summary":"Get all workspaces for an organization","parameters":[{"name":"organizationId","required":true,"in":"path","description":"Organization ID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceResponseDto"}}}}}},"tags":["Workspaces"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}":{"get":{"operationId":"WorkspaceController_findOne","summary":"Get workspace by ID","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceResponseDto"}}}}},"tags":["Workspaces"],"security":[{"bearer":[]}]},"patch":{"operationId":"WorkspaceController_update","summary":"Update workspace","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkspaceDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceResponseDto"}}}}},"tags":["Workspaces"],"security":[{"bearer":[]}]},"delete":{"operationId":"WorkspaceController_remove","summary":"Delete workspace","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["Workspaces"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/logo":{"post":{"operationId":"WorkspaceController_uploadLogo","summary":"Upload workspace logo","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Logo image file (JPEG, PNG, WebP or SVG, max 2MB)"}}}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceResponseDto"}}}}},"tags":["Workspaces"],"security":[{"bearer":[]}]},"delete":{"operationId":"WorkspaceController_removeLogo","summary":"Remove workspace logo","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceResponseDto"}}}}},"tags":["Workspaces"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/members":{"post":{"operationId":"MembersController_addMember","summary":"Add a member to the workspace by email + role (direct-add, no accept step)","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddMemberByEmailDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceMemberResponseDto"}}}}},"tags":["Workspace Members"],"security":[{"access-token":[]}]},"get":{"operationId":"MembersController_getMembers","summary":"Get all members of a workspace","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceMemberResponseDto"}}}}}},"tags":["Workspace Members"],"security":[{"access-token":[]}]}},"/v1/workspaces/{workspaceId}/members/{memberId}":{"get":{"operationId":"MembersController_getMember","summary":"Get a workspace member by membership ID","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}},{"name":"memberId","required":true,"in":"path","description":"Membership ID (UserOrg.id)","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceMemberResponseDto"}}}},"404":{"description":""}},"tags":["Workspace Members"],"security":[{"access-token":[]}]}},"/v1/workspaces/{workspaceId}/members/leave":{"delete":{"operationId":"MembersController_leave","summary":"Leave the workspace (voluntary)","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["Workspace Members"],"security":[{"access-token":[]}]}},"/v1/workspaces/{workspaceId}/members/{userId}":{"delete":{"operationId":"MembersController_removeUser","summary":"Remove a user from the workspace (admin)","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","description":"User ID to remove","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["Workspace Members"],"security":[{"access-token":[]}]}},"/v1/roles":{"get":{"operationId":"PermissionsController_listRoles","summary":"List roles of current organization","parameters":[],"responses":{"200":{"description":"Roles retrieved"}},"tags":["Roles & Permissions Admin"],"security":[{"bearer":[]}]},"post":{"operationId":"PermissionsController_createRole","summary":"Create role","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRoleAbacDto"}}}},"responses":{"201":{"description":"Role created"},"409":{"description":"Role name conflict"}},"tags":["Roles & Permissions Admin"],"security":[{"bearer":[]}]}},"/v1/roles/{roleId}":{"get":{"operationId":"PermissionsController_getRole","summary":"Get role detail","parameters":[{"name":"roleId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Role detail"},"404":{"description":"Role not found"}},"tags":["Roles & Permissions Admin"],"security":[{"bearer":[]}]},"put":{"operationId":"PermissionsController_updateRole","summary":"Update role (replace permissions/tags)","parameters":[{"name":"roleId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRoleAbacDto"}}}},"responses":{"200":{"description":"Role updated"}},"tags":["Roles & Permissions Admin"],"security":[{"bearer":[]}]},"delete":{"operationId":"PermissionsController_deleteRole","summary":"Delete role","parameters":[{"name":"roleId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Role deleted"},"409":{"description":"System roles cannot be deleted"}},"tags":["Roles & Permissions Admin"],"security":[{"bearer":[]}]}},"/v1/permissions":{"get":{"operationId":"PermissionsController_listAvailablePermissions","summary":"List available permission keys (system + org)","parameters":[],"responses":{"200":{"description":"Permission catalog"}},"tags":["Roles & Permissions Admin"],"security":[{"bearer":[]}]}},"/v1/me/scope":{"get":{"operationId":"MeScopeController_getScope","summary":"Effective ABAC scope of current user (Story 1.6 + 9.1 AC3)","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeScopeResponseDto"}}}}},"tags":["Me"],"security":[{"bearer":[]}]}},"/v1/ingestion/runs":{"post":{"operationId":"IngestionRunController_create","summary":"Submete arquivo XLSX/CSV/PDF para a pipeline canonical de ingestão","description":"Cria `IngestionLog` status=queued + enfileira job canonical `ingestion-run-canonical` para hand-off à pipeline.","parameters":[{"name":"workspaceId","required":true,"in":"query","description":"Workspace alvo da ingestão. Também enviado no body multipart para compatibilidade.","schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","workspaceId"],"properties":{"file":{"type":"string","format":"binary","description":"Arquivo XLSX/CSV/PDF (≤200MB)."},"workspaceId":{"type":"string","format":"uuid"},"familyHint":{"type":"string","maxLength":200}}}}}},"responses":{"202":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIngestionRunResponseDto"}}}},"400":{"description":"Arquivo inválido — `INGESTION_INVALID_FILE` com reason taxonômico."},"403":{"description":"Sem permissão (silencioso)."}},"tags":["Data Ingestion"],"security":[{"bearer":[]}]}},"/v1/ingestion/runs/upload-url":{"post":{"operationId":"IngestionRunController_initUpload","summary":"Inicia upload direto para bucket via URL pré-assinada","description":"Cria `IngestionLog` status=uploading e devolve uma URL assinada do BucketPort. O browser envia o arquivo direto ao bucket e chama complete ao final.","parameters":[{"name":"workspaceId","required":true,"in":"query","description":"Workspace alvo da ingestão. Também enviado no body multipart para compatibilidade.","schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitIngestionRunUploadDto"}}}},"responses":{"202":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitIngestionRunUploadResponseDto"}}}}},"tags":["Data Ingestion"],"security":[{"bearer":[]}]}},"/v1/ingestion/runs/{ingestionRunId}/complete":{"post":{"operationId":"IngestionRunController_completeUpload","summary":"Finaliza upload direto e enfileira ingestão canonical","description":"Valida metadados e assinatura do arquivo armazenado, move o `IngestionLog` para status=queued e enfileira `ingestion-run-canonical`.","parameters":[{"name":"ingestionRunId","required":true,"in":"path","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"query","description":"Workspace alvo da ingestão. Também enviado no body multipart para compatibilidade.","schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteIngestionRunUploadDto"}}}},"responses":{"202":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIngestionRunResponseDto"}}}}},"tags":["Data Ingestion"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/pipeline-runs":{"get":{"operationId":"IngestionRunsController_list","summary":"List ingestion pipeline runs (cursor-based)","parameters":[{"name":"limit","required":false,"in":"query","description":"Default 50, max 200","schema":{"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Keyset cursor opaco (base64url)","schema":{}},{"name":"to","required":false,"in":"query","description":"ISO 8601 datetime lte filter on createdAt","schema":{}},{"name":"from","required":false,"in":"query","description":"ISO 8601 datetime gte filter on createdAt","schema":{}},{"name":"ingestionLogId","required":false,"in":"query","description":"Filter by canonical IngestionLog id returned as ingestionRunId by POST /v1/ingestion/runs","schema":{}},{"name":"familyId","required":false,"in":"query","schema":{}},{"name":"status","required":false,"in":"query","schema":{"enum":["PENDING","RUNNING","COMPLETED","FAILED","CANCELLED","WAITING_FOR_GATE","PAUSED"],"type":"string"}}],"responses":{"200":{"description":"Paginated list of pipeline runs"},"401":{"description":"Unauthorized"},"403":{"description":"Missing pipeline.read permission"}},"tags":["Pipeline Runs (Ingestion Canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/pipeline-runs/family-coverage":{"get":{"operationId":"IngestionRunsController_familyCoverage","summary":"Per-family ingestion coverage (FR6 multifamília)","description":"Aggregates WorkspaceMaterial.familyId (per-material) into per-family coverage. A single ingestion can mix families; PipelineRun.familyId (single-valued) cannot represent that distribution — this endpoint can.","parameters":[{"name":"ingestionLogId","required":false,"in":"query","description":"Restrict coverage to a single ingestion (ingestionRunId). Omit to aggregate the whole workspace.","schema":{"type":"string"}}],"responses":{"200":{"description":"Per-family coverage list"},"403":{"description":"Missing pipeline.read permission"}},"tags":["Pipeline Runs (Ingestion Canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/pipeline-runs/{runId}":{"get":{"operationId":"IngestionRunsController_getById","summary":"Get pipeline run detail with steps timeline + ingestion outcome/duplicates","description":"Compõe o detalhe da run (steps + payloads) com `outcome` (breakdown canonical/workspace + duplicados detectados) e `duplicates` (evidência por externalId), derivados dos `PipelineStepRun.output` curados. Ambos `null` quando o passo de origem não produziu output legível (FE é render-when-present).","parameters":[{"name":"runId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Pipeline run detail with outcome + duplicates"},"403":{"description":"Missing pipeline.read permission"},"404":{"description":"Pipeline run not found"}},"tags":["Pipeline Runs (Ingestion Canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/pipeline-runs/{runId}/source/raw":{"get":{"operationId":"IngestionRunsController_getSourceRaw","summary":"Bytes da planilha ORIGINAL (proxy same-origin)","description":"Faz proxy dos bytes do arquivo original do GCS pelo MESMO origin, para o preview do FE renderizar sem esbarrar na CSP (que bloqueia a signed URL do GCS). Stream direto (não carrega tudo em memória). 404 quando o run não tem planilha original persistida (runs antigos sem gcsFilePath).","parameters":[{"name":"runId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Bytes do arquivo original (stream)"},"403":{"description":"Missing pipeline.read permission"},"404":{"description":"Run não encontrado ou planilha original indisponível"}},"tags":["Pipeline Runs (Ingestion Canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/pipeline-runs/{runId}/source":{"get":{"operationId":"IngestionRunsController_getSourceFile","summary":"Signed URL para a planilha ORIGINAL da ingestão (preview/download)","description":"Resolve IngestionLog.gcsFilePath do run e devolve uma URL assinada inline com TTL curto. 404 quando o run não tem arquivo original persistido (runs antigos sem gcsFilePath).","parameters":[{"name":"runId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Signed source file URL"},"403":{"description":"Missing pipeline.read permission"},"404":{"description":"Run não encontrado ou planilha original indisponível"}},"tags":["Pipeline Runs (Ingestion Canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/pipeline-runs/{runId}/materials":{"get":{"operationId":"IngestionRunsController_listMaterials","summary":"Materiais (WorkspaceMaterial) gerados por esta ingestão","description":"Lista paginada (keyset) por ingestionLogId do run. Complementa family-coverage (sumário) com o detalhe linha-a-linha de cada material persistido.","parameters":[{"name":"runId","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Default 50, max 200","schema":{"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Keyset cursor opaco (base64url)","schema":{}}],"responses":{"200":{"description":"Paginated list of materials produced by the run"},"403":{"description":"Missing pipeline.read permission"},"404":{"description":"Pipeline run not found"}},"tags":["Pipeline Runs (Ingestion Canonical)"],"security":[{"bearer":[]}]}},"/v1/governance/rules":{"get":{"operationId":"GovernanceController_listRules","summary":"Lista governance rules visíveis ao workspace atual.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GovernanceRuleDto"}}}}}},"tags":["Governance"],"security":[{"bearer":[]}]}},"/v1/governance/approval-requests":{"get":{"operationId":"GovernanceController_listPendingApprovals","summary":"Lista approval requests pendentes do workspace atual.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GovernanceApprovalRequestDto"}}}}}},"tags":["Governance"],"security":[{"bearer":[]}]}},"/v1/families":{"get":{"operationId":"FamilyController_findAll","summary":"List canonical families for the current Org","parameters":[{"name":"status","required":false,"in":"query","schema":{"enum":["DRAFT","ACTIVE","DEPRECATED"],"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Opaque keyset cursor encoded as base64url.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"x-workspace-id","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of canonical families"}},"tags":["Families (canonical)"],"security":[{"bearer":[]}]},"post":{"operationId":"FamilyController_create","summary":"Create a canonical family in DRAFT status","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFamilyDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyResponseDto"}}}},"409":{"description":"Family with similar name already exists"}},"tags":["Families (canonical)"],"security":[{"bearer":[]}]}},"/v1/families/{id}":{"get":{"operationId":"FamilyController_findById","summary":"Get canonical family by id","parameters":[{"name":"id","required":true,"in":"path","description":"Family ID","schema":{"type":"string"}},{"name":"x-workspace-id","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyResponseDto"}}}},"404":{"description":"Family not found"}},"tags":["Families (canonical)"],"security":[{"bearer":[]}]},"patch":{"operationId":"FamilyController_update","summary":"Update canonical family metadata or archive status","parameters":[{"name":"id","required":true,"in":"path","description":"Family ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFamilyDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyResponseDto"}}}},"403":{"description":"Manual ACTIVE transition rejected"},"404":{"description":"Family not found"}},"tags":["Families (canonical)"],"security":[{"bearer":[]}]},"delete":{"operationId":"FamilyController_delete","summary":"Soft-delete canonical family by marking it DEPRECATED","parameters":[{"name":"id","required":true,"in":"path","description":"Family ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Family soft-deleted"},"404":{"description":"Family not found"}},"tags":["Families (canonical)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/taxonomy/import":{"post":{"operationId":"CategorizationController_importTaxonomy","summary":"Importa (idempotente) a taxonomia CSN 3 níveis + regras da Org","description":"Upsert da árvore por (level, code); nó ausente vira DEPRECATED; regra alterada cria versão N+1. Re-import idêntico é no-op.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportTaxonomyDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaxonomyImportResultDto"}}}},"400":{"description":"Payload de taxonomia inválido"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/taxonomy":{"get":{"operationId":"CategorizationController_getTaxonomy","summary":"Árvore de categorias CSN ACTIVE da Org (hierárquica)","parameters":[],"responses":{"200":{"description":"Árvore 3 níveis"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/rules":{"get":{"operationId":"CategorizationController_getRules","summary":"Regras de categorização","description":"Sem `categoryNodeId`: regras ACTIVE da Org. Com `categoryNodeId`: histórico completo de versões daquela categoria.","parameters":[{"name":"categoryNodeId","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/classify":{"post":{"operationId":"CategorizationController_classify","summary":"Classifica materiais on-demand (síncrono)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassifyMaterialsDto"}}}},"responses":{"200":{"description":"Sumário + resultado por material"},"409":{"description":"Org sem taxonomia de categorização configurada"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/batch":{"post":{"operationId":"CategorizationController_batch","summary":"Enfileira categorização em lote de uma família","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCategorizeDto"}}}},"responses":{"202":{"description":"Lote enfileirado"},"404":{"description":"Família não encontrada"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/results":{"get":{"operationId":"CategorizationController_listResults","summary":"Lista resultados de categorização (keyset cursor)","parameters":[{"name":"familyId","required":false,"in":"query","description":"Filtra por família.","schema":{"type":"string"}},{"name":"reviewRequired","required":false,"in":"query","description":"Filtra por necessidade de revisão humana.","schema":{"type":"boolean"}},{"name":"cursor","required":false,"in":"query","description":"Cursor keyset de paginação.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Itens por página (1-200, default 50).","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/results/{materialId}":{"get":{"operationId":"CategorizationController_getResult","summary":"Resultado de categorização de um material","parameters":[{"name":"materialId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"404":{"description":"Material sem categorização"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/divergences/diagnose":{"post":{"operationId":"CategorizationDivergencesController_diagnose","summary":"Enfileira o diagnóstico de mal-categorizados de uma família","description":"Re-roda o motor de categorização contra as regras vigentes e abre divergências para os materiais cuja categoria atual diverge.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiagnoseFamilyDto"}}}},"responses":{"202":{"description":"Diagnóstico enfileirado"},"404":{"description":"Família não encontrada"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/divergences/bulk-accept":{"post":{"operationId":"CategorizationDivergencesController_bulkAccept","summary":"Aceita N reclassificações numa transação","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkAcceptDivergencesDto"}}}},"responses":{"200":{"description":"Resumo do bulk-accept"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/divergences":{"get":{"operationId":"CategorizationDivergencesController_listDivergences","summary":"Lista divergências (keyset cursor, ordenadas por severidade)","parameters":[{"name":"familyId","required":false,"in":"query","description":"Filtra por família.","schema":{"type":"string"}},{"name":"severity","required":false,"in":"query","schema":{"enum":["HIGH","MEDIUM","LOW"],"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["OPEN","ACCEPTED","REJECTED"],"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Cursor keyset de paginação.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Itens por página (1-200, default 50).","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/divergences/{id}":{"get":{"operationId":"CategorizationDivergencesController_getDivergence","summary":"Detalhe de uma divergência","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"404":{"description":"Divergência não encontrada"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/divergences/{id}/accept":{"post":{"operationId":"CategorizationDivergencesController_accept","summary":"Aceita a reclassificação — grava MANUAL_OVERRIDE","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Divergência aceita"},"404":{"description":"Divergência OPEN não encontrada"},"409":{"description":"Taxonomia mudou — re-diagnostique"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/categorization/divergences/{id}/reject":{"post":{"operationId":"CategorizationDivergencesController_reject","summary":"Rejeita a reclassificação — registra o motivo, mantém a categoria atual","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectDivergenceDto"}}}},"responses":{"404":{"description":"Divergência OPEN não encontrada"}},"tags":["Categorization (CSN)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/families/{familyId}/sub-family-suggestions/analyze":{"post":{"operationId":"SubFamilySuggestionController_analyze","summary":"Enqueue parametric dispersion analysis for a family","parameters":[{"name":"familyId","required":true,"in":"path","description":"Family ID","schema":{"type":"string"}}],"responses":{"202":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnqueueSubFamilyAnalysisResultDto"}}}},"404":{"description":"Family not found"}},"tags":["Sub-Family Suggestions (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/families/{familyId}/sub-family-suggestions":{"get":{"operationId":"SubFamilySuggestionController_list","summary":"List sub-family suggestions for a family (FR12a)","parameters":[{"name":"familyId","required":true,"in":"path","description":"Family ID","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["PENDING","ACCEPTED","REJECTED","MODIFIED"],"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SubFamilySuggestionListItemDto"}}}}}},"tags":["Sub-Family Suggestions (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/families/{familyId}/sub-family-suggestions/{suggestionId}":{"get":{"operationId":"SubFamilySuggestionController_detail","summary":"Get a sub-family suggestion with its native clusters","parameters":[{"name":"familyId","required":true,"in":"path","description":"Family ID","schema":{"type":"string"}},{"name":"suggestionId","required":true,"in":"path","description":"Sub-family suggestion ID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubFamilySuggestionDetailDto"}}}},"404":{"description":"Suggestion not found"}},"tags":["Sub-Family Suggestions (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/families/{familyId}/sub-family-suggestions/{suggestionId}/accept":{"post":{"operationId":"SubFamilySuggestionController_accept","summary":"Accept a suggestion — create the selected sub-families (FR12)","parameters":[{"name":"familyId","required":true,"in":"path","description":"Family ID","schema":{"type":"string"}},{"name":"suggestionId","required":true,"in":"path","description":"Sub-family suggestion ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptSubFamilySuggestionDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptSubFamilyResultDto"}}}},"404":{"description":"Suggestion or family not found"},"409":{"description":"Suggestion already decided, or slug collision"}},"tags":["Sub-Family Suggestions (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/families/{familyId}/sub-family-suggestions/{suggestionId}/reject":{"post":{"operationId":"SubFamilySuggestionController_reject","summary":"Reject a suggestion — the family stays whole (FR12)","parameters":[{"name":"familyId","required":true,"in":"path","description":"Family ID","schema":{"type":"string"}},{"name":"suggestionId","required":true,"in":"path","description":"Sub-family suggestion ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectSubFamilySuggestionDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubFamilySuggestionDetailDto"}}}},"404":{"description":"Suggestion not found"},"409":{"description":"Suggestion already decided"}},"tags":["Sub-Family Suggestions (canonical)"],"security":[{"bearer":[]}]}},"/v1/families/{familyId}/schema-versions":{"get":{"operationId":"FamilySchemaVersionController_list","summary":"List schema versions of a family (newest first)","parameters":[{"name":"familyId","required":true,"in":"path","schema":{"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Opaque cursor (base64url).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaVersionListResponseDto"}}}}},"tags":["Family Schema Versions"],"security":[{"bearer":[]}]},"post":{"operationId":"FamilySchemaVersionController_bump","summary":"Bump schema version (non-structural change — peso / similarityMatrix / thresholds)","parameters":[{"name":"familyId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BumpSchemaVersionRequestDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaVersionDetailDto"}}}},"400":{"description":"Structural change detected or invalid payload"},"403":{"description":"Governance requires CR approval"}},"tags":["Family Schema Versions"],"security":[{"bearer":[]}]}},"/v1/families/{familyId}/schema-versions/{versionId}":{"get":{"operationId":"FamilySchemaVersionController_get","summary":"Get a single schema version with full definition + audit trail","parameters":[{"name":"familyId","required":true,"in":"path","schema":{"type":"string"}},{"name":"versionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaVersionDetailDto"}}}}},"tags":["Family Schema Versions"],"security":[{"bearer":[]}]}},"/v1/families/{familyId}/schema-versions/structural":{"post":{"operationId":"FamilySchemaVersionController_structural","summary":"Apply structural change to family schema (add/remove field, type, eliminatory) — Dooor super-admin only","parameters":[{"name":"familyId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StructuralChangeRequestDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaVersionDetailDto"}}}},"400":{"description":"No structural change detected"},"403":{"description":"Requires Dooor super-admin"}},"tags":["Family Schema Versions"],"security":[{"bearer":[]}]}},"/v1/families/taxonomy/diagnose":{"post":{"operationId":"TaxonomyCleanupController_diagnose","summary":"Enfileira diagnose Org-wide da taxonomia","description":"Roda 3 detectores determinísticos (categorias subutilizadas, hierarquias N/A, Vendor List fragmentada via Manufacturer proxy) sobre toda a árvore. Materializa recomendações via upsert idempotente.","parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiagnoseTaxonomyDto"}}}},"responses":{"202":{"description":"Diagnose enfileirado"},"404":{"description":"Organização não encontrada"}},"tags":["Taxonomy Cleanup (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/taxonomy/recommendations":{"get":{"operationId":"TaxonomyCleanupController_list","summary":"Lista recomendações (keyset cursor, default status=OPEN)","parameters":[{"name":"severity","required":false,"in":"query","schema":{"enum":["HIGH","MEDIUM","LOW"],"type":"string"}},{"name":"type","required":false,"in":"query","schema":{"enum":["MERGE","SPLIT","RENAME","DELETE"],"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["OPEN","APPROVED","REJECTED","APPLIED","DISMISSED"],"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Cursor keyset de paginação.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Itens por página (1-100, default 50).","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["Taxonomy Cleanup (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/taxonomy/recommendations/{id}":{"get":{"operationId":"TaxonomyCleanupController_get","summary":"Detalhe de uma recomendação","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"404":{"description":"Recomendação não encontrada"}},"tags":["Taxonomy Cleanup (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/taxonomy/recommendations/{id}/simulate":{"post":{"operationId":"TaxonomyCleanupController_simulate","summary":"Recalcula impactPreview da recomendação","description":"Re-executa cascade preview determinístico. Útil quando a base mudou desde a detecção (novos materiais ingeridos, manufacturer atualizado).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Preview atualizado"},"404":{"description":"Recomendação não encontrada"}},"tags":["Taxonomy Cleanup (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/taxonomy/recommendations/{id}/apply":{"post":{"operationId":"TaxonomyCleanupController_apply","summary":"Aplica a recomendação (MERGE/SPLIT/RENAME/DELETE)","description":"Mutação transacional. DELETE com materiais ativos → 409 (MERGE primeiro). RENAME com code conflitante → 409. SPLIT exige proposedChildren preenchido.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyTaxonomyRecommendationDto"}}}},"responses":{"200":{"description":"Recomendação aplicada"},"404":{"description":"Recomendação OPEN não encontrada"},"409":{"description":"Conflito (materiais ativos, code duplicado, etc.)"}},"tags":["Taxonomy Cleanup (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/taxonomy/recommendations/{id}/reject":{"post":{"operationId":"TaxonomyCleanupController_reject","summary":"Rejeita a recomendação com motivo (≥5 chars)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectTaxonomyRecommendationDto"}}}},"responses":{"200":{"description":"Recomendação rejeitada"},"404":{"description":"OPEN não encontrada"}},"tags":["Taxonomy Cleanup (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/taxonomy/recommendations/{id}/dismiss":{"post":{"operationId":"TaxonomyCleanupController_dismiss","summary":"Descarta a recomendação (sem motivo obrigatório)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Recomendação descartada"},"404":{"description":"OPEN não encontrada"}},"tags":["Taxonomy Cleanup (CSN)"],"security":[{"bearer":[]}]}},"/v1/families/taxonomy/recommendations/bulk-dismiss":{"post":{"operationId":"TaxonomyCleanupController_bulkDismiss","summary":"Descarta N recomendações em transação (max 100)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkDismissTaxonomyRecommendationsDto"}}}},"responses":{"200":{"description":"Resumo do bulk-dismiss"}},"tags":["Taxonomy Cleanup (CSN)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/similarity/materials":{"get":{"operationId":"SimilarityController_findSimilarMaterials","summary":"Find similar materials (FR9+FR10) — schema-driven, deterministic, with FR9 breakdown and loosened fallback","parameters":[{"name":"material","required":true,"in":"query","description":"materialId (UUID) of the reference material","schema":{"type":"string"}},{"name":"family","required":false,"in":"query","description":"familyId (UUID) — optional override","schema":{"type":"string"}},{"name":"excludeObsolete","required":false,"in":"query","description":"Exclude obsolete materials (default: true). Hook only — full semantics in Epic 6.","schema":{"default":true,"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max candidates to return (1–50, default 20)","schema":{"default":20,"type":"number"}},{"name":"allowLoosening","required":false,"in":"query","description":"Allow loosened tolerance-band search when strict pass returns 0 candidates. Default true. Set false to receive honest fallback immediately.","schema":{"default":true,"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"responses":{"200":{"description":"Ranked candidates with FR9 breakdown. Includes looseningAttempted/looseningApplied/message for FR10. DEATH-1: {candidates:[], message:...} when nothing passes min confidence.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MaterialSimilarityResponseDto"}}}},"400":{"description":"Invalid material/family or no active schema version"},"403":{"description":"Insufficient permissions (requires material.read)"},"404":{"description":"Material or workspace not found"}},"tags":["Similarity Pipeline"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/similarity/analyze":{"post":{"operationId":"SimilarityController_analyze","summary":"Análise de similaridade ad-hoc — grupos efêmeros + FR9 breakdown, com weights/eliminatoryFields/threshold/scope parametrizáveis","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeSimilarityDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeSimilarityResponseDto"}}}},"400":{"description":"Schema inválido ou anchorMaterialId ausente em scope=anchor"},"404":{"description":"Família ou material âncora não encontrado"}},"tags":["Similarity Pipeline"],"security":[{"bearer":[]}]}},"/v1/chats":{"post":{"operationId":"ChatController_createChat","summary":"Create chat atomically with first user message + stream AI response via SSE (Story 7.4 Pattern F).","description":"POST /v1/chats now persists Chat + first UserMessage in a single Prisma.$transaction (chat never orphan) and immediately opens an SSE stream with the AI response. First SSE event is `chat_created` carrying { chatId, title }; subsequent events are the same as POST /chats/:id/messages (token, function_call, message_completed, done). Pattern D cost tracking covers both the user message persist and the AI turn.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateChatDto"}}}},"responses":{"200":{"description":"SSE stream — first event `chat_created` then AI tokens"}},"tags":["Chat"],"security":[{"bearer":[]}]},"get":{"operationId":"ChatController_listChats","summary":"List chats — caller sees own+public, or all when chat.read.all is granted","parameters":[{"name":"cursor","required":false,"in":"query","description":"Pagination cursor (chat id)","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size","schema":{"minimum":1,"maximum":100,"default":50,"type":"number"}}],"responses":{"200":{"description":"Chat list"}},"tags":["Chat"],"security":[{"bearer":[]}]}},"/v1/chats/{id}":{"get":{"operationId":"ChatController_getChat","summary":"Get chat history with all messages (scope-aware)","parameters":[{"name":"id","required":true,"in":"path","description":"Chat ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Chat detail"}},"tags":["Chat"],"security":[{"bearer":[]}]},"delete":{"operationId":"ChatController_deleteChat","summary":"Soft-delete a chat (creator only — chat.read.all is read-only)","parameters":[{"name":"id","required":true,"in":"path","description":"Chat ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Chat deleted"}},"tags":["Chat"],"security":[{"bearer":[]}]}},"/v1/chats/{id}/messages":{"post":{"operationId":"ChatController_sendMessage","summary":"Send a message and stream the assistant response via SSE","parameters":[{"name":"id","required":true,"in":"path","description":"Chat ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageDto"}}}},"responses":{"200":{"description":"SSE stream"}},"tags":["Chat"],"security":[{"bearer":[]}]}},"/v1/chats/{chatId}/messages/{messageId}/feedback":{"post":{"operationId":"ChatController_submitFeedback","summary":"Submit or update thumb up/down feedback on an AI message (Story 7.10 FR36)","description":"Upserts a feedback record for the given ASSISTANT message. Re-voting replaces the previous entry. Only the chat creator can submit feedback. Returns `wasCreated: true` on first submission.","parameters":[{"name":"chatId","required":true,"in":"path","description":"Chat ID","schema":{"type":"string"}},{"name":"messageId","required":true,"in":"path","description":"ChatMessage ID (ASSISTANT role only)","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitFeedbackDto"}}}},"responses":{"200":{"description":"Feedback upserted"},"400":{"description":"Message is not ASSISTANT role"},"403":{"description":"Caller is not the chat creator"},"404":{"description":"Chat or message not found / cross-workspace"}},"tags":["Chat"],"security":[{"bearer":[]}]}},"/v1/chats/{chatId}/attachments":{"post":{"operationId":"ChatController_uploadAttachment","summary":"Upload attachment (foto/plaqueta) and run Gemini Vision extraction (Story 7.6 AC1+AC2)","description":"Persists uploaded image to GCS, runs Gemini Vision extraction synchronously, returns signed URL (TTL 7d) + extraction result (manufacturer, model, dimensions, detectedTags, confidence). FE renders thumbnail + extraction chip in chat-message bubble. FE then sends gcsPath in attachments[] of subsequent POST /chats or POST /chats/:id/messages — orchestrator uses extracted context to enrich Gemini text call (AC3).","parameters":[{"name":"chatId","required":true,"in":"path","description":"Chat ID owning this attachment","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Image file: JPEG/PNG/HEIC/WEBP, ≤10MB."}}}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatAttachmentResponseDto"}}}},"400":{"description":"Missing file, unsupported MIME type, or >10MB."},"403":{"description":"No chat.write permission (silent — Story 1.15)."},"404":{"description":"Chat not found or not writable by caller."}},"tags":["Chat"],"security":[{"bearer":[]}]}},"/v1/admin/ai-tools":{"get":{"operationId":"AiToolsAdminController_list","summary":"List all registered AI tools (Story 7.1 PR-A)","description":"Returns all tools currently registered in AiToolsRegistry, with metadata (name, description, requiredPermissions, hasInputSchema, hasOutputSchema, sourceModule). Useful for operational audit + debugging the registry state in production. Tools legacy (sem @AITool decorator) aparecem com hasInputSchema=false/hasOutputSchema=false.","parameters":[],"responses":{"200":{"description":"Array of tool metadata (sorted by name)"},"403":{"description":"User is not Dooor super-admin"}},"tags":["Dooor Admin · AI Tools"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research/start":{"post":{"operationId":"SchemaResearchController_startResearch","summary":"Start schema-research pipeline for a canonical Family (Story 8.9 guardrailed)","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}},{"name":"x-reprocess-confirmation","in":"header","description":"Story 8.9 guardrail — confirmation token user-provided. Format: \"RE-RUN <SCOPE>\". User deve digitar exatamente o token retornado por POST /v1/reprocess/dry-run. Server compara case-sensitive; mismatch → 400.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartSchemaResearchDto"}}}},"responses":{"201":{"description":"Pipeline started; returns runId"},"400":{"description":"Missing/mismatch confirmation token or missing justification"},"404":{"description":"Family not found in org"},"409":{"description":"Active pipeline already exists for this family, or no materials found"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research/{runId}/resume":{"post":{"operationId":"SchemaResearchController_resumeResearch","summary":"Resume a failed or paused schema-research pipeline","parameters":[{"name":"runId","required":true,"in":"path","description":"PipelineRun ID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Pipeline resumed"},"404":{"description":"PipelineRun not found"},"409":{"description":"Pipeline not in FAILED or PAUSED status"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research/{runId}/cancel":{"post":{"operationId":"SchemaResearchController_cancelResearch","summary":"Cancel a running, gate-waiting, or paused schema-research pipeline","parameters":[{"name":"runId","required":true,"in":"path","description":"PipelineRun ID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Pipeline cancelled"},"404":{"description":"PipelineRun not found"},"409":{"description":"Pipeline not in cancellable status"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research/{runId}/pause":{"post":{"operationId":"SchemaResearchController_pauseResearch","summary":"Pause a running schema-research pipeline after the current step completes","parameters":[{"name":"runId","required":true,"in":"path","description":"PipelineRun ID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Pause signal sent"},"404":{"description":"PipelineRun not found"},"409":{"description":"Pipeline not in RUNNING status"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research/{runId}/steps/{stepName}/gate":{"post":{"operationId":"SchemaResearchController_submitGateDecision","summary":"Submit gate decision for an HITL gate step","parameters":[{"name":"runId","required":true,"in":"path","description":"PipelineRun ID","schema":{"type":"string"}},{"name":"stepName","required":true,"in":"path","description":"Gate step name (human-review-gate-1 | human-validation-gate-2)","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaResearchGateDecisionDto"}}}},"responses":{"200":{"description":"Gate decision recorded and pipeline resumed"},"400":{"description":"Invalid gate step name or missing required fields"},"404":{"description":"PipelineRun or gate step not found"},"409":{"description":"Pipeline not in WAITING_FOR_GATE status (race condition)"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research/{runId}/steps/{stepName}/artifact":{"patch":{"operationId":"SchemaResearchController_patchStepArtifact","summary":"Edit a step artifact during gate review (HITL)","parameters":[{"name":"runId","required":true,"in":"path","description":"PipelineRun ID","schema":{"type":"string"}},{"name":"stepName","required":true,"in":"path","description":"Gate step name whose artifact to override","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaResearchPatchArtifactDto"}}}},"responses":{"200":{"description":"Artifact override persisted"},"400":{"description":"Invalid gate step name"},"404":{"description":"PipelineRun or gate step not found"},"409":{"description":"Pipeline not in WAITING_FOR_GATE status"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]},"get":{"operationId":"SchemaResearchController_getStepArtifact","summary":"Get output artifact for a specific pipeline step","parameters":[{"name":"runId","required":true,"in":"path","description":"PipelineRun ID","schema":{"type":"string"}},{"name":"stepName","required":true,"in":"path","description":"Canonical step name (e.g. propose-fields, refine-schema)","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Step artifact (JSON)"},"404":{"description":"PipelineRun or step not found"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research":{"get":{"operationId":"SchemaResearchController_listRuns","summary":"List schema-research pipeline runs (cursor-based pagination)","parameters":[{"name":"limit","required":false,"in":"query","description":"Default 50, max 200","schema":{"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Keyset cursor (base64url opaque)","schema":{}},{"name":"to","required":false,"in":"query","description":"ISO 8601 datetime lte filter on createdAt","schema":{}},{"name":"from","required":false,"in":"query","description":"ISO 8601 datetime gte filter on createdAt","schema":{}},{"name":"familyId","required":false,"in":"query","description":"Filter by canonical Family ID","schema":{}},{"name":"status","required":false,"in":"query","schema":{"enum":["PENDING","RUNNING","COMPLETED","FAILED","CANCELLED","WAITING_FOR_GATE","PAUSED"],"type":"string"}}],"responses":{"200":{"description":"Paginated list of schema-research runs"},"401":{"description":"Unauthorized"},"403":{"description":"Missing family.read permission"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research/{runId}/detail":{"get":{"operationId":"SchemaResearchController_getRunDetail","summary":"Get shaped schema-research run detail for gate-review FE","parameters":[{"name":"runId","required":true,"in":"path","description":"PipelineRun ID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Schema-research run detail with gate state and editedSchemaOverrides"},"404":{"description":"PipelineRun not found"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research/{runId}":{"get":{"operationId":"SchemaResearchController_getRun","summary":"Get schema-research run details with steps timeline","parameters":[{"name":"runId","required":true,"in":"path","description":"PipelineRun ID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Schema-research run with steps timeline"},"404":{"description":"PipelineRun not found"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/schema-research/{runId}/gate-history":{"get":{"operationId":"SchemaResearchController_getGateHistory","summary":"Get gate decision history for a schema-research run","parameters":[{"name":"runId","required":true,"in":"path","description":"PipelineRun ID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Gate history array from PipelineRun.parameters"},"404":{"description":"PipelineRun not found"}},"tags":["Schema Research Pipeline (canonical)"],"security":[{"bearer":[]}]}},"/v1/reprocess/dry-run":{"post":{"operationId":"ReprocessGuardrailController_dryRun","summary":"Story 8.9 — pré-flight pra UI dialog 2-step confirmation","description":"Conta materials affected + retorna confirmation token sugerido. NÃO executa ação. UI usa pra preencher Dialog: \"Re-processar X afetará N materiais. Digite [token] pra confirmar.\"","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReprocessDryRunDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReprocessDryRunResponseDto"}}}}},"tags":["Reprocess Guardrails"],"security":[{"bearer":[]}]}},"/v1/metrics":{"get":{"operationId":"MetricsController_scrape","summary":"Prometheus scrape endpoint (text/plain, bearer-token gated)","parameters":[],"responses":{"200":{"description":""}},"tags":["Health"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/materials/{materialId}/spectra-check":{"get":{"operationId":"SpectraCheckController_get","summary":"Latest Spectra Check snapshot for a material (Story 5.7-impl)","description":"Returns the most recent computed score for the material, with per-axis breakdown and `degraded` flag. When `neverComputed=true`, UI should render a \"compute pending\" affordance (UX-DR9 humility — no intermediate badge state).","parameters":[{"name":"materialId","required":true,"in":"path","description":"Material UUID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"responses":{"200":{"description":"Latest snapshot or {neverComputed: true}"},"404":{"description":"Material not in workspace org"}},"tags":["Spectra Check"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/materials/{materialId}/spectra-check/recompute":{"post":{"operationId":"SpectraCheckController_recompute","summary":"Force a Spectra Check recompute (engineer-triggered)","description":"Runs SpectraCheckService.compute on demand. Persists new snapshot + updates Material denormalized fields. Returns the freshly-computed snapshot.","parameters":[{"name":"materialId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Snapshot computed + persisted"},"404":{"description":"Material not in workspace org"},"503":{"description":"No active SpectraCheckConfig in DB (seed missing)"}},"tags":["Spectra Check"],"security":[{"bearer":[]}]}},"/v1/admin/spectra-check/config":{"post":{"operationId":"SpectraCheckAdminController_bumpConfig","summary":"Bump SpectraCheckConfig — create new active version (Story 5.7-impl PR-C)","description":"Cria nova versão de pesos/threshold + marca como isActive=true. Atomic (deactivate antiga + insert nova em transação). Histórico permanente — versions antigas preservadas (snapshots históricos referenciam configVersion). Após bump, próximo compute pega config nova (cache invalidado).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BumpSpectraCheckConfigDto"}}}},"responses":{"201":{"description":"New config created + marked active"},"400":{"description":"Payload inválido (DTO validation)"},"403":{"description":"User is not Dooor super-admin"},"409":{"description":"Version já existe OR race condition (2 bumps concorrentes)"}},"tags":["Dooor Admin · Spectra Check"],"security":[{"bearer":[]}]},"get":{"operationId":"SpectraCheckAdminController_getConfig","summary":"Get active SpectraCheckConfig + last 5 versions history","description":"Returns the currently active config (consumed by SpectraCheckService.compute) plus the 5 most recent versions ordered by createdAt desc — useful for auditing calibration history. Includes `historicalCount` for total versions count.","parameters":[],"responses":{"200":{"description":"Active config + recent history"},"403":{"description":"User is not Dooor super-admin"}},"tags":["Dooor Admin · Spectra Check"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/materials":{"get":{"operationId":"MaterialController_findAll","summary":"List workspace materials (paginated, with filters)","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (1-based)","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Items per page","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"sortBy","required":false,"in":"query","description":"Sort field (legacy alias). Allowed: codigoSap, createdAt, updatedAt, estoque, precoUnitario","schema":{"enum":["codigoSap","createdAt","updatedAt","estoque","precoUnitario"],"type":"string"}},{"name":"sortOrder","required":false,"in":"query","description":"Sort direction","schema":{"default":"desc","enum":["asc","desc"],"type":"string"}},{"name":"familiaId","required":false,"in":"query","description":"Filter by Family ID (Family.id). Aliases: familyId (EN).","schema":{"type":"string"}},{"name":"familyId","required":false,"in":"query","description":"EN alias for familiaId — service merges both.","schema":{"type":"string"}},{"name":"fabricanteId","required":false,"in":"query","description":"Filter by Manufacturer ID. Aliases: manufacturerId (EN).","schema":{"type":"string"}},{"name":"manufacturerId","required":false,"in":"query","description":"EN alias for fabricanteId — service merges both.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Full text search across SAP code, model, series and part number","schema":{"type":"string"}},{"name":"hasSpecs","required":false,"in":"query","description":"Filter materials that have specs (true) or none (false). Omit to disable filter.","schema":{"type":"boolean"}},{"name":"excludeObsolete","required":false,"in":"query","description":"Story 6.3 — exclude DEPRECATED materials (default true). Set false for historical analysis or CIG review of obsolete flagged items.","schema":{"default":true,"type":"boolean"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Paginated material list"}},"tags":["Materials"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/materials/sap/{codigoSap}":{"get":{"operationId":"MaterialController_findByCodigoSap","summary":"Lookup material by SAP code (externalId)","parameters":[{"name":"codigoSap","required":true,"in":"path","description":"SAP material code (WorkspaceMaterial.externalId)","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Material found"},"403":{"description":"Missing material.read permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"Material not found in workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Materials"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/materials/{id}":{"get":{"operationId":"MaterialController_findById","summary":"Get material detail (with specs merged)","parameters":[{"name":"id","required":true,"in":"path","description":"WorkspaceMaterial ID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Material detail"},"403":{"description":"Missing material.read permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"Material not found in workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Materials"],"security":[{"bearer":[]}]},"delete":{"operationId":"MaterialController_delete","summary":"Delete a workspace material (operational instance)","parameters":[{"name":"id","required":true,"in":"path","description":"WorkspaceMaterial ID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"204":{"description":"Material deleted"},"403":{"description":"Missing material.delete permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"Material not found in workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Materials"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/materials/{id}/documents":{"get":{"operationId":"MaterialController_findDocuments","summary":"List material documents (datasheet/CAD/certificate/manual) with signed URLs","parameters":[{"name":"id","required":true,"in":"path","description":"WorkspaceMaterial ID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace ID","schema":{}}],"responses":{"200":{"description":"Array de documentos com signed URLs (inline, 7-day TTL)"},"403":{"description":"Missing material.read permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"Material not found in workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Materials"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/materials/{materialId}/cross-plant":{"get":{"operationId":"MaterialController_getCrossPlantInventory","summary":"Story 10.1 (FR60) — Cross-plant inventory com saldo + freshness + ETA frete","parameters":[{"name":"materialId","required":true,"in":"path","description":"Material canonical UUID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Origin workspace (caller perspective for ETA lookup)","schema":{}}],"responses":{"200":{"description":"Array de plants accessible ao caller; vazio se material sem inventory"},"403":{"description":"Missing material.read permission OR no workspace access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"Material not found in org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Materials"],"security":[{"bearer":[]}]}},"/v1/materials/{materialId}/taxonomy/sanity-check":{"post":{"operationId":"MaterialTaxonomyController_runSanityCheck","summary":"Cruza o material contra ECLASS/UNSPSC/NCM (sanity check FR13)","description":"Consulta a taxonomia internacional (Spectra Global) e devolve {eclassMatch, unspscMatch, ncmMatch, alignment, divergences, notes}. Read-only por default — passe `persistSnapshot=true` para gravar o resultado em MaterialTextSpec com `source=AI_INFERRED`. Consultivo, não fonte de verdade primária (FR13).","parameters":[{"name":"materialId","required":true,"in":"path","description":"Material canonical ID","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunMaterialTaxonomySanityCheckDto"}}}},"responses":{"200":{"description":"Sanity check result"},"404":{"description":"Material not found in org"}},"tags":["Materials — Taxonomy Sanity Check (FR13)"],"security":[{"bearer":[]}]}},"/v1/materials/submit-emissor":{"post":{"operationId":"MaterialDraftsController_submitEmissor","summary":"Submete um material via fluxo emissor (FR16a). Dedup → AUTO_LINK | DRAFT_CREATED.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmissorMaterialSubmissionDto"}}}},"responses":{"200":{"description":""}},"tags":["Material Drafts (FR16a / FR46)"],"security":[{"bearer":[]}]}},"/v1/materials/submit-emissor-via-chat/draft":{"post":{"operationId":"MaterialDraftsController_submitEmissorViaChatDraft","summary":"Story 7.8 (FR35) — emissor confirmou criação de draft via chat (sem match). Delega ao mesmo MaterialDraftService.submitAsEmissor; audit action canonical derivada do outcome do service (Pattern C dinâmico, P5 fix).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmissorChatSubmissionDraftDto"}}}},"responses":{"200":{"description":""}},"tags":["Material Drafts (FR16a / FR46)"],"security":[{"bearer":[]}]}},"/v1/materials/submit-emissor-via-chat/link":{"post":{"operationId":"MaterialDraftsController_submitEmissorViaChatLink","summary":"Story 7.8 (FR35) — emissor clicou \"Vincular esta planta\" num MaterialResultCard. Service força AUTO_LINK contra o `targetMaterialId` selecionado (P4 fix). Audit action derivada do outcome (Pattern C dinâmico).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmissorChatSubmissionLinkDto"}}}},"responses":{"200":{"description":""}},"tags":["Material Drafts (FR16a / FR46)"],"security":[{"bearer":[]}]}},"/v1/materials/drafts":{"get":{"operationId":"MaterialDraftsController_listDrafts","summary":"Lista drafts (default status=DRAFT) com keyset cursor.","parameters":[{"name":"status","required":false,"in":"query","description":"Filter by status (default DRAFT)","schema":{"enum":["DRAFT","ACTIVE","REJECTED","DEPRECATED"],"type":"string"}},{"name":"familyId","required":false,"in":"query","description":"Filter by familyId","schema":{"type":"string"}},{"name":"workspaceOrigin","required":false,"in":"query","description":"Filter by submittedFromWorkspaceId","schema":{"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Cursor for keyset pagination","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size (default 25, max 100)","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["Material Drafts (FR16a / FR46)"],"security":[{"bearer":[]}]}},"/v1/materials/drafts/{id}":{"get":{"operationId":"MaterialDraftsController_getDraft","summary":"Detalha um draft incluindo workspaceMaterials linkados.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"404":{"description":"Draft not found"}},"tags":["Material Drafts (FR16a / FR46)"],"security":[{"bearer":[]}]}},"/v1/materials/drafts/{id}/promote":{"post":{"operationId":"MaterialDraftsController_promoteDraft","summary":"Promove DRAFT → ACTIVE (Marcos aprovou).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoteDraftDto"}}}},"responses":{"200":{"description":""}},"tags":["Material Drafts (FR16a / FR46)"],"security":[{"bearer":[]}]}},"/v1/materials/drafts/{id}/link-to-existing":{"post":{"operationId":"MaterialDraftsController_linkToExisting","summary":"Vincula WorkspaceMaterial do draft a um Material ACTIVE.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkDraftToExistingDto"}}}},"responses":{"200":{"description":""}},"tags":["Material Drafts (FR16a / FR46)"],"security":[{"bearer":[]}]}},"/v1/materials/drafts/{id}/reject":{"post":{"operationId":"MaterialDraftsController_rejectDraft","summary":"Rejeita o draft (SAP code mantido, materialId orphaned).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectDraftDto"}}}},"responses":{"200":{"description":""}},"tags":["Material Drafts (FR16a / FR46)"],"security":[{"bearer":[]}]}},"/v1/materials/drafts/bulk-promote":{"post":{"operationId":"MaterialDraftsController_bulkPromote","summary":"Bulk-promote (cap 100). Backend usa transação serializable.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkPromoteDraftsDto"}}}},"responses":{"200":{"description":""}},"tags":["Material Drafts (FR16a / FR46)"],"security":[{"bearer":[]}]}},"/v1/families/{familyId}/duplicates/detect":{"post":{"operationId":"DuplicateClustersController_enqueueDetect","summary":"Enfileira job de detecção de duplicidades por família.","parameters":[{"name":"familyId","required":true,"in":"path","description":"Family UUID","schema":{"type":"string"}}],"responses":{"202":{"description":"Detection job enqueued"}},"tags":["Duplicate Clusters (FR16)"],"security":[{"bearer":[]}]}},"/v1/duplicate-clusters":{"get":{"operationId":"DuplicateClustersController_list","summary":"Lista clusters (default status=OPEN) ordenados por aggregateScore desc.","parameters":[{"name":"familyId","required":false,"in":"query","description":"Filter by familyId","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by status (default OPEN)","schema":{"enum":["OPEN","CONFIRMED","REJECTED","SUPERSEDED"],"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Cursor for keyset pagination","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size (default 25, max 100)","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["Duplicate Clusters (FR16)"],"security":[{"bearer":[]}]}},"/v1/duplicate-clusters/{id}":{"get":{"operationId":"DuplicateClustersController_get","summary":"Detalhe do cluster com materiais + workspaceMaterials por planta.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Duplicate Clusters (FR16)"],"security":[{"bearer":[]}]}},"/v1/duplicate-clusters/{id}/confirm":{"post":{"operationId":"DuplicateClustersController_confirm","summary":"Confirma o cluster (CIG: estes materiais são a mesma coisa).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmClusterDto"}}}},"responses":{"200":{"description":""}},"tags":["Duplicate Clusters (FR16)"],"security":[{"bearer":[]}]}},"/v1/duplicate-clusters/{id}/reject":{"post":{"operationId":"DuplicateClustersController_reject","summary":"Rejeita o cluster (falso positivo).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectClusterDto"}}}},"responses":{"200":{"description":""}},"tags":["Duplicate Clusters (FR16)"],"security":[{"bearer":[]}]}},"/v1/obsolescence/materials/{materialId}/revert-deprecation":{"post":{"operationId":"ObsolescenceController_revertDeprecation","summary":"Reverte depreciação automática de um material (CIG manual override)","description":"Material.status DEPRECATED → ACTIVE; deprecatedAt/Reason cleared. Audit material.deprecation_reverted persistido com ator + reason.","parameters":[{"name":"materialId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevertDeprecationDto"}}}},"responses":{"204":{"description":""}},"tags":["Obsolescence"],"security":[{"bearer":[]}]}},"/v1/command-center/overview":{"get":{"operationId":"CommandCenterController_getOverview","summary":"Story 8.1 — counts agregados pra landing /comando (cards \"Hoje\" + \"Atividade\").","description":"Aggregator: pendingApprovals (governance), openPriceAnomalies, pendingMaterialDocuments, recentAuditCount (24h), pendingDraftMaterials (Story 4.5), auditCoverage (V1 sentinel 100%/ok; real metric → deferred 8.1-AUDIT-COVERAGE-REAL).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommandCenterOverviewDto"}}}}},"tags":["Command Center"],"security":[{"bearer":[]}]}},"/v1/command-center/recent-activity":{"get":{"operationId":"CommandCenterController_getRecentActivity","summary":"Story 8.1 — timeline atividade Command Center (audit_log filtrado CC-actions).","description":"Keyset cursor pagination. Actions whitelist: material.draft_*, change_request.*, pipeline.*, export.*, material.auto_deprecated, material.deprecation_reverted, material.workspace_linked. Default limit=20, max=100.","parameters":[{"name":"limit","required":false,"in":"query","description":"Max items per page (default 20, max 100).","schema":{"default":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Cursor base64 keyset {id, createdAt} pra próxima página.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecentActivityResponseDto"}}}}},"tags":["Command Center"],"security":[{"bearer":[]}]}},"/v1/command-center/productivity":{"get":{"operationId":"CommandCenterController_getProductivity","summary":"Story 8.11 — métricas de produtividade do backoffice (Jornada 6 Marcos / PRD §401).","description":"Card \"Produtividade hoje\" + tendência 7 dias + baseline mês anterior + audit coverage. Whitelist canônica: cr.approved/rejected, material.draft_approved/promoted, material.workspace_linked, material.deprecation_reverted, ingestion.submitted, export.csv_generated/sap_flat_file, view.export.blocked_ip_protected. auditCoverage V1 sentinel 100%/ok (real metric → deferred 8.11-AUDIT-COVERAGE-PROMETHEUS).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductivityOverviewDto"}}}}},"tags":["Command Center"],"security":[{"bearer":[]}]}},"/v1/command-center/obsolescence-queue":{"get":{"operationId":"ObsolescenceReviewController_list","summary":"Lista a queue unificada de materiais flagados (FR30): obsolescenceFlag, DEPRECATED, PriceAnomaly.open, obsolete-em-uso best-effort.","parameters":[{"name":"workspaceId","required":true,"in":"query","description":"Workspace alvo da queue unificada.","schema":{"format":"uuid","type":"string"}},{"name":"familyId","required":false,"in":"query","description":"Filtra por família.","schema":{"format":"uuid","type":"string"}},{"name":"signalTypes","required":false,"in":"query","description":"Filtra por tipos de sinal de obsolescência ativos.","schema":{"maxItems":3,"type":"array","items":{"type":"string","enum":["GLOBAL_DISCONTINUED","SPECTRA_CHECK_DECAYED","MANUFACTURER_PRE_ANNOUNCEMENT"]}}},{"name":"sourceFilter","required":false,"in":"query","description":"Recorta a queue para uma fonte específica. Default ALL (união das 4 fontes).","schema":{"enum":["ALL","DEPRECATED_ONLY","PRICE_ANOMALY_ONLY","OBSOLETE_IN_USE_ONLY"],"type":"string"}},{"name":"severityMin","required":false,"in":"query","description":"Severidade composta mínima inclusiva.","schema":{"minimum":0,"maximum":100,"type":"number"}},{"name":"hasCrossPlantStock","required":false,"in":"query","description":"Quando true, retorna apenas materiais com totalCrossPlantStock > 0. Cross-plant stock é sempre agregado — este filtro recorta o resultado pós-fetch. Knob `includeCrossPlantStock` foi removido pra garantir severityComposite e flags.obsoleteInUse consistentes entre list e detail.","schema":{"type":"boolean"}},{"name":"cursor","required":false,"in":"query","description":"Cursor opaco retornado pela página anterior.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Tamanho da página. Default 50, máximo 100.","schema":{"minimum":1,"maximum":100,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListObsolescenceQueueResponseDto"}}}}},"tags":["Command Center — Obsolescence Queue"],"security":[{"bearer":[]}]}},"/v1/command-center/obsolescence-queue/{materialId}":{"get":{"operationId":"ObsolescenceReviewController_detail","summary":"Detalhe da queue: signals ativos, anomalias preço abertas, breakdown cross-plant. alternativeSuggestion=null (reservado Story 6.6).","parameters":[{"name":"materialId","required":true,"in":"path","schema":{"format":"uuid","type":"string"}},{"name":"workspaceId","required":true,"in":"query","description":"Workspace alvo (cross-plant breakdown filtra por workspaces da org acessíveis).","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObsolescenceQueueItemDetailDto"}}}}},"tags":["Command Center — Obsolescence Queue"],"security":[{"bearer":[]}]}},"/v1/command-center/obsolescence-queue/{materialId}/revert":{"post":{"operationId":"ObsolescenceReviewController_revert","summary":"Reverte manualmente Material.status DEPRECATED → ACTIVE (Story 6.2 §AC3 manual path).","parameters":[{"name":"materialId","required":true,"in":"path","schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevertDeprecationDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevertDeprecationResponseDto"}}}}},"tags":["Command Center — Obsolescence Queue"],"security":[{"bearer":[]}]}},"/v1/admin/obsolescence/config":{"post":{"operationId":"ObsolescenceConfigAdminController_bumpConfig","summary":"Bump ObsolescenceConfig — cria nova versão active (Story 8.10 PR-B)","description":"Cria nova versão de thresholds (decayDays/minScore/autoDeprecateAfter) + marca isActive=true. Atomic deactivate antiga + insert nova em $transaction. Histórico permanente — versions antigas preservadas (ObsolescenceSignal.configVersion FK). Próximo cron run (obsolescence-detection 04:00 UTC / auto-deprecate 05:00) lê config nova sem deploy.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BumpObsolescenceConfigDto"}}}},"responses":{"201":{"description":"Nova config criada + ativa"},"400":{"description":"Payload inválido (DTO validation)"},"403":{"description":"User não é Dooor super-admin"},"409":{"description":"Version já existe OU race condition (2 bumps concorrentes)"}},"tags":["Dooor Admin · Obsolescence"],"security":[{"bearer":[]}]},"get":{"operationId":"ObsolescenceConfigAdminController_getOverview","summary":"Active ObsolescenceConfig + last 5 versions history","description":"Retorna config ativa (consumida por ObsolescenceService.getActiveConfig) + 5 versions mais recentes pra audit de calibração. Inclui historicalCount.","parameters":[],"responses":{"200":{"description":"Overview retornado"},"403":{"description":"User não é Dooor super-admin"}},"tags":["Dooor Admin · Obsolescence"],"security":[{"bearer":[]}]}},"/v1/admin/cost-governance/threshold/bump":{"post":{"operationId":"CostThresholdConfigAdminController_bumpConfig","summary":"Bump CostThresholdConfig — cria nova versão ATIVA por Org (Story 9.5)","description":"Cria nova versão (`warnUsdPerWorkspaceHour`, `warnUsdPerOrgHour`, `evaluationWindowMinutes`, `notificationCooldownMin`) + marca isActive=true para a Org alvo. Atomic deactivate antiga + insert nova em $transaction. Histórico permanente — versions antigas preservadas (CostAlertLog.thresholdConfigId FK). Próximo cron run (`cost-governance-warn` top of every hour) lê config nova sem deploy.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BumpCostThresholdConfigDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostThresholdConfigDto"}}}},"400":{"description":"Payload inválido (DTO validation)"},"403":{"description":"User não é Dooor super-admin"},"409":{"description":"Version já existe na Org OU race (2 bumps concorrentes)"}},"tags":["Dooor Admin · Cost Governance"],"security":[{"bearer":[]}]}},"/v1/admin/cost-governance/threshold/overview":{"get":{"operationId":"CostThresholdConfigAdminController_getOverview","summary":"Active CostThresholdConfig + last 5 versions history por Org","description":"Story 9.5 Review H5 fix — contrato canonical AC2: orgId vai como query param `?orgId=<uuid>` (não path). Retorna config ativa atual (consumida pelo cron) + 5 versions mais recentes pra audit de calibração. Inclui `totalCount`.","parameters":[{"name":"orgId","required":true,"in":"query","description":"UUID da Organization alvo","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostThresholdConfigOverviewDto"}}}},"403":{"description":"User não é Dooor super-admin"}},"tags":["Dooor Admin · Cost Governance"],"security":[{"bearer":[]}]}},"/v1/admin/cost-governance/alerts":{"get":{"operationId":"CostAlertLogAdminController_listAlerts","summary":"Lista CostAlertLog paginado cursor-based (Story 9.5)","description":"Filtros opcionais: orgId, workspaceId, alertType, from/to ISO 8601. Default range: últimos 30 dias. Default limit 50, max 200. Ordena desc por detectedAt. Cursor opaque = id do último item da página anterior.","parameters":[{"name":"orgId","required":false,"in":"query","description":"Filtrar por Org. Omitido = todas (cross-tenant).","schema":{"type":"string"}},{"name":"workspaceId","required":false,"in":"query","description":"Filtrar por workspace. Omitido = todos.","schema":{"type":"string"}},{"name":"alertType","required":false,"in":"query","description":"Filtrar por tipo.","schema":{"enum":["warn_workspace_hour","warn_org_hour"],"type":"string"}},{"name":"from","required":false,"in":"query","description":"ISO 8601 — detectedAt >=. Default: 30d atrás.","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","description":"ISO 8601 — detectedAt <=. Default: agora.","schema":{"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Cursor opaque (id do último alert da página anterior).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size (default 50, max 200).","schema":{"example":50,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCostAlertLogsResponseDto"}}}},"403":{"description":"User não é Dooor super-admin"}},"tags":["Dooor Admin · Cost Governance"],"security":[{"bearer":[]}]}},"/v1/admin/cost-governance/summary":{"get":{"operationId":"CostGovernanceSummaryController_getSummary","summary":"KPI summary do dashboard cost-governance (Story 9.5 Review M17)","description":"Agregados de ai_call_log + cost_alert_log na janela. Sem orgId, calcula cross-tenant.","parameters":[{"name":"windowHours","required":false,"in":"query","description":"Janela em horas (default 24, max 720).","schema":{"type":"number"}},{"name":"orgId","required":false,"in":"query","description":"UUID da Organization. Omitido = platform-wide.","schema":{}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostGovernanceSummaryDto"}}}},"403":{"description":"User não é Dooor super-admin"}},"tags":["Dooor Admin · Cost Governance"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/builder-usage/quota":{"get":{"operationId":"WorkspaceBuilderUsageController_getQuota","summary":"Quota builder ativa do workspace (Story 9.9)","description":"Retorna a quota com `isActive=true`. 404 quando workspace ainda não foi configurado — fluxo válido (modo observability-only sem alerts). Não há histórico aqui — endpoint dedicado seria 9.9b.","parameters":[{"name":"workspaceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBuilderQuotaResponseDto"}}}},"404":{"description":"Workspace sem quota configurada"}},"tags":["builder-usage"],"security":[{"bearer":[]}]},"put":{"operationId":"WorkspaceBuilderUsageController_upsertQuota","summary":"Cria/atualiza quota builder (versionada, audit trail completo)","description":"Bump cria nova row + desativa anterior em `$transaction`. `version` precisa ser diferente da ativa (409 caso duplicate). queryQuotaDaily e costBudgetMonthlyUsd são ambos opcionais — cliente seta uma OU outra OU ambas OU nenhuma (modo observability).","parameters":[{"name":"workspaceId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertWorkspaceBuilderQuotaDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBuilderQuotaResponseDto"}}}},"400":{"description":"Payload inválido (DTO validation)"},"403":{"description":"Auth via API key não pode mutar quota — use sessão de usuário"},"409":{"description":"Version já existe — use uma nova"}},"tags":["builder-usage"],"security":[{"bearer":[]}]},"delete":{"operationId":"WorkspaceBuilderUsageController_deactivateQuota","summary":"Soft-delete da quota ativa (isActive=false; histórico preservado)","description":"Idempotente: chamar 2x não retorna erro. NotificationIDs e alertLogs continuam referenciando via FK Restrict — auditoria preservada.","parameters":[{"name":"workspaceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Quota desativada (ou já estava inativa)"},"403":{"description":"Auth via API key não pode mutar quota — use sessão de usuário"}},"tags":["builder-usage"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/builder-usage/summary":{"get":{"operationId":"WorkspaceBuilderUsageController_getSummary","summary":"KPI builder-usage do workspace","description":"queries 24h/7d/MTD + cost USD MTD + utilização % vs quota ativa. mcpToolCallsCount sempre 0 (V1+ deferred — 9.9-MCP-TELEMETRY).","parameters":[{"name":"workspaceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BuilderUsageSummaryResponseDto"}}}}},"tags":["builder-usage"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/builder-usage/timeseries":{"get":{"operationId":"WorkspaceBuilderUsageController_getTimeseries","summary":"Série temporal queries + cost (bucket day|week, cap 90d)","description":"Default from=30d atrás, to=now, bucket=day. Cap janela: 90 dias (400 se exceder).","parameters":[{"name":"workspaceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"bucket","required":false,"in":"query","description":"Granularidade do bucket temporal.","schema":{"default":"day","enum":["day","week"],"type":"string"}},{"name":"from","required":false,"in":"query","description":"ISO 8601. Default = 30 dias atrás.","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","description":"ISO 8601. Default = agora. Cap janela: 90 dias.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BuilderUsageTimeseriesResponseDto"}}}},"400":{"description":"Janela > 90 dias"}},"tags":["builder-usage"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/builder-usage/alerts":{"get":{"operationId":"WorkspaceBuilderUsageController_listAlerts","summary":"Alertas builder-usage do workspace (paginação cursor-based)","description":"Default últimos 30 dias, limit 50 (max 200). Filtros opcionais: alertType, from/to. Ordenado desc por detectedAt. Use `nextCursor` na próxima request.","parameters":[{"name":"workspaceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Cursor opaco (id do último item da página anterior).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"from","required":false,"in":"query","description":"ISO 8601. Default = 30 dias atrás.","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","description":"ISO 8601. Default = agora.","schema":{"type":"string"}},{"name":"alertType","required":false,"in":"query","schema":{"enum":["queries_warn","queries_breach","cost_warn","cost_breach"],"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBuilderUsageAlertsResponseDto"}}}}},"tags":["builder-usage"],"security":[{"bearer":[]}]}},"/v1/admin/builder-usage/summary":{"get":{"operationId":"AdminBuilderUsageController_getAdminSummary","summary":"Cross-tenant summary builder-usage (Story 9.9)","description":"Total queries+cost no período + workspaces at-risk (utilizationPct ≥ 80%) + top 10 workspaces por cost. Default range 30d. Calibração comercial tier builder (PRD §817).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminBuilderUsageSummaryResponseDto"}}}},"403":{"description":"User não é Dooor super-admin"}},"tags":["Dooor Admin · Builder Usage"],"security":[{"bearer":[]}]}},"/v1/admin/builder-usage/by-workspace":{"get":{"operationId":"AdminBuilderUsageController_getAdminByWorkspace","summary":"Breakdown builder-usage por workspace (cursor pagination, cap 500)","description":"Ordenado desc por totalCostUsd. `minQueries` filtra workspaces inativos. `hasActiveQuota` indica se há quota configurada (sinaliza adoção do feature pelo cliente).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminBuilderUsageByWorkspaceResponseDto"}}}}},"tags":["Dooor Admin · Builder Usage"],"security":[{"bearer":[]}]}},"/v1/admin/builder-usage/alerts":{"get":{"operationId":"AdminBuilderUsageController_listAdminAlerts","summary":"Cross-tenant alerts builder-usage (filtros opcionais orgId/workspaceId/alertType)","description":"Default últimos 30 dias. Útil pra inspecionar histórico de alertas em todos os clientes (calibração + cobrança).","parameters":[{"name":"cursor","required":false,"in":"query","description":"Cursor opaco (id do último item da página anterior).","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"from","required":false,"in":"query","description":"ISO 8601. Default = 30 dias atrás.","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","description":"ISO 8601. Default = agora.","schema":{"type":"string"}},{"name":"alertType","required":false,"in":"query","schema":{"enum":["queries_warn","queries_breach","cost_warn","cost_breach"],"type":"string"}},{"name":"orgId","required":false,"in":"query","schema":{"type":"string"}},{"name":"workspaceId","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBuilderUsageAlertsResponseDto"}}}}},"tags":["Dooor Admin · Builder Usage"],"security":[{"bearer":[]}]}},"/v1/availability-confirmations":{"post":{"operationId":"AvailabilityConfirmationController_requestConfirmation","summary":"Request human confirmation of cross-plant availability (Story 7.11 FR62)","description":"Creates a PENDING AvailabilityConfirmationRequest gating the assumption of stock. Idempotent: a second click within 24h on the same (material, targetWorkspace, user) returns the existing PENDING row with `wasCreated:false`.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestAvailabilityConfirmationDto"}}}},"responses":{"200":{"description":"Request created or already PENDING"},"400":{"description":"Invalid body"},"403":{"description":"Target workspace not accessible (cross-tenant or tag mismatch)"},"404":{"description":"Material not found"}},"tags":["Availability"],"security":[{"bearer":[]}]}},"/v1/availability-confirmations/{id}/outcome":{"post":{"operationId":"AvailabilityConfirmationController_decideOutcome","summary":"Decide outcome of a pending availability gate (Story 10.4 FR62)","description":"The destination-plant decider confirms or rejects a PENDING availability gate. CONFIRMED unblocks the requester; REJECTED requires a reason and triggers an AI re-search for alternatives in the originating chat. Only PENDING gates transition.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecideAvailabilityConfirmationDto"}}}},"responses":{"200":{"description":"Gate transitioned to CONFIRMED/REJECTED"},"400":{"description":"Invalid body OR gate already decided (non-PENDING)"},"403":{"description":"Missing permission OR target workspace not accessible"},"404":{"description":"Gate not found in caller Org"}},"tags":["Availability"],"security":[{"bearer":[]}]}},"/v1/admin/analytics/cost":{"get":{"operationId":"AnalyticsController_costReport","summary":"Cost report (AI call log primary source + pipeline metadata)","parameters":[{"name":"workspaceId","required":false,"in":"query","description":"Workspace ID (omit for global view)","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"Data início (ISO 8601)","schema":{"example":"2026-03-01","type":"string"}},{"name":"to","required":false,"in":"query","description":"Data fim (ISO 8601)","schema":{"example":"2026-03-31","type":"string"}}],"responses":{"200":{"description":""}},"tags":["Dooor Admin · Analytics (Cost)"],"security":[{"bearer":[]}]}},"/v1/admin/analytics/cost/daily":{"get":{"operationId":"AnalyticsController_costDaily","summary":"Live daily token and search budget snapshot","parameters":[],"responses":{"200":{"description":""}},"tags":["Dooor Admin · Analytics (Cost)"],"security":[{"bearer":[]}]}},"/v1/admin/analytics/chat/feedback":{"get":{"operationId":"ChatFeedbackAnalyticsController_feedbackReport","summary":"Aggregated chat feedback report by family/tool/intent (Story 7.10 FR36)","description":"Returns thumb-up/down counts, optional groupBy, and a `coverage` field indicating data quality. groupBy=intent always returns coverage=mostly_unknown (intent classifier is V1+). groupBy=tool uses traceId join on AiCallLog (imperfect — deferred 7.10-TOOL-INVOCATION-CANONICAL-LINK). Default range: last 30 days. Max range: 365 days. NOTE: `sampleComments` returns raw user-submitted text (intentional — see class docstring). Each call is persisted in audit_log_v2 (NFR18). Rate-limited at 5/min.","parameters":[{"name":"from","required":false,"in":"query","description":"Start date (ISO 8601)","schema":{"example":"2026-04-01","type":"string"}},{"name":"to","required":false,"in":"query","description":"End date (ISO 8601)","schema":{"example":"2026-05-01","type":"string"}},{"name":"groupBy","required":false,"in":"query","description":"Aggregate by family/tool/intent. See coverage field for data quality.","schema":{"default":"none","enum":["none","family","tool","intent"],"type":"string"}},{"name":"orgId","required":false,"in":"query","description":"Filter to a single organization. **Strongly recommended** for multi-tenant queries — when omitted, aggregates across ALL orgs the super-admin has visibility into.","schema":{"type":"string"}},{"name":"workspaceId","required":false,"in":"query","description":"Filter to a single workspace (omit for cross-workspace)","schema":{"type":"string"}}],"responses":{"200":{"description":"Feedback analytics result"},"400":{"description":"Date range exceeds 365 days or from > to"},"403":{"description":"Dooor admin only"},"429":{"description":"Too many requests (5/min)"}},"tags":["Dooor Admin · Analytics (Chat Feedback)"],"security":[{"bearer":[]}]}},"/v1/admin/analytics/behavior":{"get":{"operationId":"BehavioralTelemetryController_behaviorReport","summary":"Behavioral telemetry report (Story 10.7-BE FR66)","description":"Aggregated cross-tenant behavioral signals: queryVolume (by tool), toolLatencyP95, reQuestioningRate, decisionReversalRate. Default range: last 30 days. Max: 24 months (audit retention NFR18). `coverage` per-signal flags proxy/approximation (re_questioning is a tool-proxy for intent; decisionReversal covers obsolescence reversals only). Super-admin Dooor only. No PII in the payload.","parameters":[{"name":"from","required":false,"in":"query","description":"Start date (ISO 8601). Default: now - 30d.","schema":{"example":"2026-05-01","type":"string"}},{"name":"to","required":false,"in":"query","description":"End date (ISO 8601). Default: now.","schema":{"example":"2026-05-29","type":"string"}},{"name":"orgId","required":false,"in":"query","description":"Filter to a single organization. When omitted, aggregates cross-tenant (super-admin Dooor only).","schema":{"type":"string"}},{"name":"workspaceId","required":false,"in":"query","description":"Filter to a single workspace (requires orgId).","schema":{"type":"string"}}],"responses":{"200":{"description":"Behavioral telemetry result"},"400":{"description":"from > to, range > 24 months, or workspaceId without orgId"},"403":{"description":"Dooor admin only"},"429":{"description":"Too many requests (10/min)"}},"tags":["Dooor Admin · Analytics (Behavioral Telemetry)"],"security":[{"bearer":[]}]}},"/v1/audit-logs":{"get":{"operationId":"AuditLogsController_list","summary":"List audit logs (org-scoped, filtered, cursor-paginated)","parameters":[{"name":"limit","required":false,"in":"query","description":"Default 50, max 200","schema":{"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Keyset cursor opaco (base64)","schema":{}},{"name":"to","required":false,"in":"query","description":"ISO 8601 datetime lte filter on createdAt","schema":{}},{"name":"from","required":false,"in":"query","description":"ISO 8601 datetime gte filter on createdAt","schema":{}},{"name":"traceId","required":false,"in":"query","schema":{}},{"name":"entityId","required":false,"in":"query","schema":{}},{"name":"entityType","required":false,"in":"query","schema":{}},{"name":"action","required":false,"in":"query","description":"Exact match, ou prefix com sufixo \"*\" (ex: \"material.*\")","schema":{}},{"name":"actorId","required":false,"in":"query","schema":{}},{"name":"workspaceId","required":false,"in":"query","schema":{}}],"responses":{"200":{"description":"Paginated list of audit log entries"},"401":{"description":"Unauthorized"},"403":{"description":"Missing audit.read permission"}},"tags":["Audit Logs"],"security":[{"bearer":[]}]}},"/v1/audit-logs/export.csv":{"get":{"operationId":"AuditLogsController_exportCsv","summary":"Export audit logs query as CSV (max 10k rows)","description":"Mesmos filtros do GET list; CSV header inclui id,actor,workspaceId,action,entityType,entityId,truncated,traceId,createdAt. RFC 4180-escaped.","parameters":[],"responses":{"200":{"description":"CSV body, content-type text/csv"},"403":{"description":"Missing audit.read permission"}},"tags":["Audit Logs"],"security":[{"bearer":[]}]}},"/v1/audit-logs/{id}":{"get":{"operationId":"AuditLogsController_getById","summary":"Get audit log by id (with full payload + signed URL if truncated)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"createdAt","required":false,"in":"query","description":"ISO 8601 datetime — quando informado, habilita partition pruning. UI passa o createdAt da row do list response.","schema":{"type":"string"}}],"responses":{"200":{"description":"Audit log detail"},"403":{"description":"Missing audit.read permission"},"404":{"description":"Audit log not found"}},"tags":["Audit Logs"],"security":[{"bearer":[]}]}},"/v1/api-keys":{"post":{"operationId":"ApiKeysController_create","summary":"Cria nova API key. Plaintext exposto UMA VEZ no response.","description":"Story 8.10 — gera key com prefix visível (spec_live_/test_) + plaintext irrecuperável. DB armazena só hash via scrypt + salt random per-key.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponseDto"}}}},"400":{"description":"Invalid permissions/scopeTags (anti-escalation: subset of creator effective scope required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"403":{"description":"Missing api_key.write permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["API Keys"],"security":[{"bearer":[]}]},"get":{"operationId":"ApiKeysController_list","summary":"Lista API keys da org. NUNCA inclui hash nem plaintext.","parameters":[{"name":"includeRevoked","required":false,"in":"query","description":"Inclui keys revogadas (default false).","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListApiKeysResponseDto"}}}}},"tags":["API Keys"],"security":[{"bearer":[]}]}},"/v1/api-keys/{id}/rotate":{"post":{"operationId":"ApiKeysController_rotate","summary":"Rotaciona API key. Antiga é revoked imediato; nova plaintext UMA VEZ.","description":"V1 sem grace period (deferred 8.10-API-KEYS-ROTATION-GRACE). Clients devem trocar a key imediatamente após receber o response.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateApiKeyResponseDto"}}}},"403":{"description":"Missing api_key.write permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"API key not found or already revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["API Keys"],"security":[{"bearer":[]}]}},"/v1/api-keys/{id}":{"delete":{"operationId":"ApiKeysController_revoke","summary":"Revoke API key (soft delete imediato; idempotente).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyResponseDto"}}}},"403":{"description":"Missing api_key.write permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["API Keys"],"security":[{"bearer":[]}]}},"/v1/ingestion/price-delta":{"post":{"operationId":"PriceUpdatesController_applyPriceUpdate","summary":"Aplica delta de preços a partir de flat-file CSV/XLSX","description":"Canal síncrono: parseia o arquivo conforme `PriceDeltaMapping`, valida linha-a-linha, aplica writes atômicos em `WorkspaceMaterial.precoUnitario` e emite `price.updated` via WS para cada workspace afetado.","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","mappingId"],"properties":{"file":{"type":"string","format":"binary","description":"Arquivo CSV/XLSX (≤50MB)."},"mappingId":{"type":"string","format":"uuid"},"workspaceId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyPriceUpdateResponseDto"}}}},"400":{"description":"Arquivo inválido ou ratio de erros >50% — códigos: INGESTION_PRICE_DELTA_INVALID_FILE | INGESTION_PRICE_DELTA_MAPPING_NOT_FOUND | INGESTION_PRICE_DELTA_TOO_MANY_ERRORS."},"403":{"description":"Sem permissão (silencioso)."}},"tags":["Data Ingestion"],"security":[{"bearer":[]}]}},"/v1/ingestion/price-delta/mappings":{"post":{"operationId":"PriceUpdatesMappingController_create","summary":"Cria um mapping de delta de preços para a Org","description":"Configura o parser e o destino de aplicação para arquivos enviados em `POST /v1/ingestion/price-delta`. Único por (orgId, name).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePriceUpdatesMappingDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceUpdatesMappingResponseDto"}}}},"400":{"description":"Body inválido — `INGESTION_PRICE_DELTA_MAPPING_INVALID_COLUMN_MAP`."},"409":{"description":"Nome duplicado — `INGESTION_PRICE_DELTA_MAPPING_DUPLICATE_NAME`."}},"tags":["Data Ingestion"],"security":[{"bearer":[]}]},"get":{"operationId":"PriceUpdatesMappingController_list","summary":"Lista mappings de delta de preços da Org","description":"Ordenados por `createdAt desc`.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListPriceUpdatesMappingsResponseDto"}}}}},"tags":["Data Ingestion"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/change-requests":{"post":{"operationId":"ChangeRequestsController_create","summary":"Propose a schema Change Request (FR11)","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateChangeRequestDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeRequestDetailDto"}}}},"400":{"description":"Family has no active schema / invalid evidence clusters"},"404":{"description":"Family not found"}},"tags":["Change Requests"],"security":[{"bearer":[]}]},"get":{"operationId":"ChangeRequestsController_list","summary":"List Change Requests (FR11)","parameters":[{"name":"family","required":false,"in":"query","description":"Filter by familyId (UUID)","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by CR status","schema":{"enum":["AWAITING_CONSENSUS","APPROVED","REJECTED","APPROVED_CONSENSUAL"],"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeRequestListResponseDto"}}}}},"tags":["Change Requests"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/change-requests/{crId}":{"get":{"operationId":"ChangeRequestsController_detail","summary":"Change Request detail with schema diff (FR11)","parameters":[{"name":"crId","required":true,"in":"path","description":"Change Request UUID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeRequestDetailDto"}}}},"404":{"description":"Change Request not found"}},"tags":["Change Requests"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/change-requests/{crId}/votes":{"post":{"operationId":"ChangeRequestsController_vote","summary":"Record an area vote on a Change Request (FR11)","parameters":[{"name":"crId","required":true,"in":"path","description":"Change Request UUID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoteChangeRequestDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoteChangeRequestResponseDto"}}}},"404":{"description":"Change Request not found"},"409":{"description":"Change Request no longer open for voting"}},"tags":["Change Requests"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/change-requests/{crId}/resubmit":{"post":{"operationId":"ChangeRequestsController_resubmit","summary":"Resubmit a rejected Change Request as a fresh CR (FR14)","parameters":[{"name":"crId","required":true,"in":"path","description":"Original (rejected) Change Request UUID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResubmitChangeRequestDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeRequestDetailDto"}}}},"400":{"description":"Original CR is not REJECTED or field no longer in schema"},"404":{"description":"Original Change Request not found"},"409":{"description":"Original CR has already been resubmitted"}},"tags":["Change Requests"],"security":[{"bearer":[]}]}},"/v1/dashboards":{"get":{"operationId":"DashboardsController_list","summary":"Listar dashboards (defaults da persona + próprios) — FR63","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DashboardResponseDto"}}}}},"403":{"description":"Missing dashboard.read permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Dashboards"],"security":[{"bearer":[]}]},"post":{"operationId":"DashboardsController_create","summary":"Criar dashboard configurável próprio — FR63","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDashboardDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardResponseDto"}}}},"400":{"description":"Invalid layout (widgetType ∉ enum dos 7) or payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"403":{"description":"Missing dashboard.write permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Dashboards"],"security":[{"bearer":[]}]}},"/v1/dashboards/{id}":{"get":{"operationId":"DashboardsController_detail","summary":"Detalhe de um dashboard — FR63","parameters":[{"name":"id","required":true,"in":"path","description":"Dashboard UUID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardResponseDto"}}}},"403":{"description":"Missing dashboard.read permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"Dashboard not found in org/scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Dashboards"],"security":[{"bearer":[]}]},"put":{"operationId":"DashboardsController_update","summary":"Editar dashboard próprio — FR63","parameters":[{"name":"id","required":true,"in":"path","description":"Dashboard UUID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDashboardDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardResponseDto"}}}},"403":{"description":"Editing a default template or another user dashboard","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"Dashboard not found in org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Dashboards"],"security":[{"bearer":[]}]},"delete":{"operationId":"DashboardsController_remove","summary":"Excluir dashboard próprio — FR63","parameters":[{"name":"id","required":true,"in":"path","description":"Dashboard UUID","schema":{"type":"string"}}],"responses":{"204":{"description":"Dashboard deleted"},"403":{"description":"Deleting a default template or another user dashboard","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"Dashboard not found in org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Dashboards"],"security":[{"bearer":[]}]}},"/v1/dashboards/widgets/{widgetType}":{"get":{"operationId":"DashboardWidgetsController_getWidgetData","summary":"Dados auditáveis de um widget de KPI (data + breakdown) — FR63","parameters":[{"name":"widgetType","required":true,"in":"path","description":"Um dos 7 widget types canônicos","schema":{"enum":["compras_evitaveis","estoque_consolidavel","qualidade_catalogo","cobertura_familias","cobertura_spectra_check","alertas_anomalia_preco","top_familias_savings"],"type":"string"}},{"name":"familyId","required":false,"in":"query","description":"Restringe a métrica a uma família","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"Início do range (ISO 8601) — aplicável a métricas event-based","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","description":"Fim do range (ISO 8601)","schema":{"type":"string"}}],"responses":{"200":{"description":"{ widgetType, data, breakdown, estimateNote? }"},"400":{"description":"Unknown widgetType (INVALID_WIDGET_TYPE)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"403":{"description":"Missing dashboard.read permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Dashboards"],"security":[{"bearer":[]}]}},"/v1/workspaces/{workspaceId}/materials/{materialId}/ip-mark":{"post":{"operationId":"IpProtectionController_markIp","summary":"Mark a material as IP-protected (Story 5.6 manual flow)","description":"Engineer (Renata persona) flags a material as containing intellectual property. Sets Material.isIpProtected=true and writes an audit row to ip_protection_log. Once marked, the material is BLOCKED from linkage with Spectra Global Catalog (NFR16) — override requires super-admin Dooor (Story 5.6 PR-B).","parameters":[{"name":"materialId","required":true,"in":"path","description":"Material UUID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkMaterialIpDto"}}}},"responses":{"204":{"description":"Material marked successfully (no body)"},"400":{"description":"Empty/whitespace justification"},"403":{"description":"User lacks material.write permission"},"404":{"description":"Material not found in workspace org"}},"tags":["IP Protection"],"security":[{"bearer":[]}]}},"/v1/admin/ip-protection/materials":{"get":{"operationId":"IpProtectionAdminController_list","summary":"Listar materiais IP-protected cross-org (super-admin Dooor)","description":"Sem `orgId`: lista cross-org (todos os Orgs). Com `orgId`: filtra para aquele Org. Filtro opcional `wasLinkedBeforeIpDetection=true` retorna apenas casos forenses (material já estava vinculado ao Spectra Global antes da detecção de IP). Paginação cursor-keyset base64url.","parameters":[{"name":"orgId","required":false,"in":"query","description":"Org UUID (cross-org se omitido)","schema":{"example":"90d43f6d-0ebf-461a-aeed-406a348fef03","type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Cursor base64url do response anterior","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size 1-200, default 50","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}},{"name":"wasLinkedBeforeIpDetection","required":false,"in":"query","description":"Filtro forense — apenas materiais que estavam vinculados antes do IP","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Lista paginada com nextCursor"},"400":{"description":"Cursor inválido ou query DTO inválido"},"403":{"description":"User não é super-admin Dooor"}},"tags":["Dooor Admin · IP Protection"],"security":[{"bearer":[]}]}},"/v1/admin/ip-protection/material/{materialId}/revert":{"post":{"operationId":"IpProtectionAdminController_revert","summary":"Revert Material.isIpProtected=true → false (super-admin Dooor override)","description":"Use case: Gemini false-positive (engineer/admin Dooor concluded material is NOT IP-protected after manual inspection). Reverts the flag + audits via ip_protection_log (detectionType=override_revert) + audit_log (via @AuditableMutation). Does NOT re-link automatically — engineer re-ingests XLSX if linkage retry desired. `wasLinkedBeforeIpDetection` flag is NOT cleared (forensic preservation).","parameters":[{"name":"materialId","required":true,"in":"path","description":"Material UUID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevertIpOverrideDto"}}}},"responses":{"204":{"description":"Override applied (no body)"},"400":{"description":"Empty/short justification"},"403":{"description":"User is not Dooor super-admin"},"404":{"description":"Material not found in given orgId"},"409":{"description":"Material is already not IP-protected (no-op)"}},"tags":["Dooor Admin · IP Protection"],"security":[{"bearer":[]}]}},"/v1/price-anomalies":{"get":{"operationId":"PriceAnomaliesController_list","summary":"Lista a fila workspace-scoped de anomalias de preço.","parameters":[{"name":"workspaceId","required":true,"in":"query","description":"Workspace alvo da fila de anomalias.","schema":{"format":"uuid","type":"string"}},{"name":"familyId","required":false,"in":"query","description":"Filtra a fila por família.","schema":{"format":"uuid","type":"string"}},{"name":"status","required":false,"in":"query","description":"Status da anomalia. Quando omitido, a fila retorna apenas anomalias abertas.","schema":{"enum":["open","accepted","rejected","resolved"],"type":"string"}},{"name":"severityMin","required":false,"in":"query","description":"Severidade mínima inclusiva.","schema":{"minimum":0,"maximum":100,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Cursor opaco retornado pela página anterior.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Tamanho da página. Default 50, máximo 100.","schema":{"minimum":1,"maximum":100,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListPriceAnomaliesResponseDto"}}}}},"tags":["Price Anomalies"],"security":[{"bearer":[]}]}},"/v1/price-anomalies/detect":{"post":{"operationId":"PriceAnomaliesController_detect","summary":"Executa detecção determinística de anomalias de preço para um workspace/família.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetectPriceAnomaliesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetectPriceAnomaliesResponseDto"}}}}},"tags":["Price Anomalies"],"security":[{"bearer":[]}]}},"/v1/price-anomalies/bulk-resolve":{"post":{"operationId":"PriceAnomaliesController_bulkResolve","summary":"Resolve/aceita/rejeita anomalias de preço em lote sem alterar preço.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResolvePriceAnomaliesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResolvePriceAnomaliesResponseDto"}}}}},"tags":["Price Anomalies"],"security":[{"bearer":[]}]}},"/v1/price-anomalies/{id}/suggest":{"post":{"operationId":"PriceAnomaliesController_suggest","summary":"Enfileira sugestão IA de preço para uma anomalia.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestPriceAnomalyDto"}}}},"responses":{"202":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestPriceAnomalyResponseDto"}}}}},"tags":["Price Anomalies"],"security":[{"bearer":[]}]}},"/v1/org-tags":{"post":{"operationId":"OrgTagsController_create","summary":"Cria nova tag no catálogo da Org","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrgTagDto"}}}},"responses":{"201":{"description":"Tag criada"},"409":{"description":"Tag já existe para esta organização"}},"tags":["Org Tags"],"security":[{"bearer":[]}]},"get":{"operationId":"OrgTagsController_list","summary":"Lista tags da Org (paginated full list ou autocomplete por prefix).","description":"Com `prefix`, retorna até 20 tags `tag ILIKE prefix%` (autocomplete, sem pagination). Sem `prefix`, retorna page paginated cursor-based (default 50, max 100) ordenada `tag asc, id asc`. `includeUsage=true` agrega `usage: { workspaces, users }` por tag.","parameters":[{"name":"prefix","required":false,"in":"query","description":"Prefixo case-insensitive para autocomplete. Ausente OU vazio retorna lista paginated (cursor-based, default 50). Presente retorna até 20 (tag asc). Wildcards LIKE (%, _, \\\\) são escapados antes do query (busca literal).","schema":{"maxLength":50,"example":"wr","type":"string"}},{"name":"cursor","required":false,"in":"query","description":"Cursor (UUID da última tag da página anterior) para paginação. Aplicável apenas no modo sem prefix. Ordenação cursor-stable: `tag asc, id asc`.","schema":{"format":"uuid","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Tamanho da página (modo sem prefix). Default 50, max 100. Ignorado no modo prefix (sempre cap 20).","schema":{"minimum":1,"maximum":100,"default":50,"type":"number"}},{"name":"includeUsage","required":false,"in":"query","description":"Controla agregação de `usage: { workspaces, users }` por tag (2 `count` queries por tag por página). Quando ausente, o service decide pelo modo: paginated (sem prefix) → `true` (AC4 da Story 8.6 exige `usage` real no endpoint principal), prefix mode → `false` (autocomplete Story 1.14 é lightweight). Quando explícito (`true`/`false`), sempre respeitado.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListOrgTagsResponseDto"}}}},"400":{"description":"Cursor inválido (`errorCode: INVALID_CURSOR`) — a tag referenciada pelo cursor não pertence à Org (stale, cross-org ou deletada entre páginas). Cliente deve recarregar a lista do início."}},"tags":["Org Tags"],"security":[{"bearer":[]}]}},"/v1/org-tags/eligible-workspaces":{"get":{"operationId":"OrgTagsController_listEligibleWorkspaces","summary":"Lista workspaces da Org elegíveis para receber tag (cap defensivo).","description":"CR-FE-5: o painel de assignment precisa de TODOS os workspaces da Org, incluindo aqueles fora do tag-intersect do caller (justamente os candidatos para atribuição). O endpoint genérico `/v1/workspaces/organization/:id` filtra por hasWorkspaceAccess e esconde candidatos. Cap defensivo aplicado — `truncated: true` quando Org excede o limite.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EligibleWorkspacesResponseDto"}}}}},"tags":["Org Tags"],"security":[{"bearer":[]}]}},"/v1/org-tags/eligible-users":{"get":{"operationId":"OrgTagsController_listEligibleUsers","summary":"Lista UserOrgs (membros) da Org elegíveis para receber tag.","description":"CR-FE-5: o endpoint genérico de listagem de membros exige `user.read`, permission que não está mapeada à role `cig_cadmat`. Este endpoint expõe payload mínimo (membership id, email, name) sob `tag.taxonomy.read`. Cap defensivo aplicado — `truncated: true` quando Org excede o limite.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EligibleUsersResponseDto"}}}}},"tags":["Org Tags"],"security":[{"bearer":[]}]}},"/v1/org-tags/{tagId}":{"delete":{"operationId":"OrgTagsController_delete","summary":"Deleta tag da Org + cascata em workspace.tags e user_org.tags.","description":"Idempotente em counters (deleta mesmo com 0 atribuições). Retorna `affectedWorkspaces` e `affectedUsers`. Audit metadata inclui IDs afetados (truncado a 100 itens cada se overflow — flag `truncated: true`).","parameters":[{"name":"tagId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteOrgTagResponseDto"}}}},"404":{"description":"Tag não pertence à Org"}},"tags":["Org Tags"],"security":[{"bearer":[]}]}},"/v1/org-tags/{tagId}/assignments":{"get":{"operationId":"OrgTagsController_getAssignments","summary":"Lista workspaces e userOrgs que possuem esta tag atribuída.","parameters":[{"name":"tagId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagAssignmentsResponseDto"}}}},"404":{"description":"Tag não pertence à Org"}},"tags":["Org Tags"],"security":[{"bearer":[]}]}},"/v1/org-tags/{tagId}/assignments/workspaces/{workspaceId}":{"post":{"operationId":"OrgTagsController_assignToWorkspace","summary":"Atribui tag a workspace (idempotente).","parameters":[{"name":"tagId","required":true,"in":"path","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Tag atribuída (ou já presente)"},"404":{"description":"Tag ou workspace fora da Org"}},"tags":["Org Tags"],"security":[{"bearer":[]}]},"delete":{"operationId":"OrgTagsController_unassignFromWorkspace","summary":"Remove tag de workspace (idempotente).","parameters":[{"name":"tagId","required":true,"in":"path","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Tag removida (ou já ausente)"},"404":{"description":"Tag ou workspace fora da Org"}},"tags":["Org Tags"],"security":[{"bearer":[]}]}},"/v1/org-tags/{tagId}/assignments/users/{userOrgId}":{"post":{"operationId":"OrgTagsController_assignToUser","summary":"Atribui tag a userOrg (membership; idempotente).","parameters":[{"name":"tagId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userOrgId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Tag atribuída (ou já presente)"},"404":{"description":"Tag ou userOrg fora da Org"}},"tags":["Org Tags"],"security":[{"bearer":[]}]},"delete":{"operationId":"OrgTagsController_unassignFromUser","summary":"Remove tag de userOrg (membership; idempotente).","parameters":[{"name":"tagId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userOrgId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Tag removida (ou já ausente)"},"404":{"description":"Tag ou userOrg fora da Org"}},"tags":["Org Tags"],"security":[{"bearer":[]}]}},"/v1/health":{"get":{"operationId":"HealthController_check","summary":"Health check (db + redis aggregate, 1s timeout)","parameters":[],"responses":{"200":{"description":""}},"tags":["Health"]}},"/v1/health/audit-partitions":{"get":{"operationId":"HealthController_checkAuditPartitions","summary":"Audit log partition rotation health (cron sanity)","parameters":[],"responses":{"200":{"description":""}},"tags":["Health"]}},"/v1/health/detailed":{"get":{"operationId":"HealthController_checkDetailed","summary":"Detailed health check of all services","parameters":[],"responses":{"200":{"description":""}},"tags":["Health"]}},"/v1/ready":{"get":{"operationId":"ReadyController_ready","summary":"Readiness probe (503 until post-bootstrap)","parameters":[],"responses":{"200":{"description":""}},"tags":["Health"]}},"/v1/views/export":{"post":{"operationId":"PdfViewsController_exportView","summary":"Story 7.3 — dispara geração de vista PDF (Story 7.3 / FR37).","description":"Resolve o material (Org-scoped) + verifica permission ABAC + bloqueia engineering-report quando isIpProtected=true (NFR16/DEATH-1). Cache hit retorna 200 com signed URL TTL 7d imediato. Cache miss retorna 202 com jobId; processor renderiza e emite WS pdf.ready.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportPdfViewDto"}}}},"responses":{"200":{"description":"Cache hit — PDF pronto","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportPdfViewCachedResponseDto"}}}},"202":{"description":"Enfileirado — aguarde WS pdf.ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportPdfViewQueuedResponseDto"}}}},"403":{"description":"Permission ABAC negada ou material com IP protegido (engineering-report)"},"404":{"description":"Material não encontrado na Org"},"429":{"description":"Rate limit — máx. 10 exports/min por user"}},"tags":["PDF Views"],"security":[{"bearer":[]}]}},"/v1/views/data/{viewType}/{materialId}":{"get":{"operationId":"ViewsDataController_getRenderPayload","summary":"Story 7.3 (INTERNAL) — payload agregado para renderizar vista headless","description":"Auth via header `x-pdf-internal-token` (JWT TTL 5min, secret separado). Não é endpoint público. Server Component Next.js consome este payload; nenhum browser de usuário acessa. Throttle 60/min/IP defende contra brute-force de token.","parameters":[{"name":"materialId","required":true,"in":"path","description":"UUID do Material canonical","schema":{"type":"string"}},{"name":"viewType","required":true,"in":"path","schema":{"enum":["engineering-report","buyer-onepager","maintenance-onepager"],"type":"string"}}],"responses":{"200":{"description":"Payload pronto para render SSR"},"401":{"description":"JWT interno inválido / expirado / purpose errado"},"404":{"description":"Material não existe na Org do claim"},"429":{"description":"Throttle 60/min — provável brute-force"}},"tags":["PDF Views (Internal)"]}},"/v1/manufacturers":{"get":{"operationId":"ManufacturersController_listManufacturers","summary":"List canonical manufacturers for the current Org (facet endpoint for chat filters FR34)","parameters":[{"name":"search","required":false,"in":"query","description":"Case-insensitive name substring filter","schema":{"maxLength":200,"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max results (default 50, max 200)","schema":{"minimum":1,"maximum":200,"type":"number"}}],"responses":{"200":{"description":"List of manufacturers with total count"},"403":{"description":"Missing manufacturer.read permission"}},"tags":["Manufacturers"],"security":[{"bearer":[]}]}},"/v1/exports/eligible-materials":{"get":{"operationId":"ExportController_listEligibleMaterials","summary":"Lista materiais elegíveis para export SAP (status=ACTIVE)","parameters":[{"name":"familyId","required":false,"in":"query","schema":{"type":"string"}},{"name":"workspaceId","required":false,"in":"query","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["ACTIVE","DEPRECATED"],"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["exports"]}},"/v1/exports/sap":{"post":{"operationId":"ExportController_createExport","summary":"Cria export SAP flat-file (XLSX/CSV) — job assíncrono BullMQ","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateExportDto"}}}},"responses":{"201":{"description":""}},"tags":["exports"]}},"/v1/exports":{"get":{"operationId":"ExportController_listExports","summary":"Lista histórico de exports SAP da Org (paginado por cursor)","parameters":[{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["exports"]}},"/v1/exports/{id}/download-url":{"get":{"operationId":"ExportController_getDownloadUrl","summary":"Gera signed URL TTL 5min para download do arquivo GCS (on-demand, não persiste)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["exports"]}},"/v1/workspaces/{workspaceId}/materials/{materialId}/enriched-context":{"get":{"operationId":"EnrichedContextController_getEnrichedContext","summary":"Story 9.10 (FR64) — material + similars + duplicates + priceAnomaly + spectraCheck + obsolescence em UMA resposta composta","description":"Compose 6 sub-services (material base + 5 enrichment components) via Promise.allSettled paralelo com per-component timeout 5s. Response inteira NUNCA falha por 1 componente — apenas o degradado vem com partial:true + error. 404 propagado apenas se material base (MaterialService.findById) falhar. crossPlantInventory wired via Story 10.1-FOLLOWUP — agrega saldo por workspace accessible (tag-intersect ABAC) + freshness + distancia + ETA via tabela frete.","parameters":[{"name":"materialId","required":true,"in":"path","description":"Material UUID","schema":{"type":"string"}},{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichedContextResponseDto"}}}},"403":{"description":"Missing material.read permission OR workspace tag intersect empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}},"404":{"description":"Material not found in workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"tags":["Enriched Context (DooorOS apps)"],"security":[{"api-key":[]},{"bearer":[]}]}}},"info":{"title":"Spectra Develop API","description":"DOOOR's Spectra Develop backend API.\n\n**Versioning:** URI-based (/v1/).\n**Auth:** JWT Bearer (humans) OR X-API-Key header (system/builder integrations).\n**Errors:** Pattern H envelope — {statusCode, errorCode, message, traceId, timestamp, details?}.\n**Tenant isolation:** orgId resolvido pelo auth layer (NUNCA enviado pelo consumer).\n\nDocs site + cookbook (curl/Python/TypeScript): docs.spectra.dooor.ai (Story 9.7).","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"securitySchemes":{"access-token":{"scheme":"bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"},"api-key":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key prefixed token (Story 8.10 PR-A). Format: <prefix>.<secret>. Generate via POST /v1/api-keys (returned plaintext UMA VEZ — Stripe pattern)."}},"schemas":{"RequestMagicLinkDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address to send magic link to","example":"user@example.com"}},"required":["email"]},"ApiErrorResponseDto":{"type":"object","properties":{"statusCode":{"type":"number","description":"HTTP status code (espelha codigo de resposta).","example":404},"errorCode":{"type":"string","description":"Codigo de erro semantico canonical (enum interno `ErrorCode`). Cliente deve programar contra `errorCode`, NAO contra `message` que eh i18n-livre.","example":"MATERIAL_NOT_FOUND"},"message":{"description":"Mensagem human-readable. Pode ser string unica OU array (ValidationPipe class-validator multi-field errors).","oneOf":[{"type":"string","example":"Material not found in org"},{"type":"array","items":{"type":"string"},"example":["familyId must be UUID"]}]},"traceId":{"type":"string","description":"Trace ID propagado via OpenTelemetry (Story 1.11). Use em report de bug pra dev correlacionar logs + spans.","example":"00-abc123def456-7890ghi-01"},"timestamp":{"type":"string","description":"ISO 8601 UTC timestamp quando erro foi gerado.","example":"2026-05-27T12:34:56.789Z"},"details":{"type":"object","description":"Campos custom acionaveis pelo cliente (whitelist em http-error-mapper.ts). Exemplos: { code: \"LEGACY_001\" }, { reason: \"duplicate_sku\" }, { errorReportUrl: \"https://...\" }.","additionalProperties":true}},"required":["statusCode","errorCode","message","traceId","timestamp"]},"VerifyMagicLinkDto":{"type":"object","properties":{"token":{"type":"string","description":"Magic link token from email","example":"abc123def456..."}},"required":["token"]},"RefreshTokenDto":{"type":"object","properties":{"refreshToken":{"type":"string","description":"Session token expirado ou proximo de expirar. Server valida signature (ignore expiration) + UserSession.valid e issue novo token. Antigo marcado invalido apos rotation (anti replay).","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ii4uLiJ9.signature","maxLength":2048}},"required":["refreshToken"]},"SsoExchangeDto":{"type":"object","properties":{"token":{"type":"string","description":"DooorOS-issued JWT (signed with the IdP RSA/ES key).","example":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."},"from":{"type":"string","description":"Consumer identifier (e.g. `robot-planejador`, `smart-rg`). When provided, it is treated as an untrusted UX hint only. Audit and billing always use the signed `from` claim from the token.","example":"robot-planejador"},"next":{"type":"string","description":"Relative redirect path (must start with `/`). When provided, the response includes this value in `redirectTo` so the frontend can navigate to the intended deep-link without server-side open redirect risk (validated again client-side).","example":"/materials/abc-123"}},"required":["token"]},"UserDTO":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the user","example":"123e4567-e89b-12d3-a456-426614174000"},"googleId":{"type":"object","description":"Google ID for the user (when authenticated via Google)","example":"1234567890123456789","nullable":true},"email":{"type":"string","description":"Email address of the user","example":"john.doe@example.com"},"name":{"type":"object","description":"User full name","example":"John Doe","nullable":true},"image":{"type":"object","description":"User profile image URL","example":"https://storage.googleapis.com/bucket/profile-images/user-id.jpg","nullable":true},"createdAt":{"format":"date-time","type":"string","description":"Date when the user was created","example":"2023-01-01T00:00:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Date when the user was last updated","example":"2023-01-02T00:00:00.000Z"},"isDooorAdmin":{"type":"boolean","description":"Whether the user is a Dooor super-admin (User.isDooorAdmin). Dooor admins acessam o módulo `dooor-admin/*` e `admin/analytics`.","example":false},"isSuperadmin":{"type":"boolean","description":"Alias de `isDooorAdmin` para consumidor FE (`useIsSuperadmin`). Mesma semântica.","example":false},"workspaces":{"description":"List of workspaces the user is a member of","items":{"type":"array"},"type":"array"}},"required":["id","googleId","email","name","image","createdAt","updatedAt","isDooorAdmin","isSuperadmin","workspaces"]},"UpdateUserSettingsDTO":{"type":"object","properties":{"email":{"type":"string","description":"Email address for the user","example":"user@example.com"},"name":{"type":"string","description":"User full name","example":"John Doe"}}},"UpdateUserSettingsResponseDTO":{"type":"object","properties":{"user":{"description":"Updated user information","allOf":[{"$ref":"#/components/schemas/UserDTO"}]},"message":{"type":"string","description":"Success message","example":"User settings updated successfully"}},"required":["user","message"]},"UpsertUserPreferenceDto":{"type":"object","properties":{"value":{"type":"object","description":"Preference value (JSON)","example":{"visibleColumns":["reqId","status"]}}},"required":["value"]},"CreateOrganizationDto":{"type":"object","properties":{"name":{"type":"string","description":"Organization name","example":"My Company","maxLength":100},"description":{"type":"string","description":"Organization description","example":"A company focused on innovation","maxLength":500}},"required":["name"]},"OrganizationResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"Organization unique identifier","example":"cl9eb9q6y00003b6f0q1g7c8d"},"name":{"type":"string","description":"Organization name","example":"My Company"},"description":{"type":"object","description":"Organization description","example":"A company focused on innovation"},"ownerId":{"type":"string","description":"Organization owner ID","example":"cl9eb9q6y00003b6f0q1g7c8d"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp","example":"2023-10-26T10:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp","example":"2023-10-26T10:30:00.000Z"}},"required":["id","name","ownerId","createdAt","updatedAt"]},"WorkspaceSummaryDto":{"type":"object","properties":{"id":{"type":"string","description":"Workspace unique identifier","example":"cl9eb9q6y00003b6f0q1g7c8d"},"name":{"type":"string","description":"Workspace name","example":"Development Team"},"description":{"type":"object","description":"Workspace description","example":"Workspace for development team collaboration"},"logoUrl":{"type":"object","description":"Workspace logo URL (signed)","example":"https://storage.googleapis.com/..."},"membersCount":{"type":"number","description":"Number of members in this workspace","example":5},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp","example":"2023-10-26T10:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp","example":"2023-10-26T10:30:00.000Z"}},"required":["id","name","membersCount","createdAt","updatedAt"]},"OrganizationWithWorkspacesResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"Organization unique identifier","example":"cl9eb9q6y00003b6f0q1g7c8d"},"name":{"type":"string","description":"Organization name","example":"My Company"},"description":{"type":"object","description":"Organization description","example":"A company focused on innovation"},"ownerId":{"type":"string","description":"Organization owner ID","example":"cl9eb9q6y00003b6f0q1g7c8d"},"workspaces":{"description":"Workspaces in this organization","type":"array","items":{"$ref":"#/components/schemas/WorkspaceSummaryDto"}},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp","example":"2023-10-26T10:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp","example":"2023-10-26T10:30:00.000Z"}},"required":["id","name","ownerId","workspaces","createdAt","updatedAt"]},"UpdateOrganizationDto":{"type":"object","properties":{"name":{"type":"string","description":"Organization name","example":"My Updated Company","maxLength":100},"description":{"type":"string","description":"Organization description","example":"An updated company description","maxLength":500}}},"OrganizationMemberRoleDto":{"type":"object","properties":{"id":{"type":"string","description":"Role ID"},"name":{"type":"string","description":"Role name (slug)","example":"admin"},"displayName":{"type":"string","description":"Human-friendly role label","example":"Administrator"}},"required":["id","name","displayName"]},"OrganizationMemberResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"Membership ID (UserOrg.id)"},"userId":{"type":"string","description":"User ID"},"userEmail":{"type":"string","description":"Email of the underlying User (denormalized for UI)"},"userDisplayName":{"type":"string","description":"Display name of the underlying User (denormalized for UI)"},"tags":{"description":"Tags applied to the membership (ABAC scoping)","type":"array","items":{"type":"string"}},"permissions":{"description":"Direct permissions granted to the membership","type":"array","items":{"type":"string"}},"roles":{"description":"All roles attached to the membership","type":"array","items":{"$ref":"#/components/schemas/OrganizationMemberRoleDto"}},"isOwner":{"type":"boolean","description":"Whether this membership is the org owner"}},"required":["id","userId","tags","permissions","roles","isOwner"]},"UpdateOrganizationMemberDto":{"type":"object","properties":{"roleIds":{"description":"Replace the full set of role IDs connected to the membership","type":"array","items":{"type":"string"}},"tags":{"description":"Replace the full set of tags on the UserOrg","type":"array","items":{"type":"string"}},"permissions":{"description":"Replace the full set of direct permissions. Each key MUST exist in the org permission registry. The wildcard `*` is reserved for the org owner and is rejected here.","type":"array","items":{"type":"string"}}}},"WorkspaceMemberRoleDto":{"type":"object","properties":{"id":{"type":"string","description":"Role ID","example":"cl9eb9q6y00003b6f0q1g7c8d"},"name":{"type":"string","description":"Role name (slug)","example":"admin"},"displayName":{"type":"string","description":"Human-friendly role label","example":"Administrator"}},"required":["id","name","displayName"]},"WorkspaceMemberResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"Membership ID (UserOrg.id)","example":"cl9eb9q6y00003b6f0q1g7c8d"},"userId":{"type":"string","description":"User ID","example":"cl9eb9q6y00003b6f0q1g7c8d"},"userEmail":{"type":"string","description":"Email of the underlying User (denormalized for UI)","example":"jane.doe@csn.com.br"},"userDisplayName":{"type":"string","description":"Display name of the underlying User (denormalized for UI)","example":"Jane Doe"},"role":{"type":"string","description":"Name of the primary role (legacy single-role display field)","example":"admin"},"tags":{"description":"Tags applied to the membership (ABAC scoping)","type":"array","items":{"type":"string"}},"permissions":{"description":"Direct permissions granted to the membership","type":"array","items":{"type":"string"}},"roles":{"description":"All roles attached to the membership","type":"array","items":{"$ref":"#/components/schemas/WorkspaceMemberRoleDto"}}},"required":["id","userId"]},"WorkspaceResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"Workspace unique identifier","example":"cl9eb9q6y00003b6f0q1g7c8d"},"name":{"type":"string","description":"Workspace name","example":"Development Team"},"description":{"type":"object","description":"Workspace description","example":"Workspace for development team collaboration"},"logoUrl":{"type":"object","description":"Workspace logo URL","example":"https://storage.googleapis.com/bucket/workspace-logos/ws-123.png"},"organizationId":{"type":"string","description":"Organization ID","example":"cl9eb9q6y00003b6f0q1g7c8d"},"tags":{"description":"Workspace tags (used for ABAC scoping)","type":"array","items":{"type":"string"}},"members":{"description":"Workspace members (UserOrgs whose tags intersect workspace.tags)","type":"array","items":{"$ref":"#/components/schemas/WorkspaceMemberResponseDto"}},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp","example":"2023-10-26T10:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp","example":"2023-10-26T10:30:00.000Z"}},"required":["id","name","organizationId","createdAt","updatedAt"]},"CreateWorkspaceDto":{"type":"object","properties":{"name":{"type":"string","description":"Workspace name","example":"Development Team","maxLength":100},"description":{"type":"string","description":"Workspace description","example":"Workspace for development team collaboration","maxLength":500},"organizationId":{"type":"string","description":"Organization ID where this workspace belongs","example":"cl9eb9q6y00003b6f0q1g7c8d"},"tags":{"description":"Workspace tags (ABAC scoping). Members whose `UserOrg.tags` intersect this set will have access.","type":"array","items":{"type":"string"}}},"required":["name","organizationId"]},"UpdateWorkspaceDto":{"type":"object","properties":{"name":{"type":"string","description":"Workspace name","example":"Updated Development Team","maxLength":100},"description":{"type":"string","description":"Workspace description","example":"Updated workspace description","maxLength":500},"tags":{"description":"Workspace tags (ABAC scoping). Replaces the current set.","type":"array","items":{"type":"string"}}}},"AddMemberByEmailDto":{"type":"object","properties":{"email":{"type":"string","description":"Email of the person to add (find-or-create User by email, lowercased)","example":"jane.doe@csn.com.br"},"role":{"type":"string","description":"System role to attach to the membership (org-scoped)","enum":["admin","member","guest"],"example":"member"}},"required":["email","role"]},"CreateRoleAbacDto":{"type":"object","properties":{"name":{"type":"string","description":"Nome interno da role (snake_case)"},"displayName":{"type":"string","description":"Display name humano"},"description":{"type":"string","description":"Descrição da role"},"permissions":{"description":"Permission keys (ex: \"audit.read\", \"material.write\")","type":"array","items":{"type":"string"}},"tags":{"description":"Tags ABAC (ex: [\"plant:cig\"]) usadas no tag intersect com Workspace.tags","type":"array","items":{"type":"string"}}},"required":["name","displayName"]},"UpdateRoleAbacDto":{"type":"object","properties":{"displayName":{"type":"string"},"description":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}}}},"MeScopeResponseDto":{"type":"object","properties":{"orgId":{"type":"string","description":"Organization ID do usuario logado.","example":"org-uuid"},"workspaceId":{"type":"string","description":"Workspace ID resolvido pelo header X-Workspace-Id ou route param. Undefined quando request eh @OrgScoped (cross-workspace)."},"permissions":{"description":"Permissions efetivas (UserOrg.permissions ∪ Role.permissions). ['*'] indica owner bypass — pode tudo.","example":["command-center.read","materials.read"],"type":"array","items":{"type":"string"}},"accessibleAreas":{"description":"UI hint: areas do menu lateral acessiveis baseado em permissions. Owner ('*') retorna todas areas conhecidas.","example":["command-center","materials","similarity"],"type":"array","items":{"type":"string"}},"role":{"type":"object","description":"Role name legacy (RBAC pre-ABAC). Null V1 — RBAC migrado pra ABAC pura (Story 1.6). Campo presente apenas pra compat futura.","example":null},"effectivePermissions":{"deprecated":true,"description":"DEPRECATED — use `permissions` (mesmo array). Mantido pra FE compat (consumers pre-Story 9.1 esperam este nome). Remocao em V2 — migrar ja.","example":["command-center.read"],"type":"array","items":{"type":"string"}},"isOrgOwner":{"type":"boolean","deprecated":true,"description":"DEPRECATED — use `permissions.includes('*')` em vez de checar este campo. Mantido pra FE compat (consumers pre-Story 9.1 esperam owner bypass via flag dedicada). Remocao em V2 — migrar ja.","example":false}},"required":["orgId","permissions","accessibleAreas"]},"CreateIngestionRunResponseDto":{"type":"object","properties":{"ingestionRunId":{"type":"string","description":"ID canonical do `IngestionLog` criado (exposto como `ingestionRunId`).","format":"uuid"},"pipelineRunId":{"type":"string","description":"ID do `PipelineRun` correlato quando já existe no momento do 202 (B-010). Caso ausente, o cliente pode obter via `GET /workspaces/:ws/pipeline-runs?ingestionLogId=...`.","format":"uuid"},"status":{"type":"string","description":"Status inicial — sempre `queued`.","example":"queued"}},"required":["ingestionRunId","status"]},"InitIngestionRunUploadDto":{"type":"object","properties":{"fileName":{"type":"string","description":"Nome original do arquivo."},"contentType":{"type":"string","description":"Content-Type que será usado no PUT assinado."},"sizeBytes":{"type":"number","description":"Tamanho do arquivo em bytes.","maximum":209715200},"familyHint":{"type":"string","description":"Hint opcional do nome/slug da família para acelerar resolução pelo pipeline.","maxLength":200}},"required":["fileName","contentType","sizeBytes"]},"InitIngestionRunUploadResponseDto":{"type":"object","properties":{"ingestionRunId":{"type":"string","description":"ID canonical do `IngestionLog` criado em status `uploading`.","format":"uuid"},"status":{"type":"string","description":"Status enquanto o browser envia direto para o bucket.","example":"uploading"},"upload":{"type":"object","description":"URL assinada neutra do BucketPort para upload direto do browser."}},"required":["ingestionRunId","status","upload"]},"CompleteIngestionRunUploadDto":{"type":"object","properties":{"familyHint":{"type":"string","description":"Hint opcional do nome/slug da família reenviado no complete do upload direto.","maxLength":200}}},"GovernanceRuleDto":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"actionType":{"type":"string","enum":["PIPELINE_STARTED","PIPELINE_RESUMED","PIPELINE_CANCELLED","STEP_RERUN","GATE_DECISION","MATERIAL_UPLOADED","MATERIAL_ENRICHED","RULE_CREATED","RULE_UPDATED","RULE_DELETED","APPROVAL_REQUESTED","APPROVAL_GRANTED","APPROVAL_DENIED","OPERATIONAL_DATA_UPDATE","FAMILY_SCHEMA_VERSIONED","FAMILY_SCHEMA_STRUCTURAL_CHANGE"]},"effect":{"type":"string","enum":["ALLOW","REQUIRE_JUSTIFICATION","REQUIRE_APPROVAL","DENY"]},"pipelineType":{"type":"object","nullable":true},"stepType":{"type":"string","nullable":true,"enum":["PARSE_XLSX","PRE_CLEAN","ANALYZE_XLSX","SCAN_MANUFACTURERS","DOMAIN_RESEARCH","MANUFACTURER_RESEARCH","SCHEMA_GENERATION","TRIAL_EXTRACTION","SCHEMA_REFINEMENT","EXTRACTION_PROMPT","MANUFACTURER_RULES","SIMILARITY_CONFIG","RESEARCH_LOG","MASS_EXTRACTION","INFERENCE","ENRICHMENT","SIMILARITY_COMPUTE","KNOWLEDGE_BASE"]},"roleId":{"type":"object","nullable":true},"approverRoleId":{"type":"object","nullable":true},"description":{"type":"object","nullable":true},"isActive":{"type":"boolean"},"priority":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","workspaceId","actionType","effect","isActive","priority","createdAt","updatedAt"]},"GovernanceApprovalRequestDto":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"requesterId":{"type":"string"},"ruleId":{"type":"object","nullable":true},"actionType":{"type":"string","enum":["PIPELINE_STARTED","PIPELINE_RESUMED","PIPELINE_CANCELLED","STEP_RERUN","GATE_DECISION","MATERIAL_UPLOADED","MATERIAL_ENRICHED","RULE_CREATED","RULE_UPDATED","RULE_DELETED","APPROVAL_REQUESTED","APPROVAL_GRANTED","APPROVAL_DENIED","OPERATIONAL_DATA_UPDATE","FAMILY_SCHEMA_VERSIONED","FAMILY_SCHEMA_STRUCTURAL_CHANGE"]},"pipelineType":{"type":"object","nullable":true},"stepType":{"type":"string","nullable":true,"enum":["PARSE_XLSX","PRE_CLEAN","ANALYZE_XLSX","SCAN_MANUFACTURERS","DOMAIN_RESEARCH","MANUFACTURER_RESEARCH","SCHEMA_GENERATION","TRIAL_EXTRACTION","SCHEMA_REFINEMENT","EXTRACTION_PROMPT","MANUFACTURER_RULES","SIMILARITY_CONFIG","RESEARCH_LOG","MASS_EXTRACTION","INFERENCE","ENRICHMENT","SIMILARITY_COMPUTE","KNOWLEDGE_BASE"]},"familyId":{"type":"object","nullable":true},"justification":{"type":"object","nullable":true},"actionPayload":{"type":"object"},"ingestionLogId":{"type":"object","nullable":true},"status":{"type":"string","enum":["PENDING","APPROVED","DENIED","EXPIRED"]},"resolvedById":{"type":"object","nullable":true},"resolvedAt":{"type":"object","nullable":true},"resolverComment":{"type":"object","nullable":true},"expiresAt":{"format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","workspaceId","requesterId","actionType","actionPayload","status","expiresAt","createdAt","updatedAt"]},"CreateFamilyDto":{"type":"object","properties":{"name":{"type":"string","example":"Bearings","maxLength":120,"minLength":2},"description":{"type":"string","example":"Canonical family for radial and axial bearings.","maxLength":500}},"required":["name"]},"ActiveFamilySchemaVersionDto":{"type":"object","properties":{"version":{"type":"number","example":1},"activatedAt":{"type":"string","example":"2026-05-15T10:00:00.000Z"}},"required":["version","activatedAt"]},"ParentFamilySummaryDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","example":"rolamentos"},"name":{"type":"string","example":"Rolamentos"}},"required":["id","slug","name"]},"SubFamilySummaryDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","example":"rolamentos-skf"},"name":{"type":"string","example":"Rolamentos SKF"},"status":{"type":"string","enum":["DRAFT","ACTIVE","DEPRECATED"]},"workspaceMaterialCount":{"type":"number","example":0},"canonicalMaterialCount":{"type":"number","example":0}},"required":["id","slug","name","status","workspaceMaterialCount","canonicalMaterialCount"]},"FamilyCoverageDto":{"type":"object","properties":{"extractionPct":{"type":"number","example":80,"description":"Percentual com 1 casa decimal"},"categorizationPct":{"type":"number","example":75.5,"description":"Percentual com 1 casa decimal"},"materialCount":{"type":"number","example":10},"extractedCount":{"type":"number","example":8},"categorizedCount":{"type":"number","example":7}},"required":["extractionPct","categorizationPct","materialCount","extractedCount","categorizedCount"]},"FamilyResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","example":"bearings"},"name":{"type":"string","example":"Bearings"},"description":{"type":"object","example":"Canonical family for bearings."},"status":{"type":"string","enum":["DRAFT","ACTIVE","DEPRECATED"]},"totalMaterials":{"type":"number","example":0},"workspaceMaterialCount":{"type":"number","example":0},"canonicalMaterialCount":{"type":"number","example":0},"activeSchemaVersion":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ActiveFamilySchemaVersionDto"}]},"activeCrCount":{"type":"object","example":2,"nullable":true,"description":"Count de CRs em AWAITING_CONSENSUS. null = usuário sem permissão family.cr.read."},"parentFamilyId":{"type":"object","format":"uuid","nullable":true,"description":"ID da família-pai (sub-família criada via Story 4.7)."},"parentFamily":{"nullable":true,"description":"Resumo da família-pai. Populado apenas em findById.","allOf":[{"$ref":"#/components/schemas/ParentFamilySummaryDto"}]},"subFamilies":{"description":"Sub-famílias diretas ordenadas por createdAt asc. Populado apenas em findById.","type":"array","items":{"$ref":"#/components/schemas/SubFamilySummaryDto"}},"coverage":{"nullable":true,"description":"Cobertura de extração e categorização. null quando a família não tem materiais.","allOf":[{"$ref":"#/components/schemas/FamilyCoverageDto"}]},"createdAt":{"type":"string","example":"2026-05-15T10:00:00.000Z"},"updatedAt":{"type":"string","example":"2026-05-15T10:00:00.000Z"}},"required":["id","slug","name","status","totalMaterials","workspaceMaterialCount","canonicalMaterialCount","createdAt","updatedAt"]},"UpdateFamilyDto":{"type":"object","properties":{"name":{"type":"string","example":"Bearings","maxLength":120,"minLength":2},"description":{"type":"object","example":"Canonical family for radial and axial bearings.","maxLength":500,"nullable":true},"status":{"type":"string","enum":["DRAFT","ACTIVE","DEPRECATED"]}}},"ImportTaxonomyDto":{"type":"object","properties":{"nodes":{"type":"array","description":"Nós da árvore CSN (níveis 1-3). Validados por Zod no service.","items":{"type":"object"}},"rules":{"type":"array","description":"Regras parametrizadas por categoria. Validadas por Zod no service.","items":{"type":"object"}}},"required":["nodes"]},"TaxonomyImportResultDto":{"type":"object","properties":{"nodesUpserted":{"type":"number","description":"Nós criados ou atualizados (drift)."},"nodesDeprecated":{"type":"number","description":"Nós ACTIVE ausentes do payload marcados DEPRECATED."},"rulesCreated":{"type":"number","description":"Versões de regra criadas."},"rulesSuperseded":{"type":"number","description":"Versões de regra anteriores marcadas SUPERSEDED."}},"required":["nodesUpserted","nodesDeprecated","rulesCreated","rulesSuperseded"]},"ClassifyMaterialsDto":{"type":"object","properties":{"materialIds":{"description":"IDs de materiais canônicos a classificar (máx. 100).","type":"array","items":{"type":"string"}}},"required":["materialIds"]},"BatchCategorizeDto":{"type":"object","properties":{"familyId":{"type":"string","description":"ID da família cujos materiais pendentes serão classificados."}},"required":["familyId"]},"DiagnoseFamilyDto":{"type":"object","properties":{"familyId":{"type":"string","description":"ID da família a diagnosticar."}},"required":["familyId"]},"BulkAcceptDivergencesDto":{"type":"object","properties":{"divergenceIds":{"description":"IDs de divergências a aceitar (máx. 200).","type":"array","items":{"type":"string"}}},"required":["divergenceIds"]},"RejectDivergenceDto":{"type":"object","properties":{"reason":{"type":"string","description":"Motivo da rejeição da reclassificação."}},"required":["reason"]},"EnqueueSubFamilyAnalysisResultDto":{"type":"object","properties":{"familyId":{"type":"string","format":"uuid"},"jobId":{"type":"string"},"status":{"type":"string","enum":["queued","already_queued"]},"acceptedAt":{"type":"string"}},"required":["familyId","jobId","status","acceptedAt"]},"SubFamilySuggestionListItemDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"familyId":{"type":"string","format":"uuid"},"divergentField":{"type":"string","example":"tipo_carga"},"dispersionScore":{"type":"number","example":0.82,"description":"Força da dispersão ∈ [0,1]"},"status":{"type":"string","enum":["PENDING","ACCEPTED","REJECTED","MODIFIED"]},"materialCount":{"type":"number","example":540,"description":"Materiais da família analisados"},"candidateCount":{"type":"number","example":2,"description":"Clusters nativos candidatos"},"rationale":{"type":"object"},"generatedAt":{"type":"string"},"decidedAt":{"type":"object"}},"required":["id","familyId","divergentField","dispersionScore","status","materialCount","candidateCount","generatedAt"]},"SubFamilyCandidateDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string","example":"tipo_carga = radial"},"dominantValue":{"type":"string","example":"radial"},"materialCount":{"type":"number","example":320},"createdSubFamilyId":{"type":"object","format":"uuid","description":"Family filha criada (após aceite)"}},"required":["id","label","dominantValue","materialCount"]},"SubFamilySuggestionDetailDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"familyId":{"type":"string","format":"uuid"},"divergentField":{"type":"string","example":"tipo_carga"},"dispersionScore":{"type":"number","example":0.82,"description":"Força da dispersão ∈ [0,1]"},"status":{"type":"string","enum":["PENDING","ACCEPTED","REJECTED","MODIFIED"]},"materialCount":{"type":"number","example":540,"description":"Materiais da família analisados"},"candidateCount":{"type":"number","example":2,"description":"Clusters nativos candidatos"},"rationale":{"type":"object"},"generatedAt":{"type":"string"},"decidedAt":{"type":"object"},"candidates":{"type":"array","items":{"$ref":"#/components/schemas/SubFamilyCandidateDto"}}},"required":["id","familyId","divergentField","dispersionScore","status","materialCount","candidateCount","generatedAt","candidates"]},"SubFamilySelectionDto":{"type":"object","properties":{"candidateId":{"type":"string","format":"uuid","description":"ID do SubFamilyClusterCandidate"},"name":{"type":"string","description":"Nome da sub-família a criar","minLength":2,"maxLength":120},"materialIds":{"description":"IDs de materiais da sub-família. Ausente = usa os materiais do candidato. Presente = proposta modificada pela engenheira.","type":"array","items":{"type":"string"}}},"required":["candidateId","name"]},"AcceptSubFamilySuggestionDto":{"type":"object","properties":{"selections":{"description":"Candidatos a materializar","type":"array","items":{"$ref":"#/components/schemas/SubFamilySelectionDto"}}},"required":["selections"]},"CreatedSubFamilyDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Rolamentos de carga radial"},"slug":{"type":"string","example":"rolamentos-de-carga-radial"},"materialCount":{"type":"number","example":320},"schemaResearchTriggered":{"type":"boolean","description":"true se o schema-research da sub-família foi disparado com sucesso"}},"required":["id","name","slug","materialCount","schemaResearchTriggered"]},"AcceptSubFamilyResultDto":{"type":"object","properties":{"suggestionId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["PENDING","ACCEPTED","REJECTED","MODIFIED"]},"createdSubFamilies":{"type":"array","items":{"$ref":"#/components/schemas/CreatedSubFamilyDto"}}},"required":["suggestionId","status","createdSubFamilies"]},"RejectSubFamilySuggestionDto":{"type":"object","properties":{"rationale":{"type":"string","description":"Justificativa técnica da rejeição","maxLength":1000}},"required":["rationale"]},"SchemaVersionListItemDto":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"status":{"type":"string"},"isActive":{"type":"boolean"},"activatedAt":{"type":"string","nullable":true},"deactivatedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"},"proposedById":{"type":"string","nullable":true},"approvedByCRId":{"type":"string","nullable":true},"justification":{"type":"string","nullable":true},"changeSet":{"type":"object","additionalProperties":true,"nullable":true}},"required":["id","version","status","isActive","activatedAt","deactivatedAt","createdAt","proposedById","approvedByCRId","justification"]},"SchemaVersionListResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SchemaVersionListItemDto"}},"nextCursor":{"type":"string","nullable":true}},"required":["items","nextCursor"]},"SchemaVersionDetailDto":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"status":{"type":"string"},"isActive":{"type":"boolean"},"activatedAt":{"type":"string","nullable":true},"deactivatedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"},"proposedById":{"type":"string","nullable":true},"approvedByCRId":{"type":"string","nullable":true},"justification":{"type":"string","nullable":true},"changeSet":{"type":"object","additionalProperties":true,"nullable":true},"definition":{"type":"object","additionalProperties":true}},"required":["id","version","status","isActive","activatedAt","deactivatedAt","createdAt","proposedById","approvedByCRId","justification","definition"]},"BumpSchemaVersionRequestDto":{"type":"object","properties":{"newDefinition":{"type":"object","description":"Nova definição completa do schema (FamilySchemaDefinition). Validada via Zod no service.","additionalProperties":true},"justification":{"type":"string","description":"Justificativa da mudança (mínimo 10 caracteres)."},"crId":{"type":"string","description":"ID do Change Request aprovado (Epic 4 plug). Se ausente, requer permissão direta de bump."}},"required":["newDefinition","justification"]},"StructuralChangeRequestDto":{"type":"object","properties":{"newDefinition":{"type":"object","description":"Nova definição estrutural do schema (add/remove field, type, eliminatory).","additionalProperties":true},"justification":{"type":"string","description":"Justificativa da mudança estrutural (mínimo 10 caracteres)."}},"required":["newDefinition","justification"]},"DiagnoseTaxonomyDto":{"type":"object","properties":{}},"ApplyTaxonomyRecommendationDto":{"type":"object","properties":{"justification":{"type":"string","description":"Justificativa opcional do apply (audit)."}}},"RejectTaxonomyRecommendationDto":{"type":"object","properties":{"reason":{"type":"string","description":"Motivo da rejeição (mín 5 chars)."}},"required":["reason"]},"BulkDismissTaxonomyRecommendationsDto":{"type":"object","properties":{"recommendationIds":{"description":"IDs de recomendações a descartar (max 100).","type":"array","items":{"type":"string"}}},"required":["recommendationIds"]},"SimilarityCandidate":{"type":"object","properties":{"materialId":{"type":"string","description":"materialId (UUID) of the candidate"},"score":{"type":"object","description":"Similarity score ∈ [0, 1]. Null when schema has zero non-eliminatory fields.","nullable":true},"breakdown":{"description":"FR9 breakdown: eliminatory entries (weight:\"eliminatorio\", contribution:\"pass\") merged with scored entries (fieldScore/weight/contribution numeric).","type":"array","items":{"type":"string"}},"spectraCheckScore":{"type":"number","description":"Spectra Check score — null until Spectra Check feature exists.","nullable":true},"isCrossPlant":{"type":"boolean","description":"True when this candidate belongs to a different workspace plant."},"loosened":{"type":"boolean","description":"True when this candidate was found via loosened tolerance-band search (FR10). False when found in the strict pass.","default":false},"loosenedRationale":{"type":"object","description":"Loosening rationale — acceptedAtTier, toleranceFactorApplied, expandedFields. Present only when loosened=true."}},"required":["materialId","breakdown","isCrossPlant","loosened"]},"MaterialSimilarityResponseDto":{"type":"object","properties":{"candidates":{"type":"array","items":{"$ref":"#/components/schemas/SimilarityCandidate"}},"queryAst":{"type":"object","description":"The deterministic AST built from eliminatory specs of the reference material."},"generatedAt":{"type":"string","description":"ISO 8601 timestamp of when this response was generated."},"looseningAttempted":{"type":"boolean","description":"True when the loosened search was attempted (strict pass returned 0 candidates). False when strict pass succeeded or allowLoosening=false was passed."},"looseningApplied":{"type":"object","description":"Summary of the loosening applied — tier, factor, expanded fields. Present only when loosening produced ≥1 candidate."},"message":{"type":"string","description":"Honest fallback message — present only when no candidate passed minimum confidence. Value: \"Nenhum similar encontrado com confiança suficiente\"."}},"required":["candidates","queryAst","generatedAt"]},"WeightOverrideDto":{"type":"object","properties":{"field":{"type":"string","description":"Nome canonical do campo (snake_case)."},"weight":{"type":"number","description":"Peso aplicado ao campo no cálculo do score (∈ [0,1]). UI normaliza 0–100%. 0 = ignora o campo no scoring.","minimum":0,"maximum":1}},"required":["field","weight"]},"AnalyzeSimilarityDto":{"type":"object","properties":{"familyId":{"type":"string","description":"UUID da família a ser analisada."},"scope":{"type":"string","enum":["family","anchor"],"description":"'family' — todos os grupos conexos da família. 'anchor' — top similares ao material âncora."},"anchorMaterialId":{"type":"string","description":"UUID do material âncora (obrigatório quando scope=anchor)."},"weights":{"description":"Override por campo do peso usado no scoring. Campos não listados usam o peso default do schema (uniforme).","type":"array","items":{"$ref":"#/components/schemas/WeightOverrideDto"}},"eliminatoryFields":{"description":"Lista canonical de campos que devem ser tratados como eliminatórios nesta análise (sobrescreve o flag `eliminatory` do schema). Campos não listados deixam de ser eliminatórios.","type":"array","items":{"type":"string"}},"threshold":{"type":"number","description":"Score mínimo (∈ [0,1]) que um par precisa atingir para virar aresta no grafo de grupos. Default = similarityThresholds.alta do schema (ou 0.8).","minimum":0,"maximum":1}},"required":["familyId","scope"]},"AnalyzeParamsAppliedDto":{"type":"object","properties":{"familyId":{"type":"string"},"scope":{"type":"string","enum":["family","anchor"]},"anchorMaterialId":{"type":"string"},"schemaVersionId":{"type":"string","description":"Schema version ativo no momento da análise — fixa o snapshot determinístico."},"threshold":{"type":"number","minimum":0,"maximum":1},"weights":{"type":"object","description":"Pesos efetivos por campo após aplicar overrides. Campos não-overridden vêm com weight=1 (uniform default do Pattern R).","additionalProperties":{"type":"number"}},"eliminatoryFields":{"description":"Lista de campos tratados como eliminatórios nesta execução (depois dos overrides).","type":"array","items":{"type":"string"}}},"required":["familyId","scope","schemaVersionId","threshold","weights","eliminatoryFields"]},"AnalyzeMemberDto":{"type":"object","properties":{"materialId":{"type":"string"},"score":{"type":"number","minimum":0,"maximum":1},"fr9Breakdown":{"type":"array","description":"Justificativa FR9 (eliminatórios aplicados + scored fields). Refletindo weights overrides quando passados."}},"required":["materialId","score","fr9Breakdown"]},"AnalyzeGroupDto":{"type":"object","properties":{"anchorMaterialId":{"type":"string","description":"Material representativo do grupo (anchor). No scope=anchor, é o material passado pelo cliente."},"materialCount":{"type":"number","minimum":2},"avgScore":{"type":"number","minimum":0,"maximum":1,"description":"Média dos scores dos membros vs anchor."},"members":{"type":"array","items":{"$ref":"#/components/schemas/AnalyzeMemberDto"}}},"required":["anchorMaterialId","materialCount","avgScore","members"]},"AnalyzeSimilarityResponseDto":{"type":"object","properties":{"paramsApplied":{"$ref":"#/components/schemas/AnalyzeParamsAppliedDto"},"materialsConsidered":{"type":"number","description":"Quantidade de materiais da família considerados na análise."},"groups":{"description":"Grupos resultantes. Lista vazia quando nada bate no threshold.","type":"array","items":{"$ref":"#/components/schemas/AnalyzeGroupDto"}}},"required":["paramsApplied","materialsConsidered","groups"]},"FilterContextDto":{"type":"object","properties":{"familyId":{"type":"string","description":"Filter by Family UUID (family.read permission required)"},"manufacturerId":{"type":"string","description":"Filter by Manufacturer UUID (manufacturer.read permission required)"},"scoreMin":{"type":"number","description":"Minimum similarity score [0, 1]. Applied via Math.max(input.threshold, scoreMin) in get_similar."}}},"CreateChatDto":{"type":"object","properties":{"firstMessage":{"type":"string","description":"First user message that seeds the conversation","maxLength":8000},"attachments":{"description":"Optional pre-uploaded attachment URLs (Story 7.6 multimodal). Validated shape only here.","maxItems":10,"type":"array","items":{"type":"string"}},"filterContext":{"description":"Story 7.7 (FR34) — initial filter context for tool scoping (familyId, manufacturerId, scoreMin).","allOf":[{"$ref":"#/components/schemas/FilterContextDto"}]}},"required":["firstMessage"]},"SendMessageDto":{"type":"object","properties":{"content":{"type":"string","description":"User message content","maxLength":8000},"attachments":{"description":"Optional gcsPath list from prior uploads to enrich AI text turn with Gemini Vision extracted context (Story 7.6-N1 AC3). Format: {orgId}/chat/{chatId}/{uuid}.{ext}. Controller validates ownership via regex and caps extraction at 3 items.","maxItems":10,"type":"array","items":{"type":"string"}},"filterContext":{"description":"Story 7.7 (FR34) — filter context for this message turn. undefined = use persisted session filters (sticky). null = clear all filters. object = replace entire filterContext (not field-level merge).","allOf":[{"$ref":"#/components/schemas/FilterContextDto"}]}},"required":["content"]},"SubmitFeedbackDto":{"type":"object","properties":{"vote":{"type":"string","enum":["UP","DOWN"],"description":"Feedback vote polarity"},"comment":{"type":"object","description":"Optional freeform comment, max 2000 chars. **3-state semantics** (Gemini PR-#206 fix): (a) field ausente do body → preserva o comment server-side existente em re-votes; (b) `null` explícito → limpa comment server-side; (c) string → atualiza (trimmed; whitespace-only trata como null).","maxLength":2000,"nullable":true}},"required":["vote"]},"ChatAttachmentResponseDto":{"type":"object","properties":{"gcsPath":{"type":"string","description":"GCS object path (org/chat/uuid).","example":"org-1/chat-c1/abc-def.jpg"},"signedUrl":{"type":"string","description":"Signed URL TTL 7d for FE thumbnail/preview.","example":"https://storage.googleapis.com/..."},"filename":{"type":"string","description":"Original filename (sanitized).","example":"plaqueta-bomba.jpg"},"mimeType":{"type":"string","description":"MIME type stored.","example":"image/jpeg"},"sizeBytes":{"type":"number","description":"File size in bytes.","example":1245678},"extraction":{"type":"object","description":"Extraction result when Gemini Vision succeeded. Omitted on extraction failure.","example":{"manufacturer":"KSB","model":"Etanorm","dimensions":"DN150","detectedTags":["plaqueta_KSB","modelo_Etanorm","DN_150"],"confidence":"high"}},"extractionError":{"type":"string","description":"Failure reason when Gemini Vision did not produce usable output.","example":"extraction_failed"}},"required":["gcsPath","signedUrl","filename","mimeType","sizeBytes"]},"StartSchemaResearchDto":{"type":"object","properties":{"familyId":{"type":"string","description":"Canonical Family ID (UUID v4) to run schema-research on"},"mode":{"type":"string","description":"Pipeline execution mode. AUTO runs all steps without pausing. MANUAL pauses at gate steps for human review.","enum":["AUTO","MANUAL"],"default":"AUTO"},"justification":{"type":"string","description":"Justification for governance compliance (required when governance rules mandate it)"}},"required":["familyId"]},"SchemaResearchGateDecisionDto":{"type":"object","properties":{"decision":{"type":"string","description":"Gate decision","enum":["approve","reject-with-reason","edit-field"]},"reason":{"type":"string","description":"Reason text (required when decision is reject-with-reason). Injected as [FEEDBACK] in the re-run step so the AI receives the engineer's critique."},"editedArtifact":{"type":"object","description":"Engineer-edited artifact (required when decision is edit-field). Must be a valid GeneratedSchema JSON object. Stored in PipelineRun.parameters.editedSchemaOverrides and consumed by finalize-schema."},"rejectTargetStepName":{"type":"string","description":"Override the default rejection target step name. Defaults to the step immediately before the gate. Only valid when decision is reject-with-reason."},"justification":{"type":"string","description":"Justification for governance compliance"}},"required":["decision"]},"SchemaResearchPatchArtifactDto":{"type":"object","properties":{"artifact":{"type":"object","description":"The edited artifact (any JSON-serializable value). Typically a GeneratedSchema object for the propose-fields or refine-schema step. Stored via gate-decision edit-field path in PipelineRun.parameters.editedSchemaOverrides."}},"required":["artifact"]},"ReprocessDryRunDto":{"type":"object","properties":{"kind":{"type":"string","description":"Tipo de re-processamento. schema-research re-roda pipeline IA inteiro de uma família.","enum":["schema-research","categorization","linkage"]},"familyId":{"type":"string","description":"Family ID quando scope=family (default pra schema-research)."},"workspaceId":{"type":"string","description":"Workspace ID opcional pra further scoping."}},"required":["kind"]},"ReprocessDryRunResponseDto":{"type":"object","properties":{"materialsAffected":{"type":"number","description":"Total materials potencialmente re-avaliados."},"scopeLabel":{"type":"string","description":"Scope label pra UI dialog (ex: \"Família Rolamentos\")."},"suggestedConfirmationToken":{"type":"string","description":"Token sugerido pra UI mostrar no input de confirmação. User deve digitar EXATAMENTE este valor; server compara case-sensitive ao receber em X-Reprocess-Confirmation header.","example":"RE-RUN ROLAMENTOS"}},"required":["materialsAffected","scopeLabel","suggestedConfirmationToken"]},"BumpSpectraCheckConfigDto":{"type":"object","properties":{"version":{"type":"string","description":"Semver string da nova versão. Histórico permanente — não pode colidir com version existente (UNIQUE constraint).","minLength":5,"maxLength":64,"example":"v0.2.0-calibrated"},"sourceQualityMap":{"type":"object","description":"Map { sourceName: qualityWeight }. Ex.: {\"motion\": 0.7, \"schaeffler\": 1.0}. Tabela vinculante: spike §4. Sources não mapeados caem em unknownSourceQuality.","example":{"motion":0.7,"schaeffler":1,"skf":1}},"unknownSourceQuality":{"type":"number","description":"Fallback weight para source desconhecida.","example":0.4},"multiSourceBonus":{"type":"number","description":"Bonus quando ≥2 sources distintas.","example":0.2},"assetBonus":{"type":"number","description":"Bonus quando image + (docs OR 3D).","example":0.1},"localValidationBonus":{"type":"number","description":"Bonus quando engineer confirmou linkage.","example":0.15},"temporalDecayMax":{"type":"number","description":"Decay máximo aplicado em material >= window dias.","example":0.3},"temporalDecayWindowDays":{"type":"number","description":"Janela em dias para decay máximo (default v0.1.0 = 365 = 12 meses).","minimum":30,"maximum":3650,"example":365},"verifiedThreshold":{"type":"number","description":"Threshold de aceitação para verified=true.","example":0.6}},"required":["version","sourceQualityMap","unknownSourceQuality","multiSourceBonus","assetBonus","localValidationBonus","temporalDecayMax","temporalDecayWindowDays","verifiedThreshold"]},"RunMaterialTaxonomySanityCheckDto":{"type":"object","properties":{"persistSnapshot":{"type":"boolean","description":"When true, persists the result as MaterialTextSpec snapshot (source=AI_INFERRED). Default false.","default":false}}},"EmissorMaterialSubmissionDto":{"type":"object","properties":{"name":{"type":"string","description":"Descrição/nome rico do material"},"manufacturer":{"type":"string","description":"Fabricante (string livre — resolvido server-side em Manufacturer)"},"model":{"type":"string","description":"Modelo do material"},"partNumber":{"type":"string","description":"Part-number (catálogo)"},"descriptionRich":{"type":"string","description":"Descrição livre adicional"},"familyId":{"type":"string","description":"familyId UUID — pré-selecionada pelo emissor"},"workspaceId":{"type":"string","description":"workspaceId UUID — workspace de origem do submitter"}},"required":["name","familyId","workspaceId"]},"EmissorChatSubmissionDraftDto":{"type":"object","properties":{"name":{"type":"string","description":"Descrição/nome rico do material."},"manufacturer":{"type":"string","description":"Fabricante (string livre — resolvido server-side em Manufacturer)."},"model":{"type":"string","description":"Modelo do material."},"partNumber":{"type":"string","description":"Part-number (catálogo)."},"descriptionRich":{"type":"string","description":"Descrição livre adicional."},"familyId":{"type":"string","description":"familyId UUID — pré-selecionada pelo emissor."},"chatId":{"type":"string","description":"UUID do chat origem da submission."},"messageId":{"type":"string","description":"UUID da message do chat que dispara a submission."}},"required":["name","familyId","chatId","messageId"]},"EmissorChatSubmissionLinkDto":{"type":"object","properties":{"name":{"type":"string","description":"Descrição/nome rico do material."},"manufacturer":{"type":"string","description":"Fabricante (string livre — resolvido server-side em Manufacturer)."},"model":{"type":"string","description":"Modelo do material."},"partNumber":{"type":"string","description":"Part-number (catálogo)."},"descriptionRich":{"type":"string","description":"Descrição livre adicional."},"familyId":{"type":"string","description":"familyId UUID — pré-selecionada pelo emissor."},"chatId":{"type":"string","description":"UUID do chat origem da submission."},"messageId":{"type":"string","description":"UUID da message do chat que dispara a submission."},"targetMaterialId":{"type":"string","description":"UUID do Material ACTIVE alvo do vínculo — usuário clicou \"Vincular esta planta\" num MaterialResultCard."},"forceCreateNewWorkspaceMaterial":{"type":"boolean","description":"Reservado para futuro (deferred 7.8-LINK-EXISTING-WM-DISTINCT) — quando true, cria WorkspaceMaterial novo apontando ao mesmo Material mesmo que AUTO_LINK seria possível. Service core hoje não consome este flag."},"justification":{"type":"string","description":"Justificativa opcional do vínculo (≤2000 chars)."}},"required":["name","familyId","chatId","messageId","targetMaterialId"]},"PromoteDraftDto":{"type":"object","properties":{"justification":{"type":"string","description":"Justification ≥ 10 chars — surfaced in audit trail (FR16a)."}},"required":["justification"]},"LinkDraftToExistingDto":{"type":"object","properties":{"targetMaterialId":{"type":"string","description":"Existing ACTIVE Material UUID to link to."},"justification":{"type":"string","description":"Justification ≥ 10 chars — surfaced in audit trail (FR16a)."}},"required":["targetMaterialId","justification"]},"RejectDraftDto":{"type":"object","properties":{"reason":{"type":"string","description":"Reason ≥ 10 chars — surfaced in audit trail."}},"required":["reason"]},"BulkPromoteDraftsDto":{"type":"object","properties":{"draftIds":{"description":"Up to 100 draft IDs.","type":"array","items":{"type":"string"}}},"required":["draftIds"]},"ConfirmClusterDto":{"type":"object","properties":{"justification":{"type":"string"}}},"RejectClusterDto":{"type":"object","properties":{"reason":{"type":"string","description":"Reason ≥ 10 chars."}},"required":["reason"]},"RevertDeprecationDto":{"type":"object","properties":{"workspaceId":{"type":"string","description":"Workspace de contexto (audit + WS scope).","format":"uuid"},"reason":{"type":"string","description":"Justificativa human-readable da reversão. 10..500 chars.","minLength":10,"maxLength":500},"clearSignals":{"type":"boolean","description":"Quando true (default), também limpa ObsolescenceSignal ativos."}},"required":["workspaceId","reason"]},"AuditCoverageDto":{"type":"object","properties":{"percent":{"type":"number","description":"% mutations com audit_log row populado (V1 sentinel 100%; real metric → deferred 8.1-AUDIT-COVERAGE-REAL).","minimum":0,"maximum":100},"status":{"type":"string","description":"ok = cobertura ≥99.9% (target NFR); degraded = caiu (investigar).","enum":["ok","degraded"]}},"required":["percent","status"]},"CommandCenterOverviewDto":{"type":"object","properties":{"pendingApprovals":{"type":"number","description":"GovernanceApprovalRequest pendentes no scope atual."},"openPriceAnomalies":{"type":"number","description":"PriceAnomaly com status=open no scope atual."},"pendingMaterialDocuments":{"type":"number","description":"MaterialDocument sem documentType atribuído — proxy de \"aguardando classificação\" enquanto a feature de status formal não existe."},"recentAuditCount":{"type":"number","description":"AuditLog rows criadas nas últimas 24h no scope atual."},"pendingDraftMaterials":{"type":"number","description":"Story 8.1 — Material em status=DRAFT submetido por emissor (Story 4.5 FR16a fluxo two-step) aguardando aprovação CIG. Card \"Hoje\" landing /comando."},"auditCoverage":{"description":"Story 8.1 — cobertura audit trail (% mutations com audit_log row + status).","allOf":[{"$ref":"#/components/schemas/AuditCoverageDto"}]}},"required":["pendingApprovals","openPriceAnomalies","pendingMaterialDocuments","recentAuditCount","pendingDraftMaterials","auditCoverage"]},"RecentActivityItemDto":{"type":"object","properties":{"id":{"type":"string","description":"AuditLog.id (canonical reference)."},"action":{"type":"string","description":"Audit action. Whitelist CC-relevant: material.draft_*, change_request.*, pipeline.*, export.*, material.auto_deprecated, material.deprecation_reverted."},"actor":{"type":"object","description":"Actor (user que disparou a mutation). Null em system actions (cron)."},"entityType":{"type":"object","description":"Tipo da entidade afetada (Material, ChangeRequest, ...)."},"entityId":{"type":"object","description":"ID da entidade afetada (UUID, link clicável no FE)."},"occurredAt":{"type":"string","description":"ISO 8601 — quando a mutation ocorreu."}},"required":["id","action","actor","occurredAt"]},"RecentActivityResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RecentActivityItemDto"}},"nextCursor":{"type":"object","description":"Cursor pra próxima página. Null = fim."}},"required":["items"]},"ProductivityTodayDto":{"type":"object","properties":{"cadastros":{"type":"number","description":"Cadastros aprovados (material.draft_approved + workspace_linked + promoted) desde 00:00 hoje."},"crs":{"type":"number","description":"Change Requests resolvidos hoje (cr.approved + cr.rejected)."},"exports":{"type":"number","description":"Exports gerados hoje (CSV/SAP/view IP-protected)."},"ingestions":{"type":"number","description":"Ingestões XLSX submetidas hoje (FR3)."}},"required":["cadastros","crs","exports","ingestions"]},"ProductivityTrendBucketDto":{"type":"object","properties":{"date":{"type":"string","description":"Data ISO 8601 (YYYY-MM-DD)."},"cadastros":{"type":"number","description":"Cadastros aprovados no dia."},"crs":{"type":"number","description":"CRs resolvidos no dia."},"exports":{"type":"number","description":"Exports gerados no dia."},"ingestions":{"type":"number","description":"Ingestões submetidas no dia."}},"required":["date","cadastros","crs","exports","ingestions"]},"ProductivityBaselineDto":{"type":"object","properties":{"cadastrosAvgDay":{"type":"number","description":"Média diária de cadastros aprovados no mês anterior (referência pra trend)."},"crsAvgDay":{"type":"number","description":"Média diária de CRs resolvidos no mês anterior."},"exportsAvgDay":{"type":"number","description":"Média diária de exports no mês anterior."},"ingestionsAvgDay":{"type":"number","description":"Média diária de ingestões no mês anterior."}},"required":["cadastrosAvgDay","crsAvgDay","exportsAvgDay","ingestionsAvgDay"]},"ProductivityAuditCoverageDto":{"type":"object","properties":{"percent":{"type":"number","description":"Percentual de mutations com audit completo (0-100).","example":100},"status":{"type":"string","description":"Status semáforo: ok | degraded | unknown.","enum":["ok","degraded","unknown"]}},"required":["percent","status"]},"ProductivityOverviewDto":{"type":"object","properties":{"today":{"$ref":"#/components/schemas/ProductivityTodayDto"},"weeklyTrend":{"description":"Últimos 7 dias (ordem cronológica).","type":"array","items":{"$ref":"#/components/schemas/ProductivityTrendBucketDto"}},"previousMonthBaseline":{"$ref":"#/components/schemas/ProductivityBaselineDto"},"auditCoverage":{"$ref":"#/components/schemas/ProductivityAuditCoverageDto"}},"required":["today","weeklyTrend","previousMonthBaseline","auditCoverage"]},"ObsolescenceQueueFlagsDto":{"type":"object","properties":{"obsolescenceSignals":{"type":"array","items":{"type":"string","enum":["GLOBAL_DISCONTINUED","SPECTRA_CHECK_DECAYED","MANUFACTURER_PRE_ANNOUNCEMENT"]}},"deprecated":{"type":"boolean"},"priceAnomalyOpen":{"type":"boolean"},"obsoleteInUse":{"type":"boolean","description":"DEPRECATED + saldo cross-plant > 0. Story 6.5 (FR31) persistiu a verdade canonical org-wide em `Material.obsoleteInUse` (mantida via ObsoleteInUseService); esta view da queue segue computada access-scoped (só plantas acessíveis ao caller)."}},"required":["obsolescenceSignals","deprecated","priceAnomalyOpen","obsoleteInUse"]},"ObsolescenceQueuePlantStockDto":{"type":"object","properties":{"workspaceId":{"type":"string","format":"uuid"},"plant":{"type":"object","nullable":true},"stock":{"type":"object","nullable":true}},"required":["workspaceId"]},"ObsolescenceQueueCrossPlantStockDto":{"type":"object","properties":{"totalStock":{"type":"object","nullable":true,"description":"Soma de WorkspaceMaterial.stock. Null = sem dado de stock em nenhuma WM."},"plantBreakdown":{"type":"array","items":{"$ref":"#/components/schemas/ObsolescenceQueuePlantStockDto"}}},"required":["plantBreakdown"]},"ObsolescenceQueueItemDto":{"type":"object","properties":{"materialId":{"type":"string","format":"uuid"},"manufacturerName":{"type":"object","nullable":true},"model":{"type":"object","nullable":true},"partNumber":{"type":"object","nullable":true},"familyId":{"type":"string","format":"uuid"},"familyName":{"type":"string"},"status":{"type":"string","enum":["DRAFT","ACTIVE","REJECTED","DEPRECATED"]},"flags":{"$ref":"#/components/schemas/ObsolescenceQueueFlagsDto"},"severityComposite":{"type":"number","description":"Severidade composta 0..100 (helper determinístico schema-driven).","minimum":0,"maximum":100},"crossPlantStock":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ObsolescenceQueueCrossPlantStockDto"}]},"deprecatedAt":{"type":"object","nullable":true},"deprecatedReason":{"type":"object","nullable":true},"lastSignalAt":{"type":"object","nullable":true,"description":"Max(ObsolescenceSignal.detectedAt) entre sinais ATIVOS."},"updatedAt":{"type":"string"}},"required":["materialId","familyId","familyName","status","flags","severityComposite","updatedAt"]},"ListObsolescenceQueueResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ObsolescenceQueueItemDto"}},"nextCursor":{"type":"object","nullable":true}},"required":["items"]},"ObsolescenceQueueSignalDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"signalType":{"type":"string","enum":["GLOBAL_DISCONTINUED","SPECTRA_CHECK_DECAYED","MANUFACTURER_PRE_ANNOUNCEMENT"]},"payload":{"type":"object","description":"Payload Pattern P (discriminated union por signalType)."},"source":{"type":"string"},"configVersion":{"type":"string"},"detectedAt":{"type":"string"}},"required":["id","signalType","payload","source","configVersion","detectedAt"]},"ObsolescenceQueueAnomalyDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"severity":{"type":"number","minimum":0,"maximum":100},"reason":{"type":"string"},"suggestedPrice":{"type":"object","nullable":true},"detectedAt":{"type":"string"}},"required":["id","severity","reason","detectedAt"]},"ObsolescenceQueueDetailPlantBreakdownDto":{"type":"object","properties":{"workspaceId":{"type":"string","format":"uuid"},"workspaceName":{"type":"string"},"plant":{"type":"object","nullable":true},"stock":{"type":"object","nullable":true},"unitPrice":{"type":"object","nullable":true},"updatedAt":{"type":"string"}},"required":["workspaceId","workspaceName","updatedAt"]},"ObsolescenceQueueItemDetailDto":{"type":"object","properties":{"materialId":{"type":"string","format":"uuid"},"manufacturerName":{"type":"object","nullable":true},"model":{"type":"object","nullable":true},"partNumber":{"type":"object","nullable":true},"familyId":{"type":"string","format":"uuid"},"familyName":{"type":"string"},"status":{"type":"string","enum":["DRAFT","ACTIVE","REJECTED","DEPRECATED"]},"flags":{"$ref":"#/components/schemas/ObsolescenceQueueFlagsDto"},"severityComposite":{"type":"number","description":"Severidade composta 0..100 (helper determinístico schema-driven).","minimum":0,"maximum":100},"crossPlantStock":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ObsolescenceQueueCrossPlantStockDto"}]},"deprecatedAt":{"type":"object","nullable":true},"deprecatedReason":{"type":"object","nullable":true},"lastSignalAt":{"type":"object","nullable":true,"description":"Max(ObsolescenceSignal.detectedAt) entre sinais ATIVOS."},"updatedAt":{"type":"string"},"signals":{"type":"array","items":{"$ref":"#/components/schemas/ObsolescenceQueueSignalDto"}},"priceAnomalies":{"type":"array","items":{"$ref":"#/components/schemas/ObsolescenceQueueAnomalyDto"}},"crossPlantStockBreakdown":{"type":"array","items":{"$ref":"#/components/schemas/ObsolescenceQueueDetailPlantBreakdownDto"}},"alternativeSuggestion":{"type":"object","additionalProperties":true,"nullable":true,"description":"Sugestão de alternativa parametrizada. Reservado pra Story 6.6 (chat integration) — sempre null nesta story."}},"required":["materialId","familyId","familyName","status","flags","severityComposite","updatedAt","signals","priceAnomalies","crossPlantStockBreakdown"]},"RevertDeprecationResponseDto":{"type":"object","properties":{"materialId":{"type":"string","format":"uuid"},"previousStatus":{"type":"string","enum":["DRAFT","ACTIVE","REJECTED","DEPRECATED"]},"currentStatus":{"type":"string","enum":["DRAFT","ACTIVE","REJECTED","DEPRECATED"]},"clearedSignalIds":{"description":"IDs dos ObsolescenceSignal cleared na transação.","type":"array","items":{"type":"string"}},"revertedAt":{"type":"string"}},"required":["materialId","previousStatus","currentStatus","clearedSignalIds","revertedAt"]},"BumpObsolescenceConfigDto":{"type":"object","properties":{"version":{"type":"string","description":"Semver string da nova versão. Histórico permanente — não pode colidir com version existente.","minLength":5,"maxLength":64,"example":"v0.2.0-calibrated"},"spectraCheckDecayDays":{"type":"number","description":"Story 6.1 sinal 2 — dias sem reverificação Spectra Check pra detector flagar. Default v0.1.0 = 90d.","minimum":1,"maximum":3650,"example":90},"spectraCheckDecayMinScore":{"type":"number","description":"Story 6.1 sinal 2 — score mínimo (AND com decayDays) que dispara flag. Default v0.1.0 = 0.5.","minimum":0,"maximum":1,"example":0.5},"autoDeprecateMinSignalAgeDays":{"type":"number","description":"Story 6.2 — dias com sinal ativo pra promover Material ACTIVE→DEPRECATED automaticamente (só sinais determinísticos). Default v0.1.0 = 90d. CIG tem 3 meses pra revisar queue antes do auto-deprecate kick in.","minimum":1,"maximum":3650,"example":90}},"required":["version","spectraCheckDecayDays","spectraCheckDecayMinScore","autoDeprecateMinSignalAgeDays"]},"BumpCostThresholdConfigDto":{"type":"object","properties":{"orgId":{"type":"string","description":"UUID da Organization alvo. Super-admin Dooor opera cross-tenant.","example":"68393277-f92b-434f-bd34-733dbc53603d"},"version":{"type":"string","description":"Semver string da nova versão. Unique POR Org. Max 50 chars.","example":"v0.2.0-calibrated-piloto","maxLength":50},"warnUsdPerWorkspaceHour":{"type":"number","description":"Threshold Warn por workspace na janela rolante (USD/h).","example":5,"minimum":0.01,"maximum":10000},"warnUsdPerOrgHour":{"type":"number","description":"Threshold Warn agregado por Org (USD/h).","example":25,"minimum":0.01,"maximum":100000},"evaluationWindowMinutes":{"type":"number","description":"Janela de avaliação em minutos. Default herdado da config anterior (ou 60).","example":60,"minimum":5,"maximum":1440},"notificationCooldownMin":{"type":"number","description":"Cooldown em minutos entre alerts duplicados. Default 240 (4h).","example":240,"minimum":30,"maximum":10080}},"required":["orgId","version","warnUsdPerWorkspaceHour","warnUsdPerOrgHour"]},"CostThresholdConfigDto":{"type":"object","properties":{"id":{"type":"string"},"orgId":{"type":"string"},"version":{"type":"string"},"warnUsdPerWorkspaceHour":{"type":"number","example":5},"warnUsdPerOrgHour":{"type":"number","example":25},"evaluationWindowMinutes":{"type":"number","example":60},"notificationCooldownMin":{"type":"number","example":240},"isActive":{"type":"boolean"},"createdAt":{"type":"string"},"createdById":{"type":"object","nullable":true,"description":"UUID do super-admin que bumpou (ou null se seed)"}},"required":["id","orgId","version","warnUsdPerWorkspaceHour","warnUsdPerOrgHour","evaluationWindowMinutes","notificationCooldownMin","isActive","createdAt","createdById"]},"CostThresholdConfigHistoryItemDto":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"string"},"isActive":{"type":"boolean"},"createdAt":{"type":"string"},"createdById":{"type":"object","nullable":true}},"required":["id","version","isActive","createdAt","createdById"]},"CostThresholdConfigOverviewDto":{"type":"object","properties":{"active":{"nullable":true,"description":"Config ATIVA atual ou null se Org nunca foi configurada.","allOf":[{"$ref":"#/components/schemas/CostThresholdConfigDto"}]},"recentHistory":{"description":"Últimas 5 versões (desc por createdAt).","type":"array","items":{"$ref":"#/components/schemas/CostThresholdConfigHistoryItemDto"}},"totalCount":{"type":"number","description":"Total absoluto de versões persistidas para essa Org."}},"required":["active","recentHistory","totalCount"]},"CostAlertBreakdownByOperationDto":{"type":"object","properties":{"operation":{"type":"string"},"costUsd":{"type":"number"}},"required":["operation","costUsd"]},"CostAlertBreakdownDto":{"type":"object","properties":{"byOperation":{"description":"Top 10 operations por cost na janela.","type":"array","items":{"$ref":"#/components/schemas/CostAlertBreakdownByOperationDto"}},"callCount":{"type":"number","description":"Total de calls IA na janela que dispararam o alert."},"traceIdSample":{"type":"object","nullable":true,"description":"Um traceId de exemplo para investigar no Sentry/Datadog."}},"required":["byOperation","callCount","traceIdSample"]},"CostAlertLogDto":{"type":"object","properties":{"id":{"type":"string"},"orgId":{"type":"string"},"workspaceId":{"type":"object","nullable":true,"description":"null = alert per-org (alertType=warn_org_hour)"},"alertType":{"type":"string","enum":["warn_workspace_hour","warn_org_hour"]},"thresholdUsd":{"type":"number"},"observedUsd":{"type":"number"},"windowMinutes":{"type":"number"},"detectedAt":{"type":"string"},"thresholdConfigId":{"type":"string"},"notificationSent":{"type":"boolean"},"notificationSentAt":{"type":"object","nullable":true},"notificationError":{"type":"object","nullable":true},"breakdown":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/CostAlertBreakdownDto"}]},"orgName":{"type":"object","nullable":true},"workspaceName":{"type":"object","nullable":true}},"required":["id","orgId","alertType","thresholdUsd","observedUsd","windowMinutes","detectedAt","thresholdConfigId","notificationSent"]},"ListCostAlertLogsResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CostAlertLogDto"}},"nextCursor":{"type":"object","nullable":true,"description":"Cursor opaque para próxima página. Null = última."},"totalCount":{"type":"number","description":"Total contado dentro do filtro (sem limit). Approx — usar pra UI hint."}},"required":["items","totalCount"]},"CostGovernanceSummaryDto":{"type":"object","properties":{"windowHours":{"type":"number","description":"Janela em horas considerada (default 24)."},"totalCostUsd":{"type":"number","description":"Total cost USD agregado de `ai_call_log` na janela."},"totalCalls":{"type":"number","description":"Total de calls IA na janela."},"alertsActiveCount":{"type":"number","description":"Alerts ATIVOS (notificationSent=true) na janela."},"alertsNotificationFailedCount":{"type":"number","description":"Alerts com falha de notificação na janela."},"orgsAffected":{"type":"number","description":"Distinct orgs com pelo menos 1 alert na janela."},"workspacesAffected":{"type":"number","description":"Distinct workspaces com pelo menos 1 alert na janela."}},"required":["windowHours","totalCostUsd","totalCalls","alertsActiveCount","alertsNotificationFailedCount","orgsAffected","workspacesAffected"]},"WorkspaceBuilderQuotaResponseDto":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"version":{"type":"string","example":"v0.1.0-piloto-csn"},"queryQuotaDaily":{"type":"number","nullable":true,"example":1000},"costBudgetMonthlyUsd":{"type":"number","nullable":true,"example":50.5},"alertThresholdPercent":{"type":"number","example":80},"notificationCooldownMin":{"type":"number","example":240},"isActive":{"type":"boolean","example":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"createdById":{"type":"string","nullable":true}},"required":["id","workspaceId","version","alertThresholdPercent","notificationCooldownMin","isActive","createdAt","updatedAt"]},"UpsertWorkspaceBuilderQuotaDto":{"type":"object","properties":{"version":{"type":"string","description":"Semver da quota. Cliente envia \"v0.1.0-piloto\" / \"v0.2.0-calibrated\". Bump exige version diferente da ativa.","example":"v0.1.0-piloto-csn"},"queryQuotaDaily":{"type":"number","description":"Limite de queries originatedBy=builder por dia UTC. null = sem limite diário (modo observability). Story 9.9 review P11: 0 não é aceito (divisão por zero no report) — para desativar, omita o campo OU DELETE a quota.","example":1000,"minimum":1,"maximum":1000000},"costBudgetMonthlyUsd":{"type":"number","description":"Budget USD/mês UTC. Cost = sum(ai_call_log.costUsd WHERE originatedBy=builder). null = sem limite mensal. Story 9.9 review P11: 0 não é aceito (divisão por zero). P13: max alinhado com Decimal(10,4) schema.","example":50.5,"minimum":0.0001,"maximum":999999.9999},"alertThresholdPercent":{"type":"number","description":"Percentual a partir do qual emite WARN. Default 80%. Breach é sempre 100%+.","example":80,"default":80,"minimum":1,"maximum":100},"notificationCooldownMin":{"type":"number","description":"Cooldown em minutos entre alertas duplicados (mesmo workspaceId+alertType+windowLabel). Default 240 (4h). Story 9.9 review R2 M5: 0 não é aceito (zero cooldown causa spam do cron horário). Mínimo 1 minuto; usar DELETE da quota se intenção é desligar alertas.","example":240,"default":240,"minimum":1,"maximum":10080}},"required":["version"]},"BuilderQuotaUtilizationDto":{"type":"object","properties":{"dailyPct":{"type":"number","nullable":true,"description":"queries_24h / queryQuotaDaily * 100. null quando sem quota diária."},"monthlyPct":{"type":"number","nullable":true,"description":"costMonthToDate / costBudgetMonthlyUsd * 100. null quando sem budget mensal."}}},"BuilderUsageSummaryResponseDto":{"type":"object","properties":{"queries24h":{"type":"number","description":"Queries originatedBy=builder nas últimas 24h."},"queries7d":{"type":"number","description":"Queries builder nos últimos 7 dias."},"queriesMonthToDate":{"type":"number","description":"Queries builder desde o 1º dia do mês UTC."},"costEstUsdMonthToDate":{"type":"number","description":"Cost USD acumulado no mês corrente (sum ai_call_log.costUsd WHERE originatedBy=builder)."},"mcpToolCallsCount":{"type":"number","description":"MCP tool calls — V1+ deferido (9.9-MCP-TELEMETRY). Sempre 0 até MCP server existir.","example":0},"quotaUtilization":{"$ref":"#/components/schemas/BuilderQuotaUtilizationDto"},"activeQuota":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/WorkspaceBuilderQuotaResponseDto"}]}},"required":["queries24h","queries7d","queriesMonthToDate","costEstUsdMonthToDate","mcpToolCallsCount","quotaUtilization"]},"BuilderUsageTimeseriesPointDto":{"type":"object","properties":{"date":{"type":"string","format":"date-time","description":"Início do bucket (UTC)."},"queries":{"type":"number","description":"Queries builder no bucket."},"costEstUsd":{"type":"number","description":"Cost USD acumulado no bucket."}},"required":["date","queries","costEstUsd"]},"BuilderUsageTimeseriesResponseDto":{"type":"object","properties":{"bucket":{"type":"string","enum":["day","week"]},"from":{"type":"string"},"to":{"type":"string"},"points":{"type":"array","items":{"$ref":"#/components/schemas/BuilderUsageTimeseriesPointDto"}}},"required":["bucket","from","to","points"]},"BuilderUsageAlertLogResponseDto":{"type":"object","properties":{"id":{"type":"string"},"workspaceId":{"type":"string"},"quotaId":{"type":"string"},"alertType":{"type":"string","enum":["queries_warn","queries_breach","cost_warn","cost_breach"]},"thresholdPercent":{"type":"number"},"thresholdValue":{"type":"number","description":"Decimal serializado como number."},"observedValue":{"type":"number","description":"Decimal serializado como number."},"observedPercent":{"type":"number"},"windowLabel":{"type":"string","enum":["daily","monthly"]},"detectedAt":{"type":"string","format":"date-time"},"notificationSent":{"type":"boolean"},"notificationSentAt":{"type":"string","format":"date-time","nullable":true},"notificationError":{"type":"string","nullable":true},"notificationIds":{"type":"array","items":{"type":"string"}},"breakdown":{"type":"object","nullable":true,"description":"Snapshot: { byOperation: Record<op,count>, callCount, traceIdSample? }"}},"required":["id","workspaceId","quotaId","alertType","thresholdPercent","thresholdValue","observedValue","observedPercent","windowLabel","detectedAt","notificationSent","notificationIds"]},"ListBuilderUsageAlertsResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/BuilderUsageAlertLogResponseDto"}},"nextCursor":{"type":"string","nullable":true}},"required":["items"]},"WorkspaceAtRiskDto":{"type":"object","properties":{"workspaceId":{"type":"string"},"workspaceName":{"type":"string"},"orgId":{"type":"string"},"orgName":{"type":"string"},"utilizationPct":{"type":"number","description":"Maior pct entre daily e monthly. null se sem quota."},"triggeringWindow":{"type":"string","enum":["daily","monthly","both"]}},"required":["workspaceId","workspaceName","orgId","orgName","utilizationPct","triggeringWindow"]},"TopWorkspaceDto":{"type":"object","properties":{"workspaceId":{"type":"string"},"workspaceName":{"type":"string"},"orgId":{"type":"string"},"orgName":{"type":"string"},"totalQueries":{"type":"number"},"totalCostUsd":{"type":"number"}},"required":["workspaceId","workspaceName","orgId","orgName","totalQueries","totalCostUsd"]},"AdminBuilderUsageSummaryResponseDto":{"type":"object","properties":{"totalQueriesBuilder":{"type":"number","description":"Total queries originatedBy=builder no período (default 30d)."},"totalCostEstUsd":{"type":"number","description":"Cost USD total dessas queries."},"workspacesAtRisk":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceAtRiskDto"}},"topWorkspaces":{"type":"array","items":{"$ref":"#/components/schemas/TopWorkspaceDto"}},"periodFrom":{"type":"string"},"periodTo":{"type":"string"}},"required":["totalQueriesBuilder","totalCostEstUsd","workspacesAtRisk","topWorkspaces","periodFrom","periodTo"]},"AdminBuilderUsageByWorkspaceItemDto":{"type":"object","properties":{"workspaceId":{"type":"string"},"workspaceName":{"type":"string"},"orgId":{"type":"string"},"orgName":{"type":"string"},"totalQueries":{"type":"number"},"totalCostUsd":{"type":"number"},"hasActiveQuota":{"type":"boolean","description":"Quota ativa do workspace ou null."}},"required":["workspaceId","workspaceName","orgId","orgName","totalQueries","totalCostUsd","hasActiveQuota"]},"AdminBuilderUsageByWorkspaceResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AdminBuilderUsageByWorkspaceItemDto"}},"nextCursor":{"type":"string","nullable":true},"periodFrom":{"type":"string"},"periodTo":{"type":"string"}},"required":["items","nextCursor","periodFrom","periodTo"]},"RequestAvailabilityConfirmationDto":{"type":"object","properties":{"materialId":{"type":"string","format":"uuid","description":"Material ID being asked about"},"targetWorkspaceId":{"type":"string","format":"uuid","description":"Workspace where the stock is claimed (destination plant)"},"chatId":{"type":"string","format":"uuid","description":"Chat session of origin (when invoked from chat surface)"},"messageId":{"type":"string","format":"uuid","description":"ChatMessage of origin (AI message that displayed the AvailabilityCard)"},"priority":{"type":"string","enum":["LOW","NORMAL","HIGH","URGENT"],"description":"Priority hint (V1: log-only; Story 10.3 will route email urgency)."}},"required":["materialId","targetWorkspaceId"]},"DecideAvailabilityConfirmationDto":{"type":"object","properties":{"decision":{"type":"string","enum":["CONFIRMED","REJECTED"],"description":"Outcome decidido pelo humano da planta destino: CONFIRMED ou REJECTED."},"rejectReason":{"type":"string","maxLength":500,"description":"Justificativa da rejeição (OBRIGATÓRIA quando decision=REJECTED). Ex: \"material comprometido em manutenção\". Ignorada quando CONFIRMED."}},"required":["decision"]},"CreateApiKeyDto":{"type":"object","properties":{"name":{"type":"string","description":"Display name (3-64 chars) pra identificar a key.","minLength":3,"maxLength":64},"scopeTags":{"description":"Tags de scope (intersection com workspace tags pra ABAC). Default empty = sem restrição cross-workspace.","maxItems":32,"type":"array","items":{"type":"string"}},"permissions":{"description":"Permissions específicas pra esta key (dot.notation). Default empty = herda permissions do criador.","maxItems":64,"type":"array","items":{"type":"string"}}},"required":["name"]},"CreateApiKeyResponseDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"Display name."},"prefix":{"type":"string","description":"Prefix visível (ex: spec_live_abcd). Identifica a key na UI sem expor segredo."},"scopeTags":{"description":"Tags de scope.","type":"array","items":{"type":"string"}},"permissions":{"description":"Permissions atribuídas.","type":"array","items":{"type":"string"}},"lastUsedAt":{"type":"object","description":"ISO timestamp da última request autenticada com esta key.","nullable":true},"revokedAt":{"type":"object","description":"ISO timestamp de revoke (soft delete). Null = ativa.","nullable":true},"createdAt":{"type":"string","description":"ISO timestamp de criação."},"plaintext":{"type":"string","description":"⚠️ PLAINTEXT da key — exibida UMA VEZ. Salve agora; não há recuperação. Use em headers HTTP como Authorization: Bearer <key> ou X-Api-Key: <key>."}},"required":["id","name","prefix","scopeTags","permissions","lastUsedAt","revokedAt","createdAt","plaintext"]},"ApiKeyResponseDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"Display name."},"prefix":{"type":"string","description":"Prefix visível (ex: spec_live_abcd). Identifica a key na UI sem expor segredo."},"scopeTags":{"description":"Tags de scope.","type":"array","items":{"type":"string"}},"permissions":{"description":"Permissions atribuídas.","type":"array","items":{"type":"string"}},"lastUsedAt":{"type":"object","description":"ISO timestamp da última request autenticada com esta key.","nullable":true},"revokedAt":{"type":"object","description":"ISO timestamp de revoke (soft delete). Null = ativa.","nullable":true},"createdAt":{"type":"string","description":"ISO timestamp de criação."}},"required":["id","name","prefix","scopeTags","permissions","lastUsedAt","revokedAt","createdAt"]},"ListApiKeysResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyResponseDto"}}},"required":["items"]},"RotateApiKeyResponseDto":{"type":"object","properties":{"newKey":{"description":"Nova key (substituta).","allOf":[{"$ref":"#/components/schemas/CreateApiKeyResponseDto"}]},"revokedPrefix":{"type":"string","description":"Prefix da key antiga (que foi revoked nesta operação)."},"revokedAt":{"type":"string","description":"ISO timestamp do revoke da antiga."}},"required":["newKey","revokedPrefix","revokedAt"]},"ApplyPriceUpdateResponseDto":{"type":"object","properties":{"priceDeltaRunId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["completed","partial","failed","rejected_too_many_errors"],"description":"`completed` = 100% válido; `partial` = válidos aplicados + erros reportados; `rejected_too_many_errors` = >50% inválido, batch abortado (vem como 400)."},"appliedRowsCount":{"type":"number"},"errorRowsCount":{"type":"number"},"unmatchedRowsCount":{"type":"number"},"errorReportUrl":{"type":"string","description":"URL assinada do relatório CSV de erros. Presente quando errorRowsCount > 0."},"duplicate":{"type":"boolean","description":"true quando o mesmo arquivo (orgId + fileHash) já tinha sido processado."}},"required":["priceDeltaRunId","status","appliedRowsCount","errorRowsCount","unmatchedRowsCount","duplicate"]},"CreatePriceUpdatesMappingDto":{"type":"object","properties":{"name":{"type":"string","description":"Nome canonical do mapping (único por Org).","minLength":1,"maxLength":100},"sourceFormat":{"type":"string","description":"Formato esperado do flat-file.","enum":["csv","xlsx"]},"columnMap":{"type":"object","description":"Mapping canonical-field → header do flat-file. Campos aceitos: externalId (obrigatório), externalSystem, price (obrigatório), currency, effectiveDate.","example":{"externalId":"Material","externalSystem":"Origem","price":"Preço Unitário","currency":"Moeda","effectiveDate":"Vigência"}},"defaultExternalSystem":{"type":"string","description":"Default usado quando o arquivo não traz coluna externalSystem.","minLength":1,"maxLength":50},"delimiter":{"type":"string","description":"Delimitador CSV. Ignorado quando sourceFormat=xlsx. Aceita apenas um dos: `,` `;` `\\t` `|` `:`.","enum":[",",";","\t","|",":"]},"headerRow":{"type":"boolean","description":"Indica se a primeira linha do flat-file é cabeçalho. Default true.","default":true}},"required":["name","sourceFormat","columnMap","defaultExternalSystem"]},"PriceUpdatesMappingResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"sourceFormat":{"type":"string","enum":["csv","xlsx"]},"columnMap":{"type":"object","description":"Mapping canonical-field → header (após parse Zod)."},"defaultExternalSystem":{"type":"string"},"delimiter":{"type":"object"},"headerRow":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","name","sourceFormat","columnMap","defaultExternalSystem","headerRow","createdAt","updatedAt"]},"ListPriceUpdatesMappingsResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PriceUpdatesMappingResponseDto"}}},"required":["data"]},"CreateChangeRequestDto":{"type":"object","properties":{"familyId":{"type":"string","description":"familyId (UUID) the CR targets"},"type":{"type":"string","enum":["WEIGHT_CHANGE","RULE_CHANGE","MATRIX_UPDATE"],"description":"Kind of schema change proposed (accepts upper/lower-case)"},"field":{"type":"string","description":"Canonical schema field the CR targets"},"currentValue":{"type":"string","description":"Current rule value (JSON-stringified — Pattern P)"},"proposedValue":{"type":"string","description":"Proposed rule value (JSON-stringified — Pattern P)"},"rationale":{"type":"string","description":"Technical rationale for the change"},"evidenceClusterIds":{"description":"SimilarityCluster IDs that evidence the CR","type":"array","items":{"type":"string"}}},"required":["familyId","type","field","currentValue","proposedValue","rationale","evidenceClusterIds"]},"SchemaDiffDto":{"type":"object","properties":{"field":{"type":"string","description":"Canonical schema field changed"},"type":{"type":"string","enum":["WEIGHT_CHANGE","RULE_CHANGE","MATRIX_UPDATE"],"description":"Kind of change"},"before":{"type":"string","description":"Current value (JSON-stringified)"},"after":{"type":"string","description":"Proposed value (JSON-stringified)"}},"required":["field","type","before","after"]},"ChangeRequestVoteDto":{"type":"object","properties":{"id":{"type":"string","description":"Vote UUID"},"area":{"type":"string","enum":["CIG","SUPRIMENTOS","ENGENHARIA"]},"decision":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"APPROVED or REJECTED"},"comment":{"type":"object","description":"Optional vote comment","nullable":true},"voterId":{"type":"string","description":"userId of the voter"},"votedAt":{"type":"string","description":"ISO 8601 vote timestamp"}},"required":["id","area","decision","voterId","votedAt"]},"ChangeRequestDetailDto":{"type":"object","properties":{"id":{"type":"string","description":"Change Request UUID"},"familyId":{"type":"string","description":"familyId (UUID)"},"type":{"type":"string","enum":["WEIGHT_CHANGE","RULE_CHANGE","MATRIX_UPDATE"]},"field":{"type":"string","description":"Canonical schema field the CR targets"},"status":{"type":"string","enum":["AWAITING_CONSENSUS","APPROVED","REJECTED","APPROVED_CONSENSUAL"]},"cigStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"CIG area vote status"},"suprimentosStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"Suprimentos area vote status"},"engenhariaStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"Engenharia area vote status"},"createdById":{"type":"string","description":"userId of the CR author"},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp"},"previousCrId":{"type":"object","description":"UUID do CR rejeitado que originou este (chain de re-submissions)","nullable":true},"appliedVersionId":{"type":"object","description":"UUID da FamilySchemaVersion criada quando consenso fechou","nullable":true},"consensusReachedAt":{"type":"object","description":"ISO 8601 timestamp em que consenso fechou","nullable":true},"schemaVersionId":{"type":"string","description":"schemaVersionId the CR targets"},"rationale":{"type":"string","description":"Technical rationale"},"evidenceClusterIds":{"description":"SimilarityCluster IDs that evidence the CR","type":"array","items":{"type":"string"}},"schemaDiff":{"description":"The single rule change proposed","allOf":[{"$ref":"#/components/schemas/SchemaDiffDto"}]},"votes":{"description":"Vote history (newest first)","type":"array","items":{"$ref":"#/components/schemas/ChangeRequestVoteDto"}}},"required":["id","familyId","type","field","status","cigStatus","suprimentosStatus","engenhariaStatus","createdById","createdAt","schemaVersionId","rationale","evidenceClusterIds","schemaDiff","votes"]},"ChangeRequestSummaryDto":{"type":"object","properties":{"id":{"type":"string","description":"Change Request UUID"},"familyId":{"type":"string","description":"familyId (UUID)"},"type":{"type":"string","enum":["WEIGHT_CHANGE","RULE_CHANGE","MATRIX_UPDATE"]},"field":{"type":"string","description":"Canonical schema field the CR targets"},"status":{"type":"string","enum":["AWAITING_CONSENSUS","APPROVED","REJECTED","APPROVED_CONSENSUAL"]},"cigStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"CIG area vote status"},"suprimentosStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"Suprimentos area vote status"},"engenhariaStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"Engenharia area vote status"},"createdById":{"type":"string","description":"userId of the CR author"},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp"},"previousCrId":{"type":"object","description":"UUID do CR rejeitado que originou este (chain de re-submissions)","nullable":true},"appliedVersionId":{"type":"object","description":"UUID da FamilySchemaVersion criada quando consenso fechou","nullable":true},"consensusReachedAt":{"type":"object","description":"ISO 8601 timestamp em que consenso fechou","nullable":true}},"required":["id","familyId","type","field","status","cigStatus","suprimentosStatus","engenhariaStatus","createdById","createdAt"]},"ChangeRequestListResponseDto":{"type":"object","properties":{"items":{"description":"Alias canonical (preferido)","type":"array","items":{"$ref":"#/components/schemas/ChangeRequestSummaryDto"}},"changeRequests":{"description":"Alias legado — use `items`","type":"array","items":{"$ref":"#/components/schemas/ChangeRequestSummaryDto"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor de paginação. Sempre `null` enquanto endpoint não tem cursor real."}},"required":["items","changeRequests","nextCursor"]},"VoteChangeRequestDto":{"type":"object","properties":{"area":{"type":"string","enum":["CIG","SUPRIMENTOS","ENGENHARIA"],"description":"Area casting the vote (accepts upper/lower-case — normalized)"},"decision":{"type":"string","enum":["APPROVED","REJECTED"],"description":"Vote decision (accepts upper/lower-case — normalized)"},"comment":{"type":"string","description":"Optional comment (required in practice when rejecting)"}},"required":["area","decision"]},"VoteChangeRequestResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"Change Request UUID"},"status":{"type":"string","enum":["AWAITING_CONSENSUS","APPROVED","REJECTED","APPROVED_CONSENSUAL"],"description":"CR status (unchanged by 4.6-BE — consensus is 4.10)"},"cigStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]},"suprimentosStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]},"engenhariaStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]}},"required":["id","status","cigStatus","suprimentosStatus","engenhariaStatus"]},"ResubmitChangeRequestDto":{"type":"object","properties":{"rationale":{"type":"string","description":"New rationale — inherits previous if omitted"},"evidenceClusterIds":{"description":"Override evidence clusters — inherits previous if omitted","type":"array","items":{"type":"string"}}}},"DashboardResponseDto":{"type":"object","properties":{"id":{"type":"string"},"orgId":{"type":"string"},"ownerId":{"type":"string"},"name":{"type":"string"},"description":{"type":"object","nullable":true},"persona":{"type":"object","nullable":true,"description":"Persona dona do template (null = custom)"},"isDefault":{"type":"boolean","description":"true = template seed da persona (read-only)"},"editable":{"type":"boolean","description":"false quando isDefault=true — user precisa clonar para editar"},"layout":{"type":"array","description":"[{ widgetType, position{x,y,w,h}, filters? }]","items":{"type":"object"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","orgId","ownerId","name","isDefault","editable","layout","createdAt","updatedAt"]},"WidgetPositionDto":{"type":"object","properties":{"x":{"type":"number","description":"Coluna inicial no grid (>= 0)"},"y":{"type":"number","description":"Linha inicial no grid (>= 0)"},"w":{"type":"number","description":"Largura em colunas (>= 1)"},"h":{"type":"number","description":"Altura em linhas (>= 1)"}},"required":["x","y","w","h"]},"WidgetFiltersDto":{"type":"object","properties":{"familyId":{"type":"string","description":"Restringe a métrica a uma família"},"workspaceIds":{"description":"Restringe a métrica a workspaces específicos (subset do escopo do user)","type":"array","items":{"type":"string"}},"from":{"type":"string","description":"Início do range (ISO 8601)"},"to":{"type":"string","description":"Fim do range (ISO 8601)"}}},"DashboardWidgetLayoutDto":{"type":"object","properties":{"widgetType":{"type":"string","enum":["compras_evitaveis","estoque_consolidavel","qualidade_catalogo","cobertura_familias","cobertura_spectra_check","alertas_anomalia_preco","top_familias_savings"],"description":"Um dos 7 widget types canônicos"},"position":{"$ref":"#/components/schemas/WidgetPositionDto"},"filters":{"$ref":"#/components/schemas/WidgetFiltersDto"}},"required":["widgetType","position"]},"CreateDashboardDto":{"type":"object","properties":{"name":{"type":"string","description":"Nome do dashboard"},"description":{"type":"string","description":"Descrição livre"},"layout":{"description":"Composição de widgets (widgetTypes ∈ enum dos 7)","type":"array","items":{"$ref":"#/components/schemas/DashboardWidgetLayoutDto"}}},"required":["name","layout"]},"UpdateDashboardDto":{"type":"object","properties":{"name":{"type":"string","description":"Nome do dashboard"},"description":{"type":"string","description":"Descrição livre"},"layout":{"type":"array","items":{"$ref":"#/components/schemas/DashboardWidgetLayoutDto"}}}},"MarkMaterialIpDto":{"type":"object","properties":{"justification":{"type":"string","description":"Engineer-provided justification for marking this material as IP-protected. Persisted in ip_protection_log audit trail.","minLength":1,"maxLength":500,"example":"Desenho técnico contém marca d'água \"PROPRIEDADE INTELECTUAL CSN\" — verificado visualmente."}},"required":["justification"]},"RevertIpOverrideDto":{"type":"object","properties":{"orgId":{"type":"string","description":"Org UUID that owns the material (super-admin Dooor is cross-org)","example":"90d43f6d-0ebf-461a-aeed-406a348fef03"},"justification":{"type":"string","description":"Super-admin justification for reverting the IP protection flag. Persisted in ip_protection_log audit (detectionType=override_revert) AND audit_log via @AuditableMutation interceptor.","minLength":10,"maxLength":500,"example":"False positive Gemini: o \"CONFIDENCIAL\" detectado no rodapé era texto genérico do template padrão SKF, não marca específica do drawing."}},"required":["orgId","justification"]},"PriceAnomalyListItemDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspaceMaterialId":{"type":"string","format":"uuid"},"materialId":{"type":"object","format":"uuid","nullable":true},"externalId":{"type":"string"},"externalSystem":{"type":"string"},"familyId":{"type":"string","format":"uuid"},"familyName":{"type":"string"},"materialName":{"type":"string"},"currentPrice":{"type":"object","nullable":true},"precoMereceAtencao":{"type":"boolean","description":"Derivado de status=open."},"reason":{"type":"string","enum":["no_price","zero_or_negative_price","out_of_family_band","vs_history","vs_similars_placeholder","vs_global_placeholder"]},"severity":{"type":"number","minimum":0,"maximum":100},"suggestedPrice":{"type":"object","nullable":true},"confidence":{"type":"string","enum":["high","medium","low"],"nullable":true},"rationale":{"type":"object","nullable":true},"status":{"type":"string","enum":["open","accepted","rejected","resolved","reopened","archived"]},"detectedAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","workspaceMaterialId","externalId","externalSystem","familyId","familyName","materialName","precoMereceAtencao","reason","severity","status","detectedAt","updatedAt"]},"ListPriceAnomaliesResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PriceAnomalyListItemDto"}},"nextCursor":{"type":"object","nullable":true}},"required":["items"]},"DetectPriceAnomaliesDto":{"type":"object","properties":{"workspaceId":{"type":"string","description":"Workspace alvo da detecção.","format":"uuid"},"familyId":{"type":"string","description":"Limita a detecção a uma família. **Quando omitido, escaneia TODAS as famílias do workspace** (modo cross-family scan — útil pra batch overnight, atenção ao custo). B-033 docs fix.","format":"uuid"},"materialIds":{"description":"Limita a detecção a materiais específicos do workspace.","maxItems":1000,"type":"array","items":{"type":"string"}},"traceId":{"type":"string","description":"Trace externo para correlação operacional.","maxLength":120}},"required":["workspaceId"]},"DetectPriceAnomaliesResponseDto":{"type":"object","properties":{"scannedCount":{"type":"number"},"detectedCount":{"type":"number"},"createdCount":{"type":"number"},"updatedCount":{"type":"number"},"reopenedCount":{"type":"number"},"resolvedCount":{"type":"number"},"skippedHumanDecisionCount":{"type":"number"},"openCountDelta":{"type":"number"},"affectedIds":{"type":"array","items":{"type":"string"}},"byReason":{"type":"object"},"durationMs":{"type":"number"}},"required":["scannedCount","detectedCount","createdCount","updatedCount","reopenedCount","resolvedCount","skippedHumanDecisionCount","openCountDelta","affectedIds","byReason","durationMs"]},"BulkResolvePriceAnomaliesDto":{"type":"object","properties":{"workspaceId":{"type":"string","description":"Workspace alvo da resolução.","format":"uuid"},"ids":{"description":"IDs de PriceAnomaly a resolver.","minItems":1,"maxItems":500,"type":"array","items":{"type":"string"}},"status":{"type":"string","description":"Decisão aplicada em lote.","enum":["accepted","rejected","resolved","reopened","archived"]},"resolutionNote":{"type":"string","description":"Nota de resolução/auditoria.","maxLength":1000}},"required":["workspaceId","ids","status"]},"BulkResolvePriceAnomaliesResponseDto":{"type":"object","properties":{"requestedCount":{"type":"number"},"updatedCount":{"type":"number"},"notFoundCount":{"type":"number"},"affectedIds":{"type":"array","items":{"type":"string"}}},"required":["requestedCount","updatedCount","notFoundCount","affectedIds"]},"SuggestPriceAnomalyDto":{"type":"object","properties":{"workspaceId":{"type":"string","description":"Workspace alvo da sugestão.","format":"uuid"},"traceId":{"type":"string","description":"Trace externo para correlação operacional.","maxLength":120}},"required":["workspaceId"]},"SuggestPriceAnomalyResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"jobId":{"type":"string"},"status":{"type":"string","enum":["queued","already_queued"]},"acceptedAt":{"type":"string"}},"required":["id","jobId","status","acceptedAt"]},"CreateOrgTagDto":{"type":"object","properties":{"tag":{"type":"string","description":"Identificador da tag (lowercase kebab/snake case). Campo se chama `tag` matching schema OrgTag.tag — evita mapping para um alias REST.","minLength":1,"maxLength":50,"example":"wr-001"},"category":{"type":"string","description":"Categoria livre (workspace, persona, custom etc).","maxLength":50,"example":"workspace"},"description":{"type":"string","description":"Descrição opcional exibida no autocomplete.","maxLength":500,"example":"Volta Redonda Linha 1"}},"required":["tag"]},"OrgTagUsageDto":{"type":"object","properties":{"workspaces":{"type":"number","description":"Número de workspaces da Org que possuem esta tag."},"users":{"type":"number","description":"Número de userOrgs (membros) da Org que possuem esta tag."}},"required":["workspaces","users"]},"OrgTagWithUsageDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"orgId":{"type":"string","format":"uuid"},"tag":{"type":"string"},"category":{"type":"object","nullable":true},"description":{"type":"object","nullable":true},"createdAt":{"format":"date-time","type":"string"},"usage":{"nullable":true,"description":"Counters de uso da tag. `null` quando `includeUsage=false` foi explícito (autocomplete sem custo). Preenchido com counters reais no modo paginated principal (AC4 Story 8.6).","allOf":[{"$ref":"#/components/schemas/OrgTagUsageDto"}]}},"required":["id","orgId","tag","category","description","createdAt","usage"]},"ListOrgTagsResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OrgTagWithUsageDto"}},"nextCursor":{"type":"object","nullable":true,"description":"Cursor para próxima página (UUID da última tag retornada). `null` quando não há mais itens. Sempre `null` quando `prefix` foi usado (autocomplete não pagina)."}},"required":["items","nextCursor"]},"EligibleWorkspaceDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]},"EligibleWorkspacesResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EligibleWorkspaceDto"}},"truncated":{"type":"boolean","description":"true quando a Org possui mais workspaces que o cap defensivo. UI deve mostrar microcopy `mostrando N de mais`. Pagination/search via cursor fica deferred (8.6-ELIGIBLE-PAGINATION)."}},"required":["items","truncated"]},"EligibleUserDto":{"type":"object","properties":{"membershipId":{"type":"string","format":"uuid","description":"UserOrg (membership) ID."},"userId":{"type":"string","format":"uuid"},"email":{"type":"string"},"displayName":{"type":"object","nullable":true}},"required":["membershipId","userId","email","displayName"]},"EligibleUsersResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EligibleUserDto"}},"truncated":{"type":"boolean","description":"true quando a Org possui mais membros que o cap defensivo. UI deve mostrar microcopy. Pagination/search fica deferred (8.6-ELIGIBLE-PAGINATION)."}},"required":["items","truncated"]},"DeleteOrgTagResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tag":{"type":"string"},"affectedWorkspaces":{"type":"number","description":"Número de workspaces dos quais a tag foi removida."},"affectedUsers":{"type":"number","description":"Número de userOrgs dos quais a tag foi removida."}},"required":["id","tag","affectedWorkspaces","affectedUsers"]},"TagWorkspaceAssignmentDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"plant":{"type":"string","description":"Plant identifier opcional. V1 não está exposto no schema `Workspace` (R5 do story file); reservado para entrega futura."}},"required":["id","name"]},"TagUserAssignmentDto":{"type":"object","properties":{"membershipId":{"type":"string","format":"uuid","description":"ID do `UserOrg` (membership), não do `User`."},"userId":{"type":"string","format":"uuid"},"email":{"type":"string"},"displayName":{"type":"object","nullable":true}},"required":["membershipId","userId","email","displayName"]},"TagAssignmentsResponseDto":{"type":"object","properties":{"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/TagWorkspaceAssignmentDto"}},"users":{"type":"array","items":{"$ref":"#/components/schemas/TagUserAssignmentDto"}},"truncated":{"type":"boolean","description":"true quando workspaces ou users foi truncada por cap defensivo (ASSIGNMENTS_LIST_TAKE). UI deve mostrar microcopy. Pagination cursor fica deferred (8.6-ASSIGNMENTS-PAGINATION)."}},"required":["workspaces","users","truncated"]},"ExportPdfViewDto":{"type":"object","properties":{"materialId":{"type":"string","description":"UUID do Material (canonical, Org-scoped)"},"viewType":{"type":"string","description":"Tipo de vista PDF (3 vistas V1 — spike-vistas-pdf-v1 §2.1)","enum":["engineering-report","buyer-onepager","maintenance-onepager"]}},"required":["materialId","viewType"]},"ExportPdfViewCachedResponseDto":{"type":"object","properties":{"status":{"type":"string","enum":["cached"]},"signedUrl":{"type":"string","description":"Signed URL TTL 7 dias"},"expiresAt":{"type":"string","description":"ISO 8601 de expiração da signed URL"},"idempotencyKey":{"type":"string"}},"required":["status","signedUrl","expiresAt","idempotencyKey"]},"ExportPdfViewQueuedResponseDto":{"type":"object","properties":{"status":{"type":"string","enum":["queued"]},"jobId":{"type":"string","description":"Job ID BullMQ — frontend correlaciona com WS event"},"idempotencyKey":{"type":"string","description":"Hash determinístico (materialId+viewType+schemaVersion+lastModifiedAt)"}},"required":["status","jobId","idempotencyKey"]},"CreateExportDto":{"type":"object","properties":{"materialIds":{"description":"IDs explícitos dos materiais. Mutuamente exclusivo com allFilteredMatching.","type":"array","items":{"type":"string"}},"allFilteredMatching":{"type":"object","description":"Cross-page select — processor resolve os IDs via filtros."},"format":{"type":"string","enum":["XLSX","CSV"],"default":"XLSX"},"columnMap":{"type":"object","description":"Deferred V1 — ignorado; usa Mapping padrão CSN."}},"required":["format"]},"SimilarsEnvelopeDto":{"type":"object","properties":{"partial":{"type":"boolean","description":"true quando componente falhou ou timeout; data eh fallback null/[].","example":false},"error":{"type":"string","description":"Razao da falha quando partial=true (timeout, internal_error, etc)."},"data":{"type":"array","description":"Top similares; SimilarityService.findSimilarByMaterial output. Array vazio se partial.","items":{"type":"object"}}},"required":["partial","data"]},"DuplicatesEnvelopeDto":{"type":"object","properties":{"partial":{"type":"boolean","description":"true quando componente falhou ou timeout; data eh fallback null/[].","example":false},"error":{"type":"string","description":"Razao da falha quando partial=true (timeout, internal_error, etc)."},"data":{"type":"object","description":"Cluster duplicado onde este material participa (membros + cluster meta). Null se material nao pertence a cluster algum.","nullable":true}},"required":["partial","data"]},"PriceAnomalyEnvelopeDto":{"type":"object","properties":{"partial":{"type":"boolean","description":"true quando componente falhou ou timeout; data eh fallback null/[].","example":false},"error":{"type":"string","description":"Razao da falha quando partial=true (timeout, internal_error, etc)."},"data":{"type":"object","description":"Anomalia de preco open mais recente desse material. Null se sem anomalia.","nullable":true}},"required":["partial","data"]},"SpectraCheckBlockDto":{"type":"object","properties":{"score":{"type":"object","description":"Score 0-1 da ultima computacao Spectra Check (Story 5.7). Null se nunca foi computed.","nullable":true,"example":0.87},"lastComputedAt":{"type":"object","description":"ISO 8601 quando score foi computed. Null se nunca.","nullable":true}},"required":["score","lastComputedAt"]},"ObsolescenceBlockDto":{"type":"object","properties":{"partial":{"type":"boolean","description":"true quando componente falhou ou timeout; data eh fallback null/[].","example":false},"error":{"type":"string","description":"Razao da falha quando partial=true (timeout, internal_error, etc)."},"signaled":{"type":"boolean","description":"true se material tem signal de obsolescencia ativo (nao cleared)."},"signal":{"type":"object","description":"Primeiro signal active (signalType + reason + detectedAt). Null se nao signaled."}},"required":["partial","signaled"]},"EnrichedContextMetaDto":{"type":"object","properties":{"composedAt":{"type":"string","description":"ISO 8601 timestamp da composicao."},"componentsRequested":{"type":"number","description":"Numero de componentes que o service tentou compor (constante 6 V1)."},"componentsSucceeded":{"type":"number","description":"Numero de componentes que succeed sem partial flag."},"componentsDegraded":{"description":"Lista de nomes de componentes degraded (ex: [\"priceAnomaly\", \"duplicates\"]).","type":"array","items":{"type":"string"}}},"required":["composedAt","componentsRequested","componentsSucceeded","componentsDegraded"]},"EnrichedContextResponseDto":{"type":"object","properties":{"material":{"type":"object","description":"Material base (MaterialService.findById). Inclui family + manufacturer + spectraCheckScore."},"similars":{"$ref":"#/components/schemas/SimilarsEnvelopeDto"},"duplicates":{"$ref":"#/components/schemas/DuplicatesEnvelopeDto"},"priceAnomaly":{"$ref":"#/components/schemas/PriceAnomalyEnvelopeDto"},"spectraCheck":{"description":"Spectra Check score denormalizado de Material.spectraCheckScore (Story 5.7). NAO eh re-computed em GET — apenas read denorm pra evitar side effects + cost. Re-compute via cron Story 5.7 ou endpoint dedicado.","nullable":true,"allOf":[{"$ref":"#/components/schemas/SpectraCheckBlockDto"}]},"obsolescenceFlag":{"$ref":"#/components/schemas/ObsolescenceBlockDto"},"crossPlantInventory":{"type":"array","description":"Story 10.1 (FR60) — saldo cross-plant agregado por workspace + freshness ('live' | 'D-1') + distancia + ETA via tabela de frete. Envelope `partial` sinaliza falha do componente (cache miss, timeout). Array vazio quando material nao tem inventory nenhum workspace accessible. Schema: ver CrossPlantInventoryItem em modules/materials/services.","items":{"type":"object"}},"meta":{"$ref":"#/components/schemas/EnrichedContextMetaDto"}},"required":["material","similars","duplicates","priceAnomaly","spectraCheck","obsolescenceFlag","crossPlantInventory","meta"]}}}}