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.
- package/LICENSE +201 -201
- package/NOTICE +12 -12
- package/README.md +8 -92
- package/SECURITY.md +38 -40
- package/bin/workframe.js +329 -329
- package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +1 -1
- package/docs/workspace-instructions/WORKFRAME_ROUTING.md +8 -8
- package/package.json +3 -6
- package/profiles/architect/AGENTS.md +29 -29
- package/profiles/architect/SOUL.md +2 -2
- package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -27
- package/profiles/designer/AGENTS.md +26 -26
- package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -27
- package/profiles/dev/AGENTS.md +28 -28
- package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -27
- package/profiles/docs/AGENTS.md +27 -27
- package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -27
- package/profiles/research/AGENTS.md +26 -26
- package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -27
- package/profiles/visionary/AGENTS.md +25 -25
- package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -27
- package/profiles/workframe-agent/AGENTS.md +37 -37
- package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -85
- package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -58
- package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -54
- package/rules/workspace-README.md +5 -5
- package/scripts/bundle-workframe-ui.mjs +3 -3
- package/scripts/ensure-compose-host-paths.mjs +51 -51
- package/scripts/lib/install-identity.mjs +212 -212
- package/scripts/set-compose-public-url.mjs +92 -92
- package/scripts/sync-canonical-to-package.mjs +27 -9
- package/shared/WORKFRAME_AGENT_LIBRARY.md +17 -17
- package/shared/WORKFRAME_AGENT_OPERATIONS.md +15 -15
- package/shared/WORKFRAME_AGENT_PACKS.json +18 -18
- package/shared/WORKFRAME_AGENT_PACKS.yaml +8 -8
- package/shared/WORKFRAME_SKILL_CURATION.md +4 -4
- package/workframe-api/README.md +26 -28
- package/workframe-api/action_proxy.py +131 -131
- package/workframe-api/auth_rate_limit.py +49 -49
- package/workframe-api/credential_vault.py +445 -445
- package/workframe-api/data/avatar-catalog.json +41 -41
- package/workframe-api/email_sender.py +220 -220
- package/workframe-api/google_auth.py +90 -90
- package/workframe-api/install_api.py +359 -359
- package/workframe-api/internal_proxy_auth.py +150 -150
- package/workframe-api/llm_proxy.py +277 -277
- package/workframe-api/oidc_jwt.py +108 -108
- package/workframe-api/package.json +12 -13
- package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
- package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
- package/workframe-api/requirements.txt +2 -2
- package/workframe-api/site_meta.py +271 -271
- package/workframe-api/stack_config.py +427 -427
- package/workframe-api/time-bind-chat.py +99 -99
- package/workframe-api/turn_credentials.py +226 -226
- package/workframe-api/updates.py +417 -417
- package/workframe-api/vault_kek.py +159 -159
- package/workframe-api/zk_auth.py +633 -633
- package/workframe-supervisor/Dockerfile +11 -11
- package/workframe-supervisor/server.py +787 -787
- package/workframe-ui/docker/nginx.conf +85 -85
- package/workframe-ui/public/assets/{arc-CBDYvkAF.js → arc-COAT3laO.js} +1 -1
- package/workframe-ui/public/assets/architecture-7EHR7CIX-DUyH3hWG.js +1 -0
- package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-XnBRKeW0.js → architectureDiagram-3BPJPVTR-BFjWV24l.js} +1 -1
- package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-VYHUfVhd.js → blockDiagram-GPEHLZMM-DSQLPfrj.js} +1 -1
- package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BTjUcJpm.js → c4Diagram-AAUBKEIU-DKEHv1t2.js} +1 -1
- package/workframe-ui/public/assets/channel-g7r_RGaY.js +1 -0
- package/workframe-ui/public/assets/{chunk-2J33WTMH-w7uu7R-b.js → chunk-2J33WTMH-DHZg-DUi.js} +1 -1
- package/workframe-ui/public/assets/{chunk-3OPIFGDE-Cb9LtnDX.js → chunk-3OPIFGDE-BB-OYTfp.js} +1 -1
- package/workframe-ui/public/assets/{chunk-4BX2VUAB-DiQ-qCwH.js → chunk-4BX2VUAB-C93q0YIm.js} +1 -1
- package/workframe-ui/public/assets/{chunk-55IACEB6-C-mLFr7z.js → chunk-55IACEB6-MAYniqik.js} +1 -1
- package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DOesfiCI.js → chunk-5ZQYHXKU-ChgN6YJs.js} +1 -1
- package/workframe-ui/public/assets/{chunk-727SXJPM-BJ3oBZuz.js → chunk-727SXJPM-B_FYwdAv.js} +1 -1
- package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CCA6xpGs.js → chunk-AQP2D5EJ-1_Hw_h1A.js} +1 -1
- package/workframe-ui/public/assets/{chunk-BSJP7CBP-a0cMNFb2.js → chunk-BSJP7CBP-CFiDQ1Rv.js} +1 -1
- package/workframe-ui/public/assets/{chunk-CSCIHK7Q-kuqN8EIY.js → chunk-CSCIHK7Q-DZ9UMTlB.js} +1 -1
- package/workframe-ui/public/assets/{chunk-FMBD7UC4-DyPgYHCg.js → chunk-FMBD7UC4-DlMlyFgw.js} +1 -1
- package/workframe-ui/public/assets/{chunk-KSCS5N6A-CdUuvR0V.js → chunk-KSCS5N6A-DHXtQ_Hf.js} +1 -1
- package/workframe-ui/public/assets/{chunk-L5ZTLDWV-Dq9NoWmK.js → chunk-L5ZTLDWV-CuQzg-QG.js} +1 -1
- package/workframe-ui/public/assets/{chunk-LZXEDZCA-p74rddlO.js → chunk-LZXEDZCA-BHzjzCGg.js} +2 -2
- package/workframe-ui/public/assets/{chunk-ND2GUHAM-DBD2u1Gz.js → chunk-ND2GUHAM-DHXx05n2.js} +1 -1
- package/workframe-ui/public/assets/{chunk-NZK2D7GU-BeIeYFnd.js → chunk-NZK2D7GU-CV5pmDM_.js} +1 -1
- package/workframe-ui/public/assets/{chunk-O5CBEL6O-ClHc56ib.js → chunk-O5CBEL6O-6tkCHxsV.js} +1 -1
- package/workframe-ui/public/assets/chunk-QZHKN3VN-C5UQehWY.js +1 -0
- package/workframe-ui/public/assets/chunk-WU5MYG2G-DhWllrI8.js +1 -0
- package/workframe-ui/public/assets/{chunk-XPW4576I-EFr8R_1p.js → chunk-XPW4576I-BClwIiCp.js} +1 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BBM_8T8E.js +1 -0
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BBM_8T8E.js +1 -0
- package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-C7aPBODd.js → cose-bilkent-S5V4N54A-DOrGV6DQ.js} +1 -1
- package/workframe-ui/public/assets/{dagre-BM42HDAG-BdU1Rv-H.js → dagre-BM42HDAG-DXTPvJkX.js} +1 -1
- package/workframe-ui/public/assets/{diagram-2AECGRRQ-DWowSo85.js → diagram-2AECGRRQ-xX_v-pbf.js} +1 -1
- package/workframe-ui/public/assets/{diagram-5GNKFQAL-MnxBbceO.js → diagram-5GNKFQAL-Cd2pXbBe.js} +1 -1
- package/workframe-ui/public/assets/{diagram-KO2AKTUF-DQaLRXFf.js → diagram-KO2AKTUF-Df3XvUtk.js} +1 -1
- package/workframe-ui/public/assets/{diagram-LMA3HP47-CQaBud9k.js → diagram-LMA3HP47-CsijIPaD.js} +1 -1
- package/workframe-ui/public/assets/{diagram-OG6HWLK6-D8bAXbY9.js → diagram-OG6HWLK6-aq5fmfHd.js} +1 -1
- package/workframe-ui/public/assets/{dist-DGpTLHr_.js → dist-D1c0mkbB.js} +1 -1
- package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-1E-xSvBK.js → erDiagram-TEJ5UH35-DnFysVRY.js} +1 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-Ci8mdb44.js +1 -0
- package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CgOVD5hu.js → flowDiagram-I6XJVG4X-C6Ebi3su.js} +1 -1
- package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-JFYAIauo.js → ganttDiagram-6RSMTGT7-BQXQtUpa.js} +1 -1
- package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-B9REenIl.js → gitGraph-WXDBUCRP-Dt0zIs_M.js} +1 -1
- package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BQ7NcMSn.js → gitGraphDiagram-PVQCEYII-BF8gHzRn.js} +1 -1
- package/workframe-ui/public/assets/index-DpoUZAxh.css +1 -0
- package/workframe-ui/public/assets/{index-Dnw6vjqb.js → index-lRpzpNPT.js} +2 -2
- package/workframe-ui/public/assets/{info-J43DQDTF-CL6-eTjH.js → info-J43DQDTF-CSmszQJT.js} +1 -1
- package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-LJTODW4W.js → infoDiagram-5YYISTIA-CVTKGW6p.js} +1 -1
- package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-bchrQVuo.js → ishikawaDiagram-YF4QCWOH-Z8pT09Lv.js} +1 -1
- package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-DkrvYuxP.js → journeyDiagram-JHISSGLW-r3wD68_T.js} +1 -1
- package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-DFRbj0IG.js → kanban-definition-UN3LZRKU-Il8VglqN.js} +1 -1
- package/workframe-ui/public/assets/{line-Vd48P7-O.js → line-oyjpfz2A.js} +1 -1
- package/workframe-ui/public/assets/{linear-Ckizh2G7.js → linear-Cf7p5tVp.js} +1 -1
- package/workframe-ui/public/assets/{mermaid-parser.core-Bkimsnqj.js → mermaid-parser.core-YmbZ-AfY.js} +2 -2
- package/workframe-ui/public/assets/{mermaid.core-x0TvVuPo.js → mermaid.core-BFdCAqCo.js} +3 -3
- package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-6ykAFPEz.js → mindmap-definition-RKZ34NQL-Cy2iCtEl.js} +1 -1
- package/workframe-ui/public/assets/{packet-YPE3B663-Dw3xgMDt.js → packet-YPE3B663-DwOBZL6K.js} +1 -1
- package/workframe-ui/public/assets/{pie-LRSECV5Y-DATysawG.js → pie-LRSECV5Y-04PPhnKK.js} +1 -1
- package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-SJKD1S0S.js → pieDiagram-4H26LBE5-LxIpgHqi.js} +1 -1
- package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-BrYDZX8q.js → quadrantDiagram-W4KKPZXB-0nBYfYm4.js} +1 -1
- package/workframe-ui/public/assets/{radar-GUYGQ44K-BmWYPCds.js → radar-GUYGQ44K-D2-vBqps.js} +1 -1
- package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-DwL9Mc8e.js → requirementDiagram-4Y6WPE33-DbuU0nlu.js} +1 -1
- package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DYIFsL8h.js → sankeyDiagram-5OEKKPKP-B2hQ6B2x.js} +1 -1
- package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-0-FPkFk8.js → sequenceDiagram-3UESZ5HK-BBrU30e1.js} +1 -1
- package/workframe-ui/public/assets/{src-B_od6b6h.js → src-BJEDmV70.js} +1 -1
- package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BQCiBk6u.js → stateDiagram-AJRCARHV-7FGO4kkH.js} +1 -1
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DLTSizMg.js +1 -0
- package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DS3tFcXj.js → timeline-definition-PNZ67QCA-ptDm4rCN.js} +1 -1
- package/workframe-ui/public/assets/{treeView-BLDUP644-DSyUCKLY.js → treeView-BLDUP644-CS6Z-0q8.js} +1 -1
- package/workframe-ui/public/assets/{treemap-LRROVOQU-CEZaNh5Y.js → treemap-LRROVOQU-DqV4Y2VA.js} +1 -1
- package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-CD-Vc9NF.js → vennDiagram-CIIHVFJN-C0UrZJYt.js} +1 -1
- package/workframe-ui/public/assets/{wardley-L42UT6IY-Drq5w1Mc.js → wardley-L42UT6IY-bNDN3_Sa.js} +1 -1
- package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DouXDJoF.js → wardleyDiagram-YWT4CUSO-jWiJsefM.js} +1 -1
- package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DDf_Lol5.js → xychartDiagram-2RQKCTM6-Dsh_fLCy.js} +1 -1
- package/workframe-ui/public/favicon.svg +7 -7
- package/workframe-ui/public/index.html +50 -50
- package/workframe-ui/public/workframe-config.json +3 -3
- package/scripts/security_audit.py +0 -156
- package/scripts/test-scaffold.mjs +0 -390
- package/workframe-api/tests/__init__.py +0 -0
- package/workframe-api/tests/db_setup.py +0 -13
- package/workframe-api/tests/test_admin_updates_gated.py +0 -30
- package/workframe-api/tests/test_agent_dm_bootstrap.py +0 -196
- package/workframe-api/tests/test_agent_profile_sync.py +0 -76
- package/workframe-api/tests/test_auth_email.py +0 -222
- package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +0 -99
- package/workframe-api/tests/test_auth_rate_limit.py +0 -19
- package/workframe-api/tests/test_avatar_resolve.py +0 -77
- package/workframe-api/tests/test_child_soul_template.py +0 -71
- package/workframe-api/tests/test_credential_canary.py +0 -135
- package/workframe-api/tests/test_credential_isolation.py +0 -448
- package/workframe-api/tests/test_credential_resolution.py +0 -206
- package/workframe-api/tests/test_device_oauth.py +0 -108
- package/workframe-api/tests/test_doctor_repair.py +0 -103
- package/workframe-api/tests/test_ensure_profile_api.py +0 -77
- package/workframe-api/tests/test_gateway_compose_security.py +0 -136
- package/workframe-api/tests/test_install_secure_host.py +0 -39
- package/workframe-api/tests/test_internal_proxy_auth.py +0 -125
- package/workframe-api/tests/test_invite_runtime_bootstrap.py +0 -72
- package/workframe-api/tests/test_kanban_delegation.py +0 -185
- package/workframe-api/tests/test_llm_proxy.py +0 -155
- package/workframe-api/tests/test_login_access_policy.py +0 -183
- package/workframe-api/tests/test_mvp_model_bootstrap.py +0 -75
- package/workframe-api/tests/test_onboarding_bootstrap.py +0 -248
- package/workframe-api/tests/test_platform_auth.py +0 -47
- package/workframe-api/tests/test_profile_config_path.py +0 -56
- package/workframe-api/tests/test_profile_config_yaml_repair.py +0 -63
- package/workframe-api/tests/test_profile_create.py +0 -72
- package/workframe-api/tests/test_profile_identity_overlay.py +0 -61
- package/workframe-api/tests/test_profile_install_health.py +0 -45
- package/workframe-api/tests/test_profile_secret_policy.py +0 -57
- package/workframe-api/tests/test_profile_workspace_cwd.py +0 -34
- package/workframe-api/tests/test_provider_bootstrap.py +0 -75
- package/workframe-api/tests/test_provider_connect.py +0 -54
- package/workframe-api/tests/test_room_crud.py +0 -192
- package/workframe-api/tests/test_room_tenancy.py +0 -701
- package/workframe-api/tests/test_runtime_identity_backfill.py +0 -34
- package/workframe-api/tests/test_site_meta.py +0 -81
- package/workframe-api/tests/test_soul_stub.py +0 -42
- package/workframe-api/tests/test_space_member_sync.py +0 -99
- package/workframe-api/tests/test_stripe_stack_config.py +0 -37
- package/workframe-api/tests/test_supervisor_lifecycle.py +0 -52
- package/workframe-api/tests/test_turn_credential_vault.py +0 -125
- package/workframe-api/tests/test_updates.py +0 -176
- package/workframe-api/tests/test_user_cohort.py +0 -113
- package/workframe-api/tests/test_vault_envelope.py +0 -110
- package/workframe-api/tests/test_workspace_members.py +0 -183
- package/workframe-api/tests/test_workspace_messaging_sync.py +0 -125
- package/workframe-api/tests/test_workspace_provider_list.py +0 -57
- package/workframe-supervisor/tests/test_exec_guard.py +0 -42
- package/workframe-supervisor/tests/test_server_import.py +0 -21
- package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +0 -1
- package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +0 -1
- package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +0 -1
- package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +0 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +0 -1
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +0 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +0 -1
- package/workframe-ui/public/assets/index-DpAGxump.css +0 -1
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B89jAMFF.js +0 -1
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
"""Auto-bootstrap default workspace on API startup."""
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import tempfile
|
|
5
|
-
import unittest
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from unittest import mock
|
|
8
|
-
|
|
9
|
-
import server
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class OnboardingBootstrapTests(unittest.TestCase):
|
|
13
|
-
def setUp(self) -> None:
|
|
14
|
-
self.tmp = tempfile.TemporaryDirectory()
|
|
15
|
-
self.addCleanup(self.tmp.cleanup)
|
|
16
|
-
data = Path(self.tmp.name) / "data"
|
|
17
|
-
data.mkdir()
|
|
18
|
-
self.patches = [
|
|
19
|
-
mock.patch.object(server, "DATA_DIR", data),
|
|
20
|
-
mock.patch.object(server, "WORKSPACE", Path(self.tmp.name) / "Files"),
|
|
21
|
-
mock.patch.object(server, "_workframe_db_path", return_value=data / "workframe.db"),
|
|
22
|
-
mock.patch.dict(os.environ, {"WORKFRAME_PROJECT": "Workframe", "WORKFRAME_NATIVE_PROFILE": "workframe-agent"}, clear=False),
|
|
23
|
-
]
|
|
24
|
-
for patch in self.patches:
|
|
25
|
-
patch.start()
|
|
26
|
-
self.addCleanup(patch.stop)
|
|
27
|
-
server._ensure_workframe_db_schema()
|
|
28
|
-
|
|
29
|
-
def test_ensure_default_workspace_idempotent(self) -> None:
|
|
30
|
-
server._ensure_default_workspace()
|
|
31
|
-
server._ensure_default_workspace()
|
|
32
|
-
conn = server._workframe_db()
|
|
33
|
-
ws = conn.execute("SELECT slug, display_name FROM workspaces WHERE slug='default'").fetchone()
|
|
34
|
-
agents = conn.execute("SELECT slug FROM agent_profiles WHERE deleted_at IS NULL ORDER BY slug").fetchall()
|
|
35
|
-
rooms = conn.execute("SELECT slug FROM rooms WHERE deleted_at IS NULL").fetchall()
|
|
36
|
-
conn.close()
|
|
37
|
-
self.assertEqual(ws["slug"], "default")
|
|
38
|
-
self.assertEqual(ws["display_name"], "Workframe")
|
|
39
|
-
self.assertEqual([r[0] for r in agents], ["workframe-agent"])
|
|
40
|
-
room_slugs = [r[0] for r in rooms]
|
|
41
|
-
self.assertIn("general", room_slugs)
|
|
42
|
-
|
|
43
|
-
def test_sync_workspace_home_room_mirrors_branding(self) -> None:
|
|
44
|
-
server._ensure_default_workspace()
|
|
45
|
-
conn = server._workframe_db()
|
|
46
|
-
ws = conn.execute("SELECT id FROM workspaces WHERE slug='default'").fetchone()
|
|
47
|
-
ws_id = str(ws["id"])
|
|
48
|
-
settings = json.dumps({"tagline": "Let's go!"}, sort_keys=True)
|
|
49
|
-
conn.execute(
|
|
50
|
-
"UPDATE workspaces SET display_name = ?, settings_json = ?, updated_at = ? WHERE id = ?",
|
|
51
|
-
("My Business", settings, "2", ws_id),
|
|
52
|
-
)
|
|
53
|
-
server._sync_workspace_home_room(conn, ws_id)
|
|
54
|
-
conn.commit()
|
|
55
|
-
room = conn.execute(
|
|
56
|
-
"SELECT name, topic FROM rooms WHERE workspace_id = ? AND slug = 'general'",
|
|
57
|
-
(ws_id,),
|
|
58
|
-
).fetchone()
|
|
59
|
-
conn.close()
|
|
60
|
-
self.assertEqual(room["name"], "My Business")
|
|
61
|
-
self.assertEqual(room["topic"], "Let's go!")
|
|
62
|
-
server._ensure_default_workspace()
|
|
63
|
-
owner = "owner-user-id"
|
|
64
|
-
conn = server._workframe_db()
|
|
65
|
-
now = str(int(__import__("time").time()))
|
|
66
|
-
ws = conn.execute("SELECT id FROM workspaces WHERE slug='default'").fetchone()
|
|
67
|
-
conn.execute(
|
|
68
|
-
"INSERT INTO users (id, email, display_name, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
69
|
-
(owner, "owner@test.com", "Owner", "user", "active", now, now),
|
|
70
|
-
)
|
|
71
|
-
conn.execute(
|
|
72
|
-
"INSERT INTO workspace_memberships (id, workspace_id, user_id, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
73
|
-
("m1", ws["id"], owner, "owner", "active", now, now),
|
|
74
|
-
)
|
|
75
|
-
conn.commit()
|
|
76
|
-
conn.close()
|
|
77
|
-
with mock.patch.object(server, "_install_complete", return_value=False):
|
|
78
|
-
payload = server._onboarding_payload(owner)
|
|
79
|
-
self.assertFalse(payload["complete"])
|
|
80
|
-
self.assertEqual(payload["step"], "admin_integrations")
|
|
81
|
-
self.assertEqual(payload["credential_mode"], "byok")
|
|
82
|
-
|
|
83
|
-
def test_onboarding_complete_when_install_finished(self) -> None:
|
|
84
|
-
server._ensure_default_workspace()
|
|
85
|
-
owner = "owner-install-done"
|
|
86
|
-
conn = server._workframe_db()
|
|
87
|
-
now = str(int(__import__("time").time()))
|
|
88
|
-
ws = conn.execute("SELECT id FROM workspaces WHERE slug='default'").fetchone()
|
|
89
|
-
conn.execute(
|
|
90
|
-
"INSERT INTO users (id, email, display_name, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
91
|
-
(owner, "done@test.com", "Owner", "user", "active", now, now),
|
|
92
|
-
)
|
|
93
|
-
conn.execute(
|
|
94
|
-
"INSERT INTO workspace_memberships (id, workspace_id, user_id, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
95
|
-
("m-done", ws["id"], owner, "owner", "active", now, now),
|
|
96
|
-
)
|
|
97
|
-
conn.commit()
|
|
98
|
-
conn.close()
|
|
99
|
-
with mock.patch.object(server, "_install_complete", return_value=True), mock.patch.object(
|
|
100
|
-
server, "_user_has_llm_provider", return_value=True
|
|
101
|
-
):
|
|
102
|
-
payload = server._onboarding_payload(owner)
|
|
103
|
-
self.assertTrue(payload["complete"])
|
|
104
|
-
self.assertEqual(payload["step"], "done")
|
|
105
|
-
|
|
106
|
-
def test_onboarding_workspace_provider_after_company_mode(self) -> None:
|
|
107
|
-
server._ensure_default_workspace()
|
|
108
|
-
owner = "owner-workspace-keys"
|
|
109
|
-
conn = server._workframe_db()
|
|
110
|
-
now = str(int(__import__("time").time()))
|
|
111
|
-
ws = conn.execute("SELECT id FROM workspaces WHERE slug='default'").fetchone()
|
|
112
|
-
conn.execute(
|
|
113
|
-
"INSERT INTO users (id, email, display_name, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
114
|
-
(owner, "owner2@test.com", "Owner", "user", "active", now, now),
|
|
115
|
-
)
|
|
116
|
-
conn.execute(
|
|
117
|
-
"INSERT INTO workspace_memberships (id, workspace_id, user_id, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
118
|
-
("m2", ws["id"], owner, "owner", "active", now, now),
|
|
119
|
-
)
|
|
120
|
-
settings = '{"credential_mode":"workspace","admin_integrations_done":true,"admin_onboarding_done":true}'
|
|
121
|
-
conn.execute(
|
|
122
|
-
"UPDATE workspaces SET settings_json = ? WHERE id = ?",
|
|
123
|
-
(settings, ws["id"]),
|
|
124
|
-
)
|
|
125
|
-
conn.commit()
|
|
126
|
-
conn.close()
|
|
127
|
-
payload = server._onboarding_payload(owner)
|
|
128
|
-
self.assertFalse(payload["complete"])
|
|
129
|
-
self.assertEqual(payload["step"], "workspace_provider")
|
|
130
|
-
|
|
131
|
-
def test_member_can_mark_integrations_done_during_install(self) -> None:
|
|
132
|
-
server._ensure_default_workspace()
|
|
133
|
-
user_id = "installer-member"
|
|
134
|
-
conn = server._workframe_db()
|
|
135
|
-
now = str(int(__import__("time").time()))
|
|
136
|
-
ws = conn.execute("SELECT id FROM workspaces WHERE slug='default'").fetchone()
|
|
137
|
-
conn.execute(
|
|
138
|
-
"INSERT INTO users (id, email, display_name, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
139
|
-
(user_id, "installer@test.com", "Installer", "user", "active", now, now),
|
|
140
|
-
)
|
|
141
|
-
conn.execute(
|
|
142
|
-
"INSERT INTO workspace_memberships (id, workspace_id, user_id, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
143
|
-
("m-install", ws["id"], user_id, "member", "active", now, now),
|
|
144
|
-
)
|
|
145
|
-
conn.commit()
|
|
146
|
-
conn.close()
|
|
147
|
-
with mock.patch.object(server, "_install_window_open", return_value=True):
|
|
148
|
-
status, payload = server._patch_workspace_integrations(
|
|
149
|
-
str(ws["id"]),
|
|
150
|
-
{"admin_integrations_done": True},
|
|
151
|
-
user_id,
|
|
152
|
-
)
|
|
153
|
-
self.assertEqual(status, 200)
|
|
154
|
-
self.assertTrue(payload.get("ok"))
|
|
155
|
-
conn = server._workframe_db()
|
|
156
|
-
settings = server._parse_workspace_settings(
|
|
157
|
-
conn.execute("SELECT * FROM workspaces WHERE id = ?", (ws["id"],)).fetchone()
|
|
158
|
-
)
|
|
159
|
-
conn.close()
|
|
160
|
-
self.assertTrue(settings.get("admin_integrations_done"))
|
|
161
|
-
|
|
162
|
-
def test_owner_id_repairs_stale_member_role(self) -> None:
|
|
163
|
-
server._ensure_default_workspace()
|
|
164
|
-
user_id = "stale-owner"
|
|
165
|
-
conn = server._workframe_db()
|
|
166
|
-
now = str(int(__import__("time").time()))
|
|
167
|
-
ws = conn.execute("SELECT id FROM workspaces WHERE slug='default'").fetchone()
|
|
168
|
-
conn.execute(
|
|
169
|
-
"UPDATE workspaces SET owner_id = ? WHERE id = ?",
|
|
170
|
-
(user_id, ws["id"]),
|
|
171
|
-
)
|
|
172
|
-
conn.execute(
|
|
173
|
-
"INSERT INTO users (id, email, display_name, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
174
|
-
(user_id, "stale@test.com", "Stale", "user", "active", now, now),
|
|
175
|
-
)
|
|
176
|
-
conn.execute(
|
|
177
|
-
"INSERT INTO workspace_memberships (id, workspace_id, user_id, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
178
|
-
("m-stale", ws["id"], user_id, "member", "active", now, now),
|
|
179
|
-
)
|
|
180
|
-
conn.commit()
|
|
181
|
-
conn.close()
|
|
182
|
-
status, payload = server._patch_workspace_integrations(
|
|
183
|
-
str(ws["id"]),
|
|
184
|
-
{"admin_integrations_done": True},
|
|
185
|
-
user_id,
|
|
186
|
-
)
|
|
187
|
-
self.assertEqual(status, 200)
|
|
188
|
-
self.assertTrue(payload.get("ok"))
|
|
189
|
-
conn = server._workframe_db()
|
|
190
|
-
role = conn.execute(
|
|
191
|
-
"SELECT role FROM workspace_memberships WHERE workspace_id = ? AND user_id = ?",
|
|
192
|
-
(ws["id"], user_id),
|
|
193
|
-
).fetchone()
|
|
194
|
-
conn.close()
|
|
195
|
-
self.assertEqual(role["role"], "owner")
|
|
196
|
-
|
|
197
|
-
def test_patch_workspace_tagline_uses_existing_row(self) -> None:
|
|
198
|
-
server._ensure_default_workspace()
|
|
199
|
-
owner = "owner-tagline"
|
|
200
|
-
conn = server._workframe_db()
|
|
201
|
-
now = str(int(__import__("time").time()))
|
|
202
|
-
ws = conn.execute("SELECT id FROM workspaces WHERE slug='default'").fetchone()
|
|
203
|
-
conn.execute(
|
|
204
|
-
"INSERT INTO users (id, email, display_name, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
205
|
-
(owner, "tagline@test.com", "Owner", "user", "active", now, now),
|
|
206
|
-
)
|
|
207
|
-
conn.execute(
|
|
208
|
-
"INSERT INTO workspace_memberships (id, workspace_id, user_id, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
|
|
209
|
-
("m-tagline", ws["id"], owner, "owner", "active", now, now),
|
|
210
|
-
)
|
|
211
|
-
conn.commit()
|
|
212
|
-
conn.close()
|
|
213
|
-
status, payload = server._patch_workspace(
|
|
214
|
-
str(ws["id"]),
|
|
215
|
-
{"display_name": "Acme", "tagline": "We ship", "description": "Mission"},
|
|
216
|
-
owner,
|
|
217
|
-
)
|
|
218
|
-
self.assertEqual(status, 200)
|
|
219
|
-
self.assertTrue(payload.get("ok"))
|
|
220
|
-
self.assertEqual(payload["workspace"]["tagline"], "We ship")
|
|
221
|
-
self.assertEqual(payload["workspace"]["display_name"], "Acme")
|
|
222
|
-
conn = server._workframe_db()
|
|
223
|
-
raw = conn.execute(
|
|
224
|
-
"SELECT settings_json FROM workspaces WHERE id = ?",
|
|
225
|
-
(ws["id"],),
|
|
226
|
-
).fetchone()
|
|
227
|
-
conn.close()
|
|
228
|
-
settings = __import__("json").loads(raw["settings_json"] or "{}")
|
|
229
|
-
self.assertEqual(settings.get("tagline"), "We ship")
|
|
230
|
-
|
|
231
|
-
def test_files_tree_root_uses_workspace_display_name(self) -> None:
|
|
232
|
-
server._ensure_default_workspace()
|
|
233
|
-
conn = server._workframe_db()
|
|
234
|
-
ws = conn.execute("SELECT id FROM workspaces WHERE slug='default'").fetchone()
|
|
235
|
-
conn.execute(
|
|
236
|
-
"UPDATE workspaces SET display_name = ?, updated_at = ? WHERE id = ?",
|
|
237
|
-
("Acme Corp", "3", ws["id"]),
|
|
238
|
-
)
|
|
239
|
-
conn.commit()
|
|
240
|
-
conn.close()
|
|
241
|
-
with mock.patch.dict(os.environ, {"WORKFRAME_PROJECT": "Shmorkframe"}, clear=False):
|
|
242
|
-
self.assertEqual(server._files_tree_root_name(), "Acme Corp")
|
|
243
|
-
tree = server.files_tree()
|
|
244
|
-
self.assertEqual(tree["name"], "Acme Corp")
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if __name__ == "__main__":
|
|
248
|
-
unittest.main()
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import hashlib
|
|
2
|
-
import hmac
|
|
3
|
-
import unittest
|
|
4
|
-
|
|
5
|
-
import platform_auth
|
|
6
|
-
import stack_config
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class PlatformAuthTests(unittest.TestCase):
|
|
10
|
-
def test_verify_telegram_login_accepts_valid_hash(self) -> None:
|
|
11
|
-
bot_token = "123456:ABC-DEF"
|
|
12
|
-
payload = {
|
|
13
|
-
"id": "424242",
|
|
14
|
-
"first_name": "Test",
|
|
15
|
-
"username": "tester",
|
|
16
|
-
"auth_date": "1700000000",
|
|
17
|
-
}
|
|
18
|
-
check_line = "\n".join(f"{k}={payload[k]}" for k in sorted(payload))
|
|
19
|
-
secret = hashlib.sha256(bot_token.encode("utf-8")).digest()
|
|
20
|
-
payload["hash"] = hmac.new(secret, check_line.encode("utf-8"), hashlib.sha256).hexdigest()
|
|
21
|
-
|
|
22
|
-
old = stack_config._read_raw
|
|
23
|
-
stack_config._read_raw = lambda: {
|
|
24
|
-
"telegram_login": {"bot_username": "wfbot", "bot_token": bot_token},
|
|
25
|
-
}
|
|
26
|
-
try:
|
|
27
|
-
result = platform_auth.verify_telegram_login(payload)
|
|
28
|
-
finally:
|
|
29
|
-
stack_config._read_raw = old
|
|
30
|
-
|
|
31
|
-
self.assertTrue(result.get("ok"))
|
|
32
|
-
self.assertEqual(result.get("platform_ids", {}).get("telegram"), "424242")
|
|
33
|
-
|
|
34
|
-
def test_verify_telegram_login_rejects_bad_hash(self) -> None:
|
|
35
|
-
old = stack_config._read_raw
|
|
36
|
-
stack_config._read_raw = lambda: {
|
|
37
|
-
"telegram_login": {"bot_username": "wfbot", "bot_token": "1:2"},
|
|
38
|
-
}
|
|
39
|
-
try:
|
|
40
|
-
result = platform_auth.verify_telegram_login({"id": "1", "hash": "bad"})
|
|
41
|
-
finally:
|
|
42
|
-
stack_config._read_raw = old
|
|
43
|
-
self.assertFalse(result.get("ok"))
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if __name__ == "__main__":
|
|
47
|
-
unittest.main()
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"""Hermes:latest profiles use profile.yaml instead of config.yaml."""
|
|
2
|
-
import tempfile
|
|
3
|
-
import unittest
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from unittest import mock
|
|
6
|
-
|
|
7
|
-
import server
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ProfileConfigPathTests(unittest.TestCase):
|
|
11
|
-
def setUp(self) -> None:
|
|
12
|
-
self.tmp = tempfile.TemporaryDirectory()
|
|
13
|
-
self.addCleanup(self.tmp.cleanup)
|
|
14
|
-
self.hermes = Path(self.tmp.name) / "Agents"
|
|
15
|
-
profiles = self.hermes / "profiles" / "workframe-agent"
|
|
16
|
-
profiles.mkdir(parents=True)
|
|
17
|
-
(profiles / "profile.yaml").write_text("description: test\n", encoding="utf-8")
|
|
18
|
-
self.patch = mock.patch.object(server, "HERMES_DATA", self.hermes)
|
|
19
|
-
self.patch.start()
|
|
20
|
-
self.addCleanup(self.patch.stop)
|
|
21
|
-
|
|
22
|
-
def test_profile_config_path_prefers_either_yaml(self) -> None:
|
|
23
|
-
path = server._profile_config_path("workframe-agent")
|
|
24
|
-
self.assertIsNotNone(path)
|
|
25
|
-
self.assertEqual(path.name, "profile.yaml")
|
|
26
|
-
|
|
27
|
-
def test_runtime_profile_on_disk_accepts_profile_yaml(self) -> None:
|
|
28
|
-
self.assertTrue(server._runtime_profile_on_disk("workframe-agent"))
|
|
29
|
-
|
|
30
|
-
def test_inherit_runtime_profile_config_copies_template_yaml(self) -> None:
|
|
31
|
-
runtime = "u-test-user-workframe-agent"
|
|
32
|
-
runtime_dir = self.hermes / "profiles" / runtime
|
|
33
|
-
runtime_dir.mkdir(parents=True)
|
|
34
|
-
(runtime_dir / ".env").write_text("X=1\n", encoding="utf-8")
|
|
35
|
-
server._inherit_runtime_profile_config(runtime, "workframe-agent")
|
|
36
|
-
inherited = runtime_dir / "profile.yaml"
|
|
37
|
-
self.assertTrue(inherited.is_file())
|
|
38
|
-
self.assertIn("description:", inherited.read_text(encoding="utf-8"))
|
|
39
|
-
|
|
40
|
-
def test_configure_profile_api_writes_config_yaml_on_disk(self) -> None:
|
|
41
|
-
runtime = "z-test-runtime-agent"
|
|
42
|
-
runtime_dir = self.hermes / "profiles" / runtime
|
|
43
|
-
runtime_dir.mkdir(parents=True)
|
|
44
|
-
(runtime_dir / "config.yaml").write_text("platforms: {}\n", encoding="utf-8")
|
|
45
|
-
with mock.patch.object(server, "NATIVE_PROFILE", "workframe-agent"):
|
|
46
|
-
ok, out, port = server._configure_profile_api(runtime)
|
|
47
|
-
self.assertTrue(ok)
|
|
48
|
-
self.assertEqual(out, "ok")
|
|
49
|
-
self.assertGreater(port, 18610)
|
|
50
|
-
text = (runtime_dir / "config.yaml").read_text(encoding="utf-8")
|
|
51
|
-
self.assertIn("api_server", text)
|
|
52
|
-
self.assertIn("enabled: true", text)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if __name__ == "__main__":
|
|
56
|
-
unittest.main()
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"""Repair invalid scalar model: headers in profile config.yaml."""
|
|
2
|
-
import tempfile
|
|
3
|
-
import unittest
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from unittest import mock
|
|
6
|
-
|
|
7
|
-
import server
|
|
8
|
-
import yaml
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ProfileConfigYamlRepairTests(unittest.TestCase):
|
|
12
|
-
def test_fix_invalid_model_header_scalar_empty(self) -> None:
|
|
13
|
-
raw = "model: ''\n base_url: http://example/v1\n provider: custom\n"
|
|
14
|
-
fixed = server._fix_invalid_model_header(raw)
|
|
15
|
-
data = yaml.safe_load(fixed)
|
|
16
|
-
self.assertIsInstance(data.get("model"), dict)
|
|
17
|
-
self.assertEqual(data["model"]["base_url"], "http://example/v1")
|
|
18
|
-
|
|
19
|
-
def test_normalize_writes_repaired_file(self) -> None:
|
|
20
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
21
|
-
prof = "u-test-user-dev"
|
|
22
|
-
prof_dir = Path(tmp) / "profiles" / prof
|
|
23
|
-
prof_dir.mkdir(parents=True)
|
|
24
|
-
cfg = prof_dir / "config.yaml"
|
|
25
|
-
cfg.write_text(
|
|
26
|
-
"model: ''\n base_url: http://workframe-api:8080/internal/llm/openrouter/v1\n"
|
|
27
|
-
" provider: custom\n",
|
|
28
|
-
encoding="utf-8",
|
|
29
|
-
)
|
|
30
|
-
with mock.patch.object(server, "_profile_gateway_config_path", return_value=cfg):
|
|
31
|
-
server._normalize_profile_config_yaml(prof)
|
|
32
|
-
yaml.safe_load(cfg.read_text(encoding="utf-8"))
|
|
33
|
-
|
|
34
|
-
def test_scrub_orphan_top_level_list_lines(self) -> None:
|
|
35
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
36
|
-
prof = "u-test-user-dev"
|
|
37
|
-
prof_dir = Path(tmp) / "profiles" / prof
|
|
38
|
-
prof_dir.mkdir(parents=True)
|
|
39
|
-
cfg = prof_dir / "config.yaml"
|
|
40
|
-
cfg.write_text(
|
|
41
|
-
"model:\n default: openrouter/owl-alpha\n"
|
|
42
|
-
"- provider: openrouter\n"
|
|
43
|
-
"model:\n default: openrouter/nex-agi\n",
|
|
44
|
-
encoding="utf-8",
|
|
45
|
-
)
|
|
46
|
-
with mock.patch.object(server, "_profile_gateway_config_path", return_value=cfg), mock.patch.object(
|
|
47
|
-
server,
|
|
48
|
-
"_read_model_block",
|
|
49
|
-
return_value={
|
|
50
|
-
"default": "openrouter/owl-alpha",
|
|
51
|
-
"provider": "custom",
|
|
52
|
-
"base_url": "",
|
|
53
|
-
"fallback_chain": [],
|
|
54
|
-
},
|
|
55
|
-
):
|
|
56
|
-
server._normalize_profile_config_yaml(prof)
|
|
57
|
-
text = cfg.read_text(encoding="utf-8")
|
|
58
|
-
self.assertNotIn("- provider: openrouter\n", text)
|
|
59
|
-
yaml.safe_load(text)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if __name__ == "__main__":
|
|
63
|
-
unittest.main()
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from unittest.mock import patch
|
|
3
|
-
|
|
4
|
-
import server
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ProfileCreateTest(unittest.TestCase):
|
|
8
|
-
@patch.object(server, "_gateway_exec", return_value=(0, "created"))
|
|
9
|
-
@patch.object(server, "_patch_profile_gateway_run_script", return_value=(True, "ok"))
|
|
10
|
-
@patch.object(server, "_configure_profile_api", return_value=(True, "ok", 19001))
|
|
11
|
-
@patch.object(server, "_profile_dir")
|
|
12
|
-
def test_new_profile_uses_slug_not_route_validation(
|
|
13
|
-
self,
|
|
14
|
-
profile_dir,
|
|
15
|
-
configure_api,
|
|
16
|
-
patch_run,
|
|
17
|
-
gateway_exec,
|
|
18
|
-
) -> None:
|
|
19
|
-
profile_dir.return_value.exists.return_value = False
|
|
20
|
-
with patch.object(server, "resolve_validated_profile", return_value="workframe-agent") as resolve, patch.object(
|
|
21
|
-
server, "_ensure_workspace_agent_profile_row", return_value="agent-id"
|
|
22
|
-
), patch.object(server, "_upsert_agent_registry_row"), patch.object(
|
|
23
|
-
server, "_assign_agent_avatar", return_value={"avatar_id": "a1"}
|
|
24
|
-
), patch.object(server, "_strip_forbidden_child_skills", return_value=[]), patch.object(
|
|
25
|
-
server, "_install_child_base_artifacts", return_value=False
|
|
26
|
-
), patch.object(
|
|
27
|
-
server, "profile_gateway_lifecycle", return_value={"state": "running"}
|
|
28
|
-
):
|
|
29
|
-
result = server.profile_create("architect", description="Systems design")
|
|
30
|
-
resolve.assert_called()
|
|
31
|
-
self.assertEqual(result["profile"], "architect")
|
|
32
|
-
gateway_exec.assert_called_once()
|
|
33
|
-
prof, cmd = gateway_exec.call_args[0]
|
|
34
|
-
self.assertEqual(prof, server._primary_profile())
|
|
35
|
-
self.assertEqual(cmd[:3], ["profile", "create", "--clone-from"])
|
|
36
|
-
self.assertEqual(cmd[-1], "architect")
|
|
37
|
-
|
|
38
|
-
@patch.object(server, "_gateway_exec", return_value=(0, "created"))
|
|
39
|
-
@patch.object(server, "_patch_profile_gateway_run_script", return_value=(True, "ok"))
|
|
40
|
-
@patch.object(server, "_configure_profile_api", return_value=(True, "ok", 19001))
|
|
41
|
-
@patch.object(server, "_profile_dir")
|
|
42
|
-
@patch.object(server, "bootstrap_agent_dm_lane", return_value={"ok": True, "runtime": "u-user-architect"})
|
|
43
|
-
def test_new_profile_registers_route_before_bootstrap(
|
|
44
|
-
self,
|
|
45
|
-
bootstrap,
|
|
46
|
-
profile_dir,
|
|
47
|
-
configure_api,
|
|
48
|
-
patch_run,
|
|
49
|
-
gateway_exec,
|
|
50
|
-
) -> None:
|
|
51
|
-
profile_dir.return_value.exists.return_value = False
|
|
52
|
-
with patch.object(server, "resolve_validated_profile", return_value="workframe-agent"), patch.object(
|
|
53
|
-
server, "_register_profile_route", return_value=True
|
|
54
|
-
) as register, patch.object(
|
|
55
|
-
server, "_ensure_workspace_agent_profile_row", return_value="agent-id"
|
|
56
|
-
), patch.object(server, "_upsert_agent_registry_row"), patch.object(
|
|
57
|
-
server, "_assign_agent_avatar", return_value={"avatar_id": "a1"}
|
|
58
|
-
), patch.object(server, "_strip_forbidden_child_skills", return_value=[]), patch.object(
|
|
59
|
-
server, "_install_child_base_artifacts", return_value=False
|
|
60
|
-
), patch.object(server, "profile_gateway_lifecycle", return_value={"state": "running"}):
|
|
61
|
-
server.profile_create(
|
|
62
|
-
"elvis",
|
|
63
|
-
description="Custom agent",
|
|
64
|
-
user_id="user-1",
|
|
65
|
-
workspace_id="ws-1",
|
|
66
|
-
)
|
|
67
|
-
register.assert_called_once()
|
|
68
|
-
bootstrap.assert_called_once()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if __name__ == "__main__":
|
|
72
|
-
unittest.main()
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"""User identity overlays concatenate onto base SOUL — never replace it."""
|
|
2
|
-
import tempfile
|
|
3
|
-
import unittest
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from unittest import mock
|
|
6
|
-
|
|
7
|
-
import server
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ProfileIdentityOverlayTests(unittest.TestCase):
|
|
11
|
-
def test_user_soul_concatenates_on_base(self) -> None:
|
|
12
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
13
|
-
root = Path(tmp)
|
|
14
|
-
prof_dir = root / "profiles" / "elvis"
|
|
15
|
-
prof_dir.mkdir(parents=True)
|
|
16
|
-
(prof_dir / "SOUL.md").write_text(
|
|
17
|
-
"# Elvis\n\nBase Workframe child agent.\n" + ("operate hermes. " * 20),
|
|
18
|
-
encoding="utf-8",
|
|
19
|
-
)
|
|
20
|
-
agents = root / "workframe" / "agents.json"
|
|
21
|
-
agents.parent.mkdir(parents=True)
|
|
22
|
-
agents.write_text(
|
|
23
|
-
'{"version":1,"agents":{"elvis":{"profile":"elvis","user_soul":"Always cite sources.","display_name":"Elvis","role":"Rock legend"}}}',
|
|
24
|
-
encoding="utf-8",
|
|
25
|
-
)
|
|
26
|
-
with mock.patch.object(server, "_profile_dir", side_effect=lambda p: root / "profiles" / p), mock.patch.object(
|
|
27
|
-
server, "AGENTS_JSON", agents
|
|
28
|
-
), mock.patch.object(server, "_is_runtime_profile_slug", return_value=False):
|
|
29
|
-
text = server._profile_soul_text("elvis")
|
|
30
|
-
self.assertIn("Base Workframe child agent", text)
|
|
31
|
-
self.assertIn("## User identity", text)
|
|
32
|
-
self.assertIn("**Name:** Elvis", text)
|
|
33
|
-
self.assertIn("## Additional instructions", text)
|
|
34
|
-
self.assertIn("Always cite sources.", text)
|
|
35
|
-
|
|
36
|
-
def test_profile_soul_set_stores_child_overlay(self) -> None:
|
|
37
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
38
|
-
root = Path(tmp)
|
|
39
|
-
prof_dir = root / "profiles" / "dev"
|
|
40
|
-
prof_dir.mkdir(parents=True)
|
|
41
|
-
(prof_dir / "SOUL.md").write_text("# Dev\n\n" + ("x" * 80), encoding="utf-8")
|
|
42
|
-
agents = root / "workframe" / "agents.json"
|
|
43
|
-
agents.parent.mkdir(parents=True)
|
|
44
|
-
agents.write_text('{"version":1,"agents":{}}', encoding="utf-8")
|
|
45
|
-
with mock.patch.object(server, "_profile_dir", side_effect=lambda p: root / "profiles" / p), mock.patch.object(
|
|
46
|
-
server, "AGENTS_JSON", agents
|
|
47
|
-
), mock.patch.object(server, "ROUTES_JSON", root / "workframe" / "routes.json"), mock.patch.object(
|
|
48
|
-
server, "load_routes",
|
|
49
|
-
return_value={"routes": [{"profile": "dev"}]},
|
|
50
|
-
), mock.patch.object(server, "profile_exists", return_value=True), mock.patch.object(
|
|
51
|
-
server, "_is_native_profile", return_value=False
|
|
52
|
-
), mock.patch.object(server, "_is_runtime_profile_slug", return_value=False):
|
|
53
|
-
out = server.profile_soul_set("dev", "Ship small diffs.")
|
|
54
|
-
self.assertEqual(out.get("target"), "user_soul_overlay")
|
|
55
|
-
data = agents.read_text(encoding="utf-8")
|
|
56
|
-
self.assertIn("Ship small diffs.", data)
|
|
57
|
-
self.assertIn("# Dev", (prof_dir / "SOUL.md").read_text(encoding="utf-8"))
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if __name__ == "__main__":
|
|
61
|
-
unittest.main()
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"""Install-time profile gateway health policy — cold u-* start must not flake finish-setup."""
|
|
2
|
-
import importlib.util
|
|
3
|
-
import inspect
|
|
4
|
-
import unittest
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
ROOT = Path(__file__).resolve().parents[2]
|
|
8
|
-
API = ROOT / "workframe-api" / "server.py"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _load_api():
|
|
12
|
-
spec = importlib.util.spec_from_file_location("workframe_api", API)
|
|
13
|
-
mod = importlib.util.module_from_spec(spec)
|
|
14
|
-
assert spec and spec.loader
|
|
15
|
-
spec.loader.exec_module(mod)
|
|
16
|
-
return mod
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ProfileInstallHealthPolicyTest(unittest.TestCase):
|
|
20
|
-
def test_wait_default_covers_cold_runtime_start(self) -> None:
|
|
21
|
-
api = _load_api()
|
|
22
|
-
sig = inspect.signature(api._wait_profile_api_healthy)
|
|
23
|
-
self.assertEqual(sig.parameters["attempts"].default, 60)
|
|
24
|
-
self.assertGreaterEqual(sig.parameters["delay"].default * 60, 30.0)
|
|
25
|
-
|
|
26
|
-
def test_profile_health_uses_gateway_dns_not_container_exec(self) -> None:
|
|
27
|
-
api = _load_api()
|
|
28
|
-
src = inspect.getsource(api._profile_api_healthy)
|
|
29
|
-
self.assertIn("http://gateway:", src)
|
|
30
|
-
self.assertNotIn("_gateway_container_exec", src)
|
|
31
|
-
|
|
32
|
-
def test_wait_does_not_short_circuit_primary_without_probe(self) -> None:
|
|
33
|
-
api = _load_api()
|
|
34
|
-
src = inspect.getsource(api._wait_profile_api_healthy)
|
|
35
|
-
self.assertNotIn("_primary_profile() or _profile_api_healthy", src)
|
|
36
|
-
|
|
37
|
-
def test_bootstrap_lane_retries_gateway_start(self) -> None:
|
|
38
|
-
api = _load_api()
|
|
39
|
-
src = inspect.getsource(api.bootstrap_agent_dm_lane)
|
|
40
|
-
self.assertIn("for attempt in range(3)", src)
|
|
41
|
-
self.assertIn("ensure_profile_api(runtime", src)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if __name__ == "__main__":
|
|
45
|
-
unittest.main()
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
|
|
3
|
-
from profile_secret_policy import (
|
|
4
|
-
exec_blocked_for_profile,
|
|
5
|
-
is_secret_read_attempt,
|
|
6
|
-
touches_foreign_profile_secrets,
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ProfileSecretPolicyTests(unittest.TestCase):
|
|
11
|
-
def test_blocks_direct_secret_paths(self) -> None:
|
|
12
|
-
self.assertTrue(is_secret_read_attempt(["cat", "profiles/u-alice-dev/.env"]))
|
|
13
|
-
self.assertTrue(is_secret_read_attempt(["cat", "profiles/u-alice-dev/auth.json"]))
|
|
14
|
-
|
|
15
|
-
def test_blocks_wildcard_and_bypass_patterns(self) -> None:
|
|
16
|
-
self.assertTrue(is_secret_read_attempt("cat profiles/u-alice-dev/*"))
|
|
17
|
-
self.assertTrue(is_secret_read_attempt("cd profiles/u-x && cat .env"))
|
|
18
|
-
self.assertTrue(is_secret_read_attempt("cat profiles/u-x/.e''nv"))
|
|
19
|
-
self.assertTrue(is_secret_read_attempt("find profiles/u-x -exec cat {} +"))
|
|
20
|
-
self.assertTrue(is_secret_read_attempt("base64 profiles/u-x/.env"))
|
|
21
|
-
self.assertTrue(is_secret_read_attempt("tar -cf - profiles/u-x"))
|
|
22
|
-
|
|
23
|
-
def test_blocks_head_python_and_absolute_paths(self) -> None:
|
|
24
|
-
self.assertTrue(is_secret_read_attempt("cd /opt/data/profiles/u-bob && head .env"))
|
|
25
|
-
self.assertTrue(is_secret_read_attempt("cd profiles/u-x && python3 -c \"print(open('.env').read())\""))
|
|
26
|
-
self.assertTrue(is_secret_read_attempt("grep wf_rt_ /opt/data/profiles/u-bob/.env"))
|
|
27
|
-
|
|
28
|
-
def test_foreign_profile_touch_blocked_for_actor(self) -> None:
|
|
29
|
-
self.assertTrue(
|
|
30
|
-
touches_foreign_profile_secrets("cd profiles/u-bob && head .env", "u-alice-dev"),
|
|
31
|
-
)
|
|
32
|
-
self.assertFalse(
|
|
33
|
-
touches_foreign_profile_secrets("hermes -p u-alice-dev gateway status", "u-alice-dev"),
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
def test_exec_blocked_for_profile_combines_checks(self) -> None:
|
|
37
|
-
self.assertTrue(exec_blocked_for_profile("cd profiles/u-bob && head .env", "u-alice-dev"))
|
|
38
|
-
self.assertFalse(exec_blocked_for_profile(["hermes", "-p", "workframe-agent", "gateway", "status"], "workframe-agent"))
|
|
39
|
-
|
|
40
|
-
def test_allows_legitimate_gateway_commands(self) -> None:
|
|
41
|
-
self.assertFalse(is_secret_read_attempt(["hermes", "-p", "dev", "gateway", "run"]))
|
|
42
|
-
self.assertFalse(is_secret_read_attempt(["hermes", "-p", "workframe-agent", "gateway", "status"]))
|
|
43
|
-
|
|
44
|
-
def test_blocks_shell_variable_indirection(self) -> None:
|
|
45
|
-
self.assertTrue(is_secret_read_attempt("P=/opt/data/profiles/u-bob; cat $P/.env"))
|
|
46
|
-
self.assertTrue(is_secret_read_attempt("P=/opt/data/profiles/u-bob; dd if=$P/.env"))
|
|
47
|
-
self.assertTrue(exec_blocked_for_profile("P=/opt/data/profiles/u-bob; cat $P/.env", "u-alice-dev"))
|
|
48
|
-
|
|
49
|
-
def test_known_concat_ceiling_documented(self) -> None:
|
|
50
|
-
# ponytail: documented ceiling — regex cannot see through python string concat.
|
|
51
|
-
self.assertFalse(
|
|
52
|
-
is_secret_read_attempt("python3 -c \"p='/opt/data/pro'+'files/u-bob/.env';print(open(p).read())\"")
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if __name__ == "__main__":
|
|
57
|
-
unittest.main()
|