create-workframe 0.1.0 → 0.1.2

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.
Files changed (198) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +12 -12
  3. package/README.md +8 -92
  4. package/SECURITY.md +38 -40
  5. package/bin/workframe.js +329 -329
  6. package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +1 -1
  7. package/docs/workspace-instructions/WORKFRAME_ROUTING.md +8 -8
  8. package/package.json +3 -6
  9. package/profiles/architect/AGENTS.md +29 -29
  10. package/profiles/architect/SOUL.md +2 -2
  11. package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -27
  12. package/profiles/designer/AGENTS.md +26 -26
  13. package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -27
  14. package/profiles/dev/AGENTS.md +28 -28
  15. package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -27
  16. package/profiles/docs/AGENTS.md +27 -27
  17. package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -27
  18. package/profiles/research/AGENTS.md +26 -26
  19. package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -27
  20. package/profiles/visionary/AGENTS.md +25 -25
  21. package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -27
  22. package/profiles/workframe-agent/AGENTS.md +37 -37
  23. package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -85
  24. package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -58
  25. package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -54
  26. package/rules/workspace-README.md +5 -5
  27. package/scripts/bundle-workframe-ui.mjs +3 -3
  28. package/scripts/ensure-compose-host-paths.mjs +51 -51
  29. package/scripts/lib/install-identity.mjs +212 -212
  30. package/scripts/set-compose-public-url.mjs +92 -92
  31. package/scripts/sync-canonical-to-package.mjs +27 -9
  32. package/shared/WORKFRAME_AGENT_LIBRARY.md +17 -17
  33. package/shared/WORKFRAME_AGENT_OPERATIONS.md +15 -15
  34. package/shared/WORKFRAME_AGENT_PACKS.json +18 -18
  35. package/shared/WORKFRAME_AGENT_PACKS.yaml +8 -8
  36. package/shared/WORKFRAME_SKILL_CURATION.md +4 -4
  37. package/workframe-api/README.md +26 -28
  38. package/workframe-api/action_proxy.py +131 -131
  39. package/workframe-api/auth_rate_limit.py +49 -49
  40. package/workframe-api/credential_vault.py +445 -445
  41. package/workframe-api/data/avatar-catalog.json +41 -41
  42. package/workframe-api/email_sender.py +220 -220
  43. package/workframe-api/google_auth.py +90 -90
  44. package/workframe-api/install_api.py +359 -359
  45. package/workframe-api/internal_proxy_auth.py +150 -150
  46. package/workframe-api/llm_proxy.py +277 -277
  47. package/workframe-api/oidc_jwt.py +108 -108
  48. package/workframe-api/package.json +12 -13
  49. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  50. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  51. package/workframe-api/requirements.txt +2 -2
  52. package/workframe-api/site_meta.py +271 -271
  53. package/workframe-api/stack_config.py +427 -427
  54. package/workframe-api/time-bind-chat.py +99 -99
  55. package/workframe-api/turn_credentials.py +226 -226
  56. package/workframe-api/updates.py +417 -417
  57. package/workframe-api/vault_kek.py +159 -159
  58. package/workframe-api/zk_auth.py +633 -633
  59. package/workframe-supervisor/Dockerfile +11 -11
  60. package/workframe-supervisor/server.py +787 -787
  61. package/workframe-ui/docker/nginx.conf +85 -85
  62. package/workframe-ui/public/assets/{arc-CBDYvkAF.js → arc-COAT3laO.js} +1 -1
  63. package/workframe-ui/public/assets/architecture-7EHR7CIX-DUyH3hWG.js +1 -0
  64. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-XnBRKeW0.js → architectureDiagram-3BPJPVTR-BFjWV24l.js} +1 -1
  65. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-VYHUfVhd.js → blockDiagram-GPEHLZMM-DSQLPfrj.js} +1 -1
  66. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BTjUcJpm.js → c4Diagram-AAUBKEIU-DKEHv1t2.js} +1 -1
  67. package/workframe-ui/public/assets/channel-g7r_RGaY.js +1 -0
  68. package/workframe-ui/public/assets/{chunk-2J33WTMH-w7uu7R-b.js → chunk-2J33WTMH-DHZg-DUi.js} +1 -1
  69. package/workframe-ui/public/assets/{chunk-3OPIFGDE-Cb9LtnDX.js → chunk-3OPIFGDE-BB-OYTfp.js} +1 -1
  70. package/workframe-ui/public/assets/{chunk-4BX2VUAB-DiQ-qCwH.js → chunk-4BX2VUAB-C93q0YIm.js} +1 -1
  71. package/workframe-ui/public/assets/{chunk-55IACEB6-C-mLFr7z.js → chunk-55IACEB6-MAYniqik.js} +1 -1
  72. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DOesfiCI.js → chunk-5ZQYHXKU-ChgN6YJs.js} +1 -1
  73. package/workframe-ui/public/assets/{chunk-727SXJPM-BJ3oBZuz.js → chunk-727SXJPM-B_FYwdAv.js} +1 -1
  74. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CCA6xpGs.js → chunk-AQP2D5EJ-1_Hw_h1A.js} +1 -1
  75. package/workframe-ui/public/assets/{chunk-BSJP7CBP-a0cMNFb2.js → chunk-BSJP7CBP-CFiDQ1Rv.js} +1 -1
  76. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-kuqN8EIY.js → chunk-CSCIHK7Q-DZ9UMTlB.js} +1 -1
  77. package/workframe-ui/public/assets/{chunk-FMBD7UC4-DyPgYHCg.js → chunk-FMBD7UC4-DlMlyFgw.js} +1 -1
  78. package/workframe-ui/public/assets/{chunk-KSCS5N6A-CdUuvR0V.js → chunk-KSCS5N6A-DHXtQ_Hf.js} +1 -1
  79. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-Dq9NoWmK.js → chunk-L5ZTLDWV-CuQzg-QG.js} +1 -1
  80. package/workframe-ui/public/assets/{chunk-LZXEDZCA-p74rddlO.js → chunk-LZXEDZCA-BHzjzCGg.js} +2 -2
  81. package/workframe-ui/public/assets/{chunk-ND2GUHAM-DBD2u1Gz.js → chunk-ND2GUHAM-DHXx05n2.js} +1 -1
  82. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BeIeYFnd.js → chunk-NZK2D7GU-CV5pmDM_.js} +1 -1
  83. package/workframe-ui/public/assets/{chunk-O5CBEL6O-ClHc56ib.js → chunk-O5CBEL6O-6tkCHxsV.js} +1 -1
  84. package/workframe-ui/public/assets/chunk-QZHKN3VN-C5UQehWY.js +1 -0
  85. package/workframe-ui/public/assets/chunk-WU5MYG2G-DhWllrI8.js +1 -0
  86. package/workframe-ui/public/assets/{chunk-XPW4576I-EFr8R_1p.js → chunk-XPW4576I-BClwIiCp.js} +1 -1
  87. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BBM_8T8E.js +1 -0
  88. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BBM_8T8E.js +1 -0
  89. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-C7aPBODd.js → cose-bilkent-S5V4N54A-DOrGV6DQ.js} +1 -1
  90. package/workframe-ui/public/assets/{dagre-BM42HDAG-BdU1Rv-H.js → dagre-BM42HDAG-DXTPvJkX.js} +1 -1
  91. package/workframe-ui/public/assets/{diagram-2AECGRRQ-DWowSo85.js → diagram-2AECGRRQ-xX_v-pbf.js} +1 -1
  92. package/workframe-ui/public/assets/{diagram-5GNKFQAL-MnxBbceO.js → diagram-5GNKFQAL-Cd2pXbBe.js} +1 -1
  93. package/workframe-ui/public/assets/{diagram-KO2AKTUF-DQaLRXFf.js → diagram-KO2AKTUF-Df3XvUtk.js} +1 -1
  94. package/workframe-ui/public/assets/{diagram-LMA3HP47-CQaBud9k.js → diagram-LMA3HP47-CsijIPaD.js} +1 -1
  95. package/workframe-ui/public/assets/{diagram-OG6HWLK6-D8bAXbY9.js → diagram-OG6HWLK6-aq5fmfHd.js} +1 -1
  96. package/workframe-ui/public/assets/{dist-DGpTLHr_.js → dist-D1c0mkbB.js} +1 -1
  97. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-1E-xSvBK.js → erDiagram-TEJ5UH35-DnFysVRY.js} +1 -1
  98. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Ci8mdb44.js +1 -0
  99. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CgOVD5hu.js → flowDiagram-I6XJVG4X-C6Ebi3su.js} +1 -1
  100. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-JFYAIauo.js → ganttDiagram-6RSMTGT7-BQXQtUpa.js} +1 -1
  101. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-B9REenIl.js → gitGraph-WXDBUCRP-Dt0zIs_M.js} +1 -1
  102. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BQ7NcMSn.js → gitGraphDiagram-PVQCEYII-BF8gHzRn.js} +1 -1
  103. package/workframe-ui/public/assets/index-DpoUZAxh.css +1 -0
  104. package/workframe-ui/public/assets/{index-Dnw6vjqb.js → index-lRpzpNPT.js} +2 -2
  105. package/workframe-ui/public/assets/{info-J43DQDTF-CL6-eTjH.js → info-J43DQDTF-CSmszQJT.js} +1 -1
  106. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-LJTODW4W.js → infoDiagram-5YYISTIA-CVTKGW6p.js} +1 -1
  107. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-bchrQVuo.js → ishikawaDiagram-YF4QCWOH-Z8pT09Lv.js} +1 -1
  108. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-DkrvYuxP.js → journeyDiagram-JHISSGLW-r3wD68_T.js} +1 -1
  109. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-DFRbj0IG.js → kanban-definition-UN3LZRKU-Il8VglqN.js} +1 -1
  110. package/workframe-ui/public/assets/{line-Vd48P7-O.js → line-oyjpfz2A.js} +1 -1
  111. package/workframe-ui/public/assets/{linear-Ckizh2G7.js → linear-Cf7p5tVp.js} +1 -1
  112. package/workframe-ui/public/assets/{mermaid-parser.core-Bkimsnqj.js → mermaid-parser.core-YmbZ-AfY.js} +2 -2
  113. package/workframe-ui/public/assets/{mermaid.core-x0TvVuPo.js → mermaid.core-BFdCAqCo.js} +3 -3
  114. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-6ykAFPEz.js → mindmap-definition-RKZ34NQL-Cy2iCtEl.js} +1 -1
  115. package/workframe-ui/public/assets/{packet-YPE3B663-Dw3xgMDt.js → packet-YPE3B663-DwOBZL6K.js} +1 -1
  116. package/workframe-ui/public/assets/{pie-LRSECV5Y-DATysawG.js → pie-LRSECV5Y-04PPhnKK.js} +1 -1
  117. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-SJKD1S0S.js → pieDiagram-4H26LBE5-LxIpgHqi.js} +1 -1
  118. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-BrYDZX8q.js → quadrantDiagram-W4KKPZXB-0nBYfYm4.js} +1 -1
  119. package/workframe-ui/public/assets/{radar-GUYGQ44K-BmWYPCds.js → radar-GUYGQ44K-D2-vBqps.js} +1 -1
  120. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-DwL9Mc8e.js → requirementDiagram-4Y6WPE33-DbuU0nlu.js} +1 -1
  121. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DYIFsL8h.js → sankeyDiagram-5OEKKPKP-B2hQ6B2x.js} +1 -1
  122. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-0-FPkFk8.js → sequenceDiagram-3UESZ5HK-BBrU30e1.js} +1 -1
  123. package/workframe-ui/public/assets/{src-B_od6b6h.js → src-BJEDmV70.js} +1 -1
  124. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BQCiBk6u.js → stateDiagram-AJRCARHV-7FGO4kkH.js} +1 -1
  125. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DLTSizMg.js +1 -0
  126. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DS3tFcXj.js → timeline-definition-PNZ67QCA-ptDm4rCN.js} +1 -1
  127. package/workframe-ui/public/assets/{treeView-BLDUP644-DSyUCKLY.js → treeView-BLDUP644-CS6Z-0q8.js} +1 -1
  128. package/workframe-ui/public/assets/{treemap-LRROVOQU-CEZaNh5Y.js → treemap-LRROVOQU-DqV4Y2VA.js} +1 -1
  129. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-CD-Vc9NF.js → vennDiagram-CIIHVFJN-C0UrZJYt.js} +1 -1
  130. package/workframe-ui/public/assets/{wardley-L42UT6IY-Drq5w1Mc.js → wardley-L42UT6IY-bNDN3_Sa.js} +1 -1
  131. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DouXDJoF.js → wardleyDiagram-YWT4CUSO-jWiJsefM.js} +1 -1
  132. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DDf_Lol5.js → xychartDiagram-2RQKCTM6-Dsh_fLCy.js} +1 -1
  133. package/workframe-ui/public/favicon.svg +7 -7
  134. package/workframe-ui/public/index.html +50 -50
  135. package/workframe-ui/public/workframe-config.json +3 -3
  136. package/scripts/security_audit.py +0 -156
  137. package/scripts/test-scaffold.mjs +0 -390
  138. package/workframe-api/tests/__init__.py +0 -0
  139. package/workframe-api/tests/db_setup.py +0 -13
  140. package/workframe-api/tests/test_admin_updates_gated.py +0 -30
  141. package/workframe-api/tests/test_agent_dm_bootstrap.py +0 -196
  142. package/workframe-api/tests/test_agent_profile_sync.py +0 -76
  143. package/workframe-api/tests/test_auth_email.py +0 -222
  144. package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +0 -99
  145. package/workframe-api/tests/test_auth_rate_limit.py +0 -19
  146. package/workframe-api/tests/test_avatar_resolve.py +0 -77
  147. package/workframe-api/tests/test_child_soul_template.py +0 -71
  148. package/workframe-api/tests/test_credential_canary.py +0 -135
  149. package/workframe-api/tests/test_credential_isolation.py +0 -448
  150. package/workframe-api/tests/test_credential_resolution.py +0 -206
  151. package/workframe-api/tests/test_device_oauth.py +0 -108
  152. package/workframe-api/tests/test_doctor_repair.py +0 -103
  153. package/workframe-api/tests/test_ensure_profile_api.py +0 -77
  154. package/workframe-api/tests/test_gateway_compose_security.py +0 -136
  155. package/workframe-api/tests/test_install_secure_host.py +0 -39
  156. package/workframe-api/tests/test_internal_proxy_auth.py +0 -125
  157. package/workframe-api/tests/test_invite_runtime_bootstrap.py +0 -72
  158. package/workframe-api/tests/test_kanban_delegation.py +0 -185
  159. package/workframe-api/tests/test_llm_proxy.py +0 -155
  160. package/workframe-api/tests/test_login_access_policy.py +0 -183
  161. package/workframe-api/tests/test_mvp_model_bootstrap.py +0 -75
  162. package/workframe-api/tests/test_onboarding_bootstrap.py +0 -248
  163. package/workframe-api/tests/test_platform_auth.py +0 -47
  164. package/workframe-api/tests/test_profile_config_path.py +0 -56
  165. package/workframe-api/tests/test_profile_config_yaml_repair.py +0 -63
  166. package/workframe-api/tests/test_profile_create.py +0 -72
  167. package/workframe-api/tests/test_profile_identity_overlay.py +0 -61
  168. package/workframe-api/tests/test_profile_install_health.py +0 -45
  169. package/workframe-api/tests/test_profile_secret_policy.py +0 -57
  170. package/workframe-api/tests/test_profile_workspace_cwd.py +0 -34
  171. package/workframe-api/tests/test_provider_bootstrap.py +0 -75
  172. package/workframe-api/tests/test_provider_connect.py +0 -54
  173. package/workframe-api/tests/test_room_crud.py +0 -192
  174. package/workframe-api/tests/test_room_tenancy.py +0 -701
  175. package/workframe-api/tests/test_runtime_identity_backfill.py +0 -34
  176. package/workframe-api/tests/test_site_meta.py +0 -81
  177. package/workframe-api/tests/test_soul_stub.py +0 -42
  178. package/workframe-api/tests/test_space_member_sync.py +0 -99
  179. package/workframe-api/tests/test_stripe_stack_config.py +0 -37
  180. package/workframe-api/tests/test_supervisor_lifecycle.py +0 -52
  181. package/workframe-api/tests/test_turn_credential_vault.py +0 -125
  182. package/workframe-api/tests/test_updates.py +0 -176
  183. package/workframe-api/tests/test_user_cohort.py +0 -113
  184. package/workframe-api/tests/test_vault_envelope.py +0 -110
  185. package/workframe-api/tests/test_workspace_members.py +0 -183
  186. package/workframe-api/tests/test_workspace_messaging_sync.py +0 -125
  187. package/workframe-api/tests/test_workspace_provider_list.py +0 -57
  188. package/workframe-supervisor/tests/test_exec_guard.py +0 -42
  189. package/workframe-supervisor/tests/test_server_import.py +0 -21
  190. package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +0 -1
  191. package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +0 -1
  192. package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +0 -1
  193. package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +0 -1
  194. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +0 -1
  195. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +0 -1
  196. package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +0 -1
  197. package/workframe-ui/public/assets/index-DpAGxump.css +0 -1
  198. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B89jAMFF.js +0 -1
@@ -1,77 +0,0 @@
1
- import unittest
2
-
3
- import server
4
-
5
-
6
- class AvatarResolveTests(unittest.TestCase):
7
- def test_avatar_url_for_id_uses_catalog_file(self) -> None:
8
- url = server._avatar_url_for_id("ada")
9
- self.assertTrue(url.endswith("/ada.png"))
10
-
11
- def test_resolve_avatar_fields_from_id(self) -> None:
12
- row = {"avatar_id": "grace"}
13
- server._resolve_avatar_fields(row)
14
- self.assertIn("avatar_url", row)
15
- self.assertTrue(str(row["avatar_url"]).endswith("/grace.png"))
16
-
17
- def test_avatar_id_from_catalog_url(self) -> None:
18
- catalog_id = str((server._load_avatar_catalog().get("avatars") or [{}])[0].get("id") or "")
19
- self.assertTrue(catalog_id)
20
- url = server._avatar_url_for_id(catalog_id)
21
- self.assertEqual(server._avatar_id_from_url(url), catalog_id)
22
-
23
- def test_avatar_id_from_custom_url_is_empty(self) -> None:
24
- self.assertEqual(server._avatar_id_from_url("/files/uploads/my-face.png"), "")
25
-
26
- def test_explicit_url_survives_resolve_after_reconcile(self) -> None:
27
- # Reconciled catalog pick: id matches url, resolver reproduces the chosen url.
28
- catalog_id = str((server._load_avatar_catalog().get("avatars") or [{}])[0].get("id") or "")
29
- chosen = server._avatar_url_for_id(catalog_id)
30
- row = {"avatar_url": chosen, "avatar_id": server._avatar_id_from_url(chosen)}
31
- server._resolve_avatar_fields(row)
32
- self.assertEqual(row["avatar_url"], chosen)
33
-
34
- def test_custom_url_survives_resolve_when_id_cleared(self) -> None:
35
- row = {"avatar_url": "/files/uploads/my-face.png", "avatar_id": ""}
36
- server._resolve_avatar_fields(row)
37
- self.assertEqual(row["avatar_url"], "/files/uploads/my-face.png")
38
-
39
- def test_avatar_id_from_vite_hashed_url(self) -> None:
40
- self.assertEqual(server._avatar_id_from_url("/assets/frida-DAkwYbrd.png"), "frida")
41
- self.assertEqual(
42
- server._avatar_id_from_url("http://127.0.0.1:18644/assets/steve-CvzbBEg_.png"),
43
- "steve",
44
- )
45
-
46
- def test_normalize_agent_avatar_patch_stable(self) -> None:
47
- out = server._normalize_agent_avatar_patch("/assets/frida-DAkwYbrd.png", "")
48
- self.assertEqual(out["avatar_id"], "frida")
49
- self.assertTrue(out["avatar_url"].endswith("/frida.png"))
50
-
51
- def test_native_assign_uses_steve(self) -> None:
52
- native = str(server.NATIVE_PROFILE or "workframe-agent")
53
- picked = server._assign_agent_avatar(native)
54
- self.assertEqual(picked["avatar_id"], "steve")
55
- self.assertTrue(picked["avatar_url"].endswith("/steve.png"))
56
-
57
- def test_normalize_user_avatar_vite_hash(self) -> None:
58
- out = server._normalize_user_avatar_url("/assets/grace-CnKLjc4q.png")
59
- self.assertTrue(out.endswith("/grace.png"))
60
-
61
- def test_normalize_logo_vite_hash(self) -> None:
62
- out = server._normalize_logo_url("/assets/4-DGVBPxhf.png")
63
- self.assertTrue(out.endswith("/4.png"))
64
-
65
- def test_pick_logo_url_from_catalog(self) -> None:
66
- url = server._pick_logo_url()
67
- self.assertTrue(url.startswith("/assets/project-logos/"))
68
- self.assertTrue(url.endswith(".png"))
69
-
70
- def test_pick_user_avatar_url_from_catalog(self) -> None:
71
- url = server._pick_user_avatar_url()
72
- self.assertTrue(url.startswith("/assets/avatars/"))
73
- self.assertTrue(url.endswith(".png"))
74
-
75
-
76
- if __name__ == "__main__":
77
- unittest.main()
@@ -1,71 +0,0 @@
1
- """Child agent SOUL templates and native overlay seeding."""
2
- import tempfile
3
- import unittest
4
- from pathlib import Path
5
- from unittest import mock
6
-
7
- import server
8
-
9
-
10
- class ChildSoulTemplateTests(unittest.TestCase):
11
- def test_format_child_template_expands_native_agent_name(self) -> None:
12
- body = server._format_child_template(
13
- server._CHILD_SOUL_TEMPLATE,
14
- display_name="Elvis",
15
- role="Rock legend",
16
- )
17
- self.assertNotIn("{nativeAgentName}", body)
18
- self.assertIn("Workframe Agent", body)
19
- self.assertIn("Elvis", body)
20
-
21
- def test_install_child_base_artifacts_no_placeholder_error(self) -> None:
22
- with tempfile.TemporaryDirectory() as tmp:
23
- root = Path(tmp)
24
- prof = "elvis"
25
- prof_dir = root / "profiles" / prof
26
- prof_dir.mkdir(parents=True)
27
- (prof_dir / "SOUL.md").write_text("test", encoding="utf-8")
28
- with mock.patch.object(server, "_profile_dir", side_effect=lambda p: root / "profiles" / p), mock.patch.object(
29
- server, "PROJECT_NAME", "Workframe"
30
- ), mock.patch.object(server, "_is_native_profile", return_value=False):
31
- ok = server._install_child_base_artifacts(prof, display_name="Elvis", role="Rock legend")
32
- self.assertTrue(ok)
33
- agents = (prof_dir / "AGENTS.md").read_text(encoding="utf-8")
34
- self.assertNotIn("{nativeAgentName}", agents)
35
-
36
- def test_seed_native_user_overlay_keeps_base_soul(self) -> None:
37
- with tempfile.TemporaryDirectory() as tmp:
38
- root = Path(tmp)
39
- template = root / "profiles" / "workframe-agent"
40
- runtime = root / "profiles" / "u-user-workframe-agent"
41
- template.mkdir(parents=True)
42
- runtime.mkdir(parents=True)
43
- (template / "SOUL.md").write_text(
44
- "# Workframe Agent\n\nBase native system prompt.\n" + ("operate. " * 20),
45
- encoding="utf-8",
46
- )
47
- (runtime / "SOUL.md").write_text("test", encoding="utf-8")
48
- agents = root / "workframe" / "agents.json"
49
- agents.parent.mkdir(parents=True)
50
- agents.write_text('{"version":1,"agents":{}}', encoding="utf-8")
51
- with mock.patch.object(server, "_profile_dir", side_effect=lambda p: root / "profiles" / p), mock.patch.object(
52
- server, "AGENTS_JSON", agents
53
- ), mock.patch.object(server, "_is_runtime_profile_slug", return_value=True), mock.patch.object(
54
- server, "_runtime_template_slug", return_value="workframe-agent"
55
- ):
56
- server._seed_native_user_overlay(
57
- "u-user-workframe-agent",
58
- "workframe-agent",
59
- display_name="Andy",
60
- user_soul="Always be cheerful.",
61
- )
62
- base = (runtime / "SOUL.md").read_text(encoding="utf-8")
63
- merged = server._profile_soul_text("u-user-workframe-agent")
64
- self.assertIn("Base native system prompt", base)
65
- self.assertIn("Base native system prompt", merged)
66
- self.assertIn("## Additional instructions", merged)
67
- self.assertIn("Always be cheerful.", merged)
68
-
69
-
70
- if __name__ == "__main__":
71
- unittest.main()
@@ -1,135 +0,0 @@
1
- """Canary / red-team tests: cross-profile reads, exec guards, public tool gating."""
2
-
3
- from __future__ import annotations
4
-
5
- import tempfile
6
- import unittest
7
- from pathlib import Path
8
- from unittest.mock import patch
9
-
10
- import server
11
- from db_setup import ensure_workframe_schemas
12
-
13
-
14
- class CredentialCanaryTests(unittest.TestCase):
15
- def setUp(self) -> None:
16
- self._tmp = tempfile.TemporaryDirectory()
17
- self.addCleanup(self._tmp.cleanup)
18
- self._old_data_dir = server.DATA_DIR
19
- self._old_auth_db_path = server.AUTH_DB_PATH
20
- self._old_hermes_data = server.HERMES_DATA
21
- server.DATA_DIR = Path(self._tmp.name)
22
- server.AUTH_DB_PATH = Path(self._tmp.name) / "auth.db"
23
- server.HERMES_DATA = Path(self._tmp.name) / "hermes"
24
- (server.HERMES_DATA / "profiles").mkdir(parents=True)
25
- ensure_workframe_schemas()
26
- self.workspace_id = "ws-canary"
27
- self.user_a = "cb6a2db4-ac86-4c49-8247-14a1d68aca72"
28
- self.user_b = "44fb344c-0954-47b6-a19a-ebbcf20e9680"
29
- conn = server._workframe_db()
30
- try:
31
- now = "1"
32
- conn.execute(
33
- "INSERT INTO workspaces (id, slug, display_name, owner_id, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
34
- (self.workspace_id, "canary", "Canary", self.user_a, "active", now, now),
35
- )
36
- for uid, name, role in (
37
- (self.user_a, "Alice", "owner"),
38
- (self.user_b, "Bob", "member"),
39
- ):
40
- conn.execute(
41
- "INSERT INTO users (id, display_name, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?)",
42
- (uid, name, "member", "active", now, now),
43
- )
44
- conn.execute(
45
- """
46
- INSERT INTO workspace_memberships (id, workspace_id, user_id, role, status, created_at, updated_at)
47
- VALUES (?, ?, ?, ?, 'active', ?, ?)
48
- """,
49
- (f"wm-{uid}", self.workspace_id, uid, role, now, now),
50
- )
51
- conn.execute(
52
- """
53
- INSERT INTO agent_profiles (id, workspace_id, slug, display_name, is_native, status, created_at, updated_at)
54
- VALUES (?, ?, ?, ?, ?, 'available', ?, ?)
55
- """,
56
- ("ap-architect", self.workspace_id, "architect", "Architect", 0, now, now),
57
- )
58
- conn.commit()
59
- finally:
60
- conn.close()
61
- self.runtime_a = server._runtime_profile_slug(self.user_a, "architect")
62
- self.runtime_b = server._runtime_profile_slug(self.user_b, "architect")
63
-
64
- def tearDown(self) -> None:
65
- server.DATA_DIR = self._old_data_dir
66
- server.AUTH_DB_PATH = self._old_auth_db_path
67
- server.HERMES_DATA = self._old_hermes_data
68
-
69
- def test_member_cannot_access_peer_runtime_profile_public_mode(self) -> None:
70
- with patch.object(server, "DEPLOYMENT_MODE", "public_multi_user"):
71
- self.assertFalse(
72
- server._user_may_access_runtime_profile(self.user_b, self.runtime_a, self.workspace_id)
73
- )
74
- self.assertTrue(
75
- server._user_may_access_runtime_profile(self.user_a, self.runtime_a, self.workspace_id)
76
- )
77
-
78
- def test_owner_can_access_peer_runtime_in_trusted_team(self) -> None:
79
- with patch.object(server, "DEPLOYMENT_MODE", "trusted_team"):
80
- self.assertTrue(
81
- server._user_may_access_runtime_profile(self.user_a, self.runtime_b, self.workspace_id)
82
- )
83
- self.assertFalse(
84
- server._user_may_access_runtime_profile(self.user_b, self.runtime_a, self.workspace_id)
85
- )
86
-
87
- def test_gateway_exec_blocks_cross_profile_env_cat(self) -> None:
88
- victim_env = f"/opt/data/profiles/{self.runtime_a}/.env"
89
- with patch.object(server, "DEPLOYMENT_MODE", "trusted_team"):
90
- self.assertTrue(
91
- server._exec_targets_runtime_profile_secrets(["sh", "-lc", f"cat {victim_env}"])
92
- )
93
-
94
- def test_gateway_exec_denies_foreign_runtime_profile(self) -> None:
95
- with patch.object(server, "DEPLOYMENT_MODE", "public_multi_user"), patch.object(
96
- server, "resolve_validated_profile", return_value=self.runtime_a
97
- ), patch.object(server, "_gateway_exec") as gateway_exec:
98
- out = server.hermes_gateway_exec(
99
- "/help",
100
- self.runtime_a,
101
- user_id=self.user_b,
102
- workspace_id=self.workspace_id,
103
- )
104
- gateway_exec.assert_not_called()
105
- self.assertFalse(out.get("ok"))
106
- self.assertEqual(out.get("error"), "profile_access_denied")
107
-
108
- def test_docker_exec_guard_returns_blocked_without_running(self) -> None:
109
- victim_env = f"/opt/data/profiles/{self.runtime_b}/.env"
110
- with patch.object(server, "DEPLOYMENT_MODE", "trusted_team"), patch.object(
111
- server, "_docker_request"
112
- ) as docker_request:
113
- code, out = server._docker_exec("workframe-gateway", ["cat", victim_env])
114
- docker_request.assert_not_called()
115
- self.assertEqual(code, 1)
116
- self.assertIn("blocked", out)
117
-
118
- def test_public_mode_toolsets_include_terminal(self) -> None:
119
- with patch.object(server, "DEPLOYMENT_MODE", "public_multi_user"):
120
- self.assertEqual(server._chat_toolsets_for_profile("architect"), ("hermes-cli", "terminal"))
121
-
122
- def test_public_mode_ensure_toolsets_enables_terminal(self) -> None:
123
- prof_dir = server._profile_dir("architect")
124
- prof_dir.mkdir(parents=True, exist_ok=True)
125
- cfg_path = prof_dir / "config.yaml"
126
- cfg_path.write_text("toolsets: []\n", encoding="utf-8")
127
- with patch.object(server, "DEPLOYMENT_MODE", "public_multi_user"):
128
- server._ensure_profile_toolsets("architect")
129
- text = cfg_path.read_text(encoding="utf-8")
130
- self.assertIn("hermes-cli", text)
131
- self.assertIn("terminal", text)
132
-
133
-
134
- if __name__ == "__main__":
135
- unittest.main()