@rudderhq/server 0.7.24-canary.2 → 0.7.24-canary.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-runtimes/registry.d.ts.map +1 -1
- package/dist/agent-runtimes/registry.js +3 -17
- package/dist/agent-runtimes/registry.js.map +1 -1
- package/dist/agent-runtimes/registry.test.d.ts +2 -0
- package/dist/agent-runtimes/registry.test.d.ts.map +1 -0
- package/dist/agent-runtimes/registry.test.js +25 -0
- package/dist/agent-runtimes/registry.test.js.map +1 -0
- package/dist/app.d.ts +9 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +20 -0
- package/dist/app.js.map +1 -1
- package/dist/bootstrap/create-http-app.d.ts +2 -0
- package/dist/bootstrap/create-http-app.d.ts.map +1 -1
- package/dist/bootstrap/create-http-app.js +57 -2
- package/dist/bootstrap/create-http-app.js.map +1 -1
- package/dist/bootstrap/public-ingress-startup.d.ts +12 -0
- package/dist/bootstrap/public-ingress-startup.d.ts.map +1 -0
- package/dist/bootstrap/public-ingress-startup.js +54 -0
- package/dist/bootstrap/public-ingress-startup.js.map +1 -0
- package/dist/bootstrap/register-api-routes.d.ts +2 -1
- package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
- package/dist/bootstrap/register-api-routes.js +5 -5
- package/dist/bootstrap/register-api-routes.js.map +1 -1
- package/dist/bootstrap/types.d.ts +14 -0
- package/dist/bootstrap/types.d.ts.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +22 -0
- package/dist/config.js.map +1 -1
- package/dist/embedded-postgres-startup-logs.d.ts +6 -0
- package/dist/embedded-postgres-startup-logs.d.ts.map +1 -0
- package/dist/embedded-postgres-startup-logs.js +48 -0
- package/dist/embedded-postgres-startup-logs.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -30
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +10 -2
- package/dist/middleware/auth.js.map +1 -1
- package/dist/realtime/live-events-ws.d.ts.map +1 -1
- package/dist/realtime/live-events-ws.js +14 -0
- package/dist/realtime/live-events-ws.js.map +1 -1
- package/dist/routes/agents.d.ts +2 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +2 -10
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/agents.management-routes.d.ts.map +1 -1
- package/dist/routes/agents.management-routes.js +16 -60
- package/dist/routes/agents.management-routes.js.map +1 -1
- package/dist/routes/onboarding.d.ts +2 -1
- package/dist/routes/onboarding.d.ts.map +1 -1
- package/dist/routes/onboarding.js +50 -17
- package/dist/routes/onboarding.js.map +1 -1
- package/dist/routes/organization-rust-foundation-routes.d.ts +21 -0
- package/dist/routes/organization-rust-foundation-routes.d.ts.map +1 -0
- package/dist/routes/organization-rust-foundation-routes.js +410 -0
- package/dist/routes/organization-rust-foundation-routes.js.map +1 -0
- package/dist/routes/orgs.d.ts +4 -1
- package/dist/routes/orgs.d.ts.map +1 -1
- package/dist/routes/orgs.js +38 -102
- package/dist/routes/orgs.js.map +1 -1
- package/dist/routes/projects.d.ts +2 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +301 -28
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/public-ingress-auth.d.ts +8 -0
- package/dist/routes/public-ingress-auth.d.ts.map +1 -0
- package/dist/routes/public-ingress-auth.js +144 -0
- package/dist/routes/public-ingress-auth.js.map +1 -0
- package/dist/services/agent-run-context.d.ts.map +1 -1
- package/dist/services/agent-run-context.js +0 -1
- package/dist/services/agent-run-context.js.map +1 -1
- package/dist/services/agent-runtime-availability.d.ts.map +1 -1
- package/dist/services/agent-runtime-availability.js +0 -3
- package/dist/services/agent-runtime-availability.js.map +1 -1
- package/dist/services/assets.d.ts.map +1 -1
- package/dist/services/assets.js +9 -5
- package/dist/services/assets.js.map +1 -1
- package/dist/services/budgets.d.ts.map +1 -1
- package/dist/services/budgets.js +21 -19
- package/dist/services/budgets.js.map +1 -1
- package/dist/services/chat-assistant.helpers.d.ts +1 -1
- package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
- package/dist/services/chat-assistant.runtime-overrides.d.ts.map +1 -1
- package/dist/services/chat-assistant.runtime-overrides.js +0 -2
- package/dist/services/chat-assistant.runtime-overrides.js.map +1 -1
- package/dist/services/chats.annotation-persistence.d.ts.map +1 -1
- package/dist/services/chats.annotation-persistence.js +2 -0
- package/dist/services/chats.annotation-persistence.js.map +1 -1
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.helpers.d.ts +2 -0
- package/dist/services/chats.helpers.d.ts.map +1 -1
- package/dist/services/chats.helpers.js +17 -1
- package/dist/services/chats.helpers.js.map +1 -1
- package/dist/services/chats.js +17 -33
- package/dist/services/chats.js.map +1 -1
- package/dist/services/chats.queued-message-create.d.ts.map +1 -1
- package/dist/services/chats.queued-message-create.js +2 -0
- package/dist/services/chats.queued-message-create.js.map +1 -1
- package/dist/services/chats.user-state-initialization.d.ts +5 -0
- package/dist/services/chats.user-state-initialization.d.ts.map +1 -0
- package/dist/services/chats.user-state-initialization.js +27 -0
- package/dist/services/chats.user-state-initialization.js.map +1 -0
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +2 -0
- package/dist/services/costs.js.map +1 -1
- package/dist/services/goal-public-contract.d.ts +13 -0
- package/dist/services/goal-public-contract.d.ts.map +1 -0
- package/dist/services/goal-public-contract.js +106 -0
- package/dist/services/goal-public-contract.js.map +1 -0
- package/dist/services/goal-public-contract.test.d.ts +2 -0
- package/dist/services/goal-public-contract.test.d.ts.map +1 -0
- package/dist/services/goal-public-contract.test.js +45 -0
- package/dist/services/goal-public-contract.test.js.map +1 -0
- package/dist/services/goals.d.ts +1 -2
- package/dist/services/goals.d.ts.map +1 -1
- package/dist/services/goals.js +72 -137
- package/dist/services/goals.js.map +1 -1
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/issues.comments-attachments.d.ts.map +1 -1
- package/dist/services/issues.comments-attachments.js +3 -0
- package/dist/services/issues.comments-attachments.js.map +1 -1
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.helpers.d.ts +3 -0
- package/dist/services/issues.helpers.d.ts.map +1 -1
- package/dist/services/issues.helpers.js +23 -0
- package/dist/services/issues.helpers.js.map +1 -1
- package/dist/services/issues.js +12 -24
- package/dist/services/issues.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.core.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.core.js +0 -4
- package/dist/services/knowledge-portability/organization-portability.core.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.d.ts +6 -2
- package/dist/services/knowledge-portability/organization-portability.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.import.d.ts +5 -1
- package/dist/services/knowledge-portability/organization-portability.import.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.import.js +92 -9
- package/dist/services/knowledge-portability/organization-portability.import.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.js +3 -1
- package/dist/services/knowledge-portability/organization-portability.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.js +0 -6
- package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -1
- package/dist/services/live-events.d.ts +2 -0
- package/dist/services/live-events.d.ts.map +1 -1
- package/dist/services/live-events.js +7 -1
- package/dist/services/live-events.js.map +1 -1
- package/dist/services/live-run-read-bridge.d.ts +11 -0
- package/dist/services/live-run-read-bridge.d.ts.map +1 -0
- package/dist/services/live-run-read-bridge.js +16 -0
- package/dist/services/live-run-read-bridge.js.map +1 -0
- package/dist/services/live-run-read-bridge.test.d.ts +2 -0
- package/dist/services/live-run-read-bridge.test.d.ts.map +1 -0
- package/dist/services/live-run-read-bridge.test.js +99 -0
- package/dist/services/live-run-read-bridge.test.js.map +1 -0
- package/dist/services/migration-preflight.d.ts.map +1 -1
- package/dist/services/migration-preflight.js +1 -0
- package/dist/services/migration-preflight.js.map +1 -1
- package/dist/services/migration-preflight.test.js +24 -0
- package/dist/services/migration-preflight.test.js.map +1 -1
- package/dist/services/organization-branding-fence.d.ts +30 -0
- package/dist/services/organization-branding-fence.d.ts.map +1 -0
- package/dist/services/organization-branding-fence.js +99 -0
- package/dist/services/organization-branding-fence.js.map +1 -0
- package/dist/services/organization-branding-fence.test.d.ts +2 -0
- package/dist/services/organization-branding-fence.test.d.ts.map +1 -0
- package/dist/services/organization-branding-fence.test.js +44 -0
- package/dist/services/organization-branding-fence.test.js.map +1 -0
- package/dist/services/organization-intelligence-profile-defaults.d.ts.map +1 -1
- package/dist/services/organization-intelligence-profile-defaults.js +32 -16
- package/dist/services/organization-intelligence-profile-defaults.js.map +1 -1
- package/dist/services/organization-intelligence-profiles.d.ts +1 -1
- package/dist/services/organization-intelligence-profiles.d.ts.map +1 -1
- package/dist/services/organization-intelligence-profiles.js +3 -2
- package/dist/services/organization-intelligence-profiles.js.map +1 -1
- package/dist/services/organization-mutation-fence.d.ts +21 -0
- package/dist/services/organization-mutation-fence.d.ts.map +1 -0
- package/dist/services/organization-mutation-fence.js +40 -0
- package/dist/services/organization-mutation-fence.js.map +1 -0
- package/dist/services/organization-mutation-outbox.d.ts +22 -0
- package/dist/services/organization-mutation-outbox.d.ts.map +1 -0
- package/dist/services/organization-mutation-outbox.js +125 -0
- package/dist/services/organization-mutation-outbox.js.map +1 -0
- package/dist/services/organization-mutation-outbox.test.d.ts +2 -0
- package/dist/services/organization-mutation-outbox.test.d.ts.map +1 -0
- package/dist/services/organization-mutation-outbox.test.js +80 -0
- package/dist/services/organization-mutation-outbox.test.js.map +1 -0
- package/dist/services/organization-resource-mutation-fence.d.ts +29 -0
- package/dist/services/organization-resource-mutation-fence.d.ts.map +1 -0
- package/dist/services/organization-resource-mutation-fence.js +75 -0
- package/dist/services/organization-resource-mutation-fence.js.map +1 -0
- package/dist/services/organization-resource-rust-authority.d.ts +7 -0
- package/dist/services/organization-resource-rust-authority.d.ts.map +1 -0
- package/dist/services/organization-resource-rust-authority.js +38 -0
- package/dist/services/organization-resource-rust-authority.js.map +1 -0
- package/dist/services/organization-resource-rust-authority.test.d.ts +2 -0
- package/dist/services/organization-resource-rust-authority.test.d.ts.map +1 -0
- package/dist/services/organization-resource-rust-authority.test.js +48 -0
- package/dist/services/organization-resource-rust-authority.test.js.map +1 -0
- package/dist/services/orgs.d.ts.map +1 -1
- package/dist/services/orgs.js +20 -2
- package/dist/services/orgs.js.map +1 -1
- package/dist/services/product-analytics.d.ts +1 -1
- package/dist/services/project-goal-mutation-fence.d.ts +35 -0
- package/dist/services/project-goal-mutation-fence.d.ts.map +1 -0
- package/dist/services/project-goal-mutation-fence.js +277 -0
- package/dist/services/project-goal-mutation-fence.js.map +1 -0
- package/dist/services/project-goal-mutation-fence.test.d.ts +2 -0
- package/dist/services/project-goal-mutation-fence.test.d.ts.map +1 -0
- package/dist/services/project-goal-mutation-fence.test.js +278 -0
- package/dist/services/project-goal-mutation-fence.test.js.map +1 -0
- package/dist/services/projects-create.test.d.ts +2 -0
- package/dist/services/projects-create.test.d.ts.map +1 -0
- package/dist/services/projects-create.test.js +174 -0
- package/dist/services/projects-create.test.js.map +1 -0
- package/dist/services/projects.d.ts +19 -3
- package/dist/services/projects.d.ts.map +1 -1
- package/dist/services/projects.js +215 -57
- package/dist/services/projects.js.map +1 -1
- package/dist/services/resource-catalog.d.ts.map +1 -1
- package/dist/services/resource-catalog.js +188 -112
- package/dist/services/resource-catalog.js.map +1 -1
- package/dist/services/run-intelligence.d.ts.map +1 -1
- package/dist/services/run-intelligence.js +3 -1
- package/dist/services/run-intelligence.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.js +0 -1
- package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.misc.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.misc.js +8 -4
- package/dist/services/runtime-kernel/heartbeat.misc.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.js +2 -0
- package/dist/services/runtime-kernel/heartbeat.sessions.js.map +1 -1
- package/dist/services/rust-foundation-bridge.d.ts +95 -0
- package/dist/services/rust-foundation-bridge.d.ts.map +1 -0
- package/dist/services/rust-foundation-bridge.js +802 -0
- package/dist/services/rust-foundation-bridge.js.map +1 -0
- package/dist/services/rust-foundation-bridge.test.d.ts +2 -0
- package/dist/services/rust-foundation-bridge.test.d.ts.map +1 -0
- package/dist/services/rust-foundation-bridge.test.js +829 -0
- package/dist/services/rust-foundation-bridge.test.js.map +1 -0
- package/dist/services/side-chats.d.ts.map +1 -1
- package/dist/services/side-chats.js +38 -19
- package/dist/services/side-chats.js.map +1 -1
- package/package.json +19 -16
- package/resources/bundled-skills/rudder-docs/references/cli-reference.md +2 -1
- package/resources/bundled-skills/visualize/SKILL.md +1 -1
- package/resources/native/aarch64-apple-darwin/migration-preflight +0 -0
- package/resources/native/aarch64-apple-darwin/rudder-server-foundation +0 -0
- package/resources/native/aarch64-pc-windows-msvc/migration-preflight.exe +0 -0
- package/resources/native/aarch64-pc-windows-msvc/rudder-server-foundation.exe +0 -0
- package/resources/native/aarch64-unknown-linux-gnu/migration-preflight +0 -0
- package/resources/native/aarch64-unknown-linux-gnu/rudder-server-foundation +0 -0
- package/resources/native/x86_64-apple-darwin/migration-preflight +0 -0
- package/resources/native/x86_64-apple-darwin/rudder-server-foundation +0 -0
- package/resources/native/x86_64-pc-windows-msvc/migration-preflight.exe +0 -0
- package/resources/native/x86_64-pc-windows-msvc/rudder-server-foundation.exe +0 -0
- package/resources/native/x86_64-unknown-linux-gnu/migration-preflight +0 -0
- package/resources/native/x86_64-unknown-linux-gnu/rudder-server-foundation +0 -0
- package/resources/postinstall-embedded-postgres.mjs +193 -0
- package/resources/postinstall-native-mode.mjs +36 -0
- package/skills/rudder-docs/references/cli-reference.md +2 -1
- package/skills/visualize/SKILL.md +1 -1
- package/ui-dist/assets/{LibraryLiveSurface-BpUD1odP.js → LibraryLiveSurface-Bs8V_pMU.js} +1 -1
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BPrBDdld.js → abnfDiagram-VRR7QNED-DneqWru9.js} +1 -1
- package/ui-dist/assets/{addon-fit-DNQtwoFo.js → addon-fit-DCRNyXYv.js} +1 -1
- package/ui-dist/assets/{arc-IXEjKK3z.js → arc-D_2I_R5Z.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-D5-GsWPu.js → architectureDiagram-ZJ3FMSHR-C0ANAv78.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-BT91oUno.js → blockDiagram-677ZJIJ3-BqFGOix5.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-DZa4R-RY.js → c4Diagram-LMCZKHZV-82LfEi1P.js} +1 -1
- package/ui-dist/assets/channel-DjvWm5jR.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-BJn1mzn0.js → chunk-2Q5K7J3B-DijvvGcl.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-DX4CN1No.js → chunk-32BRIVSS-Z09s337w.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-Bvq0Cx67.js → chunk-5VM5RSS4-DgQWkO6B.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-C9g5iHPO.js → chunk-EX3LRPZG-UTEQqeds.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-DoPFngr_.js → chunk-JWPE2WC7-DAaSu1vD.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-CZeNor1c.js → chunk-MOJQB5TN-D_fTXJM3.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-DSIa1gN4.js → chunk-RYQCIY6F-C7H_axhC.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-B-t9NovP.js → chunk-V7JOEXUC-B2toa05O.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-C17_to2o.js → chunk-VR4S4FIN-CkRvPkE2.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-CMKjuqZI.js → chunk-XXDRQBXY-DOO_r0VK.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CNSRItW2.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CNSRItW2.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC--JzUailr.js → cose-bilkent-JH36ORCC-BzYjE_M-.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-Is3IDXR5.js → cynefin-VYW2F7L2-Dcf0eQJ5.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-D9pZPkr_.js → cynefinDiagram-TSTJHNR4-Cs_Lv8Ua.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-DQtVOPbA.js → dagre-VKFMJZFB-B8oZME7g.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-nzceu0kB.js → diagram-FQU43EPY-DFHkB-_t.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-Dw-D5CH5.js → diagram-G47NLZAW-FGxRm1A-.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-CoyPw9Uo.js → diagram-NH7WQ7WH-Ot83ScOb.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-Cqoi9a6k.js → diagram-OA4YK3LP-CkiHNUvw.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-D8RWrHSh.js → diagram-WEI45ONY-CxM3g_L3.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-By9B5HMb.js → ebnfDiagram-CCIWWBDH-DbcqsHcY.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-rwMzFibF.js → erDiagram-Q63AITRT-DLROlxDN.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-BaCpiX-T.js → flowDiagram-23GEKE2U-DphM_YLM.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-DzXDtk7b.js → ganttDiagram-NO4QXBWP--QIRtWYP.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-C1RILXL1.js → gitGraphDiagram-IHSO6WYX-Bg3QwNvE.js} +1 -1
- package/ui-dist/assets/{graph-RzQsSMN0.js → graph-B_1y14m_.js} +1 -1
- package/ui-dist/assets/{index-DeIqk5X9.js → index-6O1Ef9oA.js} +1 -1
- package/ui-dist/assets/{index-Ufqpd6-1.js → index-Aj3Mi7R6.js} +1 -1
- package/ui-dist/assets/{index-C9N1nzkG.js → index-B-yeMW3t.js} +1 -1
- package/ui-dist/assets/{index-Dp0HIAhW.js → index-BABow2kh.js} +1 -1
- package/ui-dist/assets/{index-DUw2q_Tn.js → index-BEgTumxB.js} +1 -1
- package/ui-dist/assets/{index-QzfB2Hm8.js → index-BQheb_6L.js} +1 -1
- package/ui-dist/assets/index-B_tEQoXz.css +32 -0
- package/ui-dist/assets/{index-S_I6m7qi.js → index-Bi9I1TFJ.js} +561 -560
- package/ui-dist/assets/{index-1CcOBUAh.js → index-BlFoD6Gg.js} +1 -1
- package/ui-dist/assets/{index-Bj8AKsm3.js → index-BueQLOw6.js} +1 -1
- package/ui-dist/assets/{index-CTY5MHTT.js → index-CMUgKK2j.js} +1 -1
- package/ui-dist/assets/{index-DLz28cdj.js → index-CS2TW9nf.js} +1 -1
- package/ui-dist/assets/{index-B7vWk3jX.js → index-CcWXJMSa.js} +1 -1
- package/ui-dist/assets/{index-Cm40qa2n.js → index-CdAtz86o.js} +1 -1
- package/ui-dist/assets/{index-DobP_OA5.js → index-E2v-0TkN.js} +1 -1
- package/ui-dist/assets/{index-Cd1EZEkn.js → index-XWYyP2hU.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-DVIzbK0n.js → infoDiagram-FWYZ7A6U-BxygKDVp.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-BoCZRszc.js → ishikawaDiagram-FXEZZL3T-47zucfG2.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-DCRkA_lv.js → journeyDiagram-5HDEW3XC-D8cyI8aY.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-sttcO1jJ.js → kanban-definition-HUTT4EX6-CrxRNese.js} +1 -1
- package/ui-dist/assets/{layout-u47UnAg9.js → layout-OyZo5VN3.js} +1 -1
- package/ui-dist/assets/{linear-Hx-0ykqm.js → linear-CuiwlwQB.js} +1 -1
- package/ui-dist/assets/{map-JHAuHxTf.js → map-rixHzQ5d.js} +1 -1
- package/ui-dist/assets/{mermaid.core-duAMtZT0.js → mermaid.core-BBG92fxH.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-BqImMmHB.js → mindmap-definition-LN4V7U3C-CivqbhCV.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-BBqSimCZ.js → pegDiagram-2B236MQR-JrPSWk9k.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-IvsJkrmf.js → pieDiagram-ENE6RG2P-F4HEWR5n.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CBW_UfNv.js → quadrantDiagram-ABIIQ3AL-BJNly8dn.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-Zjn4XqMZ.js → railroadDiagram-RFXS5EU6-DNORDnb2.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-DPk09fAM.js → requirementDiagram-TGXJPOKE-710xtGEU.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-qzaGYd61.js → sankeyDiagram-HTMAVEWB-BmWJHmHA.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-B-4BDFMx.js → sequenceDiagram-DBY2YBRQ-A73TFc3V.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-S3KZiokK.js → sizeCapture-X5ZJPWSS-B8otrPI2.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-BumYuIdY.js → stateDiagram-2N3HPSRC-BZ9k729q.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DepHJpk7.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-T_bbOzkS.js → swimlanes-5IMT3BWC-W_CH_z9Z.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-BRL-ddud.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-95nLoqCi.js → timeline-definition-FHXFAJF6-CcXbC6J1.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-DROzWeg9.js → vennDiagram-L72KCM5P-BMHmsQqg.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-f6rn8OB5.js → wardleyDiagram-EHGQE667-Bq2-CEvU.js} +1 -1
- package/ui-dist/assets/{xterm-DOe84fOC.js → xterm-8vrqRuWm.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-JdlcTNzG.js → xychartDiagram-FW5EYKEG-De3RUAME.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-GV4x9fGv.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-C9tnRpuJ.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-C9tnRpuJ.js +0 -1
- package/ui-dist/assets/index-3zlQxsgk.css +0 -32
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-Cmr2JA9o.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-BnoZ05uR.js +0 -8
- package/ui-dist/brands/google-gemini-logo.svg +0 -1
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
import { resolveRudderNativeTarget } from "@rudderhq/shared";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { chmod, copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { createRustActorEnvelope, createRustFoundationBridge, } from "./rust-foundation-bridge.js";
|
|
9
|
+
vi.mock("node:url", async (importOriginal) => {
|
|
10
|
+
const actual = await importOriginal();
|
|
11
|
+
return { ...actual, fileURLToPath: vi.fn(actual.fileURLToPath) };
|
|
12
|
+
});
|
|
13
|
+
const activeBridges = new Set();
|
|
14
|
+
const activeFixtures = new Set();
|
|
15
|
+
function fixtureSource(modePath, pidPath, envPath, requestPath) {
|
|
16
|
+
return `#!${process.execPath}
|
|
17
|
+
const fs = require("node:fs");
|
|
18
|
+
const http = require("node:http");
|
|
19
|
+
const mode = fs.readFileSync(${JSON.stringify(modePath)}, "utf8").trim();
|
|
20
|
+
fs.writeFileSync(${JSON.stringify(pidPath)}, String(process.pid));
|
|
21
|
+
fs.writeFileSync(${JSON.stringify(envPath)}, JSON.stringify(process.env));
|
|
22
|
+
let server = null;
|
|
23
|
+
let publicServer = null;
|
|
24
|
+
const shutdown = () => {
|
|
25
|
+
publicServer?.closeAllConnections?.();
|
|
26
|
+
publicServer?.close();
|
|
27
|
+
if (!server) {
|
|
28
|
+
process.exit(0);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
server.closeAllConnections?.();
|
|
32
|
+
server.close(() => process.exit(0));
|
|
33
|
+
};
|
|
34
|
+
process.on("SIGTERM", () => {
|
|
35
|
+
if (mode !== "ignore-term") shutdown();
|
|
36
|
+
});
|
|
37
|
+
if (mode === "invalid") {
|
|
38
|
+
process.stdout.write("not-json\\n");
|
|
39
|
+
} else {
|
|
40
|
+
server = http.createServer((req, res) => {
|
|
41
|
+
if (req.url === "/readyz") {
|
|
42
|
+
res.statusCode = mode === "not-ready" ? 503 : 200;
|
|
43
|
+
res.end(mode === "not-ready" ? "not ready" : "ready");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (req.url?.includes("/project-reads") || req.url?.includes("/members") || req.url?.includes("/branding") || req.url?.includes("/goal-set") || req.url?.includes("/resources/") || req.method === "DELETE" || (req.method === "POST" && req.url?.endsWith("/projects"))) {
|
|
47
|
+
const chunks = [];
|
|
48
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
49
|
+
req.on("end", () => {
|
|
50
|
+
fs.writeFileSync(${JSON.stringify(requestPath)}, JSON.stringify({
|
|
51
|
+
method: req.method,
|
|
52
|
+
url: req.url,
|
|
53
|
+
headers: req.headers,
|
|
54
|
+
body: Buffer.concat(chunks).toString("utf8"),
|
|
55
|
+
}));
|
|
56
|
+
res.setHeader("content-type", "application/json");
|
|
57
|
+
if (req.method === "POST" && !req.url?.includes("/project-reads")) res.statusCode = 201;
|
|
58
|
+
res.end(JSON.stringify({ status: "accepted" }));
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
res.statusCode = 404;
|
|
63
|
+
res.end();
|
|
64
|
+
});
|
|
65
|
+
server.listen(0, "127.0.0.1", () => {
|
|
66
|
+
const address = server.address();
|
|
67
|
+
if (!address || typeof address === "string") process.exit(2);
|
|
68
|
+
const receipt = {
|
|
69
|
+
boundAddr: "127.0.0.1:" + address.port,
|
|
70
|
+
publicListener: false,
|
|
71
|
+
productWriteAuthority: false,
|
|
72
|
+
};
|
|
73
|
+
const emit = () => process.stdout.write(JSON.stringify(receipt) + "\\n");
|
|
74
|
+
if (mode === "public-ready") {
|
|
75
|
+
publicServer = http.createServer((req, res) => res.end("ready"));
|
|
76
|
+
publicServer.listen(0, "127.0.0.1", () => {
|
|
77
|
+
receipt.publicIngress = { boundAddr: "127.0.0.1:" + publicServer.address().port, publicListener: true };
|
|
78
|
+
emit();
|
|
79
|
+
});
|
|
80
|
+
} else emit();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
setInterval(() => {}, 1000);
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
async function createFixture(initialMode, executable = true) {
|
|
87
|
+
const root = await mkdtemp(join(tmpdir(), "rudder-rust-foundation-bridge-"));
|
|
88
|
+
const binaryPath = join(root, "fixture-server-foundation");
|
|
89
|
+
const modePath = join(root, "mode");
|
|
90
|
+
const pidPath = join(root, "pid");
|
|
91
|
+
const envPath = join(root, "env.json");
|
|
92
|
+
const requestPath = join(root, "request.json");
|
|
93
|
+
await writeFile(modePath, `${initialMode}\n`, "utf8");
|
|
94
|
+
await writeFile(binaryPath, fixtureSource(modePath, pidPath, envPath, requestPath), "utf8");
|
|
95
|
+
await chmod(binaryPath, executable ? 0o755 : 0o644);
|
|
96
|
+
const fixture = {
|
|
97
|
+
binaryPath,
|
|
98
|
+
modePath,
|
|
99
|
+
pidPath,
|
|
100
|
+
envPath,
|
|
101
|
+
requestPath,
|
|
102
|
+
setMode: (mode) => writeFile(modePath, `${mode}\n`, "utf8"),
|
|
103
|
+
readEnv: async () => JSON.parse(await readFile(envPath, "utf8")),
|
|
104
|
+
readPid: async () => Number((await readFile(pidPath, "utf8")).trim()),
|
|
105
|
+
readRequest: async () => JSON.parse(await readFile(requestPath, "utf8")),
|
|
106
|
+
remove: () => rm(root, { recursive: true, force: true }),
|
|
107
|
+
};
|
|
108
|
+
activeFixtures.add(fixture);
|
|
109
|
+
return fixture;
|
|
110
|
+
}
|
|
111
|
+
function createBridge(fixture, options = {}) {
|
|
112
|
+
const bridge = createRustFoundationBridge({
|
|
113
|
+
databaseUrl: "postgres://bridge-test",
|
|
114
|
+
mode: options.mode ?? "required",
|
|
115
|
+
organizationBrandingMode: options.organizationBrandingMode ?? "off",
|
|
116
|
+
projectGoalSetMode: options.projectGoalSetMode ?? "off",
|
|
117
|
+
binaryPath: fixture.binaryPath,
|
|
118
|
+
actorEnvelopeKey: "bridge-test-secret",
|
|
119
|
+
requestTimeoutMs: 25,
|
|
120
|
+
publicIngress: options.publicIngress,
|
|
121
|
+
});
|
|
122
|
+
activeBridges.add(bridge);
|
|
123
|
+
return bridge;
|
|
124
|
+
}
|
|
125
|
+
function expectEnvelopeSignedWith(captured, expected, secret) {
|
|
126
|
+
const envelope = JSON.parse(String(captured.headers["x-rudder-actor-envelope"]));
|
|
127
|
+
expect(envelope).toEqual(createRustActorEnvelope({
|
|
128
|
+
...expected,
|
|
129
|
+
secret,
|
|
130
|
+
requestId: envelope.requestId,
|
|
131
|
+
nonce: envelope.nonce,
|
|
132
|
+
nowSeconds: envelope.expiresAt - 60,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
async function waitForProcessExit(pid, timeoutMs = 3_000) {
|
|
136
|
+
const deadline = Date.now() + timeoutMs;
|
|
137
|
+
while (Date.now() < deadline) {
|
|
138
|
+
try {
|
|
139
|
+
process.kill(pid, 0);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`process ${pid} did not exit within ${timeoutMs}ms`);
|
|
147
|
+
}
|
|
148
|
+
afterEach(async () => {
|
|
149
|
+
await Promise.allSettled([...activeBridges].map((bridge) => bridge.close()));
|
|
150
|
+
activeBridges.clear();
|
|
151
|
+
await Promise.all([...activeFixtures].map((fixture) => fixture.remove()));
|
|
152
|
+
activeFixtures.clear();
|
|
153
|
+
});
|
|
154
|
+
describe("rust foundation bridge lifecycle", () => {
|
|
155
|
+
it("owns an explicitly requested public listener and clears its identity on shutdown", async () => {
|
|
156
|
+
const fixture = await createFixture("public-ready");
|
|
157
|
+
const publicIngress = {
|
|
158
|
+
listenAddr: "127.0.0.1:0",
|
|
159
|
+
nodeUpstream: "http://127.0.0.1:3101",
|
|
160
|
+
authorizationKey: "0123456789abcdef0123456789abcdef",
|
|
161
|
+
trustedProxies: "192.0.2.1,::1",
|
|
162
|
+
};
|
|
163
|
+
const bridge = createBridge(fixture, { mode: "off", publicIngress });
|
|
164
|
+
expect(bridge.requiresStartup).toBe(true);
|
|
165
|
+
await bridge.start();
|
|
166
|
+
expect(bridge.publicIngressBaseUrl).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/);
|
|
167
|
+
await expect(bridge.waitForPublicIngressReady()).resolves.toBeUndefined();
|
|
168
|
+
expect(await fixture.readEnv()).toMatchObject({
|
|
169
|
+
RUDDER_NATIVE_PUBLIC_LISTEN: publicIngress.listenAddr,
|
|
170
|
+
RUDDER_NATIVE_NODE_UPSTREAM: publicIngress.nodeUpstream,
|
|
171
|
+
RUDDER_NATIVE_INGRESS_AUTH_KEY: publicIngress.authorizationKey,
|
|
172
|
+
RUDDER_NATIVE_INGRESS_TRUSTED_PROXIES: publicIngress.trustedProxies,
|
|
173
|
+
});
|
|
174
|
+
await bridge.close();
|
|
175
|
+
expect(bridge.publicIngressBaseUrl).toBeNull();
|
|
176
|
+
await expect(bridge.waitForPublicIngressReady()).rejects.toMatchObject({ code: "not_ready" });
|
|
177
|
+
});
|
|
178
|
+
it("rejects a missing, unexpected or wrong public listener instead of accepting private readiness", async () => {
|
|
179
|
+
for (const [mode, listenAddr] of [["ready", "127.0.0.1:0"], ["public-ready", "127.0.0.1:1"]]) {
|
|
180
|
+
const fixture = await createFixture(mode);
|
|
181
|
+
const bridge = createBridge(fixture, { publicIngress: {
|
|
182
|
+
listenAddr, nodeUpstream: "http://127.0.0.1:3101",
|
|
183
|
+
authorizationKey: "0123456789abcdef0123456789abcdef",
|
|
184
|
+
} });
|
|
185
|
+
await expect(bridge.start()).rejects.toMatchObject({ code: "startup_failed" });
|
|
186
|
+
expect(bridge.publicIngressBaseUrl).toBeNull();
|
|
187
|
+
}
|
|
188
|
+
const fixture = await createFixture("public-ready");
|
|
189
|
+
await expect(createBridge(fixture).start()).rejects.toMatchObject({ code: "startup_failed" });
|
|
190
|
+
});
|
|
191
|
+
it("defaults member reads and Project-Goal writes to required while Project reads always require startup", () => {
|
|
192
|
+
const names = [
|
|
193
|
+
"RUDDER_RUST_MEMBER_DIRECTORY_MODE",
|
|
194
|
+
"RUDDER_RUST_ORGANIZATION_BRANDING_MODE",
|
|
195
|
+
"RUDDER_RUST_PROJECT_GOAL_SET_MODE",
|
|
196
|
+
];
|
|
197
|
+
const previous = Object.fromEntries(names.map((name) => [name, process.env[name]]));
|
|
198
|
+
for (const name of names)
|
|
199
|
+
delete process.env[name];
|
|
200
|
+
try {
|
|
201
|
+
const defaultBridge = createRustFoundationBridge({ databaseUrl: "postgres://bridge-test" });
|
|
202
|
+
activeBridges.add(defaultBridge);
|
|
203
|
+
expect(defaultBridge.mode).toBe("required");
|
|
204
|
+
expect(defaultBridge.organizationBrandingMode).toBe("off");
|
|
205
|
+
expect(defaultBridge.projectGoalSetMode).toBe("required");
|
|
206
|
+
expect(defaultBridge.requiresStartup).toBe(true);
|
|
207
|
+
const disabledBridge = createRustFoundationBridge({
|
|
208
|
+
databaseUrl: "postgres://bridge-test",
|
|
209
|
+
mode: "off",
|
|
210
|
+
projectGoalSetMode: "off",
|
|
211
|
+
});
|
|
212
|
+
activeBridges.add(disabledBridge);
|
|
213
|
+
expect(disabledBridge.mode).toBe("off");
|
|
214
|
+
expect(disabledBridge.requiresStartup).toBe(true);
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
for (const name of names) {
|
|
218
|
+
const value = previous[name];
|
|
219
|
+
if (value === undefined)
|
|
220
|
+
delete process.env[name];
|
|
221
|
+
else
|
|
222
|
+
process.env[name] = value;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
it("passes only the foundation startup environment to the child", async () => {
|
|
227
|
+
const previousEnv = {
|
|
228
|
+
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
|
|
229
|
+
DATABASE_URL: process.env.DATABASE_URL,
|
|
230
|
+
RUDDER_PRIVATE_TEST_SECRET: process.env.RUDDER_PRIVATE_TEST_SECRET,
|
|
231
|
+
};
|
|
232
|
+
process.env.AWS_SECRET_ACCESS_KEY = "should-not-reach-rust";
|
|
233
|
+
process.env.DATABASE_URL = "postgres://unrelated-parent-secret";
|
|
234
|
+
process.env.RUDDER_PRIVATE_TEST_SECRET = "should-not-reach-rust-either";
|
|
235
|
+
try {
|
|
236
|
+
const fixture = await createFixture("ready");
|
|
237
|
+
const bridge = createBridge(fixture);
|
|
238
|
+
await expect(bridge.start()).resolves.toBeUndefined();
|
|
239
|
+
const childEnv = await fixture.readEnv();
|
|
240
|
+
const requiredEnv = {
|
|
241
|
+
RUDDER_NATIVE_ACTOR_ENVELOPE_KEY: "bridge-test-secret",
|
|
242
|
+
RUDDER_NATIVE_DATABASE_REQUIRED: "true",
|
|
243
|
+
RUDDER_NATIVE_DATABASE_URL: "postgres://bridge-test",
|
|
244
|
+
RUDDER_NATIVE_LISTEN: "127.0.0.1:0",
|
|
245
|
+
};
|
|
246
|
+
expect(childEnv).toMatchObject(requiredEnv);
|
|
247
|
+
expect(Object.keys(childEnv).filter((name) => (!(name in requiredEnv) && name !== "__CF_USER_TEXT_ENCODING"))).toEqual([]);
|
|
248
|
+
expect(childEnv).not.toHaveProperty("AWS_SECRET_ACCESS_KEY");
|
|
249
|
+
expect(childEnv).not.toHaveProperty("DATABASE_URL");
|
|
250
|
+
expect(childEnv).not.toHaveProperty("RUDDER_PRIVATE_TEST_SECRET");
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
for (const [name, value] of Object.entries(previousEnv)) {
|
|
254
|
+
if (value === undefined)
|
|
255
|
+
delete process.env[name];
|
|
256
|
+
else
|
|
257
|
+
process.env[name] = value;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
it("cleans up an invalid receipt and allows the same bridge to retry", async () => {
|
|
262
|
+
const fixture = await createFixture("invalid");
|
|
263
|
+
const bridge = createBridge(fixture);
|
|
264
|
+
await expect(bridge.start()).rejects.toMatchObject({ code: "startup_failed" });
|
|
265
|
+
const failedPid = await fixture.readPid();
|
|
266
|
+
await waitForProcessExit(failedPid);
|
|
267
|
+
await fixture.setMode("ready");
|
|
268
|
+
await expect(bridge.start()).resolves.toBeUndefined();
|
|
269
|
+
const readyPid = await fixture.readPid();
|
|
270
|
+
expect(readyPid).not.toBe(failedPid);
|
|
271
|
+
await bridge.close();
|
|
272
|
+
await waitForProcessExit(readyPid);
|
|
273
|
+
});
|
|
274
|
+
it("cleans up after a ready timeout and allows a retry", async () => {
|
|
275
|
+
const fixture = await createFixture("not-ready");
|
|
276
|
+
const bridge = createBridge(fixture);
|
|
277
|
+
await expect(bridge.start()).rejects.toMatchObject({ code: "not_ready" });
|
|
278
|
+
const failedPid = await fixture.readPid();
|
|
279
|
+
await waitForProcessExit(failedPid);
|
|
280
|
+
await fixture.setMode("ready");
|
|
281
|
+
await expect(bridge.start()).resolves.toBeUndefined();
|
|
282
|
+
const readyPid = await fixture.readPid();
|
|
283
|
+
await bridge.close();
|
|
284
|
+
await waitForProcessExit(readyPid);
|
|
285
|
+
}, 15_000);
|
|
286
|
+
it("fails closed for a non-executable binary and allows a retry", async () => {
|
|
287
|
+
const fixture = await createFixture("ready", false);
|
|
288
|
+
const bridge = createBridge(fixture);
|
|
289
|
+
await expect(bridge.start()).rejects.toMatchObject({ code: "binary_unavailable" });
|
|
290
|
+
await chmod(fixture.binaryPath, 0o755);
|
|
291
|
+
await expect(bridge.start()).resolves.toBeUndefined();
|
|
292
|
+
const readyPid = await fixture.readPid();
|
|
293
|
+
await bridge.close();
|
|
294
|
+
await waitForProcessExit(readyPid);
|
|
295
|
+
});
|
|
296
|
+
it("fails closed for a missing explicit path even when the default debug binary exists", async () => {
|
|
297
|
+
const fixture = await createFixture("ready");
|
|
298
|
+
const root = dirname(fixture.binaryPath);
|
|
299
|
+
const debugDirectory = join(root, "native", "target", "debug");
|
|
300
|
+
const defaultDebugBinary = join(debugDirectory, "rudder-server-foundation");
|
|
301
|
+
await mkdir(debugDirectory, { recursive: true });
|
|
302
|
+
await copyFile(fixture.binaryPath, defaultDebugBinary);
|
|
303
|
+
await chmod(defaultDebugBinary, 0o755);
|
|
304
|
+
const target = resolveRudderNativeTarget();
|
|
305
|
+
expect(target).not.toBeNull();
|
|
306
|
+
const installedDirectory = join(root, "server", "resources", "native", target);
|
|
307
|
+
await mkdir(installedDirectory, { recursive: true });
|
|
308
|
+
const installedBinaryPath = join(installedDirectory, process.platform === "win32" ? "rudder-server-foundation.exe" : "rudder-server-foundation");
|
|
309
|
+
await copyFile(fixture.binaryPath, installedBinaryPath);
|
|
310
|
+
await chmod(installedBinaryPath, 0o755);
|
|
311
|
+
const originalFileURLToPath = vi.mocked(fileURLToPath).getMockImplementation();
|
|
312
|
+
const bridgeModuleUrl = new URL("./rust-foundation-bridge.ts", import.meta.url).href;
|
|
313
|
+
vi.mocked(fileURLToPath).mockImplementation((path, options) => (path.toString() === bridgeModuleUrl
|
|
314
|
+
? join(root, "server", "src", "services", "rust-foundation-bridge.ts")
|
|
315
|
+
: originalFileURLToPath(path, options)));
|
|
316
|
+
const previousPath = process.env.RUDDER_SERVER_FOUNDATION_PATH;
|
|
317
|
+
try {
|
|
318
|
+
delete process.env.RUDDER_SERVER_FOUNDATION_PATH;
|
|
319
|
+
await rm(installedBinaryPath);
|
|
320
|
+
const fallbackBridge = createRustFoundationBridge({
|
|
321
|
+
databaseUrl: "postgres://bridge-test",
|
|
322
|
+
mode: "required",
|
|
323
|
+
actorEnvelopeKey: "bridge-test-secret",
|
|
324
|
+
});
|
|
325
|
+
activeBridges.add(fallbackBridge);
|
|
326
|
+
await expect(fallbackBridge.start()).resolves.toBeUndefined();
|
|
327
|
+
expect(fileURLToPath).toHaveBeenCalledWith(bridgeModuleUrl);
|
|
328
|
+
const fallbackPid = await fixture.readPid();
|
|
329
|
+
await fallbackBridge.close();
|
|
330
|
+
await waitForProcessExit(fallbackPid);
|
|
331
|
+
await copyFile(fixture.binaryPath, installedBinaryPath);
|
|
332
|
+
await chmod(installedBinaryPath, 0o755);
|
|
333
|
+
vi.mocked(fileURLToPath).mockClear();
|
|
334
|
+
process.env.RUDDER_SERVER_FOUNDATION_PATH = join(root, "rudder-server-foundation-missing");
|
|
335
|
+
const bridge = createRustFoundationBridge({
|
|
336
|
+
databaseUrl: "postgres://bridge-test",
|
|
337
|
+
mode: "required",
|
|
338
|
+
actorEnvelopeKey: "bridge-test-secret",
|
|
339
|
+
});
|
|
340
|
+
activeBridges.add(bridge);
|
|
341
|
+
await expect(bridge.start()).rejects.toMatchObject({ code: "binary_unavailable" });
|
|
342
|
+
// An explicit path must not even resolve the repository's default binary paths.
|
|
343
|
+
expect(fileURLToPath).not.toHaveBeenCalledWith(bridgeModuleUrl);
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
vi.mocked(fileURLToPath).mockImplementation(originalFileURLToPath);
|
|
347
|
+
if (previousPath === undefined)
|
|
348
|
+
delete process.env.RUDDER_SERVER_FOUNDATION_PATH;
|
|
349
|
+
else
|
|
350
|
+
process.env.RUDDER_SERVER_FOUNDATION_PATH = previousPath;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
it("uses only the packaged asset in compiled server layouts and fails closed without it", async () => {
|
|
354
|
+
const fixture = await createFixture("ready");
|
|
355
|
+
const target = resolveRudderNativeTarget();
|
|
356
|
+
expect(target).not.toBeNull();
|
|
357
|
+
const serverPackageRoot = join(dirname(fixture.binaryPath), "bundle", "server");
|
|
358
|
+
const serverTsconfig = JSON.parse(await readFile(new URL("../../tsconfig.json", import.meta.url), "utf8"));
|
|
359
|
+
const bundledModulePath = join(serverPackageRoot, serverTsconfig.compilerOptions.outDir, "services", "rust-foundation-bridge.js");
|
|
360
|
+
const binaryName = process.platform === "win32"
|
|
361
|
+
? "rudder-server-foundation.exe"
|
|
362
|
+
: "rudder-server-foundation";
|
|
363
|
+
const installedBinaryPath = join(serverPackageRoot, "resources", "native", target, binaryName);
|
|
364
|
+
await mkdir(dirname(installedBinaryPath), { recursive: true });
|
|
365
|
+
await copyFile(fixture.binaryPath, installedBinaryPath);
|
|
366
|
+
await chmod(installedBinaryPath, 0o755);
|
|
367
|
+
const ancestorDebugBinaryPath = join(dirname(serverPackageRoot), "native", "target", "debug", binaryName);
|
|
368
|
+
const ancestorFixture = await createFixture("ready");
|
|
369
|
+
await mkdir(dirname(ancestorDebugBinaryPath), { recursive: true });
|
|
370
|
+
await copyFile(ancestorFixture.binaryPath, ancestorDebugBinaryPath);
|
|
371
|
+
await chmod(ancestorDebugBinaryPath, 0o755);
|
|
372
|
+
const originalFileURLToPath = vi.mocked(fileURLToPath).getMockImplementation();
|
|
373
|
+
const bridgeModuleUrl = new URL("./rust-foundation-bridge.ts", import.meta.url).href;
|
|
374
|
+
const previousPath = process.env.RUDDER_SERVER_FOUNDATION_PATH;
|
|
375
|
+
try {
|
|
376
|
+
delete process.env.RUDDER_SERVER_FOUNDATION_PATH;
|
|
377
|
+
vi.mocked(fileURLToPath).mockImplementation((path, options) => (path.toString() === bridgeModuleUrl
|
|
378
|
+
? bundledModulePath
|
|
379
|
+
: originalFileURLToPath(path, options)));
|
|
380
|
+
const bridge = createRustFoundationBridge({
|
|
381
|
+
databaseUrl: "postgres://bridge-test",
|
|
382
|
+
mode: "required",
|
|
383
|
+
actorEnvelopeKey: "bridge-test-secret",
|
|
384
|
+
});
|
|
385
|
+
activeBridges.add(bridge);
|
|
386
|
+
await expect(bridge.start()).resolves.toBeUndefined();
|
|
387
|
+
const pid = await fixture.readPid();
|
|
388
|
+
await expect(readFile(ancestorFixture.pidPath, "utf8")).rejects.toThrow();
|
|
389
|
+
await bridge.close();
|
|
390
|
+
await waitForProcessExit(pid);
|
|
391
|
+
expect(fileURLToPath).toHaveBeenCalledWith(bridgeModuleUrl);
|
|
392
|
+
await rm(installedBinaryPath);
|
|
393
|
+
const missingAssetBridge = createRustFoundationBridge({
|
|
394
|
+
databaseUrl: "postgres://bridge-test",
|
|
395
|
+
mode: "required",
|
|
396
|
+
actorEnvelopeKey: "bridge-test-secret",
|
|
397
|
+
});
|
|
398
|
+
activeBridges.add(missingAssetBridge);
|
|
399
|
+
await expect(missingAssetBridge.start()).rejects.toMatchObject({ code: "binary_unavailable" });
|
|
400
|
+
await expect(readFile(ancestorFixture.pidPath, "utf8")).rejects.toThrow();
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
vi.mocked(fileURLToPath).mockImplementation(originalFileURLToPath);
|
|
404
|
+
if (previousPath === undefined)
|
|
405
|
+
delete process.env.RUDDER_SERVER_FOUNDATION_PATH;
|
|
406
|
+
else
|
|
407
|
+
process.env.RUDDER_SERVER_FOUNDATION_PATH = previousPath;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
it("captures one generated signer for child startup and every signed request", async () => {
|
|
411
|
+
const previousKey = process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
412
|
+
delete process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
413
|
+
try {
|
|
414
|
+
const fixture = await createFixture("ready");
|
|
415
|
+
const bridge = createRustFoundationBridge({
|
|
416
|
+
databaseUrl: "postgres://bridge-test",
|
|
417
|
+
mode: "required",
|
|
418
|
+
organizationBrandingMode: "required",
|
|
419
|
+
projectGoalSetMode: "required",
|
|
420
|
+
binaryPath: fixture.binaryPath,
|
|
421
|
+
requestTimeoutMs: 25,
|
|
422
|
+
});
|
|
423
|
+
activeBridges.add(bridge);
|
|
424
|
+
process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY = "changed-after-bridge-creation";
|
|
425
|
+
await bridge.start();
|
|
426
|
+
const signer = (await fixture.readEnv()).RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
427
|
+
expect(signer).toMatch(/^[a-f0-9]{64}$/);
|
|
428
|
+
expect(signer).not.toBe(process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY);
|
|
429
|
+
const readActor = {
|
|
430
|
+
type: "agent",
|
|
431
|
+
source: "agent_key",
|
|
432
|
+
agentId: "agent-1",
|
|
433
|
+
orgId: "org-1",
|
|
434
|
+
sessionId: "member-directory-session",
|
|
435
|
+
authEpoch: 3,
|
|
436
|
+
};
|
|
437
|
+
const memberPath = "/api/orgs/org-1/members/directory?limit=25";
|
|
438
|
+
const readResponse = await bridge.memberDirectory({
|
|
439
|
+
actor: readActor,
|
|
440
|
+
originalUrl: memberPath,
|
|
441
|
+
header: () => undefined,
|
|
442
|
+
}, "org-1");
|
|
443
|
+
expect(readResponse.status).toBe(200);
|
|
444
|
+
expectEnvelopeSignedWith(await fixture.readRequest(), {
|
|
445
|
+
actor: readActor,
|
|
446
|
+
organizationId: "org-1",
|
|
447
|
+
method: "GET",
|
|
448
|
+
path: memberPath,
|
|
449
|
+
action: "organization.members.directory.read",
|
|
450
|
+
body: Buffer.alloc(0),
|
|
451
|
+
}, signer);
|
|
452
|
+
const brandingBody = Buffer.from(JSON.stringify({ name: "Rudder" }), "utf8");
|
|
453
|
+
const brandingPath = "/api/orgs/org-1/branding";
|
|
454
|
+
const brandingActor = {
|
|
455
|
+
type: "board",
|
|
456
|
+
source: "local_implicit",
|
|
457
|
+
userId: "user-1",
|
|
458
|
+
sessionId: "branding-session",
|
|
459
|
+
authEpoch: 4,
|
|
460
|
+
};
|
|
461
|
+
const brandingRequest = {
|
|
462
|
+
actor: brandingActor,
|
|
463
|
+
originalUrl: brandingPath,
|
|
464
|
+
header(name) {
|
|
465
|
+
return name.toLowerCase() === "x-rudder-idempotency-key"
|
|
466
|
+
? "branding-client-key"
|
|
467
|
+
: "application/json";
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
await bridge.organizationBranding(brandingRequest, "org-1", brandingBody);
|
|
471
|
+
expectEnvelopeSignedWith(await fixture.readRequest(), {
|
|
472
|
+
actor: brandingActor,
|
|
473
|
+
organizationId: "org-1",
|
|
474
|
+
method: "PATCH",
|
|
475
|
+
path: brandingPath,
|
|
476
|
+
action: "organization.branding.update",
|
|
477
|
+
body: brandingBody,
|
|
478
|
+
idempotencyKey: "branding-client-key",
|
|
479
|
+
}, signer);
|
|
480
|
+
const goalSetBody = Buffer.from(JSON.stringify({ goalIds: ["goal-1"] }), "utf8");
|
|
481
|
+
const goalSetPath = "/api/orgs/org-1/projects/project-1/goal-set";
|
|
482
|
+
const goalSetActor = {
|
|
483
|
+
type: "agent",
|
|
484
|
+
source: "agent_key",
|
|
485
|
+
agentId: "agent-1",
|
|
486
|
+
orgId: "org-1",
|
|
487
|
+
sessionId: "goal-set-session",
|
|
488
|
+
authEpoch: 5,
|
|
489
|
+
};
|
|
490
|
+
const goalSetRequest = {
|
|
491
|
+
actor: goalSetActor,
|
|
492
|
+
originalUrl: "/api/projects/project-1",
|
|
493
|
+
header(name) {
|
|
494
|
+
return name.toLowerCase() === "x-rudder-idempotency-key"
|
|
495
|
+
? "goal-set-client-key"
|
|
496
|
+
: "application/json";
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
await bridge.projectGoalSet(goalSetRequest, "org-1", "project-1", goalSetBody, goalSetPath);
|
|
500
|
+
expectEnvelopeSignedWith(await fixture.readRequest(), {
|
|
501
|
+
actor: goalSetActor,
|
|
502
|
+
organizationId: "org-1",
|
|
503
|
+
method: "PATCH",
|
|
504
|
+
path: goalSetPath,
|
|
505
|
+
action: "project.goal_set.replace",
|
|
506
|
+
body: goalSetBody,
|
|
507
|
+
idempotencyKey: "goal-set-client-key",
|
|
508
|
+
}, signer);
|
|
509
|
+
const createData = { name: "Project", goalIds: [] };
|
|
510
|
+
const createRoots = { organizationWorkspaceRoot: "/fixture/org", projectCreateStateRoot: "/fixture/instance/data" };
|
|
511
|
+
const createResponse = await bridge.projectCreate(goalSetActor, "org-1", createData, "create-key", {}, createRoots);
|
|
512
|
+
expect(createResponse.status).toBe(201);
|
|
513
|
+
expectEnvelopeSignedWith(await fixture.readRequest(), {
|
|
514
|
+
actor: goalSetActor,
|
|
515
|
+
organizationId: "org-1",
|
|
516
|
+
method: "POST",
|
|
517
|
+
path: "/api/orgs/org-1/projects",
|
|
518
|
+
action: "project.create",
|
|
519
|
+
body: Buffer.from(JSON.stringify({ runId: null, data: createData, activityDetails: {}, ...createRoots })),
|
|
520
|
+
idempotencyKey: "create-key",
|
|
521
|
+
}, signer);
|
|
522
|
+
process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY = "changed-before-bridge-restart";
|
|
523
|
+
await bridge.close();
|
|
524
|
+
await bridge.start();
|
|
525
|
+
expect((await fixture.readEnv()).RUDDER_NATIVE_ACTOR_ENVELOPE_KEY).toBe(signer);
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
if (previousKey === undefined)
|
|
529
|
+
delete process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
530
|
+
else
|
|
531
|
+
process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY = previousKey;
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
it("rotates the generated signer for each bridge instance", async () => {
|
|
535
|
+
const previousKey = process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
536
|
+
delete process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
537
|
+
try {
|
|
538
|
+
const fixture = await createFixture("ready");
|
|
539
|
+
const firstBridge = createRustFoundationBridge({
|
|
540
|
+
databaseUrl: "postgres://bridge-test",
|
|
541
|
+
mode: "required",
|
|
542
|
+
binaryPath: fixture.binaryPath,
|
|
543
|
+
});
|
|
544
|
+
activeBridges.add(firstBridge);
|
|
545
|
+
process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY = "late-first-instance-key";
|
|
546
|
+
await firstBridge.start();
|
|
547
|
+
const firstSigner = (await fixture.readEnv()).RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
548
|
+
expect(firstSigner).not.toBe("late-first-instance-key");
|
|
549
|
+
await firstBridge.close();
|
|
550
|
+
delete process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
551
|
+
const secondBridge = createRustFoundationBridge({
|
|
552
|
+
databaseUrl: "postgres://bridge-test",
|
|
553
|
+
mode: "required",
|
|
554
|
+
binaryPath: fixture.binaryPath,
|
|
555
|
+
});
|
|
556
|
+
activeBridges.add(secondBridge);
|
|
557
|
+
process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY = "late-second-instance-key";
|
|
558
|
+
await secondBridge.start();
|
|
559
|
+
const secondSigner = (await fixture.readEnv()).RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
560
|
+
expect(secondSigner).toMatch(/^[a-f0-9]{64}$/);
|
|
561
|
+
expect(secondSigner).not.toBe(firstSigner);
|
|
562
|
+
expect(secondSigner).not.toBe("late-second-instance-key");
|
|
563
|
+
}
|
|
564
|
+
finally {
|
|
565
|
+
if (previousKey === undefined)
|
|
566
|
+
delete process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY;
|
|
567
|
+
else
|
|
568
|
+
process.env.RUDDER_NATIVE_ACTOR_ENVELOPE_KEY = previousKey;
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
it("serializes close behind start and waits for SIGKILL exit", async () => {
|
|
572
|
+
const fixture = await createFixture("ignore-term");
|
|
573
|
+
const bridge = createBridge(fixture);
|
|
574
|
+
const startPromise = bridge.start();
|
|
575
|
+
const closePromise = bridge.close();
|
|
576
|
+
await expect(startPromise).resolves.toBeUndefined();
|
|
577
|
+
const pid = await fixture.readPid();
|
|
578
|
+
const closeResult = await Promise.race([
|
|
579
|
+
closePromise.then(() => "closed"),
|
|
580
|
+
new Promise((resolve) => setTimeout(() => resolve("pending"), 100)),
|
|
581
|
+
]);
|
|
582
|
+
expect(closeResult).toBe("pending");
|
|
583
|
+
await expect(closePromise).resolves.toBeUndefined();
|
|
584
|
+
await waitForProcessExit(pid);
|
|
585
|
+
}, 10_000);
|
|
586
|
+
it.each([
|
|
587
|
+
{ projectId: null, resourcesOnly: false },
|
|
588
|
+
{ projectId: "old-node-owned-project", resourcesOnly: false },
|
|
589
|
+
{ projectId: "new-rust-created-project", resourcesOnly: true },
|
|
590
|
+
])("signs API-wide Project reads with mutation modes disabled: %j", async (selection) => {
|
|
591
|
+
const fixture = await createFixture("ready");
|
|
592
|
+
const bridge = createBridge(fixture, { mode: "off", projectGoalSetMode: "off" });
|
|
593
|
+
const actor = { type: "agent", agentId: "agent-1", orgId: "org-1", source: "agent_key" };
|
|
594
|
+
const input = { ...selection, organizationWorkspaceRoot: "/trusted/organization" };
|
|
595
|
+
const response = await bridge.projectRead(actor, "org-1", input);
|
|
596
|
+
expect(response.status).toBe(200);
|
|
597
|
+
expect(bridge.requiresStartup).toBe(true);
|
|
598
|
+
const captured = await fixture.readRequest();
|
|
599
|
+
expect(captured.method).toBe("POST");
|
|
600
|
+
expect(captured.url).toBe("/internal/orgs/org-1/project-reads");
|
|
601
|
+
expect(JSON.parse(captured.body)).toEqual(input);
|
|
602
|
+
expect(captured.headers["x-rudder-idempotency-key"]).toBeUndefined();
|
|
603
|
+
expectEnvelopeSignedWith(captured, {
|
|
604
|
+
actor,
|
|
605
|
+
organizationId: "org-1",
|
|
606
|
+
method: "POST",
|
|
607
|
+
path: captured.url,
|
|
608
|
+
action: "project.read",
|
|
609
|
+
body: Buffer.from(JSON.stringify(input)),
|
|
610
|
+
}, "bridge-test-secret");
|
|
611
|
+
});
|
|
612
|
+
it("binds branding requests to the private Actix contract", async () => {
|
|
613
|
+
const fixture = await createFixture("ready");
|
|
614
|
+
const bridge = createBridge(fixture, { mode: "off", organizationBrandingMode: "required" });
|
|
615
|
+
const body = Buffer.from(JSON.stringify({ name: "Renamed Rudder", brandColor: "#abcdef" }), "utf8");
|
|
616
|
+
const req = {
|
|
617
|
+
actor: {
|
|
618
|
+
type: "board",
|
|
619
|
+
source: "local_implicit",
|
|
620
|
+
userId: "user-1",
|
|
621
|
+
sessionId: "session-1",
|
|
622
|
+
authEpoch: 4,
|
|
623
|
+
},
|
|
624
|
+
originalUrl: "/api/orgs/org-1/branding",
|
|
625
|
+
header(name) {
|
|
626
|
+
return name.toLowerCase() === "content-type"
|
|
627
|
+
? "application/json"
|
|
628
|
+
: name.toLowerCase() === "x-rudder-idempotency-key"
|
|
629
|
+
? "branding-client-key"
|
|
630
|
+
: undefined;
|
|
631
|
+
},
|
|
632
|
+
};
|
|
633
|
+
const response = await bridge.organizationBranding(req, "org-1", body);
|
|
634
|
+
expect(response.status).toBe(200);
|
|
635
|
+
expect(bridge.requiresStartup).toBe(true);
|
|
636
|
+
const captured = await fixture.readRequest();
|
|
637
|
+
expect(captured.method).toBe("PATCH");
|
|
638
|
+
expect(captured.url).toBe("/api/orgs/org-1/branding");
|
|
639
|
+
expect(captured.body).toBe(body.toString("utf8"));
|
|
640
|
+
expect(captured.headers["x-rudder-idempotency-key"]).toBe("branding-client-key");
|
|
641
|
+
expect(captured.headers["content-type"]).toBe("application/json");
|
|
642
|
+
const envelope = JSON.parse(String(captured.headers["x-rudder-actor-envelope"]));
|
|
643
|
+
expect(envelope).toMatchObject({
|
|
644
|
+
action: "organization.branding.update",
|
|
645
|
+
method: "PATCH",
|
|
646
|
+
organizationId: "org-1",
|
|
647
|
+
actor: { kind: "user", id: "user-1" },
|
|
648
|
+
sessionId: "session-1",
|
|
649
|
+
authEpoch: 4,
|
|
650
|
+
});
|
|
651
|
+
expect(envelope.bodySha256).toBe(createHash("sha256").update(body).digest("hex"));
|
|
652
|
+
expect(envelope.idempotencyKey).toBe("branding-client-key");
|
|
653
|
+
expect(captured.headers["x-rudder-request-id"]).toBe(envelope.requestId);
|
|
654
|
+
});
|
|
655
|
+
it("identifies an invalid organization branding mode with its own environment variable", () => {
|
|
656
|
+
expect(() => createRustFoundationBridge({
|
|
657
|
+
databaseUrl: "postgres://bridge-test",
|
|
658
|
+
mode: "off",
|
|
659
|
+
organizationBrandingMode: "invalid",
|
|
660
|
+
})).toThrow("RUDDER_RUST_ORGANIZATION_BRANDING_MODE");
|
|
661
|
+
});
|
|
662
|
+
it("binds Project-Goal replacement requests to the private Actix contract", async () => {
|
|
663
|
+
const fixture = await createFixture("ready");
|
|
664
|
+
const bridge = createBridge(fixture, { mode: "off", projectGoalSetMode: "required" });
|
|
665
|
+
const body = Buffer.from(JSON.stringify({
|
|
666
|
+
goalIds: ["goal-1", "goal-2"],
|
|
667
|
+
primaryGoalId: "goal-2",
|
|
668
|
+
}), "utf8");
|
|
669
|
+
const req = {
|
|
670
|
+
actor: {
|
|
671
|
+
type: "agent",
|
|
672
|
+
source: "agent_key",
|
|
673
|
+
agentId: "agent-1",
|
|
674
|
+
orgId: "org-1",
|
|
675
|
+
sessionId: "session-1",
|
|
676
|
+
authEpoch: 3,
|
|
677
|
+
},
|
|
678
|
+
originalUrl: "/api/projects/project-1",
|
|
679
|
+
header(name) {
|
|
680
|
+
return name.toLowerCase() === "content-type"
|
|
681
|
+
? "application/json"
|
|
682
|
+
: name.toLowerCase() === "x-rudder-idempotency-key"
|
|
683
|
+
? "project-goal-client-key"
|
|
684
|
+
: undefined;
|
|
685
|
+
},
|
|
686
|
+
};
|
|
687
|
+
const response = await bridge.projectGoalSet(req, "org-1", "project-1", body, "/api/orgs/org-1/projects/project-1/goal-set");
|
|
688
|
+
expect(response.status).toBe(200);
|
|
689
|
+
expect(bridge.projectGoalSetMode).toBe("required");
|
|
690
|
+
const captured = await fixture.readRequest();
|
|
691
|
+
expect(captured.method).toBe("PATCH");
|
|
692
|
+
expect(captured.url).toBe("/api/orgs/org-1/projects/project-1/goal-set");
|
|
693
|
+
expect(captured.body).toBe(body.toString("utf8"));
|
|
694
|
+
expect(captured.headers["x-rudder-idempotency-key"]).toBe("project-goal-client-key");
|
|
695
|
+
const envelope = JSON.parse(String(captured.headers["x-rudder-actor-envelope"]));
|
|
696
|
+
expect(envelope).toMatchObject({
|
|
697
|
+
action: "project.goal_set.replace",
|
|
698
|
+
method: "PATCH",
|
|
699
|
+
organizationId: "org-1",
|
|
700
|
+
actor: { kind: "agent", id: "agent-1" },
|
|
701
|
+
sessionId: "session-1",
|
|
702
|
+
authEpoch: 3,
|
|
703
|
+
});
|
|
704
|
+
expect(envelope.bodySha256).toBe(createHash("sha256").update(body).digest("hex"));
|
|
705
|
+
expect(envelope.idempotencyKey).toBe("project-goal-client-key");
|
|
706
|
+
});
|
|
707
|
+
it("identifies an invalid Project-Goal mode with its own environment variable", () => {
|
|
708
|
+
expect(() => createRustFoundationBridge({
|
|
709
|
+
databaseUrl: "postgres://bridge-test",
|
|
710
|
+
mode: "off",
|
|
711
|
+
projectGoalSetMode: "invalid",
|
|
712
|
+
})).toThrow("RUDDER_RUST_PROJECT_GOAL_SET_MODE");
|
|
713
|
+
});
|
|
714
|
+
it.each(["update", "delete"])("signs resource %s method, scope, payload and replay key", async (operation) => {
|
|
715
|
+
const fixture = await createFixture("ready");
|
|
716
|
+
const bridge = createBridge(fixture, { mode: "off", projectGoalSetMode: "required" });
|
|
717
|
+
const actor = { type: "board", source: "session", userId: "user-1", sessionId: "session-1", authEpoch: 3 };
|
|
718
|
+
const req = { actor };
|
|
719
|
+
const body = Buffer.from(JSON.stringify({ data: operation === "update" ? { title: "Updated resource" } : {}, runId: null }));
|
|
720
|
+
const response = await bridge.organizationResourceMutation(req, "org-1", "resource-1", operation, body, " resource-key ");
|
|
721
|
+
expect(response.status).toBe(200);
|
|
722
|
+
const captured = await fixture.readRequest();
|
|
723
|
+
const method = operation === "update" ? "PATCH" : "DELETE";
|
|
724
|
+
expect(captured.method).toBe(method);
|
|
725
|
+
expect(captured.url).toBe("/api/orgs/org-1/resources/resource-1");
|
|
726
|
+
expect(captured.body).toBe(body.toString());
|
|
727
|
+
expect(captured.headers["x-rudder-idempotency-key"]).toBe("resource-key");
|
|
728
|
+
expectEnvelopeSignedWith(captured, {
|
|
729
|
+
actor, organizationId: "org-1", method, path: captured.url,
|
|
730
|
+
action: "organization.resource.mutate", body, idempotencyKey: "resource-key",
|
|
731
|
+
}, "bridge-test-secret");
|
|
732
|
+
});
|
|
733
|
+
it.each(["off", "shadow"])("rejects resource writes in %s mode before startup", async (projectGoalSetMode) => {
|
|
734
|
+
const bridge = createRustFoundationBridge({ databaseUrl: "", mode: "off", projectGoalSetMode });
|
|
735
|
+
await expect(bridge.organizationResourceMutation({ actor: { type: "board", source: "local_implicit" } }, "org-1", "resource-1", "delete", Buffer.from(JSON.stringify({ data: {}, runId: null })), "resource-key"))
|
|
736
|
+
.rejects.toMatchObject({ code: "request_failed" });
|
|
737
|
+
});
|
|
738
|
+
it("binds Project creation actor/run, payload, trusted roots and key to the captured signer", async () => {
|
|
739
|
+
const fixture = await createFixture("ready");
|
|
740
|
+
const bridge = createBridge(fixture, { mode: "off", projectGoalSetMode: "required" });
|
|
741
|
+
const actor = { type: "agent", source: "agent_key", agentId: "agent-1", orgId: "org-1", runId: "run-1" };
|
|
742
|
+
const data = { name: "Release", goalIds: [], organizationWorkspaceRoot: "/untrusted" };
|
|
743
|
+
const roots = { organizationWorkspaceRoot: "/trusted/org", projectCreateStateRoot: "/trusted/instance/data" };
|
|
744
|
+
const activityDetails = { source: "fixture" };
|
|
745
|
+
const response = await bridge.projectCreate(actor, "org-1", data, " create-key ", activityDetails, roots);
|
|
746
|
+
expect(response.status).toBe(201);
|
|
747
|
+
const captured = await fixture.readRequest();
|
|
748
|
+
const body = Buffer.from(JSON.stringify({ runId: "run-1", data, activityDetails, ...roots }));
|
|
749
|
+
expect(captured.method).toBe("POST");
|
|
750
|
+
expect(captured.url).toBe("/api/orgs/org-1/projects");
|
|
751
|
+
expect(captured.body).toBe(body.toString());
|
|
752
|
+
expect(captured.headers["x-rudder-idempotency-key"]).toBe("create-key");
|
|
753
|
+
expectEnvelopeSignedWith(captured, {
|
|
754
|
+
actor, organizationId: "org-1", method: "POST", path: captured.url,
|
|
755
|
+
action: "project.create", body, idempotencyKey: "create-key",
|
|
756
|
+
}, "bridge-test-secret");
|
|
757
|
+
});
|
|
758
|
+
it.each(["off", "shadow"])("rejects Project creation in %s mode before startup", async (projectGoalSetMode) => {
|
|
759
|
+
const bridge = createRustFoundationBridge({ databaseUrl: "", mode: "off", projectGoalSetMode });
|
|
760
|
+
await expect(bridge.projectCreate({ type: "board", source: "local_implicit" }, "org-1", { name: "Release" }, "key", {}, { organizationWorkspaceRoot: "/trusted/org", projectCreateStateRoot: "/trusted/data" }))
|
|
761
|
+
.rejects.toMatchObject({ code: "request_failed" });
|
|
762
|
+
});
|
|
763
|
+
it("propagates Project creation startup failure", async () => {
|
|
764
|
+
const bridge = createRustFoundationBridge({ databaseUrl: "", mode: "off", projectGoalSetMode: "required" });
|
|
765
|
+
await expect(bridge.projectCreate({ type: "board", source: "local_implicit" }, "org-1", { name: "Release" }, "key", {}, { organizationWorkspaceRoot: "/trusted/org", projectCreateStateRoot: "/trusted/data" }))
|
|
766
|
+
.rejects.toMatchObject({ code: "database_unconfigured" });
|
|
767
|
+
});
|
|
768
|
+
it("binds Project DELETE to the signed private bridge contract", async () => {
|
|
769
|
+
const fixture = await createFixture("ready");
|
|
770
|
+
const bridge = createBridge(fixture, { mode: "off", projectGoalSetMode: "required" });
|
|
771
|
+
const body = Buffer.from(JSON.stringify({ runId: "run-1" }), "utf8");
|
|
772
|
+
const req = {
|
|
773
|
+
actor: {
|
|
774
|
+
type: "agent",
|
|
775
|
+
source: "agent_key",
|
|
776
|
+
agentId: "agent-1",
|
|
777
|
+
orgId: "org-1",
|
|
778
|
+
runId: "run-1",
|
|
779
|
+
sessionId: "session-1",
|
|
780
|
+
authEpoch: 3,
|
|
781
|
+
},
|
|
782
|
+
originalUrl: "/api/projects/project-1",
|
|
783
|
+
header(name) {
|
|
784
|
+
return name.toLowerCase() === "content-type" ? "application/json" : undefined;
|
|
785
|
+
},
|
|
786
|
+
};
|
|
787
|
+
const requestPath = "/api/orgs/org-1/projects/project-1";
|
|
788
|
+
const response = await bridge.projectDelete(req, "org-1", "project-1", body, "project-delete-client-key", requestPath);
|
|
789
|
+
expect(response.status).toBe(200);
|
|
790
|
+
expect(bridge.projectGoalSetMode).toBe("required");
|
|
791
|
+
expect(bridge.requiresStartup).toBe(true);
|
|
792
|
+
const captured = await fixture.readRequest();
|
|
793
|
+
expect(captured.method).toBe("DELETE");
|
|
794
|
+
expect(captured.url).toBe(requestPath);
|
|
795
|
+
expect(captured.body).toBe(body.toString("utf8"));
|
|
796
|
+
expect(captured.headers["x-rudder-idempotency-key"]).toBe("project-delete-client-key");
|
|
797
|
+
expect(captured.headers["content-type"]).toBe("application/json");
|
|
798
|
+
expectEnvelopeSignedWith(captured, {
|
|
799
|
+
actor: req.actor,
|
|
800
|
+
organizationId: "org-1",
|
|
801
|
+
method: "DELETE",
|
|
802
|
+
path: requestPath,
|
|
803
|
+
action: "project.delete",
|
|
804
|
+
body,
|
|
805
|
+
idempotencyKey: "project-delete-client-key",
|
|
806
|
+
}, "bridge-test-secret");
|
|
807
|
+
});
|
|
808
|
+
it("does not activate Project DELETE from an independent undocumented mode", () => {
|
|
809
|
+
const previousMode = process.env.RUDDER_RUST_PROJECT_DELETE_MODE;
|
|
810
|
+
process.env.RUDDER_RUST_PROJECT_DELETE_MODE = "required";
|
|
811
|
+
try {
|
|
812
|
+
const bridge = createRustFoundationBridge({
|
|
813
|
+
databaseUrl: "postgres://bridge-test",
|
|
814
|
+
mode: "off",
|
|
815
|
+
projectGoalSetMode: "off",
|
|
816
|
+
});
|
|
817
|
+
activeBridges.add(bridge);
|
|
818
|
+
expect(bridge.projectGoalSetMode).toBe("off");
|
|
819
|
+
expect(bridge.requiresStartup).toBe(true);
|
|
820
|
+
}
|
|
821
|
+
finally {
|
|
822
|
+
if (previousMode === undefined)
|
|
823
|
+
delete process.env.RUDDER_RUST_PROJECT_DELETE_MODE;
|
|
824
|
+
else
|
|
825
|
+
process.env.RUDDER_RUST_PROJECT_DELETE_MODE = previousMode;
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
//# sourceMappingURL=rust-foundation-bridge.test.js.map
|