@whyshu/argent-engine 0.0.0-stage → 0.6.1
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/MANIFEST.json +341 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +576 -0
- package/engine/scripts/billing-mode-daily.sh +13 -0
- package/engine/scripts/build-engine-package.mjs +126 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +378 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/collect-evidence.mjs +42 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +135 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +47 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +261 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/final-check.mjs +43 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +735 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +87 -0
- package/engine/scripts/flow-timers.sh +184 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1025 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2278 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +195 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent-datadir.sh +18 -0
- package/engine/scripts/lib/argent_datadir.py +285 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +295 -0
- package/engine/scripts/poll-invoice.sh +76 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +71 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/retire-inventory.mjs +40 -0
- package/engine/scripts/run-tests.sh +257 -0
- package/engine/scripts/s1-audit.mjs +85 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +137 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +74 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +35 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +217 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
- package/engine/scripts/ziniao-windows.sh +154 -0
- package/engine/skills/README.md +86 -0
- package/engine/skills/argent-dev/SKILL.md +41 -0
- package/engine/skills/internal.json +37 -0
- package/engine/skills/lktop-pm/SKILL.md +35 -0
- package/engine/skills/lktopapp-dev/SKILL.md +34 -0
- package/engine/skills/ml-config-guide/SKILL.md +50 -0
- package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
- package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
- package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
- package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
- package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
- package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
- package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
- package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
- package/engine/skills/ml-invoice/SKILL.md +506 -0
- package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
- package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
- package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
- package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
- package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
- package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
- package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
- package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
- package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
- package/engine/skills/pet-dev/SKILL.md +42 -0
- package/engine/skills/whyshu-pm/SKILL.md +35 -0
- package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +738 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +22 -4
- package/requirements.txt +5 -0
- package/README.md +0 -3
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""能力×档位矩阵(迭代 3-⑦):**唯一出处 + 派生 + 校验**。
|
|
3
|
+
|
|
4
|
+
## 问题(实测已漂移)
|
|
5
|
+
"哪个角色需要哪一档"此前散在三处各判:
|
|
6
|
+
· 服务端 whyshu-svr 的 `ROLE_PLAN`;
|
|
7
|
+
· 网关 `plugins/argent-gateway/src/index.js` 的 `ROLE_PLAN`(注释写着"与服务端对齐");
|
|
8
|
+
· 客户端 `plugins/argent-skin/dist/client.js` 里写死 `ml-daily`/`ml-fulfillment` 与 `pro/enterprise`。
|
|
9
|
+
实测漂移:网关把 `ml-daily` 写成 **pro**(文档口径是**企业级**),而两条发票角色
|
|
10
|
+
(`ml-invoice-wy` / `ml-invoice-xzh`)**根本没登记** ⇒ `planOf()` 默认落回 **free**
|
|
11
|
+
⇒ 免费版会把企业级角色显示成可用(用户以为自己能用)。
|
|
12
|
+
|
|
13
|
+
## 口径(唯一出处 = 注册表)
|
|
14
|
+
- 流程角色的档位 = 其 `capability` 的 `minPlan`(`flows.<id>.capability` → `capabilities.<c>.minPlan`);
|
|
15
|
+
- 不挂流程的角色在 `entitlements.roleMinPlan` 里显式声明;
|
|
16
|
+
- 网关表由 `--emit-gateway-table` **机械派生**(不要手写);
|
|
17
|
+
- 客户端/安装器的档位判断由门禁校验(`scripts/tests/entitlements.test.mjs`)。
|
|
18
|
+
|
|
19
|
+
用法:
|
|
20
|
+
python3 scripts/entitlements.py --matrix [--json]
|
|
21
|
+
python3 scripts/entitlements.py --role ml-invoice-wy
|
|
22
|
+
python3 scripts/entitlements.py --validate [--json]
|
|
23
|
+
python3 scripts/entitlements.py --emit-gateway-table # 打印网关 ROLE_PLAN 片段
|
|
24
|
+
退出码:0 一致;1 有问题;2 读取失败。
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import pathlib
|
|
31
|
+
import re
|
|
32
|
+
import sys
|
|
33
|
+
|
|
34
|
+
HERE = pathlib.Path(__file__).resolve().parent
|
|
35
|
+
sys.path.insert(0, str(HERE / "lib"))
|
|
36
|
+
from stdio_utf8 import use_utf8_stdio # noqa: E402 出口编码钉 UTF-8(中文 Windows 的 GBK 管道下非 ASCII 会崩)
|
|
37
|
+
REGISTRY = HERE / "flow-requirements.json"
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
from flow_registry import consumer_doc as _registry_doc
|
|
41
|
+
except Exception: # noqa: BLE001 —— 半坏机器上退回单文件(升级链路要能跑)
|
|
42
|
+
def _registry_doc(path=None):
|
|
43
|
+
return json.loads((pathlib.Path(path) if path else REGISTRY).read_text(encoding="utf-8"))
|
|
44
|
+
|
|
45
|
+
GATEWAY = HERE.parent / "plugins" / "argent-gateway" / "src" / "index.js"
|
|
46
|
+
GATEWAY_CLIENT = HERE.parent / "plugins" / "argent-gateway" / "dist" / "client.js"
|
|
47
|
+
INSTALLER = HERE.parent / "install-argent.mjs"
|
|
48
|
+
CLIENT = HERE.parent / "plugins" / "argent-skin" / "dist" / "client.js"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load() -> dict:
|
|
52
|
+
return _registry_doc()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def matrix(doc: dict | None = None) -> dict:
|
|
56
|
+
"""能力×档位矩阵:{capability: {minPlan, flows, roles}} + 角色反查。"""
|
|
57
|
+
d = doc or load()
|
|
58
|
+
ents = d.get("entitlements") or {}
|
|
59
|
+
tiers = list(ents.get("planTiers") or ["free", "pro", "enterprise"])
|
|
60
|
+
caps = {k: v for k, v in (d.get("capabilities") or {}).items() if not k.startswith("_")}
|
|
61
|
+
out = {}
|
|
62
|
+
role_plan: dict = {}
|
|
63
|
+
for cid, c in caps.items():
|
|
64
|
+
flows = [fid for fid, f in (d.get("flows") or {}).items() if f.get("capability") == cid]
|
|
65
|
+
roles = sorted({str((d["flows"][fid] or {}).get("role") or "") for fid in flows} - {""})
|
|
66
|
+
# ⛔ **不再**从 `pack.presets` 派生"能力 → 角色"(2026-10-06,P0-14/功能 bug)。
|
|
67
|
+
# 2026-09-21 批 C1 曾加过这条:无 flow 的能力(HR 办公)靠 preset 提供入口 ⇒ 想让网关
|
|
68
|
+
# ROLE_PLAN 里有它的角色。但那个前提 2026-10-04 已失效 —— `hr-office` 角色**退役**,
|
|
69
|
+
# preset 只剩**墓碑**(web 老会话的 agentPreset 外键);而派生出来的角色名会被写进
|
|
70
|
+
# 客户端生成块的 `CAPABILITY_ROLES` ⇒ `capabilityAllowed("hr")` 要求角色表里出现
|
|
71
|
+
# **已退役且 gateway.json 里根本没有**的 `hr-office` ⇒ **恒判"未装"**,付费客户用不了 HR。
|
|
72
|
+
# 口径改为:**能力按能力包判**(`CAPABILITY_MINPLAN[cap]` = 注册表 minPlan;+可选
|
|
73
|
+
# `installed` 能力清单),角色映射只保留"真挂流程的角色"(那些角色必然在 gateway.json 里)。
|
|
74
|
+
# `hr` ⇒ roles=[] ⇒ 只看档位(与审计 §3-#14 的正解一致)。
|
|
75
|
+
# 退休角色的档位仍由 `entitlements.roleMinPlan` 负责(网关 ROLE_PLAN 的**唯一**出处)。
|
|
76
|
+
out[cid] = {"minPlan": str(c.get("minPlan") or ""), "flows": flows, "roles": roles}
|
|
77
|
+
for r in roles:
|
|
78
|
+
role_plan[r] = str(c.get("minPlan") or "")
|
|
79
|
+
for r, p in (ents.get("roleMinPlan") or {}).items(): # 不挂流程的角色
|
|
80
|
+
role_plan.setdefault(str(r), str(p))
|
|
81
|
+
out["_planTiers"] = tiers
|
|
82
|
+
out["_roles"] = role_plan
|
|
83
|
+
return out
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def rank(plan: str, tiers: list) -> int:
|
|
87
|
+
try:
|
|
88
|
+
return tiers.index(str(plan))
|
|
89
|
+
except ValueError:
|
|
90
|
+
return -1
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def norm(block: str) -> str:
|
|
94
|
+
"""生成块比较用:忽略每行缩进(bundle 里会整体缩进),只比内容。"""
|
|
95
|
+
return "\n".join(ln.strip() for ln in str(block).splitlines() if ln.strip())
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def ents_role_min(doc: dict | None = None) -> dict:
|
|
99
|
+
"""`entitlements.roleMinPlan`(不挂能力的角色档位;历史字段,保留兼容)。"""
|
|
100
|
+
d = doc or load()
|
|
101
|
+
return {str(k): str(v) for k, v in ((d.get("entitlements") or {}).get("roleMinPlan") or {}).items()}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def free_tier(doc: dict | None = None) -> str:
|
|
105
|
+
"""免费档名(`entitlements.freeTier`,默认 planTiers[0])——"免费版零 B 端能力"的判据锚点。"""
|
|
106
|
+
d = doc or load()
|
|
107
|
+
ents = d.get("entitlements") or {}
|
|
108
|
+
tiers = list(ents.get("planTiers") or ["free", "pro", "enterprise"])
|
|
109
|
+
return str(ents.get("freeTier") or (tiers[0] if tiers else "free"))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def plans_matrix(doc: dict | None = None) -> dict:
|
|
113
|
+
"""套餐(SKU)→ 能力集合:{planId: {title, capabilities{cap:tier}, tier, capabilities[]}}。
|
|
114
|
+
|
|
115
|
+
`tier` = 该套餐授予的最高档(用于展示与排序,不参与授权判定 —— 授权判定仍按能力的 minPlan)。
|
|
116
|
+
"""
|
|
117
|
+
d = doc or load()
|
|
118
|
+
ents = d.get("entitlements") or {}
|
|
119
|
+
tiers = list(ents.get("planTiers") or ["free", "pro", "enterprise"])
|
|
120
|
+
out: dict = {}
|
|
121
|
+
for pid, pl in (ents.get("plans") or {}).items():
|
|
122
|
+
if pid.startswith("_") or not isinstance(pl, dict):
|
|
123
|
+
continue
|
|
124
|
+
cap_map = {str(k): str(v) for k, v in (pl.get("capabilities") or {}).items()}
|
|
125
|
+
top = ""
|
|
126
|
+
for t in cap_map.values():
|
|
127
|
+
if rank(t, tiers) > rank(top, tiers):
|
|
128
|
+
top = t
|
|
129
|
+
out[pid] = {"title": str(pl.get("title") or pid), "capabilities": cap_map,
|
|
130
|
+
"tier": top, "ids": sorted(cap_map)}
|
|
131
|
+
return out
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def validate(doc: dict | None = None) -> list:
|
|
135
|
+
"""一致性校验 → 问题清单(空=通过)。"""
|
|
136
|
+
d = doc or load()
|
|
137
|
+
m = matrix(d)
|
|
138
|
+
tiers = m["_planTiers"]
|
|
139
|
+
role_plan = m["_roles"]
|
|
140
|
+
problems = []
|
|
141
|
+
|
|
142
|
+
for cid, c in m.items():
|
|
143
|
+
if cid.startswith("_"):
|
|
144
|
+
continue
|
|
145
|
+
if rank(c["minPlan"], tiers) < 0:
|
|
146
|
+
problems.append(f"能力 {cid} 的 minPlan={c['minPlan']!r} 不在 planTiers {tiers} 内")
|
|
147
|
+
pk = ((d.get("capabilities") or {}).get(cid) or {}).get("pack") or {}
|
|
148
|
+
# 「装了有入口」的三种形态(2026-09-21 批 C1 放宽):flow(流程)/ entrypoints(工作台入口)/
|
|
149
|
+
# presets(角色会话 —— HR 这类纯本地能力就是这种:无 flow,入口是 preset)。
|
|
150
|
+
if not c["flows"] and not (pk.get("entrypoints") or []) and not (pk.get("presets") or []):
|
|
151
|
+
problems.append(f"能力 {cid} 既没有 flow、也没有 pack.entrypoints / pack.presets ⇒ 装了也没入口")
|
|
152
|
+
if rank(c["minPlan"], tiers) <= rank(free_tier(d), tiers) and c["flows"]:
|
|
153
|
+
problems.append(f"能力 {cid} 是免费档却挂了 {len(c['flows'])} 条流程"
|
|
154
|
+
f"({c['flows']})⇒ C 端必须零 flow(免费档不得拥有任何 B 端流程)")
|
|
155
|
+
for r, p in role_plan.items():
|
|
156
|
+
if rank(p, tiers) < 0:
|
|
157
|
+
problems.append(f"角色 {r} 的档位 {p!r} 不在 planTiers 内")
|
|
158
|
+
|
|
159
|
+
# ── 套餐(SKU = 能力集合,2026-09-21 批 C1)────────────────────────────
|
|
160
|
+
# 为什么要有这一节:此前只有线性三档 ⇒ "只买 HR" / "只要美客多不要 HR" 都表达不了。
|
|
161
|
+
# 门禁守住三件事:① 套餐里的能力与档位都真实存在;② **每个能力至少被一个套餐授予**
|
|
162
|
+
# (否则客户买不到、装不上);③ `capabilities.<c>.minPlan` = 授予它的**最低档**
|
|
163
|
+
# (派生一致 ⇒ 网关/客户端/服务端既有的档位判定一行不用改,也不会与套餐悄悄漂移)。
|
|
164
|
+
plans = (d.get("entitlements") or {}).get("plans") or {}
|
|
165
|
+
granted: dict = {}
|
|
166
|
+
for pid, pl in plans.items():
|
|
167
|
+
if pid.startswith("_"):
|
|
168
|
+
continue
|
|
169
|
+
if not isinstance(pl, dict):
|
|
170
|
+
problems.append(f"套餐 {pid} 定义不是对象")
|
|
171
|
+
continue
|
|
172
|
+
for cid, tier in (pl.get("capabilities") or {}).items():
|
|
173
|
+
if cid not in m:
|
|
174
|
+
problems.append(f"套餐 {pid} 授予了不存在的能力 {cid!r}")
|
|
175
|
+
continue
|
|
176
|
+
if rank(str(tier), tiers) < 0:
|
|
177
|
+
problems.append(f"套餐 {pid} 给能力 {cid} 的档位 {tier!r} 不在 planTiers {tiers} 内")
|
|
178
|
+
continue
|
|
179
|
+
granted.setdefault(cid, []).append(str(tier))
|
|
180
|
+
if not plans:
|
|
181
|
+
problems.append("entitlements 缺 plans(套餐=能力集合)⇒ 无法按套餐授权/安装")
|
|
182
|
+
for cid, c in m.items():
|
|
183
|
+
if cid.startswith("_"):
|
|
184
|
+
continue
|
|
185
|
+
if cid not in granted:
|
|
186
|
+
problems.append(f"能力 {cid} 没有被任何套餐授予 ⇒ 客户买不到、装不上")
|
|
187
|
+
continue
|
|
188
|
+
derived = min(granted[cid], key=lambda t: rank(t, tiers))
|
|
189
|
+
if derived != c["minPlan"]:
|
|
190
|
+
problems.append(f"能力 {cid} 的 minPlan={c['minPlan']!r} 与套餐派生的最低档 {derived!r} 不一致"
|
|
191
|
+
f"(套餐授予档位:{sorted(set(granted[cid]))})")
|
|
192
|
+
# roleMinPlan 若同时存在,必须与套餐派生结果一致(避免两处口径)
|
|
193
|
+
for r, p in (ents_role_min(d) or {}).items():
|
|
194
|
+
if r in role_plan and role_plan[r] != str(p):
|
|
195
|
+
problems.append(f"角色 {r}:roleMinPlan={p!r} 与能力/套餐派生的档位 {role_plan[r]!r} 不一致")
|
|
196
|
+
|
|
197
|
+
# 网关的 ROLE_PLAN 必须与矩阵一致(且覆盖全部流程角色)
|
|
198
|
+
if GATEWAY.exists():
|
|
199
|
+
src = GATEWAY.read_text(encoding="utf-8")
|
|
200
|
+
blk = re.search(r"const ROLE_PLAN = Object\.assign\(Object\.create\(null\), \{(.*?)\}\)", src, re.S)
|
|
201
|
+
if not blk:
|
|
202
|
+
problems.append("网关里找不到 ROLE_PLAN 表(改过结构?请同步本校验)")
|
|
203
|
+
else:
|
|
204
|
+
got = dict(re.findall(r"'([^']+)':\s*'([^']+)'", blk.group(1)))
|
|
205
|
+
for r in sorted(role_plan):
|
|
206
|
+
if r in got and got[r] != role_plan[r]:
|
|
207
|
+
problems.append(f"网关 ROLE_PLAN[{r}]={got[r]!r} 与矩阵 {role_plan[r]!r} 不一致")
|
|
208
|
+
flow_roles = {r for cid, c in m.items() if not cid.startswith("_") for r in c["roles"]}
|
|
209
|
+
missing = sorted(flow_roles - set(got))
|
|
210
|
+
if missing:
|
|
211
|
+
problems.append(f"网关 ROLE_PLAN 漏登记流程角色 {missing}"
|
|
212
|
+
f"(planOf 会默认 free ⇒ 免费版显示成可用)")
|
|
213
|
+
extra = sorted(set(got) - set(role_plan))
|
|
214
|
+
if extra:
|
|
215
|
+
problems.append(f"网关 ROLE_PLAN 多出未知角色 {extra}(矩阵里没有声明)")
|
|
216
|
+
|
|
217
|
+
# 网关客户端 bundle:档位分组必须**读载荷 minPlan**,不许再手写"某角色=某档"
|
|
218
|
+
if GATEWAY_CLIENT.exists():
|
|
219
|
+
gsrc = GATEWAY_CLIENT.read_text(encoding="utf-8")
|
|
220
|
+
hard = re.findall(r"'(ml-[a-z-]+|hr-office|pet-dev)':\s*'(free|pro|enterprise)'", gsrc)
|
|
221
|
+
if hard:
|
|
222
|
+
problems.append(f"网关客户端 bundle 里手写了角色→档位 {sorted(set(hard))}"
|
|
223
|
+
f"(应由 roles 载荷的 minPlan 派生,档位文案也别点角色名)")
|
|
224
|
+
pt = re.search(r"var PLAN_TITLES = \{([^}]*)\}", gsrc)
|
|
225
|
+
if pt:
|
|
226
|
+
keys = re.findall(r"([A-Za-z_][\w]*)\s*:", pt.group(1))
|
|
227
|
+
bad = [k for k in keys if k not in tiers]
|
|
228
|
+
if bad:
|
|
229
|
+
problems.append(f"网关客户端 PLAN_TITLES 的档位名 {bad} 不在 planTiers {tiers} 内")
|
|
230
|
+
if "minPlan" not in gsrc:
|
|
231
|
+
problems.append("网关客户端 bundle 不再读载荷 minPlan ⇒ 档位分组失去唯一出处")
|
|
232
|
+
|
|
233
|
+
# preset 描述里的档位词必须与矩阵一致 —— 2026-09-20 实测:官网"美客多日报(Pro)"
|
|
234
|
+
# 就是从 `presets/argent-ml-daily/preset.yml` 的"(订阅 Pro)"抄来的,而矩阵是企业版。
|
|
235
|
+
# 这类"人读文案里的档位"没人守就会一直漂(还会被抄到官网/销售材料),所以让它进门禁。
|
|
236
|
+
# 中文词直接子串匹配;ASCII 档位词用词边界(否则 "provider"/"prompt"/"profile" 里的 pro 会误报)
|
|
237
|
+
tier_hits = [(r"免费", free_tier(d)), (r"\bfree\b", free_tier(d)),
|
|
238
|
+
(r"\bpro\b", "pro"), (r"专业版", "pro"),
|
|
239
|
+
(r"\benterprise\b", "enterprise"), (r"企业版", "enterprise")]
|
|
240
|
+
for role, plan in sorted(role_plan.items()):
|
|
241
|
+
py_file = HERE.parent / "presets" / f"argent-{role}" / "preset.yml"
|
|
242
|
+
if not py_file.exists():
|
|
243
|
+
continue
|
|
244
|
+
desc = ""
|
|
245
|
+
for line in py_file.read_text(encoding="utf-8").splitlines():
|
|
246
|
+
if line.startswith("description:"):
|
|
247
|
+
desc = line
|
|
248
|
+
break
|
|
249
|
+
wrong = sorted({t for pat, t in tier_hits if re.search(pat, desc, re.I)} - {plan})
|
|
250
|
+
if wrong:
|
|
251
|
+
problems.append(f"preset `presets/argent-{role}/preset.yml` 描述里的档位 {wrong} "
|
|
252
|
+
f"与矩阵({plan})不一致 —— 官网/用户看到的档位就是从这里抄走的")
|
|
253
|
+
|
|
254
|
+
# 安装器的免费角色里不得出现任何流程角色(C 端零 flow 的第二道口子)
|
|
255
|
+
if INSTALLER.exists():
|
|
256
|
+
src = INSTALLER.read_text(encoding="utf-8")
|
|
257
|
+
fr = re.search(r"FREE_ROLES = \[(.*?)\]", src, re.S)
|
|
258
|
+
if not fr:
|
|
259
|
+
problems.append("install-argent.mjs 里找不到 FREE_ROLES(改过结构?请同步本校验)")
|
|
260
|
+
else:
|
|
261
|
+
free = re.findall(r"'([^']+)'", fr.group(1))
|
|
262
|
+
bad = sorted(set(free) & set(role_plan))
|
|
263
|
+
if bad:
|
|
264
|
+
problems.append(f"install-argent.mjs 的 FREE_ROLES 含流程角色 {bad} ⇒ B 端能力会被免费安装")
|
|
265
|
+
|
|
266
|
+
# 客户端的档位判断:必须是**生成块**且与注册表派生的块逐字节一致(无第二份手写判据)
|
|
267
|
+
if CLIENT.exists():
|
|
268
|
+
src = CLIENT.read_text(encoding="utf-8")
|
|
269
|
+
want = emit_client_block(d)
|
|
270
|
+
got = re.search(r"/\* \[entitlements:begin\].*?\[entitlements:end\] \*/", src, re.S)
|
|
271
|
+
if not got:
|
|
272
|
+
problems.append("客户端 bundle 里没有 [entitlements:begin/end] 生成块"
|
|
273
|
+
"⇒ 档位判断还是手写的(跑 --emit-client-block 并整块覆盖)")
|
|
274
|
+
elif norm(got.group(0)) != norm(want):
|
|
275
|
+
problems.append("客户端 bundle 的 [entitlements] 生成块与注册表派生结果不一致"
|
|
276
|
+
"⇒ 改过注册表没重生成(跑 --emit-client-block 整块覆盖)")
|
|
277
|
+
else:
|
|
278
|
+
# 生成块在,但不许再出现绕过它的手写判据
|
|
279
|
+
rest = src.replace(got.group(0), "")
|
|
280
|
+
for m2 in re.finditer(r'\.plan === "([^"]+)"', rest):
|
|
281
|
+
if m2.group(1) not in tiers:
|
|
282
|
+
problems.append(f"客户端判断的档位名 {m2.group(1)!r} 不在 planTiers {tiers} 内")
|
|
283
|
+
for m2 in re.finditer(r'r\.role === "([^"]+)"', rest):
|
|
284
|
+
if m2.group(1) not in role_plan:
|
|
285
|
+
problems.append(f"客户端按角色名硬编码门禁,但矩阵里没有 {m2.group(1)!r}")
|
|
286
|
+
caps_known = set(m)
|
|
287
|
+
for m2 in re.finditer(r'capabilityAllowed\("([^"]+)"', rest):
|
|
288
|
+
if m2.group(1) not in caps_known:
|
|
289
|
+
problems.append(f"客户端引用了矩阵里没有的能力 {m2.group(1)!r}"
|
|
290
|
+
f"(改名/删能力必须同步客户端,否则该入口永远不显示)")
|
|
291
|
+
return problems
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def emit_gateway_table(doc: dict | None = None) -> str:
|
|
295
|
+
"""机械派生网关 ROLE_PLAN 片段(手写必然漂移)。"""
|
|
296
|
+
m = matrix(doc)
|
|
297
|
+
rp = m["_roles"]
|
|
298
|
+
lines = ["const ROLE_PLAN = Object.assign(Object.create(null), {"]
|
|
299
|
+
for r in sorted(rp):
|
|
300
|
+
lines.append(f" '{r}': '{rp[r]}',")
|
|
301
|
+
lines.append("})")
|
|
302
|
+
return "\n".join(lines)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def emit_client_block(doc: dict | None = None) -> str:
|
|
306
|
+
"""机械派生客户端档位判断块(client.js 里手写必然漂移)。"""
|
|
307
|
+
m = matrix(doc)
|
|
308
|
+
tiers = m["_planTiers"]
|
|
309
|
+
free = free_tier(doc)
|
|
310
|
+
cap_roles = {cid: c["roles"] for cid, c in m.items() if not cid.startswith("_")}
|
|
311
|
+
cap_minplan = {cid: c["minPlan"] for cid, c in m.items() if not cid.startswith("_")}
|
|
312
|
+
gated = sorted({r for roles in cap_roles.values() for r in roles})
|
|
313
|
+
body = [
|
|
314
|
+
"/* [entitlements:begin] 由 scripts/entitlements.py --emit-client-block 生成,请勿手改。",
|
|
315
|
+
" 唯一出处:scripts/flow-requirements.json(capabilities.*.minPlan / entitlements.*)。",
|
|
316
|
+
" 改注册表后重跑该命令并整块覆盖;scripts/tests/entitlements.test.mjs 断言本块无漂移。 */",
|
|
317
|
+
"var PLAN_TIERS = " + json.dumps(tiers, ensure_ascii=False) + ";",
|
|
318
|
+
"var FREE_TIER = " + json.dumps(free, ensure_ascii=False) + ";",
|
|
319
|
+
"// 能力 → 角色(角色的档位由网关按注册表 minPlan 下发;这里只留能力边界)",
|
|
320
|
+
"var CAPABILITY_ROLES = " + json.dumps(cap_roles, ensure_ascii=False, sort_keys=True) + ";",
|
|
321
|
+
"// 能力 → 所需档位(**注册表口径**):无角色的运行现状原生能力只靠这一条判定",
|
|
322
|
+
"var CAPABILITY_MINPLAN = " + json.dumps(cap_minplan, ensure_ascii=False, sort_keys=True) + ";",
|
|
323
|
+
"var GATED_ROLES = " + json.dumps(gated, ensure_ascii=False) + ";",
|
|
324
|
+
"var planRank = function (p) {",
|
|
325
|
+
" var i = PLAN_TIERS.indexOf(String(p == null ? '' : p));",
|
|
326
|
+
" return i < 0 ? 0 : i; // 未知/缺失档位一律按最低档(保守默认:宁可少给,不可让用户以为能用)",
|
|
327
|
+
"};",
|
|
328
|
+
"var isPaidPlan = function (p) { return planRank(p) > planRank(FREE_TIER); };",
|
|
329
|
+
"var capabilityRoleAllowed = function (r, cap, account) {",
|
|
330
|
+
" // 网关没下发 minPlan 时回落到**注册表口径**(r.minPlan 本来就是注册表下发的一份拷贝)",
|
|
331
|
+
" var need = (r && r.minPlan) || CAPABILITY_MINPLAN[cap];",
|
|
332
|
+
" return planRank(account && account.plan) >= planRank(need);",
|
|
333
|
+
"};",
|
|
334
|
+
"// 某能力是否可用:① 账号档位够该能力的 minPlan(注册表口径)",
|
|
335
|
+
"// ② 挑了能力包(第 4 参 `installedCaps` = **已装的能力包 id 清单**)⇒ 该能力必须在里面",
|
|
336
|
+
"// —— 「装了才给」的口径落在**能力**上,不再靠角色名映射(角色名会退役,能力不会)",
|
|
337
|
+
"// ③ 没挑能力包(第 4 参缺省,既有调用点)⇒ 有角色时按角色是否已装配判(保持原行为)",
|
|
338
|
+
"// ④ 无角色的能力(如 HR 办公,入口 = 通用助手)⇒ 只看 ①(+② 若挑了能力包)",
|
|
339
|
+
"var capabilityAllowed = function (cap, roles, account, installedCaps) {",
|
|
340
|
+
" var need = CAPABILITY_MINPLAN[cap];",
|
|
341
|
+
" if (!need) return false; // 未登记的能力 ⇒ 保守拒绝",
|
|
342
|
+
" if (planRank(account && account.plan) < planRank(need)) return false;",
|
|
343
|
+
" if (installedCaps) return (installedCaps || []).indexOf(cap) >= 0;",
|
|
344
|
+
" var want = CAPABILITY_ROLES[cap] || [];",
|
|
345
|
+
" if (!want.length) return true;",
|
|
346
|
+
" var list = (roles || []).filter(function (r) { return r && want.indexOf(r.role) >= 0; });",
|
|
347
|
+
" if (!list.length) return false; // 有角色但一个都没装配 ⇒ 未装好,不给",
|
|
348
|
+
" return list.every(function (r) { return capabilityRoleAllowed(r, cap, account); });",
|
|
349
|
+
"};",
|
|
350
|
+
"// 某档位及以上的能力(派生自注册表:客户端**不写死**\"哪些能力是企业级\")",
|
|
351
|
+
"var capabilitiesAtOrAbove = function (plan, roles, account, installedCaps) {",
|
|
352
|
+
" var out = [];",
|
|
353
|
+
" Object.keys(CAPABILITY_MINPLAN).forEach(function (c) {",
|
|
354
|
+
" if (planRank(CAPABILITY_MINPLAN[c]) >= planRank(plan) && capabilityAllowed(c, roles, account, installedCaps)) out.push(c);",
|
|
355
|
+
" });",
|
|
356
|
+
" return out;",
|
|
357
|
+
"};",
|
|
358
|
+
"var anyCapabilityRegistered = function (roles) {",
|
|
359
|
+
" return (roles || []).some(function (r) { return r && GATED_ROLES.indexOf(r.role) >= 0; });",
|
|
360
|
+
"};",
|
|
361
|
+
"var anyCapabilityAllowed = function (roles, account, installedCaps) {",
|
|
362
|
+
" // 口径 = **有角色的能力**(与既有调用点兼容;`hr` 这类无角色能力不参与「总门控」):",
|
|
363
|
+
" // 若遍历全部能力,只装了**有角色那几条能力**的 Pro 账号会因为「HR 无角色、只看档位」而凭空通过",
|
|
364
|
+
" // (实测:pro 档 false → true)⇒ 那是放大权限,不是修复。第 4 参照常透传。",
|
|
365
|
+
" var caps = Object.keys(CAPABILITY_ROLES).filter(function (c) { return (CAPABILITY_ROLES[c] || []).length > 0; });",
|
|
366
|
+
" for (var i = 0; i < caps.length; i++) {",
|
|
367
|
+
" if (capabilityAllowed(caps[i], roles, account, installedCaps)) return true;",
|
|
368
|
+
" }",
|
|
369
|
+
" return false;",
|
|
370
|
+
"};",
|
|
371
|
+
"/* [entitlements:end] */",
|
|
372
|
+
]
|
|
373
|
+
return "\n".join(body)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def main(argv=None) -> int:
|
|
377
|
+
use_utf8_stdio() # 唯一出口编码判据:见 scripts/lib/stdio_utf8.py 的事故说明
|
|
378
|
+
ap = argparse.ArgumentParser(description="能力×档位矩阵(唯一出处 + 校验 + 派生)")
|
|
379
|
+
ap.add_argument("--matrix", action="store_true")
|
|
380
|
+
ap.add_argument("--role")
|
|
381
|
+
ap.add_argument("--validate", action="store_true")
|
|
382
|
+
ap.add_argument("--emit-gateway-table", action="store_true")
|
|
383
|
+
ap.add_argument("--emit-client-block", action="store_true")
|
|
384
|
+
ap.add_argument("--write-client-block", metavar="FILE",
|
|
385
|
+
help="把 [entitlements] 生成块原地写回该文件(默认 plugins/argent-skin/dist/client.js)")
|
|
386
|
+
ap.add_argument("--plans", action="store_true", help="打印套餐(SKU=能力集合)矩阵")
|
|
387
|
+
ap.add_argument("--json", action="store_true")
|
|
388
|
+
a = ap.parse_args(argv)
|
|
389
|
+
try:
|
|
390
|
+
doc = load()
|
|
391
|
+
except Exception as exc: # noqa: BLE001
|
|
392
|
+
print(f"❌ 读取注册表失败:{exc}", file=sys.stderr)
|
|
393
|
+
return 2
|
|
394
|
+
|
|
395
|
+
if a.plans:
|
|
396
|
+
pm = plans_matrix(doc)
|
|
397
|
+
if a.json:
|
|
398
|
+
print(json.dumps(pm, ensure_ascii=False, indent=2))
|
|
399
|
+
else:
|
|
400
|
+
print(f" 套餐({len(pm)} 个,档位 = 该套餐授予的最高档):")
|
|
401
|
+
for pid, p in pm.items():
|
|
402
|
+
caps = "、".join(f"{c}:{t}" for c, t in sorted(p["capabilities"].items()))
|
|
403
|
+
print(f" {pid:18} {p['title']:22} 档位={p['tier']:11} 能力={caps}")
|
|
404
|
+
return 0
|
|
405
|
+
if a.write_client_block:
|
|
406
|
+
target = pathlib.Path(a.write_client_block)
|
|
407
|
+
if not target.exists():
|
|
408
|
+
print(f"❌ 目标文件不存在:{target}", file=sys.stderr)
|
|
409
|
+
return 2
|
|
410
|
+
src = target.read_text(encoding="utf-8")
|
|
411
|
+
block = emit_client_block(doc)
|
|
412
|
+
# 与 validate() 用**同一个**匹配式(块首是说明性多行注释,不是 `begin] */`)
|
|
413
|
+
new = re.sub(r"/\* \[entitlements:begin\].*?\[entitlements:end\] \*/", lambda _m: block, src, flags=re.S)
|
|
414
|
+
if new == src:
|
|
415
|
+
print(" ℹ️ 生成块已是最新(未改动)")
|
|
416
|
+
return 0
|
|
417
|
+
target.write_text(new, encoding="utf-8")
|
|
418
|
+
print(f" ✅ 已写回 [entitlements] 生成块 → {target}")
|
|
419
|
+
return 0
|
|
420
|
+
if a.emit_gateway_table:
|
|
421
|
+
print(emit_gateway_table(doc))
|
|
422
|
+
return 0
|
|
423
|
+
if a.emit_client_block:
|
|
424
|
+
print(emit_client_block(doc))
|
|
425
|
+
return 0
|
|
426
|
+
if a.role:
|
|
427
|
+
m = matrix(doc)
|
|
428
|
+
plan = m["_roles"].get(a.role)
|
|
429
|
+
if plan is None:
|
|
430
|
+
print(f"❌ 角色 {a.role} 不在矩阵里(流程角色由 capability 派生;其它角色请写进 entitlements.roleMinPlan)",
|
|
431
|
+
file=sys.stderr)
|
|
432
|
+
return 1
|
|
433
|
+
print(json.dumps({"role": a.role, "minPlan": plan}, ensure_ascii=False) if a.json
|
|
434
|
+
else f" {a.role} → {plan}")
|
|
435
|
+
return 0
|
|
436
|
+
if a.validate:
|
|
437
|
+
problems = validate(doc)
|
|
438
|
+
if a.json:
|
|
439
|
+
print(json.dumps({"problems": problems}, ensure_ascii=False, indent=2))
|
|
440
|
+
else:
|
|
441
|
+
print(" ✅ 能力×档位矩阵三处一致" if not problems else " ❌ 不一致:\n" + "\n".join(f" {p}" for p in problems))
|
|
442
|
+
return 1 if problems else 0
|
|
443
|
+
# 默认打矩阵
|
|
444
|
+
m = matrix(doc)
|
|
445
|
+
if a.json:
|
|
446
|
+
print(json.dumps(m, ensure_ascii=False, indent=2))
|
|
447
|
+
else:
|
|
448
|
+
print(f" 档位序列:{' < '.join(m['_planTiers'])}")
|
|
449
|
+
for cid, c in m.items():
|
|
450
|
+
if cid.startswith("_"):
|
|
451
|
+
continue
|
|
452
|
+
print(f" {cid:14} minPlan={c['minPlan']:11} 流程={c['flows']}")
|
|
453
|
+
print(" 角色 → 档位:" + "、".join(f"{r}={p}" for r, p in sorted(m["_roles"].items())))
|
|
454
|
+
return 0
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
if __name__ == "__main__":
|
|
458
|
+
raise SystemExit(main())
|