create-principles-disciple 1.121.7 → 1.121.9
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/README.md +13 -1
- package/console/dist/server/config/pd-config-store.d.ts +1 -3
- package/console/dist/server/config/pd-config-store.js +4 -4
- package/console/dist/server/feedback/channels.d.ts +2 -8
- package/console/dist/server/feedback/channels.js +0 -11
- package/console/dist/server/index.js +34 -5
- package/console/dist/server/models/ActivationsConsoleModel.d.ts +28 -1
- package/console/dist/server/models/ActivationsConsoleModel.js +72 -30
- package/console/dist/server/models/GovernanceExperienceCollector.d.ts +28 -0
- package/console/dist/server/models/GovernanceExperienceCollector.js +305 -0
- package/console/dist/server/models/GovernanceProjectionCollector.d.ts +60 -2
- package/console/dist/server/models/GovernanceProjectionCollector.js +323 -285
- package/console/dist/server/models/PrincipleClassifier.d.ts +1 -1
- package/console/dist/server/models/PrincipleClassifier.js +7 -4
- package/console/dist/server/models/PrinciplesConsoleModel.d.ts +2 -1
- package/console/dist/server/models/ReceiptsConsoleModel.d.ts +5 -0
- package/console/dist/server/models/ReceiptsConsoleModel.js +140 -13
- package/console/dist/server/routes/activations.js +1 -1
- package/console/dist/server/routes/failed-tasks.js +28 -11
- package/console/dist/server/routes/governance.d.ts +26 -0
- package/console/dist/server/routes/governance.js +51 -0
- package/console/dist/server/routes/owner-identity.d.ts +31 -0
- package/console/dist/server/routes/owner-identity.js +76 -0
- package/console/dist/server/routes/update-history.d.ts +5 -0
- package/console/dist/server/routes/update-history.js +50 -4
- package/console/dist/server/routes/update.js +186 -32
- package/console/dist/shared/feedback-channel-ids.d.ts +13 -0
- package/console/dist/shared/feedback-channel-ids.js +12 -0
- package/console/dist/shared/feedback-contract.d.ts +46 -0
- package/console/dist/shared/feedback-contract.js +45 -0
- package/console/dist/ui/App.js +1 -1
- package/console/dist/ui/api.d.ts +22 -3
- package/console/dist/ui/api.js +63 -3
- package/console/dist/ui/components/receipts/ReceiptCoverageDisclosure.d.ts +6 -0
- package/console/dist/ui/components/receipts/ReceiptCoverageDisclosure.js +66 -0
- package/console/dist/ui/i18n/en.json +198 -2
- package/console/dist/ui/i18n/zh-CN.json +196 -0
- package/console/dist/ui/pages/activation/ActivationPage.js +14 -2
- package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +25 -5
- package/console/dist/ui/pages/focus/FocusPage.d.ts +28 -1
- package/console/dist/ui/pages/focus/FocusPage.js +128 -15
- package/console/dist/ui/pages/principles/PrincipleDetailPage.d.ts +30 -0
- package/console/dist/ui/pages/principles/PrincipleDetailPage.js +49 -6
- package/console/dist/ui/pages/settings/SettingsPage.js +67 -2
- package/console/dist/ui/pages/settings/UpdatePage.js +7 -7
- package/console/dist/ui/utils/enum-labels.js +0 -1
- package/console/dist/ui/utils/validators.d.ts +53 -21
- package/console/dist/ui/utils/validators.js +248 -21
- package/console/dist/web/assets/app.css +6 -0
- package/console/dist/web/assets/app.js +2721 -1664
- package/console/package.json +4 -2
- package/core/dist/host/host-adapter.d.ts +1 -1
- package/core/dist/host/host-adapter.d.ts.map +1 -1
- package/core/dist/host/host-adapter.js +1 -0
- package/core/dist/host/host-adapter.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +12 -2
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/attack-e2e-pipeline-smoke.test.js +1 -1
- package/core/dist/runtime-v2/__tests__/attack-e2e-pipeline-smoke.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/governance-experience.test.d.ts +2 -0
- package/core/dist/runtime-v2/__tests__/governance-experience.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/__tests__/governance-experience.test.js +300 -0
- package/core/dist/runtime-v2/__tests__/governance-experience.test.js.map +1 -0
- package/core/dist/runtime-v2/__tests__/owner-identity.test.d.ts +2 -0
- package/core/dist/runtime-v2/__tests__/owner-identity.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/__tests__/owner-identity.test.js +147 -0
- package/core/dist/runtime-v2/__tests__/owner-identity.test.js.map +1 -0
- package/core/dist/runtime-v2/__tests__/product-telemetry-contract.test.d.ts +18 -0
- package/core/dist/runtime-v2/__tests__/product-telemetry-contract.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/__tests__/product-telemetry-contract.test.js +303 -0
- package/core/dist/runtime-v2/__tests__/product-telemetry-contract.test.js.map +1 -0
- package/core/dist/runtime-v2/__tests__/recovery-actions-log.test.js +40 -0
- package/core/dist/runtime-v2/__tests__/recovery-actions-log.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/status-contract.test.d.ts +2 -0
- package/core/dist/runtime-v2/__tests__/status-contract.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/__tests__/status-contract.test.js +107 -0
- package/core/dist/runtime-v2/__tests__/status-contract.test.js.map +1 -0
- package/core/dist/runtime-v2/activation/__tests__/promotion-evidence-snapshot.test.d.ts +2 -0
- package/core/dist/runtime-v2/activation/__tests__/promotion-evidence-snapshot.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/activation/__tests__/promotion-evidence-snapshot.test.js +104 -0
- package/core/dist/runtime-v2/activation/__tests__/promotion-evidence-snapshot.test.js.map +1 -0
- package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-evaluator.test.js +19 -0
- package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-evaluator.test.js.map +1 -1
- package/core/dist/runtime-v2/activation/__tests__/rulecode-owner-decision-service.test.js +20 -0
- package/core/dist/runtime-v2/activation/__tests__/rulecode-owner-decision-service.test.js.map +1 -1
- package/core/dist/runtime-v2/activation/__tests__/sqlite-activation-safety-store.test.js +40 -0
- package/core/dist/runtime-v2/activation/__tests__/sqlite-activation-safety-store.test.js.map +1 -1
- package/core/dist/runtime-v2/activation/index.d.ts +2 -0
- package/core/dist/runtime-v2/activation/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/index.js +1 -0
- package/core/dist/runtime-v2/activation/index.js.map +1 -1
- package/core/dist/runtime-v2/activation/promotion-evidence-snapshot.d.ts +30 -0
- package/core/dist/runtime-v2/activation/promotion-evidence-snapshot.d.ts.map +1 -0
- package/core/dist/runtime-v2/activation/promotion-evidence-snapshot.js +87 -0
- package/core/dist/runtime-v2/activation/promotion-evidence-snapshot.js.map +1 -0
- package/core/dist/runtime-v2/activation/promotion-readiness-evaluator.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/promotion-readiness-evaluator.js +3 -0
- package/core/dist/runtime-v2/activation/promotion-readiness-evaluator.js.map +1 -1
- package/core/dist/runtime-v2/activation/promotion-readiness-reader.d.ts +1 -1
- package/core/dist/runtime-v2/activation/promotion-readiness-reader.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/promotion-readiness-reader.js +2 -2
- package/core/dist/runtime-v2/activation/promotion-readiness-reader.js.map +1 -1
- package/core/dist/runtime-v2/activation/rulecode-owner-decision-service.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/rulecode-owner-decision-service.js +18 -9
- package/core/dist/runtime-v2/activation/rulecode-owner-decision-service.js.map +1 -1
- package/core/dist/runtime-v2/adapter/__tests__/chaos-json-repair.test.js +1 -1
- package/core/dist/runtime-v2/adapter/__tests__/chaos-json-repair.test.js.map +1 -1
- package/core/dist/runtime-v2/adapter/__tests__/json-extractor.test.js +91 -1
- package/core/dist/runtime-v2/adapter/__tests__/json-extractor.test.js.map +1 -1
- package/core/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js +59 -12
- package/core/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js.map +1 -1
- package/core/dist/runtime-v2/adapter/__tests__/runtime-config-contract.test.js +10 -9
- package/core/dist/runtime-v2/adapter/__tests__/runtime-config-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/adapter/__tests__/structured-output-repair.test.js +42 -1
- package/core/dist/runtime-v2/adapter/__tests__/structured-output-repair.test.js.map +1 -1
- package/core/dist/runtime-v2/adapter/json-extractor.d.ts +25 -0
- package/core/dist/runtime-v2/adapter/json-extractor.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/json-extractor.js +126 -0
- package/core/dist/runtime-v2/adapter/json-extractor.js.map +1 -1
- package/core/dist/runtime-v2/adapter/l2-agent-loop-adapter.d.ts +9 -4
- package/core/dist/runtime-v2/adapter/l2-agent-loop-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/l2-agent-loop-adapter.js +9 -4
- package/core/dist/runtime-v2/adapter/l2-agent-loop-adapter.js.map +1 -1
- package/core/dist/runtime-v2/adapter/output-repair-contract.d.ts +5 -1
- package/core/dist/runtime-v2/adapter/output-repair-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/output-repair-contract.js +4 -1
- package/core/dist/runtime-v2/adapter/output-repair-contract.js.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.d.ts +7 -0
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.d.ts.map +1 -0
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.js +19 -0
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.js.map +1 -0
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts +13 -15
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +104 -37
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
- package/core/dist/runtime-v2/adapter/structured-output-repair.d.ts +16 -1
- package/core/dist/runtime-v2/adapter/structured-output-repair.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/structured-output-repair.js +38 -6
- package/core/dist/runtime-v2/adapter/structured-output-repair.js.map +1 -1
- package/core/dist/runtime-v2/adapter/tools/diagnostician-tool.d.ts +1 -1
- package/core/dist/runtime-v2/adapter/tools/diagnostician-tool.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js +8 -9
- package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +27 -3
- package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-principles.test.d.ts +11 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-principles.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-principles.test.js +93 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-principles.test.js.map +1 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-workspace.test.js +41 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-workspace.test.js.map +1 -1
- package/core/dist/runtime-v2/config/index.d.ts +2 -2
- package/core/dist/runtime-v2/config/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/index.js +2 -0
- package/core/dist/runtime-v2/config/index.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-agent-binding.js +4 -13
- package/core/dist/runtime-v2/config/pd-config-agent-binding.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-effective.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-effective.js +14 -7
- package/core/dist/runtime-v2/config/pd-config-effective.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-feature-flags.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-feature-flags.js +5 -12
- package/core/dist/runtime-v2/config/pd-config-feature-flags.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.d.ts +8 -0
- package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.js +2 -0
- package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.js +48 -3
- package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
- package/core/dist/runtime-v2/core-principles/__tests__/core-axiom-block.test.js +36 -8
- package/core/dist/runtime-v2/core-principles/__tests__/core-axiom-block.test.js.map +1 -1
- package/core/dist/runtime-v2/core-principles/__tests__/core-principle-registry.test.js +36 -12
- package/core/dist/runtime-v2/core-principles/__tests__/core-principle-registry.test.js.map +1 -1
- package/core/dist/runtime-v2/core-principles/core-axiom-block.d.ts +22 -8
- package/core/dist/runtime-v2/core-principles/core-axiom-block.d.ts.map +1 -1
- package/core/dist/runtime-v2/core-principles/core-axiom-block.js +28 -13
- package/core/dist/runtime-v2/core-principles/core-axiom-block.js.map +1 -1
- package/core/dist/runtime-v2/core-principles/core-principle-registry.d.ts +29 -4
- package/core/dist/runtime-v2/core-principles/core-principle-registry.d.ts.map +1 -1
- package/core/dist/runtime-v2/core-principles/core-principle-registry.js +68 -33
- package/core/dist/runtime-v2/core-principles/core-principle-registry.js.map +1 -1
- package/core/dist/runtime-v2/core-principles/index.d.ts +3 -3
- package/core/dist/runtime-v2/core-principles/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/core-principles/index.js +1 -1
- package/core/dist/runtime-v2/core-principles/index.js.map +1 -1
- package/core/dist/runtime-v2/evolution/evolution-types.d.ts +2 -1
- package/core/dist/runtime-v2/evolution/evolution-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/evolution/evolution-types.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/__tests__/anonymous-product-telemetry-flag.test.d.ts +2 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/anonymous-product-telemetry-flag.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/anonymous-product-telemetry-flag.test.js +48 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/anonymous-product-telemetry-flag.test.js.map +1 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/codex-conversation-ingestion-flag.test.d.ts +2 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/codex-conversation-ingestion-flag.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/codex-conversation-ingestion-flag.test.js +46 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/codex-conversation-ingestion-flag.test.js.map +1 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +86 -31
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-lifecycle.test.d.ts +2 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-lifecycle.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-lifecycle.test.js +83 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-lifecycle.test.js.map +1 -0
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts +19 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +128 -10
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.d.ts +37 -0
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.d.ts.map +1 -0
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js +304 -0
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js.map +1 -0
- package/core/dist/runtime-v2/feature-flags/index.d.ts +4 -2
- package/core/dist/runtime-v2/feature-flags/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/feature-flags/index.js +3 -1
- package/core/dist/runtime-v2/feature-flags/index.js.map +1 -1
- package/core/dist/runtime-v2/governance-experience-contract.d.ts +468 -0
- package/core/dist/runtime-v2/governance-experience-contract.d.ts.map +1 -0
- package/core/dist/runtime-v2/governance-experience-contract.js +208 -0
- package/core/dist/runtime-v2/governance-experience-contract.js.map +1 -0
- package/core/dist/runtime-v2/governance-experience.d.ts +11 -0
- package/core/dist/runtime-v2/governance-experience.d.ts.map +1 -0
- package/core/dist/runtime-v2/governance-experience.js +276 -0
- package/core/dist/runtime-v2/governance-experience.js.map +1 -0
- package/core/dist/runtime-v2/index.d.ts +20 -8
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +20 -6
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/artificer-output-retry-flag.test.js +29 -10
- package/core/dist/runtime-v2/internalization/__tests__/artificer-output-retry-flag.test.js.map +1 -1
- package/core/dist/runtime-v2/owner-identity.d.ts +69 -0
- package/core/dist/runtime-v2/owner-identity.d.ts.map +1 -0
- package/core/dist/runtime-v2/owner-identity.js +179 -0
- package/core/dist/runtime-v2/owner-identity.js.map +1 -0
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js +2 -3
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
- package/core/dist/runtime-v2/product-telemetry/daily-identity.d.ts +70 -0
- package/core/dist/runtime-v2/product-telemetry/daily-identity.d.ts.map +1 -0
- package/core/dist/runtime-v2/product-telemetry/daily-identity.js +89 -0
- package/core/dist/runtime-v2/product-telemetry/daily-identity.js.map +1 -0
- package/core/dist/runtime-v2/product-telemetry/snapshot-contract.d.ts +110 -0
- package/core/dist/runtime-v2/product-telemetry/snapshot-contract.d.ts.map +1 -0
- package/core/dist/runtime-v2/product-telemetry/snapshot-contract.js +250 -0
- package/core/dist/runtime-v2/product-telemetry/snapshot-contract.js.map +1 -0
- package/core/dist/runtime-v2/pruning-read-model.d.ts +2 -1
- package/core/dist/runtime-v2/pruning-read-model.d.ts.map +1 -1
- package/core/dist/runtime-v2/pruning-read-model.js.map +1 -1
- package/core/dist/runtime-v2/receipt-coverage.d.ts +63 -0
- package/core/dist/runtime-v2/receipt-coverage.d.ts.map +1 -0
- package/core/dist/runtime-v2/receipt-coverage.js +19 -0
- package/core/dist/runtime-v2/receipt-coverage.js.map +1 -0
- package/core/dist/runtime-v2/recovery-actions-log.d.ts +7 -0
- package/core/dist/runtime-v2/recovery-actions-log.d.ts.map +1 -1
- package/core/dist/runtime-v2/recovery-actions-log.js +6 -0
- package/core/dist/runtime-v2/recovery-actions-log.js.map +1 -1
- package/core/dist/runtime-v2/runtime-protocol.js +1 -1
- package/core/dist/runtime-v2/runtime-protocol.js.map +1 -1
- package/core/dist/runtime-v2/store/task/__tests__/sqlite-task-store.failed-tasks.test.js +17 -1
- package/core/dist/runtime-v2/store/task/__tests__/sqlite-task-store.failed-tasks.test.js.map +1 -1
- package/core/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/task/sqlite-task-store.js +10 -0
- package/core/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
- package/core/dist/runtime-v2/store/task/task-types.d.ts +2 -0
- package/core/dist/runtime-v2/store/task/task-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/task-status.d.ts +9 -5
- package/core/dist/runtime-v2/task-status.d.ts.map +1 -1
- package/core/dist/runtime-v2/task-status.js +15 -7
- package/core/dist/runtime-v2/task-status.js.map +1 -1
- package/core/dist/runtime-v2/types/event-types.d.ts +4 -0
- package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/event-types.js.map +1 -1
- package/core/dist/runtime-v2/types/index.d.ts +1 -1
- package/core/dist/runtime-v2/types/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/index.js +1 -1
- package/core/dist/runtime-v2/types/index.js.map +1 -1
- package/core/dist/runtime-v2/types/principle-enums.d.ts +3 -2
- package/core/dist/runtime-v2/types/principle-enums.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/principle-enums.js +11 -5
- package/core/dist/runtime-v2/types/principle-enums.js.map +1 -1
- package/core/dist/runtime-v2/types/principle-schema.d.ts +1 -1
- package/core/dist/runtime-v2/types/principle-tree-store.d.ts +1 -1
- package/core/dist/runtime-v2/utils/cli-process-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/utils/cli-process-runner.js +3 -2
- package/core/dist/runtime-v2/utils/cli-process-runner.js.map +1 -1
- package/core/package.json +4 -5
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +4 -2
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -11
- package/dist/index.js.map +1 -1
- package/dist/installer.d.ts +31 -5
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +472 -161
- package/dist/installer.js.map +1 -1
- package/dist/installers/codex-host-installer.d.ts.map +1 -1
- package/dist/installers/codex-host-installer.js +4 -0
- package/dist/installers/codex-host-installer.js.map +1 -1
- package/dist/mvp-config.d.ts +10 -0
- package/dist/mvp-config.d.ts.map +1 -1
- package/dist/mvp-config.js +30 -3
- package/dist/mvp-config.js.map +1 -1
- package/dist/uninstaller.d.ts +9 -1
- package/dist/uninstaller.d.ts.map +1 -1
- package/dist/uninstaller.js +103 -23
- package/dist/uninstaller.js.map +1 -1
- package/dist/update/atomic-file.d.ts +35 -0
- package/dist/update/atomic-file.d.ts.map +1 -0
- package/dist/update/atomic-file.js +120 -0
- package/dist/update/atomic-file.js.map +1 -0
- package/dist/update/bootstrap-protocol.d.ts +55 -0
- package/dist/update/bootstrap-protocol.d.ts.map +1 -0
- package/dist/update/bootstrap-protocol.js +132 -0
- package/dist/update/bootstrap-protocol.js.map +1 -0
- package/dist/update/channel-metadata.d.ts +28 -0
- package/dist/update/channel-metadata.d.ts.map +1 -0
- package/dist/update/channel-metadata.js +90 -0
- package/dist/update/channel-metadata.js.map +1 -0
- package/dist/update/channel-promotion.d.ts +39 -0
- package/dist/update/channel-promotion.d.ts.map +1 -0
- package/dist/update/channel-promotion.js +55 -0
- package/dist/update/channel-promotion.js.map +1 -0
- package/dist/update/data-compatibility.d.ts +35 -0
- package/dist/update/data-compatibility.d.ts.map +1 -0
- package/dist/update/data-compatibility.js +45 -0
- package/dist/update/data-compatibility.js.map +1 -0
- package/dist/update/install-layout.d.ts +59 -0
- package/dist/update/install-layout.d.ts.map +1 -0
- package/dist/update/install-layout.js +127 -0
- package/dist/update/install-layout.js.map +1 -0
- package/dist/update/legacy-migration.d.ts +56 -0
- package/dist/update/legacy-migration.d.ts.map +1 -0
- package/dist/update/legacy-migration.js +254 -0
- package/dist/update/legacy-migration.js.map +1 -0
- package/dist/update/product-identity.d.ts +50 -0
- package/dist/update/product-identity.d.ts.map +1 -0
- package/dist/update/product-identity.js +63 -0
- package/dist/update/product-identity.js.map +1 -0
- package/dist/update/release-asset-manifest.d.ts +31 -0
- package/dist/update/release-asset-manifest.d.ts.map +1 -0
- package/dist/update/release-asset-manifest.js +161 -0
- package/dist/update/release-asset-manifest.js.map +1 -0
- package/dist/update/release-identity.d.ts +33 -0
- package/dist/update/release-identity.d.ts.map +1 -0
- package/dist/update/release-identity.js +108 -0
- package/dist/update/release-identity.js.map +1 -0
- package/dist/update/release-manager.d.ts +96 -0
- package/dist/update/release-manager.d.ts.map +1 -0
- package/dist/update/release-manager.js +253 -0
- package/dist/update/release-manager.js.map +1 -0
- package/dist/update/release-metadata.d.ts +57 -0
- package/dist/update/release-metadata.d.ts.map +1 -0
- package/dist/update/release-metadata.js +200 -0
- package/dist/update/release-metadata.js.map +1 -0
- package/dist/update/release-policy.d.ts +51 -0
- package/dist/update/release-policy.d.ts.map +1 -0
- package/dist/update/release-policy.js +103 -0
- package/dist/update/release-policy.js.map +1 -0
- package/dist/update/rollback-policy.d.ts +56 -0
- package/dist/update/rollback-policy.d.ts.map +1 -0
- package/dist/update/rollback-policy.js +61 -0
- package/dist/update/rollback-policy.js.map +1 -0
- package/dist/update/transaction-journal.d.ts +121 -0
- package/dist/update/transaction-journal.d.ts.map +1 -0
- package/dist/update/transaction-journal.js +333 -0
- package/dist/update/transaction-journal.js.map +1 -0
- package/dist/update/trust-metadata.d.ts +55 -0
- package/dist/update/trust-metadata.d.ts.map +1 -0
- package/dist/update/trust-metadata.js +170 -0
- package/dist/update/trust-metadata.js.map +1 -0
- package/dist/update/update-history.d.ts +46 -0
- package/dist/update/update-history.d.ts.map +1 -0
- package/dist/update/update-history.js +123 -0
- package/dist/update/update-history.js.map +1 -0
- package/dist/utils/env.d.ts +7 -0
- package/dist/utils/env.d.ts.map +1 -1
- package/dist/utils/env.js +134 -44
- package/dist/utils/env.js.map +1 -1
- package/host-runtime/dist/governance-observation-store.d.ts +172 -0
- package/host-runtime/dist/governance-observation-store.js +649 -0
- package/host-runtime/dist/index.d.ts +6 -0
- package/host-runtime/dist/index.js +8 -0
- package/host-runtime/dist/product-telemetry/consent-store.d.ts +107 -0
- package/host-runtime/dist/product-telemetry/consent-store.js +329 -0
- package/host-runtime/dist/product-telemetry/eligibility.d.ts +53 -0
- package/host-runtime/dist/product-telemetry/eligibility.js +69 -0
- package/host-runtime/dist/product-telemetry/exporter.d.ts +46 -0
- package/host-runtime/dist/product-telemetry/exporter.js +71 -0
- package/host-runtime/dist/product-telemetry/milestone-readers.d.ts +61 -0
- package/host-runtime/dist/product-telemetry/milestone-readers.js +234 -0
- package/host-runtime/dist/product-telemetry/service.d.ts +142 -0
- package/host-runtime/dist/product-telemetry/service.js +532 -0
- package/host-runtime/dist/product-telemetry/workspace-scope.d.ts +38 -0
- package/host-runtime/dist/product-telemetry/workspace-scope.js +64 -0
- package/host-runtime/package.json +7 -2
- package/install-layout/dist/index.d.ts +48 -0
- package/install-layout/dist/index.js +81 -0
- package/install-layout/dist/index.test.d.ts +1 -0
- package/install-layout/dist/index.test.js +34 -0
- package/install-layout/package.json +33 -0
- package/package.json +16 -6
- package/pd-cli/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts +9 -0
- package/pd-cli/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts.map +1 -0
- package/pd-cli/dist/commands/__tests__/telemetry-flag-wiring.test.js +76 -0
- package/pd-cli/dist/commands/__tests__/telemetry-flag-wiring.test.js.map +1 -0
- package/pd-cli/dist/commands/__tests__/version.test.d.ts +2 -0
- package/pd-cli/dist/commands/__tests__/version.test.d.ts.map +1 -0
- package/pd-cli/dist/commands/__tests__/version.test.js +134 -0
- package/pd-cli/dist/commands/__tests__/version.test.js.map +1 -0
- package/pd-cli/dist/commands/console.d.ts.map +1 -1
- package/pd-cli/dist/commands/console.js +29 -10
- package/pd-cli/dist/commands/console.js.map +1 -1
- package/pd-cli/dist/commands/pain-record.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-record.js +6 -15
- package/pd-cli/dist/commands/pain-record.js.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.d.ts +29 -0
- package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.js +70 -31
- package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js +2 -2
- package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
- package/pd-cli/dist/commands/telemetry.d.ts +82 -0
- package/pd-cli/dist/commands/telemetry.d.ts.map +1 -0
- package/pd-cli/dist/commands/telemetry.js +268 -0
- package/pd-cli/dist/commands/telemetry.js.map +1 -0
- package/pd-cli/dist/commands/version.d.ts +11 -0
- package/pd-cli/dist/commands/version.d.ts.map +1 -0
- package/pd-cli/dist/commands/version.js +48 -0
- package/pd-cli/dist/commands/version.js.map +1 -0
- package/pd-cli/dist/index.js +35 -2
- package/pd-cli/dist/index.js.map +1 -1
- package/pd-cli/dist/services/version-report.d.ts +44 -0
- package/pd-cli/dist/services/version-report.d.ts.map +1 -0
- package/pd-cli/dist/services/version-report.js +152 -0
- package/pd-cli/dist/services/version-report.js.map +1 -0
- package/pd-cli/package.json +3 -2
- package/plugin/dist/bundle.js +870 -932
- package/plugin/dist/core/init.d.ts +2 -1
- package/plugin/dist/core/principle-application-ledger.d.ts +10 -0
- package/plugin/dist/core/product-telemetry-trigger.d.ts +14 -0
- package/plugin/dist/core/thinking-os-parser.d.ts +5 -2
- package/plugin/dist/governance-audit.js +386 -0
- package/plugin/dist/hooks/gate-block-helper.d.ts +25 -1
- package/plugin/dist/hooks/gate.d.ts +22 -0
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/dist/rulehost-evidence.js +832 -0
- package/plugin/dist/templates/langs/en/principles/THINKING_OS.md +53 -45
- package/plugin/dist/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
- package/plugin/dist/templates/langs/zh/principles/THINKING_OS.md +62 -46
- package/plugin/dist/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
- package/plugin/dist/templates/workspace/.principles/THINKING_OS.md +66 -38
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/plugin/templates/langs/en/principles/THINKING_OS.md +53 -45
- package/plugin/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
- package/plugin/templates/langs/zh/principles/THINKING_OS.md +62 -46
- package/plugin/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
- package/plugin/templates/workspace/.principles/THINKING_OS.md +66 -38
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product telemetry service — Anonymous Product Telemetry v1
|
|
3
|
+
* (PRI-597/598/599 orchestration; review remediation: workspace unit).
|
|
4
|
+
*
|
|
5
|
+
* Single seam for the CLI control plane (`pd telemetry status/enable/disable/
|
|
6
|
+
* preview/reset`) and the two fire-and-forget export triggers (OpenClaw plugin
|
|
7
|
+
* workspace init, pd-console startup). Export requires ALL gates:
|
|
8
|
+
*
|
|
9
|
+
* canExport = feature flag anonymous_product_telemetry (workspace config)
|
|
10
|
+
* AND consent granted (~/.pd/product-telemetry.json)
|
|
11
|
+
* AND environment not suppressed (eligibility.ts)
|
|
12
|
+
*
|
|
13
|
+
* Scope model (review remediation P1-1): the MEASUREMENT UNIT is the
|
|
14
|
+
* workspace. Consent, consentVersion, and the telemetry secret are
|
|
15
|
+
* machine-scope; the daily identity, dedup, retry bookkeeping, and export
|
|
16
|
+
* lock are workspace-scope. One workspace's success/backoff/lock never
|
|
17
|
+
* suppresses another workspace on the same installation.
|
|
18
|
+
*/
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import os from 'node:os';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { bucketDateFromTime, buildProductTelemetrySnapshot, deriveDailyTelemetryId, isValidTelemetrySecretHex, } from '@principles/core/runtime-v2';
|
|
24
|
+
import { getInstallLayoutPaths, parseInstallManifest, resolveInstallLayout } from '@principles/install-layout';
|
|
25
|
+
import { loadFeatureFlagFromConfig, loadPdConfigForPlugin } from '../pd-config.js';
|
|
26
|
+
import { deniedControlState, defaultProductTelemetryControlState, getProductTelemetryStatePath, grantedControlState, pruneWorkspaceExports, readProductTelemetryControlState, resetControlState, writeProductTelemetryControlState, } from './consent-store.js';
|
|
27
|
+
import { computeTelemetryEnvironment } from './eligibility.js';
|
|
28
|
+
import { DEFAULT_PRODUCT_TELEMETRY_ENDPOINT, exportSnapshot, nextRetryDelayMs } from './exporter.js';
|
|
29
|
+
import { readMilestoneFacts } from './milestone-readers.js';
|
|
30
|
+
import { workspaceExportLockPath, workspaceScopeIdFor } from './workspace-scope.js';
|
|
31
|
+
export const ANONYMOUS_PRODUCT_TELEMETRY_FLAG = 'anonymous_product_telemetry';
|
|
32
|
+
export const PD_TELEMETRY_DISABLED_ENV = 'PD_TELEMETRY_DISABLED';
|
|
33
|
+
export const PREVIEW_BANNER = 'Preview only. Nothing was sent.';
|
|
34
|
+
/** Hard cap on export attempts per UTC day per workspace (1 initial + 4 retries; review round 2). */
|
|
35
|
+
export const MAX_DAILY_EXPORT_ATTEMPTS = 5;
|
|
36
|
+
/** A lock older than this is treated as orphaned (holder crashed) and taken over. */
|
|
37
|
+
const EXPORT_LOCK_STALE_MS = 5 * 60 * 1000;
|
|
38
|
+
/**
|
|
39
|
+
* The state-update critical section (fresh read → prune → merge → atomic
|
|
40
|
+
* write) holds for a few ms, so the poll budget and stale threshold are
|
|
41
|
+
* tiny compared to the export lock's — a crashed holder blocks bookkeeping
|
|
42
|
+
* for seconds, not minutes, and live holders can never be evicted (the
|
|
43
|
+
* hold is far shorter than the stale threshold).
|
|
44
|
+
*/
|
|
45
|
+
const STATE_UPDATE_LOCK_STALE_MS = 5 * 1000;
|
|
46
|
+
const STATE_UPDATE_LOCK_TIMEOUT_MS = 3 * 1000;
|
|
47
|
+
const STATE_UPDATE_LOCK_POLL_MS = 15;
|
|
48
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
49
|
+
/**
|
|
50
|
+
* Per-workspace cross-process export lock: exclusive create ('wx') is atomic,
|
|
51
|
+
* so exactly one of N simultaneous holders wins — and the lock name embeds
|
|
52
|
+
* only the opaque scope key, so workspace A's in-flight export never blocks
|
|
53
|
+
* workspace B's (review remediation P1-1). Stale takeover uses mtime — a
|
|
54
|
+
* crashed holder can block that workspace for at most EXPORT_LOCK_STALE_MS.
|
|
55
|
+
* Never throws: any filesystem failure degrades to "not acquired"
|
|
56
|
+
* (telemetry skips, PD unaffected).
|
|
57
|
+
*/
|
|
58
|
+
function tryAcquireExportLock(stateFilePath, workspaceScopeId) {
|
|
59
|
+
const lockPath = workspaceExportLockPath(stateFilePath, workspaceScopeId);
|
|
60
|
+
const create = () => {
|
|
61
|
+
try {
|
|
62
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
63
|
+
try {
|
|
64
|
+
fs.writeSync(fd, `${process.pid} ${new Date().toISOString()}`);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
fs.closeSync(fd);
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
if (create())
|
|
76
|
+
return { acquired: true, lockPath };
|
|
77
|
+
try {
|
|
78
|
+
const stat = fs.statSync(lockPath);
|
|
79
|
+
if (Date.now() - stat.mtimeMs > EXPORT_LOCK_STALE_MS) {
|
|
80
|
+
fs.rmSync(lockPath, { force: true });
|
|
81
|
+
if (create())
|
|
82
|
+
return { acquired: true, lockPath };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// stat/rm failure → treat as busy; never throw for telemetry
|
|
87
|
+
}
|
|
88
|
+
return { acquired: false };
|
|
89
|
+
}
|
|
90
|
+
function releaseExportLock(lockPath) {
|
|
91
|
+
try {
|
|
92
|
+
fs.rmSync(lockPath, { force: true });
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// best effort; a leaked lock is reclaimed after EXPORT_LOCK_STALE_MS
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Machine-scope state-update lock: serializes ONLY the control-state file
|
|
100
|
+
* read→merge→write across workspaces (a few ms each) — the network export
|
|
101
|
+
* itself stays fully concurrent. Review round 4: without it, two
|
|
102
|
+
* concurrently-completing exports built their writes from the same stale
|
|
103
|
+
* base and the later write silently dropped the earlier workspace's entry
|
|
104
|
+
* (lost update), defeating once/day dedup, backoff, and the attempt cap.
|
|
105
|
+
*
|
|
106
|
+
* Lock acquisition polls asynchronously (never blocks the event loop) and
|
|
107
|
+
* takes over a stale (crashed-holder) lock after STATE_UPDATE_LOCK_STALE_MS.
|
|
108
|
+
*/
|
|
109
|
+
async function acquireStateUpdateLock(stateFilePath) {
|
|
110
|
+
const lockPath = `${stateFilePath}.state-lock`;
|
|
111
|
+
const deadline = Date.now() + STATE_UPDATE_LOCK_TIMEOUT_MS;
|
|
112
|
+
const create = () => {
|
|
113
|
+
try {
|
|
114
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
115
|
+
try {
|
|
116
|
+
fs.writeSync(fd, `${process.pid} ${new Date().toISOString()}`);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
fs.closeSync(fd);
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
for (;;) {
|
|
128
|
+
if (create())
|
|
129
|
+
return { acquired: true, lockPath };
|
|
130
|
+
try {
|
|
131
|
+
const stat = fs.statSync(lockPath);
|
|
132
|
+
if (Date.now() - stat.mtimeMs > STATE_UPDATE_LOCK_STALE_MS) {
|
|
133
|
+
fs.rmSync(lockPath, { force: true });
|
|
134
|
+
if (create())
|
|
135
|
+
return { acquired: true, lockPath };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// disappeared between poll attempts → next loop's create decides
|
|
140
|
+
}
|
|
141
|
+
if (Date.now() >= deadline)
|
|
142
|
+
return { acquired: false };
|
|
143
|
+
await sleep(STATE_UPDATE_LOCK_POLL_MS);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Merge ONE workspace's export entry into the control state inside the
|
|
148
|
+
* state-update critical section. The entry itself is built by the caller
|
|
149
|
+
* (its inputs are stable for this workspace under the per-workspace export
|
|
150
|
+
* lock); the OTHER workspaces' entries come from a FRESH read under the
|
|
151
|
+
* lock, so concurrent completions compose instead of overwrite. Identity
|
|
152
|
+
* transitions that raced the export (reset/disable/secret rotation mid-
|
|
153
|
+
* flight) are detected and win — an in-flight result is never merged into
|
|
154
|
+
* a rotated/denied identity. Best-effort by contract: every non-written
|
|
155
|
+
* outcome is observable at the call site via structured logs (rc-9).
|
|
156
|
+
*/
|
|
157
|
+
async function mergeWorkspaceExportEntry(args) {
|
|
158
|
+
const stateFilePath = getProductTelemetryStatePath(args.homeDir);
|
|
159
|
+
const lock = await acquireStateUpdateLock(stateFilePath);
|
|
160
|
+
if (!lock.acquired)
|
|
161
|
+
return 'skipped_contention';
|
|
162
|
+
try {
|
|
163
|
+
const read = readProductTelemetryControlState(args.homeDir);
|
|
164
|
+
if (!read.ok)
|
|
165
|
+
return 'skipped_unreadable';
|
|
166
|
+
if (read.state.consent !== 'granted' || read.state.telemetrySecret !== args.secret)
|
|
167
|
+
return 'skipped_identity_changed';
|
|
168
|
+
const pruned = pruneWorkspaceExports(read.state, args.timestamp);
|
|
169
|
+
const nextState = {
|
|
170
|
+
...pruned,
|
|
171
|
+
workspaceExports: { ...(pruned.workspaceExports ?? {}), [args.scopeKey]: args.nextEntry },
|
|
172
|
+
};
|
|
173
|
+
return writeProductTelemetryControlState(args.homeDir, nextState).ok ? 'written' : 'skipped_unreadable';
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
releaseExportLock(lock.lockPath);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const DEFAULT_MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
180
|
+
function readJsonFile(filePath) {
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function resolveInstallContext(homeDir) {
|
|
189
|
+
const paths = getInstallLayoutPaths(homeDir);
|
|
190
|
+
const manifestParsed = parseInstallManifest(readJsonFile(paths.manifest));
|
|
191
|
+
const manifest = manifestParsed.manifest;
|
|
192
|
+
const canonicalRuntimeExists = fs.existsSync(paths.hostRuntimeDir);
|
|
193
|
+
const legacyExtensionExists = fs.existsSync(paths.openClawExtensionDir);
|
|
194
|
+
const layout = resolveInstallLayout({ homeDir, manifest, canonicalRuntimeExists, legacyExtensionExists });
|
|
195
|
+
let hostKind = 'other';
|
|
196
|
+
if (manifest && manifest.hosts.length === 1 && (manifest.hosts[0] === 'codex' || manifest.hosts[0] === 'openclaw')) {
|
|
197
|
+
[hostKind] = manifest.hosts;
|
|
198
|
+
}
|
|
199
|
+
else if (legacyExtensionExists) {
|
|
200
|
+
hostKind = 'openclaw';
|
|
201
|
+
}
|
|
202
|
+
const pluginVersion = readJsonFile(path.join(paths.pluginDir, 'package.json')) ?? readJsonFile(path.join(paths.openClawExtensionDir, 'package.json'));
|
|
203
|
+
const versionValue = pluginVersion !== null && typeof pluginVersion === 'object' && !Array.isArray(pluginVersion)
|
|
204
|
+
? pluginVersion.version
|
|
205
|
+
: undefined;
|
|
206
|
+
const pdVersion = typeof versionValue === 'string' ? versionValue.slice(0, 32) : 'unknown';
|
|
207
|
+
return { installMode: layout.mode, hostKind, pdVersion };
|
|
208
|
+
}
|
|
209
|
+
function workspaceEnvironmentOf(workspaceDir) {
|
|
210
|
+
if (!workspaceDir)
|
|
211
|
+
return 'unknown';
|
|
212
|
+
const loaded = loadPdConfigForPlugin(workspaceDir);
|
|
213
|
+
const environment = loaded.effective.config.workspace?.environment;
|
|
214
|
+
return environment ?? 'unknown';
|
|
215
|
+
}
|
|
216
|
+
export function createProductTelemetryService(deps = {}) {
|
|
217
|
+
const homeDir = () => path.resolve(deps.homeDir ?? os.homedir());
|
|
218
|
+
const env = () => deps.env ?? process.env;
|
|
219
|
+
const moduleDir = () => path.resolve(deps.moduleDir ?? DEFAULT_MODULE_DIR);
|
|
220
|
+
const now = () => (deps.now ?? Date.now)();
|
|
221
|
+
const logger = deps.logger ?? {};
|
|
222
|
+
function environmentResult(workspaceDir) {
|
|
223
|
+
return computeTelemetryEnvironment({
|
|
224
|
+
env: {
|
|
225
|
+
killSwitch: env()[PD_TELEMETRY_DISABLED_ENV],
|
|
226
|
+
ci: env().CI,
|
|
227
|
+
vitest: env().VITEST,
|
|
228
|
+
e2eMode: env().PD_E2E_MODE,
|
|
229
|
+
},
|
|
230
|
+
workspaceEnvironment: workspaceEnvironmentOf(workspaceDir),
|
|
231
|
+
installMode: resolveInstallContext(homeDir()).installMode,
|
|
232
|
+
moduleDir: moduleDir(),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function gatesFor(state, workspaceDir) {
|
|
236
|
+
let flagEnabled = null;
|
|
237
|
+
let flagSource = null;
|
|
238
|
+
if (workspaceDir) {
|
|
239
|
+
const flag = loadFeatureFlagFromConfig(workspaceDir, ANONYMOUS_PRODUCT_TELEMETRY_FLAG);
|
|
240
|
+
flagEnabled = flag.enabled;
|
|
241
|
+
flagSource = flag.source;
|
|
242
|
+
}
|
|
243
|
+
const environment = environmentResult(workspaceDir);
|
|
244
|
+
const blockers = [];
|
|
245
|
+
if (flagEnabled === null)
|
|
246
|
+
blockers.push('feature_flag_unresolved');
|
|
247
|
+
else if (flagEnabled !== true)
|
|
248
|
+
blockers.push('feature_flag_disabled');
|
|
249
|
+
if (state.consent !== 'granted')
|
|
250
|
+
blockers.push(`consent_${state.consent}`);
|
|
251
|
+
blockers.push(...environment.reasons);
|
|
252
|
+
return {
|
|
253
|
+
flagEnabled,
|
|
254
|
+
flagSource,
|
|
255
|
+
consent: state.consent,
|
|
256
|
+
environmentSuppressed: environment.suppressed,
|
|
257
|
+
suppressionReasons: environment.reasons,
|
|
258
|
+
canExport: blockers.length === 0,
|
|
259
|
+
blockers,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function nextActionFor(gates) {
|
|
263
|
+
if (gates.canExport)
|
|
264
|
+
return undefined;
|
|
265
|
+
if (gates.suppressionReasons.includes('env_kill_switch'))
|
|
266
|
+
return `Unset ${PD_TELEMETRY_DISABLED_ENV} to allow telemetry export.`;
|
|
267
|
+
if (gates.flagEnabled === false)
|
|
268
|
+
return `Set features.${ANONYMOUS_PRODUCT_TELEMETRY_FLAG}.enabled: true in the workspace .pd/config.yaml.`;
|
|
269
|
+
if (gates.consent !== 'granted')
|
|
270
|
+
return 'Run: pd telemetry enable --confirm';
|
|
271
|
+
if (gates.environmentSuppressed)
|
|
272
|
+
return `Telemetry is suppressed in this environment (${gates.suppressionReasons.join(', ')}). This is expected for CI/test/dev environments.`;
|
|
273
|
+
return 'Resolve the blockers listed above.';
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
/** Status for `pd telemetry status`. Never exposes the secret. */
|
|
277
|
+
getStatus(workspaceDir) {
|
|
278
|
+
const read = readProductTelemetryControlState(homeDir());
|
|
279
|
+
if (!read.ok)
|
|
280
|
+
return read;
|
|
281
|
+
const gates = gatesFor(read.state, workspaceDir);
|
|
282
|
+
// Export bookkeeping is workspace-scoped: show this workspace's entry
|
|
283
|
+
// (when a workspace was given and its scope can be derived).
|
|
284
|
+
let workspaceEntry;
|
|
285
|
+
const storedSecret = read.state.telemetrySecret;
|
|
286
|
+
if (workspaceDir !== undefined && isValidTelemetrySecretHex(storedSecret)) {
|
|
287
|
+
const scopeKey = workspaceScopeIdFor(storedSecret, workspaceDir);
|
|
288
|
+
workspaceEntry = read.state.workspaceExports?.[scopeKey];
|
|
289
|
+
}
|
|
290
|
+
const view = {
|
|
291
|
+
...gates,
|
|
292
|
+
consentVersion: read.state.consentVersion,
|
|
293
|
+
hasSecret: isValidTelemetrySecretHex(read.state.telemetrySecret),
|
|
294
|
+
...(workspaceEntry?.lastAttemptedAt !== undefined ? { lastAttemptedAt: workspaceEntry.lastAttemptedAt } : {}),
|
|
295
|
+
...(workspaceEntry?.lastSucceededAt !== undefined ? { lastSucceededAt: workspaceEntry.lastSucceededAt } : {}),
|
|
296
|
+
...(workspaceEntry?.lastFailureCode !== undefined ? { lastFailureCode: workspaceEntry.lastFailureCode } : {}),
|
|
297
|
+
...(workspaceEntry?.nextRetryAt !== undefined ? { nextRetryAt: workspaceEntry.nextRetryAt } : {}),
|
|
298
|
+
endpoint: deps.endpoint ?? DEFAULT_PRODUCT_TELEMETRY_ENDPOINT,
|
|
299
|
+
...(nextActionFor(gates) !== undefined ? { nextAction: nextActionFor(gates) } : {}),
|
|
300
|
+
};
|
|
301
|
+
return { ok: true, view };
|
|
302
|
+
},
|
|
303
|
+
/** Exact outbound payload for `pd telemetry preview`. Never sends. */
|
|
304
|
+
preview(workspaceDir) {
|
|
305
|
+
const read = readProductTelemetryControlState(homeDir());
|
|
306
|
+
const state = read.ok ? read.state : defaultProductTelemetryControlState();
|
|
307
|
+
const gates = gatesFor(state, workspaceDir);
|
|
308
|
+
const timestamp = now();
|
|
309
|
+
const bucketDate = bucketDateFromTime(timestamp);
|
|
310
|
+
const secretEphemeral = !isValidTelemetrySecretHex(state.telemetrySecret);
|
|
311
|
+
// Without a stored secret there is no stable daily ID; an all-zero
|
|
312
|
+
// hex placeholder keeps the preview shape exact while the note says
|
|
313
|
+
// the ID would differ after enable.
|
|
314
|
+
const secret = state.telemetrySecret ?? '0'.repeat(64);
|
|
315
|
+
const scopeKey = workspaceScopeIdFor(secret, workspaceDir);
|
|
316
|
+
const install = resolveInstallContext(homeDir());
|
|
317
|
+
const { facts, notes } = readMilestoneFacts(workspaceDir);
|
|
318
|
+
const snapshot = buildProductTelemetrySnapshot({
|
|
319
|
+
dailyTelemetryId: deriveDailyTelemetryId(secret, scopeKey, bucketDate),
|
|
320
|
+
bucketDate,
|
|
321
|
+
pdVersion: install.pdVersion,
|
|
322
|
+
hostKind: install.hostKind,
|
|
323
|
+
milestones: {
|
|
324
|
+
initialized: facts.initialized,
|
|
325
|
+
painObserved: facts.painObserved,
|
|
326
|
+
principleObserved: facts.principleObserved,
|
|
327
|
+
activationObserved: facts.activationObserved,
|
|
328
|
+
presenceReceiptObserved: facts.presenceReceiptObserved,
|
|
329
|
+
effectReceiptObserved: facts.effectReceiptObserved,
|
|
330
|
+
},
|
|
331
|
+
reliability: { initializationFailed: facts.initializationFailed },
|
|
332
|
+
});
|
|
333
|
+
const previewNotes = [...notes];
|
|
334
|
+
if (secretEphemeral)
|
|
335
|
+
previewNotes.push('payload shape is exact; dailyTelemetryId is provisional and will change after pd telemetry enable');
|
|
336
|
+
if (!read.ok)
|
|
337
|
+
previewNotes.push(`control state unreadable (${read.reason}) — showing defaults`);
|
|
338
|
+
return { banner: PREVIEW_BANNER, snapshot, gates, notes: previewNotes, secretEphemeral };
|
|
339
|
+
},
|
|
340
|
+
/** `pd telemetry enable --confirm`: explicit granted consent (+ secret). */
|
|
341
|
+
enable() {
|
|
342
|
+
const read = readProductTelemetryControlState(homeDir());
|
|
343
|
+
if (!read.ok)
|
|
344
|
+
return read;
|
|
345
|
+
const next = grantedControlState(read.state);
|
|
346
|
+
const written = writeProductTelemetryControlState(homeDir(), next);
|
|
347
|
+
if (!written.ok)
|
|
348
|
+
return written;
|
|
349
|
+
logger.info?.(`[PD:Telemetry] Consent granted (control state: ${getProductTelemetryStatePath(homeDir())}).`);
|
|
350
|
+
return { ok: true, consent: 'granted' };
|
|
351
|
+
},
|
|
352
|
+
/** `pd telemetry disable --confirm`: deny consent and delete export identity. */
|
|
353
|
+
disable() {
|
|
354
|
+
const read = readProductTelemetryControlState(homeDir());
|
|
355
|
+
if (!read.ok)
|
|
356
|
+
return read;
|
|
357
|
+
const written = writeProductTelemetryControlState(homeDir(), deniedControlState());
|
|
358
|
+
if (!written.ok)
|
|
359
|
+
return written;
|
|
360
|
+
logger.info?.('[PD:Telemetry] Consent denied; local telemetry secret and export status deleted. Future export requests: 0.');
|
|
361
|
+
return { ok: true, consent: 'denied' };
|
|
362
|
+
},
|
|
363
|
+
/** `pd telemetry reset --confirm`: delete identity; fresh secret only if still enabled. */
|
|
364
|
+
reset() {
|
|
365
|
+
const read = readProductTelemetryControlState(homeDir());
|
|
366
|
+
if (!read.ok)
|
|
367
|
+
return read;
|
|
368
|
+
const next = resetControlState(read.state);
|
|
369
|
+
const written = writeProductTelemetryControlState(homeDir(), next);
|
|
370
|
+
if (!written.ok)
|
|
371
|
+
return written;
|
|
372
|
+
logger.info?.('[PD:Telemetry] Telemetry identity reset; previous daily IDs are unlinkable going forward.');
|
|
373
|
+
return { ok: true, consent: next.consent };
|
|
374
|
+
},
|
|
375
|
+
/**
|
|
376
|
+
* One gate-checked export attempt for a normal-activity trigger.
|
|
377
|
+
* Never throws; every path returns a structured outcome.
|
|
378
|
+
*
|
|
379
|
+
* Concurrency: a per-workspace lock file
|
|
380
|
+
* (`product-telemetry.json.export-lock.<scopeId>`) closes the read→send→
|
|
381
|
+
* write race between simultaneous triggers (OpenClaw gateway init +
|
|
382
|
+
* pd-console startup in different processes) WITHOUT coupling unrelated
|
|
383
|
+
* workspaces. Stale locks (crashed holder) are taken over after
|
|
384
|
+
* EXPORT_LOCK_STALE_MS.
|
|
385
|
+
*/
|
|
386
|
+
async maybeExportDaily(workspaceDir) {
|
|
387
|
+
const read = readProductTelemetryControlState(homeDir());
|
|
388
|
+
if (!read.ok) {
|
|
389
|
+
logger.warn?.(`[PD:Telemetry] Export skipped: ${read.reason}`);
|
|
390
|
+
return { attempted: false, skipReason: 'control_state_unreadable' };
|
|
391
|
+
}
|
|
392
|
+
const { state } = read;
|
|
393
|
+
const gates = gatesFor(state, workspaceDir);
|
|
394
|
+
if (!gates.flagEnabled)
|
|
395
|
+
return { attempted: false, skipReason: 'feature_flag_disabled' };
|
|
396
|
+
if (state.consent !== 'granted')
|
|
397
|
+
return { attempted: false, skipReason: `consent_${state.consent}` };
|
|
398
|
+
if (gates.environmentSuppressed) {
|
|
399
|
+
logger.info?.(`[PD:Telemetry] Export suppressed (${gates.suppressionReasons.join(', ')}).`);
|
|
400
|
+
return { attempted: false, skipReason: `environment_suppressed:${gates.suppressionReasons.join('+')}` };
|
|
401
|
+
}
|
|
402
|
+
if (!isValidTelemetrySecretHex(state.telemetrySecret)) {
|
|
403
|
+
logger.warn?.('[PD:Telemetry] Export skipped: telemetry secret missing/invalid. Run: pd telemetry enable --confirm');
|
|
404
|
+
return { attempted: false, skipReason: 'secret_missing' };
|
|
405
|
+
}
|
|
406
|
+
const secret = state.telemetrySecret;
|
|
407
|
+
const timestamp = now();
|
|
408
|
+
const bucketDate = bucketDateFromTime(timestamp);
|
|
409
|
+
const scopeKey = workspaceScopeIdFor(secret, workspaceDir);
|
|
410
|
+
const workspaceEntry = state.workspaceExports?.[scopeKey];
|
|
411
|
+
if (workspaceEntry?.lastSucceededAt !== undefined && bucketDateFromTime(Date.parse(workspaceEntry.lastSucceededAt)) === bucketDate) {
|
|
412
|
+
return { attempted: false, skipReason: 'already_succeeded_today' };
|
|
413
|
+
}
|
|
414
|
+
if (workspaceEntry?.nextRetryAt !== undefined && timestamp < Date.parse(workspaceEntry.nextRetryAt)) {
|
|
415
|
+
return { attempted: false, skipReason: 'retry_backoff' };
|
|
416
|
+
}
|
|
417
|
+
// Hard daily attempt cap (review round 2): the 1h/6h backoff alone
|
|
418
|
+
// permits up to 5 attempts on a bad day (0h/1h/7h/13h/19h); the counter
|
|
419
|
+
// makes the bound explicit and independent of clock skew. Per-workspace.
|
|
420
|
+
const priorAttempts = workspaceEntry?.attemptBucketDate === bucketDate ? (workspaceEntry?.dailyAttemptCount ?? 0) : 0;
|
|
421
|
+
if (priorAttempts >= MAX_DAILY_EXPORT_ATTEMPTS) {
|
|
422
|
+
return { attempted: false, skipReason: 'daily_attempt_cap' };
|
|
423
|
+
}
|
|
424
|
+
const lock = tryAcquireExportLock(getProductTelemetryStatePath(homeDir()), scopeKey);
|
|
425
|
+
if (!lock.acquired || lock.lockPath === undefined) {
|
|
426
|
+
// Another process is mid-export for THIS workspace; it will record
|
|
427
|
+
// shared-day success and our next trigger will see
|
|
428
|
+
// already_succeeded_today.
|
|
429
|
+
logger.info?.('[PD:Telemetry] Export skipped: another export is in flight for this workspace (lock busy).');
|
|
430
|
+
return { attempted: false, skipReason: 'export_lock_busy' };
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
// Re-read inside the lock: the lock holder before us may have just
|
|
434
|
+
// succeeded for today (fresh state beats the stale pre-lock copy).
|
|
435
|
+
const fresh = readProductTelemetryControlState(homeDir());
|
|
436
|
+
const freshSecret = fresh.ok ? fresh.state.telemetrySecret : undefined;
|
|
437
|
+
const effectiveState = fresh.ok && freshSecret === secret
|
|
438
|
+
? fresh.state
|
|
439
|
+
: state;
|
|
440
|
+
if (isValidTelemetrySecretHex(effectiveState.telemetrySecret)) {
|
|
441
|
+
const freshEntry = effectiveState.workspaceExports?.[scopeKey];
|
|
442
|
+
if (freshEntry?.lastSucceededAt !== undefined && bucketDateFromTime(Date.parse(freshEntry.lastSucceededAt)) === bucketDate) {
|
|
443
|
+
return { attempted: false, skipReason: 'already_succeeded_today' };
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const install = resolveInstallContext(homeDir());
|
|
447
|
+
const { facts } = readMilestoneFacts(workspaceDir);
|
|
448
|
+
const snapshot = buildProductTelemetrySnapshot({
|
|
449
|
+
dailyTelemetryId: deriveDailyTelemetryId(secret, scopeKey, bucketDate),
|
|
450
|
+
bucketDate,
|
|
451
|
+
pdVersion: install.pdVersion,
|
|
452
|
+
hostKind: install.hostKind,
|
|
453
|
+
milestones: {
|
|
454
|
+
initialized: facts.initialized,
|
|
455
|
+
painObserved: facts.painObserved,
|
|
456
|
+
principleObserved: facts.principleObserved,
|
|
457
|
+
activationObserved: facts.activationObserved,
|
|
458
|
+
presenceReceiptObserved: facts.presenceReceiptObserved,
|
|
459
|
+
effectReceiptObserved: facts.effectReceiptObserved,
|
|
460
|
+
},
|
|
461
|
+
reliability: { initializationFailed: facts.initializationFailed },
|
|
462
|
+
});
|
|
463
|
+
const result = await exportSnapshot({
|
|
464
|
+
snapshot,
|
|
465
|
+
...(deps.endpoint !== undefined ? { endpoint: deps.endpoint } : {}),
|
|
466
|
+
...(deps.fetchFn !== undefined ? { fetchFn: deps.fetchFn } : {}),
|
|
467
|
+
});
|
|
468
|
+
const priorEntry = effectiveState.workspaceExports?.[scopeKey];
|
|
469
|
+
const failedRecently = priorEntry?.lastAttemptedAt !== undefined && timestamp - Date.parse(priorEntry.lastAttemptedAt) < 24 * 60 * 60 * 1000;
|
|
470
|
+
const nextEntry = result.ok
|
|
471
|
+
? {
|
|
472
|
+
lastAttemptedAt: new Date(timestamp).toISOString(),
|
|
473
|
+
lastSucceededAt: new Date(timestamp).toISOString(),
|
|
474
|
+
}
|
|
475
|
+
: {
|
|
476
|
+
lastAttemptedAt: new Date(timestamp).toISOString(),
|
|
477
|
+
lastFailureCode: result.code,
|
|
478
|
+
nextRetryAt: new Date(timestamp + nextRetryDelayMs(failedRecently)).toISOString(),
|
|
479
|
+
dailyAttemptCount: priorAttempts + 1,
|
|
480
|
+
attemptBucketDate: bucketDate,
|
|
481
|
+
};
|
|
482
|
+
// Review round 4: merge under the machine-scope state-update lock —
|
|
483
|
+
// the OTHER workspaces' entries come from a fresh read inside the
|
|
484
|
+
// critical section, so concurrently-completing exports compose
|
|
485
|
+
// instead of dropping each other's bookkeeping (lost update). The
|
|
486
|
+
// network export above stayed concurrent; only this ms-scale file
|
|
487
|
+
// RMW is serialized. Every non-written outcome is logged (rc-9).
|
|
488
|
+
const merged = await mergeWorkspaceExportEntry({
|
|
489
|
+
homeDir: homeDir(),
|
|
490
|
+
secret,
|
|
491
|
+
scopeKey,
|
|
492
|
+
timestamp,
|
|
493
|
+
nextEntry,
|
|
494
|
+
});
|
|
495
|
+
if (merged === 'skipped_contention') {
|
|
496
|
+
logger.warn?.(`[PD:Telemetry] Export ${result.ok ? 'succeeded' : `failed (${result.code})`} but control state update was skipped after ${STATE_UPDATE_LOCK_TIMEOUT_MS / 1000}s of state-lock contention — this workspace may re-attempt today.`);
|
|
497
|
+
}
|
|
498
|
+
else if (merged === 'skipped_unreadable') {
|
|
499
|
+
logger.warn?.(`[PD:Telemetry] Export ${result.ok ? 'succeeded' : `failed (${result.code})`} but control state could not be read/updated — ${getProductTelemetryStatePath(homeDir())}`);
|
|
500
|
+
}
|
|
501
|
+
else if (merged === 'skipped_identity_changed') {
|
|
502
|
+
logger.info?.('[PD:Telemetry] Export finished after a consent/secret change; stale export bookkeeping was NOT written back.');
|
|
503
|
+
}
|
|
504
|
+
if (result.ok) {
|
|
505
|
+
logger.info?.('[PD:Telemetry] Daily anonymous product telemetry snapshot exported.');
|
|
506
|
+
return { attempted: true, ok: true, httpStatus: result.status };
|
|
507
|
+
}
|
|
508
|
+
logger.warn?.(`[PD:Telemetry] Export failed (${result.code}); next retry after ${nextEntry.nextRetryAt}. PD behavior is unaffected.`);
|
|
509
|
+
return { attempted: true, ok: false, failureCode: result.code };
|
|
510
|
+
}
|
|
511
|
+
finally {
|
|
512
|
+
releaseExportLock(lock.lockPath);
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Fire-and-forget scheduling for normal-activity triggers. The timer is
|
|
519
|
+
* unref'd so it never keeps a host process alive, and every failure path is
|
|
520
|
+
* contained — telemetry can never block or crash PD startup or hooks.
|
|
521
|
+
*/
|
|
522
|
+
export function scheduleProductTelemetryExport(service, workspaceDir, scheduleLogger) {
|
|
523
|
+
const timer = setTimeout(() => {
|
|
524
|
+
void service
|
|
525
|
+
.maybeExportDaily(workspaceDir)
|
|
526
|
+
.catch((error) => {
|
|
527
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
528
|
+
scheduleLogger?.warn?.(`[PD:Telemetry] Unexpected export error (PD unaffected): ${message}`);
|
|
529
|
+
});
|
|
530
|
+
}, 0);
|
|
531
|
+
timer.unref?.();
|
|
532
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace scope resolution — Anonymous Product Telemetry v1
|
|
3
|
+
* (review remediation: measurement unit = workspace).
|
|
4
|
+
*
|
|
5
|
+
* Telemetry bookkeeping (dedup, retry, export lock) is keyed per workspace
|
|
6
|
+
* while consent and the telemetry secret stay machine-scope. The key is a
|
|
7
|
+
* keyed fingerprint of the canonical workspace path:
|
|
8
|
+
*
|
|
9
|
+
* workspaceScopeId = HMAC(telemetrySecret, "workspace:" + canonicalPath)
|
|
10
|
+
*
|
|
11
|
+
* — opaque, locally stable, and carrying no path information without the
|
|
12
|
+
* secret (a plain path hash would be enumerable; this is not). It exists
|
|
13
|
+
* only in `~/.pd/product-telemetry.json` and lock filenames. The server
|
|
14
|
+
* never sees it.
|
|
15
|
+
*
|
|
16
|
+
* A workspace that is moved or renamed resolves to a new scope and is
|
|
17
|
+
* deliberately treated as a new telemetry workspace — no cross-path
|
|
18
|
+
* identity is created.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Canonicalize a workspace directory for scope derivation.
|
|
22
|
+
*
|
|
23
|
+
* Invariants (tested): `D:\Code\PD`, `d:\code\pd\`, and any casing variant
|
|
24
|
+
* on Windows resolve to ONE canonical form, so equivalent spellings never
|
|
25
|
+
* produce two telemetry units. On POSIX (case-sensitive filesystems) case
|
|
26
|
+
* is preserved. realpath collapses symlinks and Windows 8.3 short names;
|
|
27
|
+
* when realpath is unavailable (permissions, vanished dir) the resolved
|
|
28
|
+
* path is used as-is — a degraded key, never a thrown error.
|
|
29
|
+
*/
|
|
30
|
+
export declare function canonicalizeWorkspacePath(workspaceDir: string): string;
|
|
31
|
+
/** Compute the local workspace scope key (hex16) for one secret + workspace. */
|
|
32
|
+
export declare function workspaceScopeIdFor(telemetrySecretHex: string, workspaceDir: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Per-workspace export lock path. The filename embeds ONLY the opaque hex
|
|
35
|
+
* scope key — never the workspace path — so one workspace's in-flight
|
|
36
|
+
* export never blocks another's (review remediation P1-1).
|
|
37
|
+
*/
|
|
38
|
+
export declare function workspaceExportLockPath(stateFilePath: string, workspaceScopeId: string): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace scope resolution — Anonymous Product Telemetry v1
|
|
3
|
+
* (review remediation: measurement unit = workspace).
|
|
4
|
+
*
|
|
5
|
+
* Telemetry bookkeeping (dedup, retry, export lock) is keyed per workspace
|
|
6
|
+
* while consent and the telemetry secret stay machine-scope. The key is a
|
|
7
|
+
* keyed fingerprint of the canonical workspace path:
|
|
8
|
+
*
|
|
9
|
+
* workspaceScopeId = HMAC(telemetrySecret, "workspace:" + canonicalPath)
|
|
10
|
+
*
|
|
11
|
+
* — opaque, locally stable, and carrying no path information without the
|
|
12
|
+
* secret (a plain path hash would be enumerable; this is not). It exists
|
|
13
|
+
* only in `~/.pd/product-telemetry.json` and lock filenames. The server
|
|
14
|
+
* never sees it.
|
|
15
|
+
*
|
|
16
|
+
* A workspace that is moved or renamed resolves to a new scope and is
|
|
17
|
+
* deliberately treated as a new telemetry workspace — no cross-path
|
|
18
|
+
* identity is created.
|
|
19
|
+
*/
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { deriveWorkspaceScopeId } from '@principles/core/runtime-v2';
|
|
23
|
+
/**
|
|
24
|
+
* Canonicalize a workspace directory for scope derivation.
|
|
25
|
+
*
|
|
26
|
+
* Invariants (tested): `D:\Code\PD`, `d:\code\pd\`, and any casing variant
|
|
27
|
+
* on Windows resolve to ONE canonical form, so equivalent spellings never
|
|
28
|
+
* produce two telemetry units. On POSIX (case-sensitive filesystems) case
|
|
29
|
+
* is preserved. realpath collapses symlinks and Windows 8.3 short names;
|
|
30
|
+
* when realpath is unavailable (permissions, vanished dir) the resolved
|
|
31
|
+
* path is used as-is — a degraded key, never a thrown error.
|
|
32
|
+
*/
|
|
33
|
+
export function canonicalizeWorkspacePath(workspaceDir) {
|
|
34
|
+
let resolved = path.resolve(workspaceDir);
|
|
35
|
+
try {
|
|
36
|
+
resolved = fs.realpathSync(resolved);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Non-throwing by contract (telemetry must never break PD); the resolved
|
|
40
|
+
// path is still a usable local key.
|
|
41
|
+
}
|
|
42
|
+
let canonical = resolved.split(path.sep).join('/');
|
|
43
|
+
if (process.platform === 'win32') {
|
|
44
|
+
// Windows filesystems are case-insensitive: normalize away casing drift
|
|
45
|
+
// (drive letter included) so D:\Code\PD == d:\code\pd.
|
|
46
|
+
canonical = canonical.toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
if (canonical.length > 1 && canonical.endsWith('/')) {
|
|
49
|
+
canonical = canonical.slice(0, -1);
|
|
50
|
+
}
|
|
51
|
+
return canonical;
|
|
52
|
+
}
|
|
53
|
+
/** Compute the local workspace scope key (hex16) for one secret + workspace. */
|
|
54
|
+
export function workspaceScopeIdFor(telemetrySecretHex, workspaceDir) {
|
|
55
|
+
return deriveWorkspaceScopeId(telemetrySecretHex, canonicalizeWorkspacePath(workspaceDir));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Per-workspace export lock path. The filename embeds ONLY the opaque hex
|
|
59
|
+
* scope key — never the workspace path — so one workspace's in-flight
|
|
60
|
+
* export never blocks another's (review remediation P1-1).
|
|
61
|
+
*/
|
|
62
|
+
export function workspaceExportLockPath(stateFilePath, workspaceScopeId) {
|
|
63
|
+
return `${stateFilePath}.export-lock.${workspaceScopeId}`;
|
|
64
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/host-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Shared host-neutral orchestration for Principles Disciple MVP-Core hook paths.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,16 +22,21 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@principles/core": "file:../core",
|
|
25
|
+
"@principles/install-layout": "file:../install-layout",
|
|
25
26
|
"better-sqlite3": "^13.0.3",
|
|
26
27
|
"js-yaml": "^5.2.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"@types/better-sqlite3": "^
|
|
30
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
30
31
|
"@types/node": "^26.1.2",
|
|
31
32
|
"@types/js-yaml": "^4.0.9",
|
|
32
33
|
"typescript": "^7.0.2",
|
|
33
34
|
"vite": "^8.0.16",
|
|
34
35
|
"vitest": "^4.1.10"
|
|
35
36
|
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/csuzngjh/principles.git"
|
|
40
|
+
},
|
|
36
41
|
"license": "MIT"
|
|
37
42
|
}
|