evolcore 0.0.1 → 0.0.3

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 (379) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION-0.5.0.md +378 -0
  4. package/README.md +318 -14
  5. package/ROLE_ACCESS_CONTROL.md +174 -0
  6. package/assets/.env.template +4 -0
  7. package/bin/ec-safe-output.js +161 -0
  8. package/bin/ec.js +29 -0
  9. package/dist/agents/baseagent.js +163 -0
  10. package/dist/agents/claude-runner.js +2565 -0
  11. package/dist/agents/codex-app-server-client.js +448 -0
  12. package/dist/agents/codex-runner.js +2682 -0
  13. package/dist/agents/gemini-runner.js +666 -0
  14. package/dist/agents/runner-types.js +75 -0
  15. package/dist/aun/aid/agentmd.js +216 -0
  16. package/dist/aun/aid/client.js +132 -0
  17. package/dist/aun/aid/control-aid.js +91 -0
  18. package/dist/aun/aid/identity.js +518 -0
  19. package/dist/aun/aid/index.js +4 -0
  20. package/dist/aun/aid/store.js +74 -0
  21. package/dist/aun/aid/types.js +1 -0
  22. package/dist/aun/aid/validation.js +21 -0
  23. package/dist/aun/group-identity.js +10 -0
  24. package/dist/aun/msg/group-index.js +6 -0
  25. package/dist/aun/msg/group.js +1231 -0
  26. package/dist/aun/msg/history.js +123 -0
  27. package/dist/aun/msg/index.js +5 -0
  28. package/dist/aun/msg/p2p.js +393 -0
  29. package/dist/aun/msg/payload-type.js +27 -0
  30. package/dist/aun/msg/upload.js +137 -0
  31. package/dist/aun/outbox.js +168 -0
  32. package/dist/aun/rpc/caller.js +42 -0
  33. package/dist/aun/rpc/connection.js +25 -0
  34. package/dist/aun/rpc/index.js +2 -0
  35. package/dist/aun/service-proxy.js +225 -0
  36. package/dist/aun/storage/download.js +29 -0
  37. package/dist/aun/storage/index.js +3 -0
  38. package/dist/aun/storage/manage.js +10 -0
  39. package/dist/aun/storage/upload.js +68 -0
  40. package/dist/channels/aun.js +4164 -0
  41. package/dist/channels/contact-bind-code.js +134 -0
  42. package/dist/channels/daemon.js +422 -0
  43. package/dist/channels/dingtalk.js +1479 -0
  44. package/dist/channels/feishu.js +1865 -0
  45. package/dist/channels/qqbot.js +409 -0
  46. package/dist/channels/wechat.js +817 -0
  47. package/dist/channels/wecom-card.js +101 -0
  48. package/dist/channels/wecom-onboarding.js +82 -0
  49. package/dist/channels/wecom-state.js +191 -0
  50. package/dist/channels/wecom.js +1157 -0
  51. package/dist/cli/agent-command.js +642 -0
  52. package/dist/cli/agent.js +1059 -0
  53. package/dist/cli/aun-commands.js +2003 -0
  54. package/dist/cli/bench.js +1228 -0
  55. package/dist/cli/cli-argv.js +66 -0
  56. package/dist/cli/code-stats.js +329 -0
  57. package/dist/cli/command-log.js +82 -0
  58. package/dist/cli/config-selector.js +69 -0
  59. package/dist/cli/config.js +261 -0
  60. package/dist/cli/contact.js +71 -0
  61. package/dist/cli/ctl-command.js +62 -0
  62. package/dist/cli/daemon-commands.js +2750 -0
  63. package/dist/cli/fs-command.js +1447 -0
  64. package/dist/cli/handoff-command.js +302 -0
  65. package/dist/cli/help.js +35 -0
  66. package/dist/cli/index.js +374 -0
  67. package/dist/cli/init-channel.js +1372 -0
  68. package/dist/cli/init.js +590 -0
  69. package/dist/cli/link-rules.js +240 -0
  70. package/dist/cli/model.js +591 -0
  71. package/dist/cli/net-check.js +723 -0
  72. package/dist/cli/queue-command.js +150 -0
  73. package/dist/cli/raw-key-input.js +25 -0
  74. package/dist/cli/response.js +344 -0
  75. package/dist/cli/restart-monitor.js +480 -0
  76. package/dist/cli/stats.js +609 -0
  77. package/dist/cli/task-context.js +80 -0
  78. package/dist/cli/trigger-command.js +545 -0
  79. package/dist/cli/version.js +93 -0
  80. package/dist/cli/watch-logs.js +33 -0
  81. package/dist/cli/watch-msg.js +673 -0
  82. package/dist/config/boot-log.js +266 -0
  83. package/dist/config/builtin-role-templates.js +42 -0
  84. package/dist/config/builtin-roles.js +91 -0
  85. package/dist/config/config-batch-get.js +11 -0
  86. package/dist/config/config-field-policy.js +261 -0
  87. package/dist/config/config-manager.js +1120 -0
  88. package/dist/config/config-operation-service.js +384 -0
  89. package/dist/config/contact-alias.js +68 -0
  90. package/dist/config/contact-book-store.js +454 -0
  91. package/dist/config/contact-book-v2-startup.js +35 -0
  92. package/dist/config/contact-book.js +224 -0
  93. package/dist/config/contact-operation-service.js +110 -0
  94. package/dist/config/gateway-config.js +858 -0
  95. package/dist/config/lifecycle.js +17 -0
  96. package/dist/config/mention-mode.js +27 -0
  97. package/dist/config/merge.js +161 -0
  98. package/dist/config/owner-policy.js +4 -0
  99. package/dist/config/peer-role-resolver.js +218 -0
  100. package/dist/config/resolved-config-op.js +483 -0
  101. package/dist/config/role-config-v4-startup.js +32 -0
  102. package/dist/config/role-config-v5-startup.js +27 -0
  103. package/dist/config/role-ranks.js +18 -0
  104. package/dist/config/role-schema.js +105 -0
  105. package/dist/config/role-service.js +156 -0
  106. package/dist/config/role-store.js +215 -0
  107. package/dist/config/roles.js +64 -0
  108. package/dist/config/schema-registry.js +154 -0
  109. package/dist/config/snapshot.js +598 -0
  110. package/dist/config-store.js +501 -0
  111. package/dist/core/auth/agent-delegation.js +111 -0
  112. package/dist/core/auth/auth-gateway.js +166 -0
  113. package/dist/core/auth/authenticated-actor.js +6 -0
  114. package/dist/core/auth/authorization-audit.js +119 -0
  115. package/dist/core/auth/operation-authorizer.js +720 -0
  116. package/dist/core/auth/operation-catalog.js +731 -0
  117. package/dist/core/baseagent-loader.js +54 -0
  118. package/dist/core/bootstrap-service.js +175 -0
  119. package/dist/core/capability/capability-manager.js +316 -0
  120. package/dist/core/capability/providers/claude-capability-provider.js +176 -0
  121. package/dist/core/capability/providers/codex-capability-provider.js +148 -0
  122. package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
  123. package/dist/core/capability/types.js +27 -0
  124. package/dist/core/causation/audit.js +103 -0
  125. package/dist/core/causation/aun-association.js +111 -0
  126. package/dist/core/causation/context.js +93 -0
  127. package/dist/core/causation/index.js +4 -0
  128. package/dist/core/causation/types.js +2 -0
  129. package/dist/core/channel-loader.js +277 -0
  130. package/dist/core/command/agent-control.js +616 -0
  131. package/dist/core/command/cli-intent-parser.js +225 -0
  132. package/dist/core/command/command-handler.js +1733 -0
  133. package/dist/core/command/connect-menu.js +374 -0
  134. package/dist/core/command/menu-handler.js +3452 -0
  135. package/dist/core/command/menu-protocol.js +247 -0
  136. package/dist/core/command/role-menu.js +1623 -0
  137. package/dist/core/command/slash-gate.js +148 -0
  138. package/dist/core/command/slash-handler.js +3066 -0
  139. package/dist/core/daemon-file-cache.js +222 -0
  140. package/dist/core/event-bus.js +32 -0
  141. package/dist/core/event-catalog.js +810 -0
  142. package/dist/core/evolagent-registry.js +545 -0
  143. package/dist/core/evolagent.js +342 -0
  144. package/dist/core/handoff/dispatcher.js +229 -0
  145. package/dist/core/handoff/mutex.js +46 -0
  146. package/dist/core/handoff/runtime.js +324 -0
  147. package/dist/core/handoff/store.js +537 -0
  148. package/dist/core/handoff/types.js +12 -0
  149. package/dist/core/inference/text-inference.js +173 -0
  150. package/dist/core/interaction-registration.js +10 -0
  151. package/dist/core/interaction-router.js +278 -0
  152. package/dist/core/message/create-status.js +67 -0
  153. package/dist/core/message/im-renderer.js +659 -0
  154. package/dist/core/message/items-formatter.js +76 -0
  155. package/dist/core/message/logical-queue-bridge.js +123 -0
  156. package/dist/core/message/message-bridge.js +874 -0
  157. package/dist/core/message/message-cache.js +56 -0
  158. package/dist/core/message/message-log.js +339 -0
  159. package/dist/core/message/message-processor.js +4 -0
  160. package/dist/core/message/message-queue.js +1446 -0
  161. package/dist/core/message/message-utils.js +76 -0
  162. package/dist/core/message/peer-mode.js +105 -0
  163. package/dist/core/message/pending-hints.js +232 -0
  164. package/dist/core/message/response-depth.js +33 -0
  165. package/dist/core/message/response-engine.js +4020 -0
  166. package/dist/core/message/response-snapshot.js +83 -0
  167. package/dist/core/message/send-receipt.js +24 -0
  168. package/dist/core/message/stream-debouncer.js +139 -0
  169. package/dist/core/message/stream-idle-monitor.js +124 -0
  170. package/dist/core/model/config-scope.js +162 -0
  171. package/dist/core/model/field-scope.js +78 -0
  172. package/dist/core/model/model-catalog.js +227 -0
  173. package/dist/core/model/model-diagnostics.js +182 -0
  174. package/dist/core/model/model-permission.js +90 -0
  175. package/dist/core/permission/approval-gateway.js +1017 -0
  176. package/dist/core/permission/ec-command-parser.js +347 -0
  177. package/dist/core/permission/execution-sandbox.js +16 -0
  178. package/dist/core/permission/index.js +6 -0
  179. package/dist/core/permission/mode.js +24 -0
  180. package/dist/core/permission/sandbox-runtime.js +265 -0
  181. package/dist/core/permission/tool-policy.js +1019 -0
  182. package/dist/core/permission/unix-socket-policy.js +99 -0
  183. package/dist/core/protected-paths.js +332 -0
  184. package/dist/core/relation/peer-identity.js +222 -0
  185. package/dist/core/relation/peer-key.js +1 -0
  186. package/dist/core/role/runtime-policy.js +141 -0
  187. package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
  188. package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
  189. package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
  190. package/dist/core/session/session-file-adapter.js +7 -0
  191. package/dist/core/session/session-file-health.js +45 -0
  192. package/dist/core/session/session-fs-store.js +273 -0
  193. package/dist/core/session/session-key.js +24 -0
  194. package/dist/core/session/session-manager.js +1643 -0
  195. package/dist/core/session/session-mapper.js +100 -0
  196. package/dist/core/session/session-renew.js +314 -0
  197. package/dist/core/session/session-title.js +128 -0
  198. package/dist/core/session/session-turn-coordinator.js +205 -0
  199. package/dist/core/session/session-turns.js +67 -0
  200. package/dist/core/system-channels.js +29 -0
  201. package/dist/eck/baseagent-caps.js +18 -0
  202. package/dist/eck/detect.js +47 -0
  203. package/dist/eck/group-rules-sync.js +345 -0
  204. package/dist/eck/init.js +77 -0
  205. package/dist/eck/kit-renderer.js +359 -0
  206. package/dist/eck/manifest-engine.js +446 -0
  207. package/dist/eck/message-renderer.js +199 -0
  208. package/dist/eck/rules-loader.js +28 -0
  209. package/dist/index.js +2926 -4
  210. package/dist/ipc.js +777 -0
  211. package/dist/paths.js +262 -0
  212. package/dist/product.js +18 -0
  213. package/dist/response-system/context-builder.js +71 -0
  214. package/dist/response-system/coordinator.js +117 -0
  215. package/dist/response-system/decision-executor.js +86 -0
  216. package/dist/response-system/engines/v1/index.js +21 -0
  217. package/dist/response-system/engines/v1/interactive-flow.js +27 -0
  218. package/dist/response-system/engines/v1/proactive-flow.js +137 -0
  219. package/dist/response-system/engines/v1/types.js +1 -0
  220. package/dist/response-system/extensions.js +41 -0
  221. package/dist/response-system/index.js +6 -0
  222. package/dist/response-system/modes/index.js +7 -0
  223. package/dist/response-system/modes/single-session/index.js +72 -0
  224. package/dist/response-system/queues/fifo-queue.js +44 -0
  225. package/dist/response-system/queues/index.js +6 -0
  226. package/dist/response-system/queues/lifo-queue.js +42 -0
  227. package/dist/response-system/queues/priority-queue.js +63 -0
  228. package/dist/response-system/registry.js +97 -0
  229. package/dist/response-system/resolver.js +37 -0
  230. package/dist/response-system/selector.js +23 -0
  231. package/dist/response-system/types.js +7 -0
  232. package/dist/stats/billing.js +163 -0
  233. package/dist/stats/budget.js +93 -0
  234. package/dist/stats/db.js +403 -0
  235. package/dist/stats/eck-vars.js +89 -0
  236. package/dist/stats/index.js +11 -0
  237. package/dist/stats/normalizer.js +80 -0
  238. package/dist/stats/price-resolver.js +138 -0
  239. package/dist/stats/query.js +763 -0
  240. package/dist/stats/role-budget.js +168 -0
  241. package/dist/stats/writer.js +151 -0
  242. package/dist/trigger/anomaly-store.js +258 -0
  243. package/dist/trigger/audit.js +152 -0
  244. package/dist/trigger/event-source.js +119 -0
  245. package/dist/trigger/feedback.js +685 -0
  246. package/dist/trigger/history.js +290 -0
  247. package/dist/trigger/manager.js +294 -0
  248. package/dist/trigger/parser.js +595 -0
  249. package/dist/trigger/patch.js +155 -0
  250. package/dist/trigger/scheduler.js +1602 -0
  251. package/dist/trigger/script-executor.js +155 -0
  252. package/dist/trigger/state.js +145 -0
  253. package/dist/trigger/types.js +1 -0
  254. package/dist/trigger/validation.js +634 -0
  255. package/dist/types.js +12 -0
  256. package/dist/utils/aid-bind.js +313 -0
  257. package/dist/utils/atomic-write.js +95 -0
  258. package/dist/utils/avatar-upload.js +123 -0
  259. package/dist/utils/cross-platform.js +297 -0
  260. package/dist/utils/ecweb-utils.js +73 -0
  261. package/dist/utils/error-dict.json +153 -0
  262. package/dist/utils/error-utils.js +349 -0
  263. package/dist/utils/instance-registry.js +444 -0
  264. package/dist/utils/locale.js +21 -0
  265. package/dist/utils/log-writer.js +270 -0
  266. package/dist/utils/logger.js +89 -0
  267. package/dist/utils/markdown-to-plain-text.js +20 -0
  268. package/dist/utils/media-cache.js +274 -0
  269. package/dist/utils/model-prices.jsonl +20 -0
  270. package/dist/utils/npm-ops.js +210 -0
  271. package/dist/utils/process-introspect.js +133 -0
  272. package/dist/utils/process-tree-stats.js +271 -0
  273. package/dist/utils/project-path.js +74 -0
  274. package/dist/utils/restart-safety.js +31 -0
  275. package/dist/utils/stats.js +410 -0
  276. package/dist/utils/system-memory.js +62 -0
  277. package/dist/utils/tool-summary.js +284 -0
  278. package/dist/utils/welcome.js +268 -0
  279. package/kits/docs/GUIDE.md +20 -0
  280. package/kits/docs/INDEX.md +66 -0
  281. package/kits/docs/aun/CHEATSHEET.md +19 -0
  282. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  283. package/kits/docs/channels/aun.md +65 -0
  284. package/kits/docs/channels/feishu.md +56 -0
  285. package/kits/docs/context-assembly.md +366 -0
  286. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  287. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  288. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  289. package/kits/docs/eck_templates/runtime.template.md +19 -0
  290. package/kits/docs/evolcore/INDEX.md +68 -0
  291. package/kits/docs/evolcore/agent.md +77 -0
  292. package/kits/docs/evolcore/aid.md +52 -0
  293. package/kits/docs/evolcore/config.md +149 -0
  294. package/kits/docs/evolcore/contact.md +57 -0
  295. package/kits/docs/evolcore/ctl.md +46 -0
  296. package/kits/docs/evolcore/event.md +216 -0
  297. package/kits/docs/evolcore/fs-architecture.md +1215 -0
  298. package/kits/docs/evolcore/fs.md +110 -0
  299. package/kits/docs/evolcore/group-fs.md +17 -0
  300. package/kits/docs/evolcore/group-rules.md +226 -0
  301. package/kits/docs/evolcore/group.md +150 -0
  302. package/kits/docs/evolcore/model.md +50 -0
  303. package/kits/docs/evolcore/msg.md +136 -0
  304. package/kits/docs/evolcore/response.md +75 -0
  305. package/kits/docs/evolcore/rpc.md +37 -0
  306. package/kits/docs/evolcore/self-summary.md +29 -0
  307. package/kits/docs/evolcore/stats.md +83 -0
  308. package/kits/docs/evolcore/storage.md +50 -0
  309. package/kits/docs/evolcore/trigger.md +539 -0
  310. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
  311. package/kits/docs/identity/PATH_OPS.md +16 -0
  312. package/kits/docs/identity/ROLE_DETAIL.md +23 -0
  313. package/kits/docs/identity/identity-tools.md +26 -0
  314. package/kits/docs/path-registry.md +43 -0
  315. package/kits/docs/prompt-loading-architecture.md +266 -0
  316. package/kits/docs/venues/aun-group.md +45 -0
  317. package/kits/docs/venues/aun-private.md +10 -0
  318. package/kits/docs/venues/client-desktop.md +10 -0
  319. package/kits/docs/venues/client-mobile.md +10 -0
  320. package/kits/docs/venues/feishu-group.md +13 -0
  321. package/kits/docs/venues/feishu-private.md +9 -0
  322. package/kits/docs/venues/group.md +25 -0
  323. package/kits/docs/venues/private.md +10 -0
  324. package/kits/eck_manifest.auxiliary.json +43 -0
  325. package/kits/eck_manifest.json +203 -0
  326. package/kits/eck_message_manifest.json +63 -0
  327. package/kits/migrations/README-role-config-v4.md +32 -0
  328. package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
  329. package/kits/migrations/migrate-role-config-v4.mjs +623 -0
  330. package/kits/migrations/migrate-role-config-v5.mjs +346 -0
  331. package/kits/migrations/rename-config-file.mjs +99 -0
  332. package/kits/rules/01-overview.md +142 -0
  333. package/kits/rules/02-navigation.md +76 -0
  334. package/kits/rules/03-identity.md +34 -0
  335. package/kits/rules/04-relation.md +59 -0
  336. package/kits/rules/05-venue.md +44 -0
  337. package/kits/rules/06-channel.md +59 -0
  338. package/kits/schemas/_meta.json +32 -0
  339. package/kits/schemas/agent-config.schema.1.json +177 -0
  340. package/kits/schemas/agent-config.schema.2.json +239 -0
  341. package/kits/schemas/agent-config.schema.3.json +119 -0
  342. package/kits/schemas/agent-config.schema.4.json +208 -0
  343. package/kits/schemas/agent-config.schema.5.json +326 -0
  344. package/kits/schemas/agent-config.schema.6.json +322 -0
  345. package/kits/schemas/contact-book.schema.1.json +36 -0
  346. package/kits/schemas/contact-book.schema.2.json +43 -0
  347. package/kits/schemas/daemon.schema.1.json +90 -0
  348. package/kits/schemas/defaults.schema.1.json +81 -0
  349. package/kits/schemas/menu-exec-schema-commands.md +208 -0
  350. package/kits/schemas/migrations/README.md +28 -0
  351. package/kits/schemas/relation-config.schema.1.json +158 -0
  352. package/kits/schemas/relation-config.schema.2.json +73 -0
  353. package/kits/schemas/relation-config.schema.3.json +50 -0
  354. package/kits/schemas/relation-config.schema.4.json +47 -0
  355. package/kits/schemas/relation-config.schema.5.json +47 -0
  356. package/kits/schemas/role-config.schema.1.json +201 -0
  357. package/kits/schemas/role-registry.schema.1.json +35 -0
  358. package/kits/schemas/single-session.schema.1.json +31 -0
  359. package/kits/templates/bootstrap-welcome.md +15 -0
  360. package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
  361. package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
  362. package/kits/templates/message-fragments/inject-default.md +2 -0
  363. package/kits/templates/message-fragments/item.md +2 -0
  364. package/kits/templates/roles/admin.json +9 -0
  365. package/kits/templates/roles/member.json +41 -0
  366. package/kits/templates/roles/owner.json +9 -0
  367. package/kits/templates/roles/visitor.json +40 -0
  368. package/kits/templates/system-fragments/baseagent.md +14 -0
  369. package/kits/templates/system-fragments/bootstrap.md +16 -0
  370. package/kits/templates/system-fragments/channel.md +48 -0
  371. package/kits/templates/system-fragments/commands.md +28 -0
  372. package/kits/templates/system-fragments/identity.md +11 -0
  373. package/kits/templates/system-fragments/relation.md +19 -0
  374. package/kits/templates/system-fragments/session.md +53 -0
  375. package/kits/templates/system-fragments/venue.md +31 -0
  376. package/package.json +50 -15
  377. package/dist/index.d.ts +0 -7
  378. package/dist/index.d.ts.map +0 -1
  379. package/dist/index.js.map +0 -1
@@ -0,0 +1,483 @@
1
+ import { normalizeCliArgv } from '../cli/cli-argv.js';
2
+ import { parseConfigSelector } from '../cli/config-selector.js';
3
+ import { parseConfigFieldValue, resolveConfigFieldRule, } from './config-field-policy.js';
4
+ import { ConfigError, routeFieldPath } from './config-manager.js';
5
+ const SELECTOR_OPTIONS = {
6
+ '--self': 'value',
7
+ '--peer': 'value',
8
+ '--default': 'boolean',
9
+ '--process': 'boolean',
10
+ '--daemon': 'boolean',
11
+ };
12
+ const FIELD_OPTIONS = {
13
+ ...SELECTOR_OPTIONS,
14
+ '--format': 'value',
15
+ };
16
+ const SCOPED_SUBCOMMANDS = new Set(['show', 'effective', 'fields', 'validate', 'init']);
17
+ const GLOBAL_SUBCOMMANDS = new Set(['list', 'snapshot', 'prune', 'history', 'diff', 'restore', 'current', 'boots', 'schema']);
18
+ export function resolveConfigCommand(inputArgv, options = {}) {
19
+ let argv = normalizeCliArgv(inputArgv);
20
+ if (argv[0] !== 'config') {
21
+ return { ok: false, code: 'INVALID_CONFIG_COMMAND', reason: 'Expected a config command' };
22
+ }
23
+ const subcommand = argv[1];
24
+ if (!subcommand) {
25
+ return { ok: false, code: 'MISSING_ARG', reason: 'A config subcommand is required' };
26
+ }
27
+ if (options.defaultRelation?.self && options.defaultRelation.peerKey
28
+ && isScopedConfigSubcommand(subcommand)
29
+ && !hasExplicitConfigSelector(argv)) {
30
+ argv = [...argv, '--self', options.defaultRelation.self, '--peer', options.defaultRelation.peerKey];
31
+ }
32
+ if (subcommand === 'get' || subcommand === 'set' || subcommand === 'unset') {
33
+ const field = resolveFieldCommand(argv, subcommand, options);
34
+ return field.ok ? { ok: true, command: field.op } : field;
35
+ }
36
+ if (SCOPED_SUBCOMMANDS.has(subcommand)) {
37
+ return resolveScopedCommand(argv, subcommand);
38
+ }
39
+ if (GLOBAL_SUBCOMMANDS.has(subcommand)) {
40
+ return resolveGlobalCommand(argv, subcommand);
41
+ }
42
+ return {
43
+ ok: false,
44
+ code: 'UNKNOWN_CONFIG_SUBCOMMAND',
45
+ reason: `Unknown config subcommand: ${subcommand}`,
46
+ };
47
+ }
48
+ export function resolveConfigOperation(inputArgv, options = {}) {
49
+ const resolved = resolveConfigCommand(inputArgv, options);
50
+ if (!resolved.ok)
51
+ return resolved;
52
+ if (resolved.command.kind !== 'field') {
53
+ return {
54
+ ok: false,
55
+ code: 'NOT_CONFIG_FIELD_COMMAND',
56
+ reason: `Config subcommand ${resolved.command.subcommand} is not field-scoped`,
57
+ };
58
+ }
59
+ return { ok: true, op: resolved.command };
60
+ }
61
+ export function isResolvedConfigMutation(command) {
62
+ if (command.kind === 'field')
63
+ return command.subcommand !== 'get';
64
+ if (command.kind === 'scoped')
65
+ return command.subcommand === 'init';
66
+ return command.subcommand === 'snapshot' || command.subcommand === 'prune' || command.subcommand === 'restore';
67
+ }
68
+ const FIELD_USAGE = {
69
+ get: 'config get <field> [selector]',
70
+ set: 'config set <field> <value> [selector]',
71
+ unset: 'config unset <field> [selector]',
72
+ };
73
+ function resolveFieldCommand(argv, subcommand, options = {}) {
74
+ const businessCount = subcommand === 'set' ? 2 : 1;
75
+ const business = parseLeadingPositionals(argv.slice(2), businessCount, FIELD_USAGE[subcommand]);
76
+ if (!business.ok)
77
+ return business;
78
+ const parsedOptions = parseOptions(business.rest, FIELD_OPTIONS);
79
+ if (!parsedOptions.ok)
80
+ return parsedOptions;
81
+ const formatCheck = validateOutputFormat(parsedOptions.values.format);
82
+ if (!formatCheck.ok)
83
+ return formatCheck;
84
+ // 先按"不强制 selector"解析,拿到用于路由的 scope;selector 是否缺失的判定推迟到字段校验之后,
85
+ // 这样非法字段会先于 SELECTOR_REQUIRED 暴露真正的 UNKNOWN_FIELD,而不是被通用的"缺 selector"掩盖。
86
+ const selector = parseConfigSelector(business.rest, { requireSelector: false });
87
+ if (!selector.ok)
88
+ return { ok: false, code: selector.code, reason: selector.reason };
89
+ const field = business.values[0].trim();
90
+ if (!field)
91
+ return { ok: false, code: 'MISSING_ARG', reason: `config ${subcommand} requires a field` };
92
+ if (field === 'permissionMode') {
93
+ return {
94
+ ok: false,
95
+ code: 'ROLE_POLICY_MANAGED',
96
+ reason: 'permissionMode is defined by roles; use the role policy editor',
97
+ };
98
+ }
99
+ if (field === 'role') {
100
+ return {
101
+ ok: false,
102
+ code: 'ROLE_POLICY_MANAGED',
103
+ reason: 'Relation role assignments are managed through the role assignment service',
104
+ };
105
+ }
106
+ if (subcommand !== 'get' && (field === 'roles' || field.startsWith('roles.'))) {
107
+ return {
108
+ ok: false,
109
+ code: 'ROLE_POLICY_MANAGED',
110
+ reason: 'Agent role policies are managed through the role policy editor',
111
+ };
112
+ }
113
+ const fieldRule = resolveConfigFieldRule(field);
114
+ if (subcommand !== 'get' && fieldRule.class === 'safe-readonly-object') {
115
+ return {
116
+ ok: false,
117
+ code: 'UNSUPPORTED_CONFIG_VALUE',
118
+ reason: `Config field ${field} does not support object writes`,
119
+ };
120
+ }
121
+ const commandScope = selector.scope === 'defaults' ? 'process' : selector.scope;
122
+ const isSafe = fieldRule.class === 'safe-scalar' || fieldRule.class === 'safe-readonly-object';
123
+ const isManagementOperation = commandScope === 'process' || !isSafe;
124
+ const operationId = isManagementOperation
125
+ ? subcommand === 'get' ? 'config.read' : 'config.write'
126
+ : `config.${subcommand}`;
127
+ let route;
128
+ const routeRequired = subcommand !== 'get' || !isManagementOperation;
129
+ try {
130
+ route = routeFieldPath(field, selector.scope);
131
+ }
132
+ catch (error) {
133
+ if (routeRequired)
134
+ return configErrorResult(error);
135
+ }
136
+ // 字段已校验通过,再补齐"写操作必须显式 selector"的约束(get 不要求)。
137
+ const selectorMissing = selector.scope === 'agent' && !selector.self;
138
+ if (subcommand !== 'get' && selectorMissing) {
139
+ return {
140
+ ok: false,
141
+ code: 'SELECTOR_REQUIRED',
142
+ reason: `config ${subcommand} ${field} needs a target: --self <aid> [--peer <peerKey>] | --default | --process`,
143
+ };
144
+ }
145
+ // get 在独立 CLI 模式(无 defaultRelation)也要求显式 selector,避免"不知道读谁的"。
146
+ if (subcommand === 'get' && selectorMissing && !options.defaultRelation?.self) {
147
+ return {
148
+ ok: false,
149
+ code: 'SELECTOR_REQUIRED',
150
+ reason: `config get ${field} needs a target: --self <aid> [--peer <peerKey>] | --default | --process`,
151
+ };
152
+ }
153
+ if (subcommand === 'unset' && selector.scope === 'process') {
154
+ return { ok: false, code: 'UNSET_PROCESS_REJECT', reason: 'daemon.json has no lower config layer to fall back to' };
155
+ }
156
+ const rawValue = subcommand === 'set' ? business.values[1] : undefined;
157
+ let value;
158
+ if (subcommand === 'set') {
159
+ if (fieldRule.class === 'safe-scalar') {
160
+ const parsedValue = parseConfigFieldValue(field, rawValue);
161
+ if (!parsedValue.ok)
162
+ return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: parsedValue.reason };
163
+ value = parsedValue.value;
164
+ }
165
+ else {
166
+ if (!route)
167
+ return { ok: false, code: 'UNKNOWN_FIELD', reason: `Unknown config field: ${field}` };
168
+ value = coerceConfigValue(rawValue, route);
169
+ }
170
+ }
171
+ return {
172
+ ok: true,
173
+ op: {
174
+ kind: 'field',
175
+ canonicalArgv: buildFieldCanonicalArgv(subcommand, field, rawValue, selector, parsedOptions.values.format),
176
+ subcommand,
177
+ configScope: selector.scope,
178
+ commandScope,
179
+ operationId,
180
+ dangerous: operationId === 'config.read' || operationId === 'config.write',
181
+ field,
182
+ fieldRule,
183
+ route,
184
+ ...(selector.self ? { self: selector.self } : {}),
185
+ ...(selector.peerKey ? { peerKey: selector.peerKey } : {}),
186
+ ...(rawValue !== undefined ? { rawValue, value } : {}),
187
+ ...(parsedOptions.values.format ? { format: parsedOptions.values.format } : {}),
188
+ },
189
+ };
190
+ }
191
+ function resolveScopedCommand(argv, subcommand) {
192
+ const positionalCount = subcommand === 'fields' ? { min: 0, max: 1 } : { min: 0, max: 0 };
193
+ const parsed = parseStructuredArgs(argv.slice(2), {
194
+ options: { ...SELECTOR_OPTIONS, '--format': 'value' },
195
+ ...positionalCount,
196
+ });
197
+ if (!parsed.ok)
198
+ return parsed;
199
+ const formatCheck = validateOutputFormat(parsed.values.format);
200
+ if (!formatCheck.ok)
201
+ return formatCheck;
202
+ const selector = parseConfigSelector(argv.slice(2), { requireSelector: subcommand !== 'fields' });
203
+ if (!selector.ok)
204
+ return { ok: false, code: selector.code, reason: selector.reason };
205
+ const commandScope = selector.scope === 'defaults' ? 'process' : selector.scope;
206
+ const format = parsed.values.format;
207
+ const field = parsed.positionals[0];
208
+ return {
209
+ ok: true,
210
+ command: {
211
+ kind: 'scoped',
212
+ canonicalArgv: buildScopedCanonicalArgv(subcommand, selector, field, format),
213
+ subcommand,
214
+ configScope: selector.scope,
215
+ commandScope,
216
+ operationId: `config.${subcommand}`,
217
+ dangerous: true,
218
+ ...(selector.self ? { self: selector.self } : {}),
219
+ ...(selector.peerKey ? { peerKey: selector.peerKey } : {}),
220
+ ...(field ? { field } : {}),
221
+ ...(format ? { format } : {}),
222
+ },
223
+ };
224
+ }
225
+ function resolveGlobalCommand(argv, subcommand) {
226
+ const specs = {
227
+ list: { min: 0, max: 0, options: { '--format': 'value' } },
228
+ snapshot: { min: 0, max: 0, options: { '--full': 'boolean', '--desc': 'value', '--format': 'value' } },
229
+ prune: { min: 0, max: 0, options: { '--keep-full': 'value', '--keep-delta': 'value', '--yes': 'boolean', '--format': 'value' } },
230
+ history: { min: 0, max: 0, options: { '--format': 'value' } },
231
+ diff: { min: 2, max: 2, options: { '--format': 'value' } },
232
+ restore: { min: 1, max: 1, options: { '--format': 'value' } },
233
+ current: { min: 0, max: 0, options: { '--format': 'value' } },
234
+ boots: { min: 0, max: 0, options: { '-n': 'value', '--num': 'value', '--format': 'value' }, aliases: [['-n', '--num']] },
235
+ schema: { min: 0, max: 2, options: { '--list': 'boolean', '--format': 'value' } },
236
+ };
237
+ const parsed = parseStructuredArgs(argv.slice(2), specs[subcommand]);
238
+ if (!parsed.ok)
239
+ return parsed;
240
+ const formatCheck = validateOutputFormat(parsed.values.format);
241
+ if (!formatCheck.ok)
242
+ return formatCheck;
243
+ const format = parsed.values.format;
244
+ const command = {
245
+ kind: 'global',
246
+ canonicalArgv: [],
247
+ subcommand,
248
+ configScope: 'process',
249
+ commandScope: 'process',
250
+ operationId: `config.${subcommand}`,
251
+ dangerous: true,
252
+ ...(format ? { format } : {}),
253
+ };
254
+ if (subcommand === 'snapshot') {
255
+ command.full = parsed.flags.has('full');
256
+ if (parsed.values.desc !== undefined)
257
+ command.description = parsed.values.desc;
258
+ }
259
+ else if (subcommand === 'prune') {
260
+ command.yes = parsed.flags.has('yes');
261
+ const keepFull = parseNonNegativeInteger(parsed.values.keepFull, '--keep-full');
262
+ if (!keepFull.ok)
263
+ return keepFull;
264
+ const keepDelta = parseNonNegativeInteger(parsed.values.keepDelta, '--keep-delta');
265
+ if (!keepDelta.ok)
266
+ return keepDelta;
267
+ if (keepFull.value !== undefined)
268
+ command.keepFull = keepFull.value;
269
+ if (keepDelta.value !== undefined)
270
+ command.keepDelta = keepDelta.value;
271
+ }
272
+ else if (subcommand === 'diff') {
273
+ command.versions = [parsed.positionals[0], parsed.positionals[1]];
274
+ }
275
+ else if (subcommand === 'restore') {
276
+ command.version = parsed.positionals[0];
277
+ }
278
+ else if (subcommand === 'boots') {
279
+ const count = parsePositiveInteger(parsed.values.n ?? parsed.values.num, '-n/--num');
280
+ if (!count.ok)
281
+ return count;
282
+ command.count = count.value ?? 10;
283
+ }
284
+ else if (subcommand === 'schema') {
285
+ command.schemaList = parsed.flags.has('list');
286
+ if (parsed.positionals[0])
287
+ command.schemaName = parsed.positionals[0];
288
+ if (parsed.positionals[1] !== undefined) {
289
+ if (command.schemaList) {
290
+ return { ok: false, code: 'INVALID_CONFIG_COMMAND', reason: 'config schema --list does not take a version argument' };
291
+ }
292
+ const version = parseNonNegativeInteger(parsed.positionals[1], 'version');
293
+ if (!version.ok)
294
+ return version;
295
+ command.schemaVersion = version.value;
296
+ }
297
+ if (!command.schemaName && (command.schemaList || command.schemaVersion !== undefined)) {
298
+ return { ok: false, code: 'MISSING_ARG', reason: 'config schema requires a schema name' };
299
+ }
300
+ }
301
+ command.canonicalArgv = buildGlobalCanonicalArgv(command);
302
+ return { ok: true, command };
303
+ }
304
+ function isScopedConfigSubcommand(subcommand) {
305
+ return subcommand === 'get' || subcommand === 'set' || subcommand === 'unset'
306
+ || SCOPED_SUBCOMMANDS.has(subcommand);
307
+ }
308
+ function hasExplicitConfigSelector(argv) {
309
+ const selectors = new Set(['--self', '--peer', '--default', '--process', '--daemon']);
310
+ return argv.some(arg => selectors.has(arg));
311
+ }
312
+ function parseLeadingPositionals(argv, count, usage) {
313
+ const values = argv.slice(0, count);
314
+ if (values.length !== count || values.some(value => !value || value.startsWith('-'))) {
315
+ const noun = count === 1 ? '<field>' : '<field> <value>';
316
+ return {
317
+ ok: false,
318
+ code: 'MISSING_ARG',
319
+ reason: `Missing ${noun}. Usage: ${usage}`,
320
+ };
321
+ }
322
+ const rest = argv.slice(count);
323
+ if (rest[0] && !rest[0].startsWith('-')) {
324
+ return { ok: false, code: 'INVALID_CONFIG_COMMAND', reason: `Unexpected argument "${rest[0]}". Usage: ${usage}` };
325
+ }
326
+ return { ok: true, values, rest };
327
+ }
328
+ function parseOptions(argv, options) {
329
+ const parsed = parseStructuredArgs(argv, { min: 0, max: 0, options, aliases: [['--process', '--daemon']] });
330
+ if (!parsed.ok)
331
+ return parsed;
332
+ return { ok: true, values: parsed.values, flags: parsed.flags };
333
+ }
334
+ function parseStructuredArgs(argv, spec) {
335
+ const positionals = [];
336
+ const values = {};
337
+ const flags = new Set();
338
+ const seen = new Set();
339
+ const aliasGroups = spec.aliases ?? [];
340
+ for (let i = 0; i < argv.length; i++) {
341
+ const arg = argv[i];
342
+ if (arg === '--') {
343
+ return { ok: false, code: 'INVALID_CONFIG_COMMAND', reason: '-- is not supported in config commands' };
344
+ }
345
+ if (!arg.startsWith('-')) {
346
+ positionals.push(arg);
347
+ continue;
348
+ }
349
+ const kind = spec.options[arg];
350
+ if (!kind)
351
+ return { ok: false, code: 'INVALID_CONFIG_COMMAND', reason: `Unexpected config argument: ${arg}` };
352
+ const aliasGroup = aliasGroups.find(group => group.includes(arg));
353
+ if (seen.has(arg) || aliasGroup?.some(alias => seen.has(alias))) {
354
+ return { ok: false, code: 'SELECTOR_CONFLICT', reason: `Duplicate config option: ${arg}` };
355
+ }
356
+ seen.add(arg);
357
+ const key = optionKey(arg);
358
+ if (kind === 'boolean') {
359
+ flags.add(key);
360
+ continue;
361
+ }
362
+ const value = argv[i + 1];
363
+ if (!value || value.startsWith('-')) {
364
+ return { ok: false, code: 'MISSING_FLAG_VALUE', reason: `${arg} requires a value` };
365
+ }
366
+ values[key] = value;
367
+ i += 1;
368
+ }
369
+ if (positionals.length < spec.min) {
370
+ return { ok: false, code: 'MISSING_ARG', reason: `Config command requires ${spec.min} positional argument${spec.min === 1 ? '' : 's'}` };
371
+ }
372
+ if (positionals.length > spec.max) {
373
+ return { ok: false, code: 'INVALID_CONFIG_COMMAND', reason: `Unexpected positional argument: ${positionals[spec.max]}` };
374
+ }
375
+ return { ok: true, positionals, values, flags };
376
+ }
377
+ function optionKey(option) {
378
+ return option.replace(/^-+/, '').replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
379
+ }
380
+ function parseNonNegativeInteger(raw, option) {
381
+ if (raw === undefined)
382
+ return { ok: true };
383
+ if (!/^\d+$/.test(raw))
384
+ return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: `${option} requires a non-negative integer` };
385
+ const value = Number(raw);
386
+ if (!Number.isSafeInteger(value)) {
387
+ return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: `${option} is outside the supported integer range` };
388
+ }
389
+ return { ok: true, value };
390
+ }
391
+ function parsePositiveInteger(raw, option) {
392
+ const parsed = parseNonNegativeInteger(raw, option);
393
+ if (!parsed.ok || parsed.value === undefined)
394
+ return parsed;
395
+ if (parsed.value < 1)
396
+ return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: `${option} requires a positive integer` };
397
+ return parsed;
398
+ }
399
+ function validateOutputFormat(format) {
400
+ if (format === undefined || format === 'json' || format === 'text')
401
+ return { ok: true };
402
+ return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: `Unsupported config output format: ${format}` };
403
+ }
404
+ function buildFieldCanonicalArgv(subcommand, field, rawValue, selector, format) {
405
+ const argv = ['config', subcommand, field];
406
+ if (rawValue !== undefined)
407
+ argv.push(rawValue);
408
+ appendCanonicalSelector(argv, selector);
409
+ if (format)
410
+ argv.push('--format', format);
411
+ return argv;
412
+ }
413
+ function buildScopedCanonicalArgv(subcommand, selector, field, format) {
414
+ const argv = ['config', subcommand];
415
+ if (field)
416
+ argv.push(field);
417
+ appendCanonicalSelector(argv, selector);
418
+ if (format)
419
+ argv.push('--format', format);
420
+ return argv;
421
+ }
422
+ function appendCanonicalSelector(argv, selector) {
423
+ if (selector.scope === 'process')
424
+ argv.push('--process');
425
+ if (selector.scope === 'defaults')
426
+ argv.push('--default');
427
+ if (selector.self)
428
+ argv.push('--self', selector.self);
429
+ if (selector.peerKey)
430
+ argv.push('--peer', selector.peerKey);
431
+ }
432
+ function buildGlobalCanonicalArgv(command) {
433
+ const argv = ['config', command.subcommand];
434
+ if (command.subcommand === 'diff' && command.versions)
435
+ argv.push(...command.versions);
436
+ if (command.subcommand === 'restore' && command.version)
437
+ argv.push(command.version);
438
+ if (command.subcommand === 'snapshot') {
439
+ if (command.full)
440
+ argv.push('--full');
441
+ if (command.description !== undefined)
442
+ argv.push('--desc', command.description);
443
+ }
444
+ if (command.subcommand === 'prune') {
445
+ if (command.keepFull !== undefined)
446
+ argv.push('--keep-full', String(command.keepFull));
447
+ if (command.keepDelta !== undefined)
448
+ argv.push('--keep-delta', String(command.keepDelta));
449
+ if (command.yes)
450
+ argv.push('--yes');
451
+ }
452
+ if (command.subcommand === 'boots' && command.count !== undefined)
453
+ argv.push('-n', String(command.count));
454
+ if (command.subcommand === 'schema') {
455
+ if (command.schemaName)
456
+ argv.push(command.schemaName);
457
+ if (command.schemaVersion !== undefined)
458
+ argv.push(String(command.schemaVersion));
459
+ if (command.schemaList)
460
+ argv.push('--list');
461
+ }
462
+ if (command.format)
463
+ argv.push('--format', command.format);
464
+ return argv;
465
+ }
466
+ function coerceConfigValue(raw, route) {
467
+ if (route.merge === 'list')
468
+ return [raw];
469
+ if (route.enum)
470
+ return raw;
471
+ if (raw === 'true')
472
+ return true;
473
+ if (raw === 'false')
474
+ return false;
475
+ if (/^-?\d+(\.\d+)?$/.test(raw))
476
+ return Number(raw);
477
+ return raw;
478
+ }
479
+ function configErrorResult(error) {
480
+ if (error instanceof ConfigError)
481
+ return { ok: false, code: error.code, reason: error.message };
482
+ return { ok: false, code: 'CONFIG_ERROR', reason: error instanceof Error ? error.message : String(error) };
483
+ }
@@ -0,0 +1,32 @@
1
+ import path from 'path';
2
+ import { pathToFileURL } from 'url';
3
+ import { getPackageRoot, resolvePaths } from '../paths.js';
4
+ import { assertRoleConfigV4Ready, ConfigError } from './config-manager.js';
5
+ /**
6
+ * Automatically applies the one-off role config migration before daemon startup.
7
+ * The migration script owns backup, validation, atomic writes, rollback, and locking.
8
+ */
9
+ export async function ensureRoleConfigV4OnStartup() {
10
+ try {
11
+ assertRoleConfigV4Ready();
12
+ return null;
13
+ }
14
+ catch (error) {
15
+ if (!(error instanceof ConfigError) || error.code !== 'ROLE_CONFIG_MIGRATION_REQUIRED')
16
+ throw error;
17
+ }
18
+ const home = resolvePaths().root;
19
+ const script = path.join(getPackageRoot(), 'kits', 'migrations', 'migrate-role-config-v4.mjs');
20
+ try {
21
+ const migration = await import(pathToFileURL(script).href);
22
+ const result = migration.runMigration({ home, apply: true });
23
+ assertRoleConfigV4Ready();
24
+ return result;
25
+ }
26
+ catch (error) {
27
+ const detail = error instanceof Error ? error.message : String(error);
28
+ throw new ConfigError('ROLE_CONFIG_MIGRATION_FAILED', `Automatic role config v4 migration failed: ${detail}\n` +
29
+ `Fix the reported config or run manually:\n` +
30
+ `node kits/migrations/migrate-role-config-v4.mjs --apply --home ${JSON.stringify(home)}`, { cause: error });
31
+ }
32
+ }
@@ -0,0 +1,27 @@
1
+ import path from 'path';
2
+ import { pathToFileURL } from 'url';
3
+ import { getPackageRoot, resolvePaths } from '../paths.js';
4
+ import { assertRoleConfigV5Ready, ConfigError } from './config-manager.js';
5
+ export async function ensureRoleConfigV5OnStartup() {
6
+ try {
7
+ assertRoleConfigV5Ready();
8
+ return null;
9
+ }
10
+ catch (error) {
11
+ if (!(error instanceof ConfigError) || error.code !== 'ROLE_CONFIG_V5_MIGRATION_REQUIRED')
12
+ throw error;
13
+ }
14
+ const home = resolvePaths().root;
15
+ const script = path.join(getPackageRoot(), 'kits', 'migrations', 'migrate-role-config-v5.mjs');
16
+ try {
17
+ const migration = await import(pathToFileURL(script).href);
18
+ const result = migration.runMigration({ home, apply: true });
19
+ assertRoleConfigV5Ready();
20
+ return result;
21
+ }
22
+ catch (error) {
23
+ const detail = error instanceof Error ? error.message : String(error);
24
+ throw new ConfigError('ROLE_CONFIG_V5_MIGRATION_FAILED', `Automatic standalone role migration failed: ${detail}\n` +
25
+ `node kits/migrations/migrate-role-config-v5.mjs --apply --home ${JSON.stringify(home)}`, { cause: error });
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ export const ROLE_RANKS = Object.freeze({
2
+ owner: 900,
3
+ admin: 700,
4
+ custom: 500,
5
+ member: 300,
6
+ visitor: 100,
7
+ });
8
+ export function expectedRoleRank(role) {
9
+ if (role === 'owner')
10
+ return ROLE_RANKS.owner;
11
+ if (role === 'admin')
12
+ return ROLE_RANKS.admin;
13
+ if (role === 'member')
14
+ return ROLE_RANKS.member;
15
+ if (role === 'visitor')
16
+ return ROLE_RANKS.visitor;
17
+ return ROLE_RANKS.custom;
18
+ }
@@ -0,0 +1,105 @@
1
+ import { loadSchema } from './schema-registry.js';
2
+ export function rolePermissionFields() {
3
+ const raw = loadSchema('role-config').raw['x-permission-fields'];
4
+ if (!Array.isArray(raw))
5
+ return [];
6
+ return raw
7
+ .filter(isDescriptor)
8
+ .map(field => ({
9
+ value: field.value,
10
+ label: field.label,
11
+ valueType: field.valueType,
12
+ hasOptions: field.hasOptions,
13
+ relationOverrideApplied: field.relationOverrideApplied,
14
+ constraintsApplied: field.constraintsApplied,
15
+ editable: field.editable,
16
+ runtimeApplied: field['x-runtimeApplied'],
17
+ optionsSource: field['x-optionsSource'],
18
+ options: field.options?.map((option) => ({ ...option })),
19
+ patterns: field.patterns?.map((pattern) => ({ ...pattern, kind: 'pattern' })),
20
+ }));
21
+ }
22
+ function isDescriptor(value) {
23
+ if (!value || typeof value !== 'object')
24
+ return false;
25
+ const item = value;
26
+ return typeof item.value === 'string'
27
+ && typeof item.label === 'string'
28
+ && (item.valueType === 'enum' || item.valueType === 'model')
29
+ && typeof item.hasOptions === 'boolean'
30
+ && typeof item.relationOverrideApplied === 'boolean'
31
+ && typeof item.constraintsApplied === 'boolean'
32
+ && typeof item.editable === 'boolean'
33
+ && typeof item['x-runtimeApplied'] === 'boolean'
34
+ && (item['x-optionsSource'] === 'static-enum' || item['x-optionsSource'] === 'model-catalog');
35
+ }
36
+ export function rolePermissionFieldOptions(field) {
37
+ return field.options?.map(option => ({ ...option })) ?? [];
38
+ }
39
+ export function rolePermissionFieldPatterns(field) {
40
+ return field.patterns?.map(pattern => ({ ...pattern })) ?? [];
41
+ }
42
+ /** Shared RoleService/Menu validation backed by the published role schema. */
43
+ export function validateRoleConfig(value) {
44
+ const normalized = isRecord(value) && !Object.hasOwn(value, '$schema_version')
45
+ ? { ...value, $schema_version: 1 }
46
+ : value;
47
+ const schema = loadSchema('role-config');
48
+ if (!schema.validate(normalized)) {
49
+ return (schema.validate.errors ?? []).map(error => ({
50
+ field: schemaErrorField(error),
51
+ code: String(error.keyword || 'SCHEMA_INVALID').toUpperCase(),
52
+ message: error.message || 'role configuration is invalid',
53
+ }));
54
+ }
55
+ return validateRoleConfigSemantics(normalized);
56
+ }
57
+ /** Validate constraints that JSON Schema draft-07 cannot express across sibling fields. */
58
+ export function validateRoleConfigSemantics(value) {
59
+ if (!isRecord(value) || !isRecord(value.permissions))
60
+ return [];
61
+ const issues = [];
62
+ for (const [field, rawPermission] of Object.entries(value.permissions)) {
63
+ if (!isRecord(rawPermission))
64
+ continue;
65
+ const defaultValue = rawPermission.default;
66
+ if (Array.isArray(rawPermission.allowedValues)
67
+ && !rawPermission.allowedValues.some(candidate => Object.is(candidate, defaultValue))) {
68
+ issues.push({
69
+ field: `permissions.${field}.default`,
70
+ code: 'VALUE_NOT_ALLOWED',
71
+ message: 'default must be included in allowedValues',
72
+ });
73
+ }
74
+ if (Array.isArray(rawPermission.allowedModels)
75
+ && typeof defaultValue === 'string'
76
+ && !rawPermission.allowedModels.some(pattern => typeof pattern === 'string' && wildcardMatch(pattern, defaultValue))) {
77
+ issues.push({
78
+ field: `permissions.${field}.default`,
79
+ code: 'MODEL_NOT_ALLOWED',
80
+ message: 'default model must be allowed by allowedModels',
81
+ });
82
+ }
83
+ }
84
+ return issues;
85
+ }
86
+ function wildcardMatch(pattern, value) {
87
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
88
+ return new RegExp(`^${escaped}$`).test(value);
89
+ }
90
+ function isRecord(value) {
91
+ return !!value && typeof value === 'object' && !Array.isArray(value);
92
+ }
93
+ function schemaErrorField(error) {
94
+ const path = String(error?.instancePath || '')
95
+ .split('/')
96
+ .filter(Boolean)
97
+ .map((part) => part.replace(/~1/g, '/').replace(/~0/g, '~'));
98
+ if (error?.keyword === 'required' && typeof error?.params?.missingProperty === 'string') {
99
+ path.push(error.params.missingProperty);
100
+ }
101
+ else if (error?.keyword === 'additionalProperties' && typeof error?.params?.additionalProperty === 'string') {
102
+ path.push(error.params.additionalProperty);
103
+ }
104
+ return path.filter(part => part !== '$schema_version').join('.') || 'definition';
105
+ }