schemaVersion: 1
exclusions:
  - { id: hub-push-worker, scope: hub, methods: [GET], path: /push-worker.js, reason: push-only service worker asset }
  - { id: workspace-notification-stream-refused, scope: workspace, methods: [GET], path: "/s/{workspaceId}/api/chat/notifications/events", reason: "internal child-to-hub notification feed; not proxied to browser clients" }
  - { id: hub-login-page, scope: hub, methods: [GET], path: /login, reason: browser HTML flow }
  - { id: hub-form-login, scope: hub, methods: [POST], path: /login, condition: content-type is not application/json, reason: browser HTML form variant }
  - { id: hub-cookie-logout, scope: hub, methods: [POST], path: /logout, condition: cookie or absent credential transport, reason: browser redirect variant (303 + cookie clear, also for credential-less requests); bearer JSON operation is public }
  - { id: hub-dashboard, scope: hub, methods: [GET], path: /, reason: HTML application shell }
  - { id: hub-clone-page, scope: hub, methods: [GET], path: /clone, reason: HTML application shell }
  - { id: hub-settings-page, scope: hub, methods: [GET], path: /settings, reason: HTML application shell }
  - { id: hub-assets, scope: hub, methods: [GET], pathPattern: /hub-assets/*, reason: static assets }
  - { id: hub-manifest, scope: hub, methods: [GET], path: /manifest.webmanifest, reason: browser installation metadata }
  - { id: workspace-api, scope: workspace, methods: [GET, POST, PATCH, DELETE], pathPattern: "/s/{workspaceId}/api/*", reason: "internal session protocol between the Hub, the workspace child, and the web client shipped in the same build. It covers documents, search, chat, terminal sessions and the terminal WebSocket, and it changes with that build rather than under a public revision. It left the public contract at workspace revision 16. It does not cover /s/{workspaceId}/api/personal-state, which the Hub serves itself and publishes as the public Hub operations workspaceGetPersonalState and workspacePatchPersonalState. Public clients use the Hub API and receive workspace updates on GET /api/hub/live" }
  - { id: workspace-state-stream-refused, scope: workspace, methods: [GET], path: "/s/{workspaceId}/api/events", reason: "not proxied. The Hub answers 410 Gone with Cache-Control no-store and a JSON body whose replacement field is /api/hub/live. Its document topic carries the same workspace state" }
  - { id: workspace-inventory-stream-refused, scope: workspace, methods: [GET], path: "/s/{workspaceId}/api/chat/conversations/events", reason: "not proxied. The Hub answers 410 Gone with Cache-Control no-store and a JSON body whose replacement field is /api/hub/live. Its inventory topic carries the same invalidation" }
  - { id: workspace-conversation-stream-refused, scope: workspace, methods: [GET], path: "/s/{workspaceId}/api/chat/conversations/{conversationId}/events", reason: "not proxied. The Hub answers 410 Gone with Cache-Control no-store and a JSON body whose replacement field is /api/hub/live. Its conversation topic carries the same events and resync" }
  - { id: workspace-activity-stream-refused, scope: workspace, methods: [GET], path: "/s/{workspaceId}/api/activity", reason: "not proxied. The child activity stream feeds only the Hub, which merges it into the live activity topic. The Hub answers 410 Gone with Cache-Control no-store and a JSON body whose replacement field is /api/hub/live" }
  - { id: workspace-navigation, scope: workspace, methods: [GET], pathPattern: "/s/{workspaceId}/*", condition: unmatched non-API navigation, reason: HTML application shell and static files }
  - { id: workspace-assets, scope: workspace, methods: [GET], pathPattern: "/s/{workspaceId}/assets/*", reason: static assets }
  - { id: workspace-manifest, scope: workspace, methods: [GET], path: "/s/{workspaceId}/manifest.webmanifest", reason: browser installation metadata }
  - { id: workspace-debug, scope: workspace, methods: [GET], path: "/s/{workspaceId}/debug/metrics", reason: opt-in internal diagnostics }
  - { id: workspace-terminal-cookie-auth, scope: workspace, methods: [POST], path: "/s/{workspaceId}/api/auth", reason: browser bootstrap flow requiring a Hub-private child token }
  - { id: e2e-reset, scope: test, methods: [POST], path: /__e2e/reset, reason: test-only route }
  - { id: e2e-terminal-token, scope: test, methods: [GET], path: /__e2e/terminal-token, reason: test-only credential helper }
  - { id: e2e-chat, scope: test, methods: [POST], path: /__e2e/chat, reason: test-only chat fixture control }
  - { id: e2e-personal-state, scope: test, methods: [GET, PATCH], path: /api/personal-state, reason: direct-child test substitute for the Hub's public personal-state operations }
  - { id: direct-child-api, scope: workspace, methods: [GET, POST, PATCH, DELETE], pathPattern: /api/*, reason: "internal Hub-to-child protocol on the child's loopback listener, reached only through the Hub. It includes the SSE routes the Hub's live broker subscribes to (/api/events, /api/chat/conversations/events, /api/chat/conversations/{conversationId}/events) and the workspace activity stream (/api/activity)" }
  - { id: unsupported-methods, scope: all, methods: [OPTIONS, TRACE], pathPattern: "*", reason: no supported public operations; undocumented methods on documented paths likewise reject (405 or 404) without a documented contract }
