create-principles-disciple 1.100.0 → 1.102.0
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/console/dist/server/index.js +9 -0
- package/console/dist/server/models/ActivationsConsoleModel.d.ts +37 -1
- package/console/dist/server/models/ActivationsConsoleModel.js +114 -13
- package/console/dist/server/routes/onboarding.d.ts +34 -0
- package/console/dist/server/routes/onboarding.js +324 -0
- package/console/dist/ui/App.js +74 -15
- package/console/dist/ui/api.d.ts +17 -1
- package/console/dist/ui/api.js +6 -1
- package/console/dist/ui/components/onboarding/CircuitDiagram.d.ts +8 -0
- package/console/dist/ui/components/onboarding/CircuitDiagram.js +18 -0
- package/console/dist/ui/components/onboarding/DemoResultView.d.ts +22 -0
- package/console/dist/ui/components/onboarding/DemoResultView.js +15 -0
- package/console/dist/ui/i18n/en.json +127 -0
- package/console/dist/ui/i18n/zh-CN.json +127 -0
- package/console/dist/ui/pages/activation/ActivationValidators.js +1 -1
- package/console/dist/ui/pages/control-center/AgentCard.js +10 -3
- package/console/dist/ui/pages/control-center/ControlCenterPage.js +1 -1
- package/console/dist/ui/pages/settings/SettingsPage.js +64 -5
- package/console/dist/ui/pages/signal-keywords/KeywordEditDialog.d.ts +12 -0
- package/console/dist/ui/pages/signal-keywords/KeywordEditDialog.js +41 -0
- package/console/dist/ui/pages/signal-keywords/KeywordListSection.d.ts +10 -0
- package/console/dist/ui/pages/signal-keywords/KeywordListSection.js +39 -0
- package/console/dist/ui/pages/signal-keywords/PendingTermsSection.d.ts +14 -0
- package/console/dist/ui/pages/signal-keywords/PendingTermsSection.js +35 -0
- package/console/dist/ui/pages/signal-keywords/SignalKeywordsPage.d.ts +1 -0
- package/console/dist/ui/pages/signal-keywords/SignalKeywordsPage.js +49 -0
- package/console/dist/ui/pages/welcome/WelcomePage.d.ts +5 -0
- package/console/dist/ui/pages/welcome/WelcomePage.js +185 -0
- package/console/dist/ui/utils/__tests__/onboarding-state.test.d.ts +1 -0
- package/console/dist/ui/utils/__tests__/onboarding-state.test.js +72 -0
- package/console/dist/ui/utils/agent-metadata.d.ts +13 -0
- package/console/dist/ui/utils/agent-metadata.js +26 -0
- package/console/dist/ui/utils/onboarding-state.d.ts +25 -0
- package/console/dist/ui/utils/onboarding-state.js +77 -0
- package/console/dist/ui/utils/signal-keywords-api.d.ts +68 -0
- package/console/dist/ui/utils/signal-keywords-api.js +121 -0
- package/console/dist/ui/utils/signal-keywords-types.d.ts +57 -0
- package/console/dist/ui/utils/signal-keywords-types.js +10 -0
- package/console/dist/ui/utils/signal-keywords-validators.d.ts +20 -0
- package/console/dist/ui/utils/signal-keywords-validators.js +114 -0
- package/console/dist/web/assets/app.css +401 -0
- package/console/dist/web/assets/app.js +6904 -1835
- package/core/dist/index.d.ts +2 -0
- package/core/dist/index.d.ts.map +1 -1
- package/core/dist/index.js +1 -0
- package/core/dist/index.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/admission-gate.test.js +63 -1
- package/core/dist/runtime-v2/__tests__/admission-gate.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +21 -5
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/chain-integrity-attack.test.js +17 -0
- package/core/dist/runtime-v2/__tests__/chain-integrity-attack.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/chain-integrity-real-path.test.js +12 -0
- package/core/dist/runtime-v2/__tests__/chain-integrity-real-path.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/evaluator-runner-vslice-v2.test.js +28 -0
- package/core/dist/runtime-v2/__tests__/evaluator-runner-vslice-v2.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/internalization-chain-integrity-read-model.test.js +168 -1
- package/core/dist/runtime-v2/__tests__/internalization-chain-integrity-read-model.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/pain-to-principle-service.test.js +8 -0
- package/core/dist/runtime-v2/__tests__/pain-to-principle-service.test.js.map +1 -1
- package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js +53 -0
- package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js.map +1 -1
- package/core/dist/runtime-v2/activation/__tests__/helpers.d.ts +21 -0
- package/core/dist/runtime-v2/activation/__tests__/helpers.d.ts.map +1 -0
- package/core/dist/runtime-v2/activation/__tests__/helpers.js +132 -0
- package/core/dist/runtime-v2/activation/__tests__/helpers.js.map +1 -0
- package/core/dist/runtime-v2/activation/__tests__/story-a-acceptance.test.js +2 -117
- package/core/dist/runtime-v2/activation/__tests__/story-a-acceptance.test.js.map +1 -1
- package/core/dist/runtime-v2/activation/activation-dispatcher.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/activation-dispatcher.js +18 -2
- package/core/dist/runtime-v2/activation/activation-dispatcher.js.map +1 -1
- package/core/dist/runtime-v2/activation/index.d.ts +1 -1
- package/core/dist/runtime-v2/activation/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/index.js +1 -1
- package/core/dist/runtime-v2/activation/index.js.map +1 -1
- package/core/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js +253 -6
- package/core/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js.map +1 -1
- package/core/dist/runtime-v2/activation/writers/rule-host-writer.d.ts +12 -0
- package/core/dist/runtime-v2/activation/writers/rule-host-writer.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/writers/rule-host-writer.js +96 -2
- package/core/dist/runtime-v2/activation/writers/rule-host-writer.js.map +1 -1
- package/core/dist/runtime-v2/admission-gate.d.ts +27 -0
- package/core/dist/runtime-v2/admission-gate.d.ts.map +1 -1
- package/core/dist/runtime-v2/admission-gate.js +55 -0
- package/core/dist/runtime-v2/admission-gate.js.map +1 -1
- package/core/dist/runtime-v2/cli/diagnose.d.ts +4 -0
- package/core/dist/runtime-v2/cli/diagnose.d.ts.map +1 -1
- package/core/dist/runtime-v2/cli/diagnose.js +25 -0
- package/core/dist/runtime-v2/cli/diagnose.js.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/internal-agent-names.test.d.ts +2 -0
- package/core/dist/runtime-v2/config/__tests__/internal-agent-names.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/__tests__/internal-agent-names.test.js +15 -0
- package/core/dist/runtime-v2/config/__tests__/internal-agent-names.test.js.map +1 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js +7 -0
- 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 +28 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-redaction-deep.test.js +1 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-redaction-deep.test.js.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js +1 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js.map +1 -1
- package/core/dist/runtime-v2/config/index.d.ts +5 -3
- package/core/dist/runtime-v2/config/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/index.js +8 -2
- package/core/dist/runtime-v2/config/index.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +2 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.js +12 -0
- package/core/dist/runtime-v2/config/pd-config-defaults.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 +25 -1
- 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 +6 -1
- package/core/dist/runtime-v2/config/pd-config-feature-flags.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.js +9 -0
- package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.d.ts +110 -1
- package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.js +7 -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 +114 -1
- package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-profile-constants.d.ts +27 -0
- package/core/dist/runtime-v2/config/pd-profile-constants.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/pd-profile-constants.js +145 -0
- package/core/dist/runtime-v2/config/pd-profile-constants.js.map +1 -0
- package/core/dist/runtime-v2/config/pd-validate-profile.d.ts +28 -0
- package/core/dist/runtime-v2/config/pd-validate-profile.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/pd-validate-profile.js +482 -0
- package/core/dist/runtime-v2/config/pd-validate-profile.js.map +1 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +26 -0
- package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/__tests__/plugin-surface-registry.test.js +12 -1
- package/core/dist/runtime-v2/feature-flags/__tests__/plugin-surface-registry.test.js.map +1 -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 +8 -0
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.d.ts.map +1 -1
- package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.js +8 -2
- package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +6 -4
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +5 -3
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js +25 -0
- package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/artificer-rule-output.test.js +139 -0
- package/core/dist/runtime-v2/internalization/__tests__/artificer-rule-output.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/diag-chain-e2e.test.js +20 -1
- package/core/dist/runtime-v2/internalization/__tests__/diag-chain-e2e.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-intent-tension.test.js +7 -0
- package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-intent-tension.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js +21 -0
- package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/rule-host-input-builder.test.js +23 -0
- package/core/dist/runtime-v2/internalization/__tests__/rule-host-input-builder.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-output.d.ts +7 -0
- package/core/dist/runtime-v2/internalization/artificer-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-output.js +22 -1
- package/core/dist/runtime-v2/internalization/artificer-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js +2 -0
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-runner.js +13 -0
- package/core/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-distiller-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-distiller-runner.js +4 -1
- package/core/dist/runtime-v2/internalization/diag-distiller-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-rootcause-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js +7 -1
- package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.js +4 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js +7 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/index.d.ts +1 -1
- package/core/dist/runtime-v2/internalization/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/index.js +1 -1
- package/core/dist/runtime-v2/internalization/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/internalization-task-guards.d.ts +7 -0
- package/core/dist/runtime-v2/internalization/internalization-task-guards.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/internalization-task-guards.js +8 -0
- package/core/dist/runtime-v2/internalization/internalization-task-guards.js.map +1 -1
- package/core/dist/runtime-v2/internalization-chain-integrity-read-model.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization-chain-integrity-read-model.js +57 -1
- package/core/dist/runtime-v2/internalization-chain-integrity-read-model.js.map +1 -1
- package/core/dist/runtime-v2/proven-channel-baseline.js +1 -1
- package/core/dist/runtime-v2/proven-channel-baseline.js.map +1 -1
- package/core/dist/runtime-v2/runner/__tests__/diagnose.test.js +91 -1
- package/core/dist/runtime-v2/runner/__tests__/diagnose.test.js.map +1 -1
- package/core/dist/runtime-v2/signal-collector/__tests__/keyword-stage.test.d.ts +2 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/keyword-stage.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/keyword-stage.test.js +39 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/keyword-stage.test.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/llm-stage.test.d.ts +2 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/llm-stage.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/llm-stage.test.js +34 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/llm-stage.test.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/signal-collector.test.d.ts +2 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/signal-collector.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/signal-collector.test.js +222 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/signal-collector.test.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/types.test.d.ts +2 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/types.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/types.test.js +35 -0
- package/core/dist/runtime-v2/signal-collector/__tests__/types.test.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/index.d.ts +5 -0
- package/core/dist/runtime-v2/signal-collector/index.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/index.js +5 -0
- package/core/dist/runtime-v2/signal-collector/index.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts +20 -0
- package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/keyword-stage.js +61 -0
- package/core/dist/runtime-v2/signal-collector/keyword-stage.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/llm-stage.d.ts +16 -0
- package/core/dist/runtime-v2/signal-collector/llm-stage.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/llm-stage.js +34 -0
- package/core/dist/runtime-v2/signal-collector/llm-stage.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/signal-collector.d.ts +16 -0
- package/core/dist/runtime-v2/signal-collector/signal-collector.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/signal-collector.js +71 -0
- package/core/dist/runtime-v2/signal-collector/signal-collector.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/types.d.ts +61 -0
- package/core/dist/runtime-v2/signal-collector/types.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/types.js +45 -0
- package/core/dist/runtime-v2/signal-collector/types.js.map +1 -0
- package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.js +19 -0
- package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.js.map +1 -1
- package/core/dist/runtime-v2/store/schema-conformance.test.js +73 -0
- package/core/dist/runtime-v2/store/schema-conformance.test.js.map +1 -1
- package/core/dist/runtime-v2/store/sqlite-connection-schema-version.test.js +41 -7
- package/core/dist/runtime-v2/store/sqlite-connection-schema-version.test.js.map +1 -1
- package/core/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/sqlite-connection.js +22 -1
- package/core/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
- package/core/dist/runtime-v2/types/event-types.d.ts +40 -3
- package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/event-types.js +9 -0
- package/core/dist/runtime-v2/types/event-types.js.map +1 -1
- package/core/dist/runtime-v2/types/index.d.ts +2 -2
- 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/workflow-funnel-loader.js +1 -1
- package/core/dist/workflow-funnel-loader.js.map +1 -1
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +6 -0
- package/dist/i18n.js.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/installer.d.ts +3 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +167 -1
- package/dist/installer.js.map +1 -1
- package/dist/mvp-config.d.ts +8 -0
- package/dist/mvp-config.d.ts.map +1 -1
- package/dist/mvp-config.js +12 -2
- package/dist/mvp-config.js.map +1 -1
- package/package.json +1 -1
- package/pd-cli/dist/commands/candidate.d.ts.map +1 -1
- package/pd-cli/dist/commands/candidate.js +89 -7
- package/pd-cli/dist/commands/candidate.js.map +1 -1
- package/pd-cli/dist/commands/pain-retry.d.ts +3 -0
- package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-retry.js +39 -14
- package/pd-cli/dist/commands/pain-retry.js.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.d.ts +4 -0
- package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.js +244 -10
- package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
- package/pd-cli/dist/index.js +7 -84
- package/pd-cli/dist/index.js.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/commands/context.js +17 -11
- package/plugin/dist/commands/samples.js +25 -0
- package/plugin/dist/core/event-log.d.ts +11 -1
- package/plugin/dist/core/event-log.js +13 -0
- package/plugin/dist/core/init.js +3 -4
- package/plugin/dist/core/rule-host.d.ts +68 -0
- package/plugin/dist/core/rule-host.js +167 -28
- package/plugin/dist/core/signal-collector-host.d.ts +105 -0
- package/plugin/dist/core/signal-collector-host.js +337 -0
- package/plugin/dist/hooks/prompt.d.ts +3 -2
- package/plugin/dist/hooks/prompt.js +47 -571
- package/plugin/dist/types/event-types.d.ts +1 -1
- package/plugin/dist/types.d.ts +5 -25
- package/plugin/dist/types.js +4 -9
- package/plugin/dist/utils/hashing.js +1 -1
|
@@ -17,6 +17,7 @@ import { handleGovernanceRoute, disposeGovernanceModels } from './routes/governa
|
|
|
17
17
|
import { handleEvidenceChainRoute, disposeEvidenceChainModels } from './routes/evidence-chain.js';
|
|
18
18
|
import { handleIntentRoute, disposeIntentModels } from './routes/intent.js';
|
|
19
19
|
import { handleIntentDecisionsRoute, disposeIntentDecisionModels } from './routes/intent-decisions.js';
|
|
20
|
+
import { handleOnboardingRoute, disposeOnboardingModels } from './routes/onboarding.js';
|
|
20
21
|
import { createWorkspacesRoutes } from './routes/workspaces.js';
|
|
21
22
|
import { handleUpdateRoute } from './routes/update.js';
|
|
22
23
|
import { handleUpdateHistoryRoute } from './routes/update-history.js';
|
|
@@ -229,6 +230,7 @@ async function closeServices() {
|
|
|
229
230
|
disposeEvidenceChainModels();
|
|
230
231
|
disposeIntentModels();
|
|
231
232
|
disposeIntentDecisionModels();
|
|
233
|
+
disposeOnboardingModels();
|
|
232
234
|
}
|
|
233
235
|
// ── Route handler ───────────────────────────────────────────────────────────
|
|
234
236
|
function handleRequest(services) {
|
|
@@ -340,6 +342,13 @@ function handleRequest(services) {
|
|
|
340
342
|
asyncHandler(() => handleIntentDecisionsRoute(req, res, services.workspaceDir, subPath))(req, res);
|
|
341
343
|
return;
|
|
342
344
|
}
|
|
345
|
+
// Onboarding wizard: POST /api/v1/onboarding/run-demo — spawns `pd demo story-a`
|
|
346
|
+
// (spec 2026-06-30-new-user-onboarding-design.md §6.3 改动 5)
|
|
347
|
+
if (urlPath === '/api/v1/onboarding' || urlPath.startsWith('/api/v1/onboarding/')) {
|
|
348
|
+
const subPath = urlPath === '/api/v1/onboarding' ? '' : urlPath.slice('/api/v1/onboarding'.length);
|
|
349
|
+
asyncHandler(() => handleOnboardingRoute(req, res, { workspaceDir: services.workspaceDir, subPath }))(req, res);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
343
352
|
// PRI-331: GET /api/v1/evidence-chain
|
|
344
353
|
if (urlPath === '/api/v1/evidence-chain') {
|
|
345
354
|
asyncHandler(() => handleEvidenceChainRoute(req, res, services.workspaceDir))(req, res);
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRI-491 — owner-observable activation record.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the CLI's `AnnotatedActivation` so the Console surfaces the same
|
|
5
|
+
* mode / status / contextVersion / evidenceRefs / nextAction fields the owner
|
|
6
|
+
* sees in `pd activation list`. Without this enrichment the Console would
|
|
7
|
+
* hide suspended-by-flag state, silently showing v2 activations as "active"
|
|
8
|
+
* even when the rulecode_context_v2 flag is off.
|
|
9
|
+
*/
|
|
1
10
|
export interface ActivationRecord {
|
|
2
11
|
id: string;
|
|
3
12
|
artifactId: string;
|
|
@@ -6,7 +15,34 @@ export interface ActivationRecord {
|
|
|
6
15
|
action: string;
|
|
7
16
|
targetRef: string;
|
|
8
17
|
activatedAt: string | null;
|
|
9
|
-
|
|
18
|
+
/** When this activation was promoted from shadow to live (null for never-promoted). */
|
|
19
|
+
promotedAt: string | null;
|
|
20
|
+
/** When this activation was deactivated (null for active). */
|
|
21
|
+
deactivatedAt: string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Lifecycle mode derived from action.
|
|
24
|
+
* - 'shadow' for code_tool_hook_shadow_activate
|
|
25
|
+
* - 'live' for code_tool_hook_live_activate
|
|
26
|
+
* - undefined for unrecognized actions (cannot be safely mode-tagged).
|
|
27
|
+
*/
|
|
28
|
+
mode?: 'shadow' | 'live';
|
|
29
|
+
/**
|
|
30
|
+
* Owner-visible status. Precedence: deactivated > suspended_by_flag > active.
|
|
31
|
+
* - 'deactivated' — deactivatedAt is non-null.
|
|
32
|
+
* - 'suspended_by_flag' — v2 artifact but rulecode_context_v2 flag is off.
|
|
33
|
+
* - 'active' — loaded and (for v2) flag is on.
|
|
34
|
+
*/
|
|
35
|
+
status: 'active' | 'deactivated' | 'suspended_by_flag';
|
|
36
|
+
/** Artifact context version derived from requiresContextVersion field. */
|
|
37
|
+
contextVersion?: 'v1' | 'v2';
|
|
38
|
+
/** Owner-labelled evidence refs preserved from the artifact (PRI-490). */
|
|
39
|
+
evidenceRefs?: string[];
|
|
40
|
+
/** Human-readable summary of evidenceRefs for display. */
|
|
41
|
+
evidenceSummary?: string;
|
|
42
|
+
/** Next CLI command the owner should run to act on this activation. */
|
|
43
|
+
nextAction?: string;
|
|
44
|
+
/** Present when the activation references a non-existent artifact. */
|
|
45
|
+
warning?: string;
|
|
10
46
|
}
|
|
11
47
|
export interface ActivationsResponse {
|
|
12
48
|
activations: ActivationRecord[];
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import { SqliteConnection, SqliteActivationStateStore, SqlitePIArtifactStore, extractPrincipleId, } from '@principles/core/runtime-v2';
|
|
1
|
+
import { SqliteConnection, SqliteActivationStateStore, SqlitePIArtifactStore, extractPrincipleId, extractEvidenceRefs, } from '@principles/core/runtime-v2';
|
|
2
|
+
import { loadPdConfig, computeFlagsFromLoadResult } from '../config/pd-config-store.js';
|
|
2
3
|
import * as fs from 'node:fs';
|
|
3
4
|
import * as path from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* Type guard for parsed JSON objects (rc-2-no-as-bypass).
|
|
7
|
+
* Replaces `as Record<string, unknown>` casts on untrusted contentJson.
|
|
8
|
+
*/
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
+
}
|
|
4
12
|
function isMissingTableError(err) {
|
|
5
13
|
if (!(err instanceof Error))
|
|
6
14
|
return false;
|
|
@@ -56,13 +64,45 @@ export class ActivationsConsoleModel {
|
|
|
56
64
|
// Without this, dreamer artifacts whose sourcePrincipleId was stripped
|
|
57
65
|
// (non-core-principle case) would show 'unlinked' even when contentJson
|
|
58
66
|
// carries a resolvable principleId.
|
|
67
|
+
//
|
|
68
|
+
// PRI-491: also collect contextVersion + evidenceRefs from contentJson so
|
|
69
|
+
// the Console can show whether a rule will block and why. rc-1/rc-2:
|
|
70
|
+
// contentJson is parsed as unknown and type-narrowed with typeof; never
|
|
71
|
+
// `as`-cast without a prior typeof check.
|
|
59
72
|
const artifactPrincipleMap = new Map();
|
|
73
|
+
const artifactMetadata = new Map();
|
|
74
|
+
const danglingArtifactIds = new Set();
|
|
60
75
|
for (const activation of allActivations) {
|
|
61
76
|
if (!artifactPrincipleMap.has(activation.artifactId)) {
|
|
62
77
|
try {
|
|
63
78
|
const artifact = await artifactStore.getArtifactById(activation.artifactId);
|
|
64
|
-
|
|
65
|
-
|
|
79
|
+
if (!artifact) {
|
|
80
|
+
danglingArtifactIds.add(activation.artifactId);
|
|
81
|
+
artifactPrincipleMap.set(activation.artifactId, null);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const principleId = extractPrincipleId(toSnapshot(artifact));
|
|
85
|
+
artifactPrincipleMap.set(activation.artifactId, principleId);
|
|
86
|
+
// PRI-491: extract contextVersion + evidenceRefs from contentJson.
|
|
87
|
+
// rc-1: treat parsed JSON as unknown; rc-2: narrow with typeof.
|
|
88
|
+
let parsedContent = null;
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(artifact.contentJson);
|
|
91
|
+
if (isRecord(parsed)) {
|
|
92
|
+
parsedContent = parsed;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Malformed contentJson — treat as no metadata. Not dangling,
|
|
97
|
+
// just unreadable; the principleId may still resolve via column.
|
|
98
|
+
}
|
|
99
|
+
const requiresCtxV2 = parsedContent !== null
|
|
100
|
+
&& Object.hasOwn(parsedContent, 'requiresContextVersion')
|
|
101
|
+
&& parsedContent.requiresContextVersion === 2;
|
|
102
|
+
const contextVersion = requiresCtxV2 ? 'v2' : 'v1';
|
|
103
|
+
const evidenceRefs = parsedContent !== null ? extractEvidenceRefs(parsedContent) : null;
|
|
104
|
+
artifactMetadata.set(activation.artifactId, { contextVersion, evidenceRefs });
|
|
105
|
+
}
|
|
66
106
|
}
|
|
67
107
|
catch (err) {
|
|
68
108
|
if (isMissingTableError(err)) {
|
|
@@ -74,16 +114,77 @@ export class ActivationsConsoleModel {
|
|
|
74
114
|
}
|
|
75
115
|
}
|
|
76
116
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
117
|
+
// PRI-491: Probe rulecode_context_v2 flag to determine suspended_by_flag
|
|
118
|
+
// status for v2 activations. When the flag is off, v2 activations are
|
|
119
|
+
// suspended (not executing) even though they remain active in the DB.
|
|
120
|
+
const featureFlags = computeFlagsFromLoadResult(loadPdConfig(this.workspaceDir));
|
|
121
|
+
const v2FlagEnabled = featureFlags.flags.rulecode_context_v2?.enabled === true;
|
|
122
|
+
const facts = allActivations.map((record) => {
|
|
123
|
+
const meta = artifactMetadata.get(record.artifactId);
|
|
124
|
+
const contextVersion = meta?.contextVersion;
|
|
125
|
+
const evidenceRefs = meta?.evidenceRefs ?? undefined;
|
|
126
|
+
const evidenceSummary = evidenceRefs && evidenceRefs.length > 0
|
|
127
|
+
? `${evidenceRefs.length} evidence ref(s): ${evidenceRefs.slice(0, 3).join(', ')}${evidenceRefs.length > 3 ? '...' : ''}`
|
|
128
|
+
: undefined;
|
|
129
|
+
// Derive mode from action (shadow_activate -> shadow, live_activate -> live).
|
|
130
|
+
const mode = record.action === 'code_tool_hook_shadow_activate'
|
|
131
|
+
? 'shadow'
|
|
132
|
+
: record.action === 'code_tool_hook_live_activate'
|
|
133
|
+
? 'live'
|
|
134
|
+
: undefined;
|
|
135
|
+
// Derive status: deactivated > suspended_by_flag > active (matches CLI).
|
|
136
|
+
let status;
|
|
137
|
+
let nextAction;
|
|
138
|
+
if (record.deactivatedAt) {
|
|
139
|
+
status = 'deactivated';
|
|
140
|
+
nextAction = undefined;
|
|
141
|
+
}
|
|
142
|
+
else if (contextVersion === 'v2' && !v2FlagEnabled) {
|
|
143
|
+
status = 'suspended_by_flag';
|
|
144
|
+
nextAction = `Enable rulecode_context_v2 flag or deactivate: pd activation deactivate --activation-id ${record.activationId} --confirm`;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
status = 'active';
|
|
148
|
+
if (mode === 'shadow') {
|
|
149
|
+
nextAction = `pd activation promote --activation-id ${record.activationId} --confirm`;
|
|
150
|
+
}
|
|
151
|
+
else if (mode === 'live') {
|
|
152
|
+
nextAction = `pd activation deactivate --activation-id ${record.activationId} --confirm`;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
nextAction = undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const enriched = {
|
|
159
|
+
id: record.activationId,
|
|
160
|
+
artifactId: record.artifactId,
|
|
161
|
+
principleId: artifactPrincipleMap.get(record.artifactId) ?? 'unlinked',
|
|
162
|
+
channel: record.channel,
|
|
163
|
+
action: record.action,
|
|
164
|
+
targetRef: record.targetRef,
|
|
165
|
+
activatedAt: record.activatedAt,
|
|
166
|
+
promotedAt: record.promotedAt ?? null,
|
|
167
|
+
deactivatedAt: record.deactivatedAt,
|
|
168
|
+
mode,
|
|
169
|
+
status,
|
|
170
|
+
contextVersion,
|
|
171
|
+
evidenceRefs,
|
|
172
|
+
evidenceSummary,
|
|
173
|
+
nextAction,
|
|
174
|
+
};
|
|
175
|
+
if (danglingArtifactIds.has(record.artifactId)) {
|
|
176
|
+
enriched.warning = `artifact_id "${record.artifactId}" does not exist in pi_artifacts - activation is orphaned`;
|
|
177
|
+
}
|
|
178
|
+
return enriched;
|
|
179
|
+
});
|
|
180
|
+
// rc-9: surface dangling references instead of silently returning a degraded list.
|
|
181
|
+
if (danglingArtifactIds.size > 0) {
|
|
182
|
+
return {
|
|
183
|
+
activations: facts,
|
|
184
|
+
generatedAt: new Date().toISOString(),
|
|
185
|
+
note: `${danglingArtifactIds.size} activation(s) reference non-existent artifact_id(s): ${Array.from(danglingArtifactIds).join(', ')}`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
87
188
|
return {
|
|
88
189
|
activations: facts,
|
|
89
190
|
generatedAt: new Date().toISOString(),
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding route - POST /api/v1/onboarding/run-demo
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/superpowers/specs/2026-06-30-new-user-onboarding-design.md 6.3 change 5
|
|
5
|
+
*
|
|
6
|
+
* Spawns `pd demo story-a --workspace <path> --json` as a subprocess, waits for
|
|
7
|
+
* it to complete, validates the JSON stdout, and returns 200 with the demo
|
|
8
|
+
* result. The console backend NEVER writes SQLite directly - all DB I/O happens
|
|
9
|
+
* inside the pd-cli subprocess (EP-06 Source of Truth).
|
|
10
|
+
*
|
|
11
|
+
* Feature flag gate: `new_user_onboarding` (default true, registered in
|
|
12
|
+
* packages/principles-core/src/runtime-v2/feature-flags/feature-flag-contract.ts).
|
|
13
|
+
*
|
|
14
|
+
* ERR entries considered:
|
|
15
|
+
* - EP-02 (Production Path Wiring): registered in server/index.ts handleRequest
|
|
16
|
+
* - EP-03 (Fail Loud): spawn failure -> 500 with reason + nextAction;
|
|
17
|
+
* flag disabled -> 403 with reason + nextAction (rc-9-no-silent-fallback)
|
|
18
|
+
* - EP-06 (Source of Truth): reuses pd-cli demo command, no direct DB writes
|
|
19
|
+
* - EP-08 (Security Boundary): no shell:true — argv passed directly to OS,
|
|
20
|
+
* eliminating command-injection risk; demo runs in a temp workspace so no
|
|
21
|
+
* user-controlled path enters the command at all
|
|
22
|
+
* - rc-1-treat-as-unknown / rc-2-no-as-bypass / rc-4-validate-array-elements:
|
|
23
|
+
* parsed stdout is validated by parseDemoStdout before use
|
|
24
|
+
* - rc-9-no-silent-fallback: timeout, error, and invalid-stdout paths each
|
|
25
|
+
* return a structured error with reason + nextAction
|
|
26
|
+
*/
|
|
27
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
28
|
+
export interface OnboardingRouteContext {
|
|
29
|
+
workspaceDir: string;
|
|
30
|
+
subPath: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function handleOnboardingRoute(req: IncomingMessage, res: ServerResponse, ctx: OnboardingRouteContext): Promise<void>;
|
|
33
|
+
/** No persistent resources to dispose - included for parity with other routes. */
|
|
34
|
+
export declare function disposeOnboardingModels(): void;
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding route - POST /api/v1/onboarding/run-demo
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/superpowers/specs/2026-06-30-new-user-onboarding-design.md 6.3 change 5
|
|
5
|
+
*
|
|
6
|
+
* Spawns `pd demo story-a --workspace <path> --json` as a subprocess, waits for
|
|
7
|
+
* it to complete, validates the JSON stdout, and returns 200 with the demo
|
|
8
|
+
* result. The console backend NEVER writes SQLite directly - all DB I/O happens
|
|
9
|
+
* inside the pd-cli subprocess (EP-06 Source of Truth).
|
|
10
|
+
*
|
|
11
|
+
* Feature flag gate: `new_user_onboarding` (default true, registered in
|
|
12
|
+
* packages/principles-core/src/runtime-v2/feature-flags/feature-flag-contract.ts).
|
|
13
|
+
*
|
|
14
|
+
* ERR entries considered:
|
|
15
|
+
* - EP-02 (Production Path Wiring): registered in server/index.ts handleRequest
|
|
16
|
+
* - EP-03 (Fail Loud): spawn failure -> 500 with reason + nextAction;
|
|
17
|
+
* flag disabled -> 403 with reason + nextAction (rc-9-no-silent-fallback)
|
|
18
|
+
* - EP-06 (Source of Truth): reuses pd-cli demo command, no direct DB writes
|
|
19
|
+
* - EP-08 (Security Boundary): no shell:true — argv passed directly to OS,
|
|
20
|
+
* eliminating command-injection risk; demo runs in a temp workspace so no
|
|
21
|
+
* user-controlled path enters the command at all
|
|
22
|
+
* - rc-1-treat-as-unknown / rc-2-no-as-bypass / rc-4-validate-array-elements:
|
|
23
|
+
* parsed stdout is validated by parseDemoStdout before use
|
|
24
|
+
* - rc-9-no-silent-fallback: timeout, error, and invalid-stdout paths each
|
|
25
|
+
* return a structured error with reason + nextAction
|
|
26
|
+
*/
|
|
27
|
+
import { spawn } from 'node:child_process';
|
|
28
|
+
import { fileURLToPath } from 'node:url';
|
|
29
|
+
import * as fs from 'fs';
|
|
30
|
+
import * as os from 'os';
|
|
31
|
+
import * as path from 'path';
|
|
32
|
+
import { loadPdConfig, computeFlagsFromLoadResult } from '../config/pd-config-store.js';
|
|
33
|
+
import { sendError, sendJson } from '../utils/response.js';
|
|
34
|
+
/** Demo subprocess timeout in milliseconds (60s). */
|
|
35
|
+
const DEMO_TIMEOUT_MS = 60_000;
|
|
36
|
+
// P1-1: Best-effort removal of the temp demo workspace.
|
|
37
|
+
function cleanupTempWorkspace(dir) {
|
|
38
|
+
try {
|
|
39
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Best-effort cleanup — temp dir will be cleaned by OS eventually.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Send a structured error with reason + nextAction (Runtime Contract rc-9). */
|
|
46
|
+
function sendStructuredError(res, payload) {
|
|
47
|
+
sendError(res, payload.statusCode, payload.error, payload.reason, {
|
|
48
|
+
reason: payload.reason,
|
|
49
|
+
nextAction: payload.nextAction,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Load the new_user_onboarding flag state from .pd/config.yaml. */
|
|
53
|
+
function loadOnboardingFlagEnabled(workspaceDir) {
|
|
54
|
+
const configResult = loadPdConfig(workspaceDir);
|
|
55
|
+
const flagsResult = computeFlagsFromLoadResult(configResult);
|
|
56
|
+
return flagsResult.flags.new_user_onboarding?.enabled === true;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the pd CLI invocation: spawn `process.execPath` (Node itself) with
|
|
60
|
+
* the absolute path to the pd-cli `dist/index.js` entry, instead of spawning
|
|
61
|
+
* the bare `pd` binary.
|
|
62
|
+
*
|
|
63
|
+
* P1-A (Windows .cmd resolution): `spawn('pd', args, { shell: false })` fails
|
|
64
|
+
* on Windows with ENOENT because Node's CreateProcess cannot resolve npm's
|
|
65
|
+
* `.cmd` shims (pd.cmd) without a shell. Spawning `process.execPath` with the
|
|
66
|
+
* JS entry path sidesteps this entirely — there is no `.cmd` involved. This is
|
|
67
|
+
* the same pattern used in installer.ts:autoLaunchConsole and
|
|
68
|
+
* pd-cli's runtime-uat.ts `findPdCliPath`.
|
|
69
|
+
*
|
|
70
|
+
* P1-2 (command injection): this approach also keeps `shell: false` — argv is
|
|
71
|
+
* passed directly to the OS, so no user-controlled value is ever interpreted
|
|
72
|
+
* by a shell (EP-08 Security Boundary).
|
|
73
|
+
*
|
|
74
|
+
* Returns `{ cmd, extraArgs }`:
|
|
75
|
+
* - On success: `{ cmd: process.execPath, extraArgs: [pdCliEntryPath] }`
|
|
76
|
+
* - On failure (pd-cli not resolvable / entry missing): falls back to
|
|
77
|
+
* `{ cmd: 'pd', extraArgs: [] }`. This works on Linux/macOS via PATH; on
|
|
78
|
+
* Windows the spawn will emit 'error', which the caller handles with a
|
|
79
|
+
* clear nextAction (rc-9-no-silent-fallback).
|
|
80
|
+
*/
|
|
81
|
+
function findPdCli() {
|
|
82
|
+
// Both the monorepo and installed extension place pd-cli beside console.
|
|
83
|
+
// source: pd-console/src/server/routes -> packages/pd-cli
|
|
84
|
+
// install: console/dist/server/routes -> principles-disciple/pd-cli
|
|
85
|
+
const entryPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../pd-cli/dist/index.js');
|
|
86
|
+
// Do not preflight with existsSync: the Console test job intentionally runs
|
|
87
|
+
// before pd-cli is built, while production installation guarantees this
|
|
88
|
+
// sibling layout. Node will exit non-zero with a structured error if the
|
|
89
|
+
// delivered entry is unexpectedly missing.
|
|
90
|
+
return { cmd: process.execPath, extraArgs: [entryPath] };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Validate the parsed JSON stdout from `pd demo story-a --json`.
|
|
94
|
+
*
|
|
95
|
+
* rc-1-treat-as-unknown: parsed JSON is treated as unknown until validated.
|
|
96
|
+
* rc-2-no-as-bypass: no `as` cast is used to bypass validation - all fields
|
|
97
|
+
* are checked with typeof / Array.isArray before the value is accepted.
|
|
98
|
+
* rc-4-validate-array-elements: the `stages` array is type-checked as an array
|
|
99
|
+
* (element-level shape validation happens downstream in the UI validator).
|
|
100
|
+
*
|
|
101
|
+
* Required fields: status (string), generatedAt (string), narrative (string),
|
|
102
|
+
* stages (array). Returns the validated object or null if invalid.
|
|
103
|
+
*/
|
|
104
|
+
function parseDemoStdout(stdout) {
|
|
105
|
+
let parsed;
|
|
106
|
+
try {
|
|
107
|
+
parsed = JSON.parse(stdout);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
113
|
+
return null;
|
|
114
|
+
const status = Object.hasOwn(parsed, 'status') ? Reflect.get(parsed, 'status') : undefined;
|
|
115
|
+
const generatedAt = Object.hasOwn(parsed, 'generatedAt') ? Reflect.get(parsed, 'generatedAt') : undefined;
|
|
116
|
+
const narrative = Object.hasOwn(parsed, 'narrative') ? Reflect.get(parsed, 'narrative') : undefined;
|
|
117
|
+
const stages = Object.hasOwn(parsed, 'stages') ? Reflect.get(parsed, 'stages') : undefined;
|
|
118
|
+
if (typeof status !== 'string' || typeof generatedAt !== 'string' || typeof narrative !== 'string')
|
|
119
|
+
return null;
|
|
120
|
+
if (!Array.isArray(stages) || !stages.every((stage) => {
|
|
121
|
+
if (typeof stage !== 'object' || stage === null)
|
|
122
|
+
return false;
|
|
123
|
+
const name = Object.hasOwn(stage, 'name') ? Reflect.get(stage, 'name') : undefined;
|
|
124
|
+
const stageStatus = Object.hasOwn(stage, 'status') ? Reflect.get(stage, 'status') : undefined;
|
|
125
|
+
return typeof name === 'string' &&
|
|
126
|
+
(stageStatus === 'passed' || stageStatus === 'failed' || stageStatus === 'degraded' || stageStatus === 'skipped');
|
|
127
|
+
}))
|
|
128
|
+
return null;
|
|
129
|
+
return parsed;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Spawn `pd demo story-a --json`, wait for it to complete, validate stdout,
|
|
133
|
+
* and return 200 with { success: true, data: { simulated: true, demo: ... } }.
|
|
134
|
+
*
|
|
135
|
+
* The demo typically completes in < 30s. We block on the subprocess so the
|
|
136
|
+
* response carries the validated demo result in a single round-trip - the
|
|
137
|
+
* frontend does not need to poll for the demo itself (it still polls
|
|
138
|
+
* /api/v1/evidence-chain in step 3 for live evidence detection).
|
|
139
|
+
*
|
|
140
|
+
* Failure modes (rc-9-no-silent-fallback - every path returns a structured
|
|
141
|
+
* error with reason + nextAction):
|
|
142
|
+
* - spawn throws synchronously (e.g. ENOENT) -> 500 demo_spawn_failed
|
|
143
|
+
* - child emits 'error' after spawn -> 500 demo_subprocess_error
|
|
144
|
+
* - subprocess exceeds DEMO_TIMEOUT_MS -> 504 demo_timeout (subprocess killed)
|
|
145
|
+
* - subprocess exits with non-zero code -> 500 demo_exit_nonzero
|
|
146
|
+
* - stdout cannot be parsed/validated -> 500 demo_invalid_stdout
|
|
147
|
+
*/
|
|
148
|
+
async function handleRunDemo(res, _workspaceDir) {
|
|
149
|
+
// P1-1: Use a temporary workspace so the demo's simulated DB writes
|
|
150
|
+
// (tasks, artifacts, approvals, activations) do NOT pollute the user's
|
|
151
|
+
// real workspace state.db.
|
|
152
|
+
const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-onboarding-demo-'));
|
|
153
|
+
let child;
|
|
154
|
+
try {
|
|
155
|
+
const { cmd, extraArgs } = findPdCli();
|
|
156
|
+
// P1-A: spawn process.execPath (Node) with the pd-cli entry JS path to
|
|
157
|
+
// avoid the Windows .cmd resolution problem. P1-2: no shell:true — argv is
|
|
158
|
+
// passed directly to the OS, eliminating command-injection risk from
|
|
159
|
+
// user-controlled paths (the temp workspace path is the only variable
|
|
160
|
+
// here, and it is a mkdtemp result, not user input).
|
|
161
|
+
child = spawn(cmd, [...extraArgs, 'demo', 'story-a', '--workspace', tempWorkspace, '--json'], {
|
|
162
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
163
|
+
// No shell:true — argv is passed directly to the OS.
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
168
|
+
cleanupTempWorkspace(tempWorkspace);
|
|
169
|
+
sendStructuredError(res, {
|
|
170
|
+
statusCode: 500,
|
|
171
|
+
error: 'demo_spawn_failed',
|
|
172
|
+
reason: `spawn failed: ${message}`,
|
|
173
|
+
nextAction: 'Check that the pd CLI is installed (npm i -g @principles/pd-cli) and that its dist/index.js entry exists, or that pd is on PATH.',
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// P2-D: declared outside the try so the finally block can clear it. Captures
|
|
178
|
+
// the 60s timeout handle to prevent the timer from leaking after demo ends.
|
|
179
|
+
let timeoutHandle;
|
|
180
|
+
try {
|
|
181
|
+
// Capture stdout into a buffer. stderr is logged for observability (rc-9).
|
|
182
|
+
const stdoutChunks = [];
|
|
183
|
+
let stderrText = '';
|
|
184
|
+
child.stdout?.on('data', (data) => {
|
|
185
|
+
stdoutChunks.push(data);
|
|
186
|
+
});
|
|
187
|
+
child.stderr?.on('data', (data) => {
|
|
188
|
+
const chunk = data.toString();
|
|
189
|
+
stderrText += chunk;
|
|
190
|
+
console.error('[pd-console] onboarding demo stderr:', chunk.trim());
|
|
191
|
+
});
|
|
192
|
+
// Wrap the subprocess lifecycle in a Promise that resolves with the exit code
|
|
193
|
+
// or rejects on 'error'. A timeout is enforced via a separate Promise that
|
|
194
|
+
// resolves with a sentinel value - Promise.race is used below.
|
|
195
|
+
const settled = new Promise((resolve) => {
|
|
196
|
+
let done = false;
|
|
197
|
+
const finish = (code, error) => {
|
|
198
|
+
if (done)
|
|
199
|
+
return;
|
|
200
|
+
done = true;
|
|
201
|
+
resolve({ code, error });
|
|
202
|
+
};
|
|
203
|
+
child.on('error', (err) => {
|
|
204
|
+
finish(null, err);
|
|
205
|
+
});
|
|
206
|
+
child.on('close', (code) => {
|
|
207
|
+
finish(code, null);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
const timeout = new Promise((resolve) => {
|
|
211
|
+
timeoutHandle = setTimeout(() => resolve('timeout'), DEMO_TIMEOUT_MS);
|
|
212
|
+
});
|
|
213
|
+
const outcome = await Promise.race([settled, timeout]);
|
|
214
|
+
// -- Timeout: kill the subprocess and return 504 --
|
|
215
|
+
if (outcome === 'timeout') {
|
|
216
|
+
try {
|
|
217
|
+
child.kill();
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// Best-effort kill; ignore failures (rc-9: the 504 response still carries
|
|
221
|
+
// the reason + nextAction so the operator is not left in the dark).
|
|
222
|
+
}
|
|
223
|
+
sendStructuredError(res, {
|
|
224
|
+
statusCode: 504,
|
|
225
|
+
error: 'demo_timeout',
|
|
226
|
+
reason: `demo subprocess exceeded ${DEMO_TIMEOUT_MS}ms timeout`,
|
|
227
|
+
nextAction: 'Retry the demo; if it persists, check pd CLI health (pd doctor).',
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// -- 'error' event after spawn (e.g. EACCES, broken pipe) -> 500 --
|
|
232
|
+
if (outcome.error !== null) {
|
|
233
|
+
sendStructuredError(res, {
|
|
234
|
+
statusCode: 500,
|
|
235
|
+
error: 'demo_subprocess_error',
|
|
236
|
+
reason: `subprocess error: ${outcome.error.message}`,
|
|
237
|
+
nextAction: 'Check pd CLI installation and permissions; see server logs for stderr.',
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// -- Non-zero exit code -> 500 --
|
|
242
|
+
if (outcome.code !== 0) {
|
|
243
|
+
sendStructuredError(res, {
|
|
244
|
+
statusCode: 500,
|
|
245
|
+
error: 'demo_exit_nonzero',
|
|
246
|
+
reason: `demo exited with code ${outcome.code}`,
|
|
247
|
+
nextAction: stderrText.trim()
|
|
248
|
+
? `pd stderr: ${stderrText.trim()}`
|
|
249
|
+
: 'Run `pd demo story-a --json` manually for diagnostics.',
|
|
250
|
+
});
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// -- Validate stdout (rc-1/rc-2/rc-4) --
|
|
254
|
+
const stdout = Buffer.concat(stdoutChunks).toString('utf8');
|
|
255
|
+
const validated = parseDemoStdout(stdout);
|
|
256
|
+
if (validated === null) {
|
|
257
|
+
sendStructuredError(res, {
|
|
258
|
+
statusCode: 500,
|
|
259
|
+
error: 'demo_invalid_stdout',
|
|
260
|
+
reason: 'demo stdout did not match the expected JSON schema',
|
|
261
|
+
nextAction: 'Run `pd demo story-a --json` manually and inspect the output.',
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// -- 200 OK with validated demo result --
|
|
266
|
+
sendJson(res, 200, {
|
|
267
|
+
success: true,
|
|
268
|
+
data: {
|
|
269
|
+
simulated: true,
|
|
270
|
+
demo: validated,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
// P2-D: clear the 60s timeout timer so it doesn't leak after the demo
|
|
276
|
+
// completes. The guard handles the case where an error was thrown before
|
|
277
|
+
// the timeout Promise executor assigned the handle.
|
|
278
|
+
if (timeoutHandle)
|
|
279
|
+
clearTimeout(timeoutHandle);
|
|
280
|
+
// P1-1: always clean up the temp workspace, whether the demo succeeded,
|
|
281
|
+
// failed, timed out, or threw. Best-effort — OS will reap temp dirs eventually.
|
|
282
|
+
cleanupTempWorkspace(tempWorkspace);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
export async function handleOnboardingRoute(req, res, ctx) {
|
|
286
|
+
const { workspaceDir, subPath } = ctx;
|
|
287
|
+
// -- Feature flag gate (spec 6.3 change 5) --
|
|
288
|
+
// Checked BEFORE method/sub-path dispatch so a disabled flag cannot be probed
|
|
289
|
+
// via 405/404 side-channels (rc-9-no-silent-fallback: explicit 403 + reason).
|
|
290
|
+
if (!loadOnboardingFlagEnabled(workspaceDir)) {
|
|
291
|
+
sendStructuredError(res, {
|
|
292
|
+
statusCode: 403,
|
|
293
|
+
error: 'flag_disabled',
|
|
294
|
+
reason: 'flag_disabled',
|
|
295
|
+
nextAction: 'Enable the new_user_onboarding feature flag in .pd/config.yaml or the Settings page.',
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// -- POST /api/v1/onboarding/run-demo --
|
|
300
|
+
if (subPath === '/run-demo') {
|
|
301
|
+
if (req.method !== 'POST') {
|
|
302
|
+
sendStructuredError(res, {
|
|
303
|
+
statusCode: 405,
|
|
304
|
+
error: 'method_not_allowed',
|
|
305
|
+
reason: 'method_not_allowed',
|
|
306
|
+
nextAction: 'Use POST to trigger the demo.',
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
await handleRunDemo(res, workspaceDir);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// -- Unknown sub-path --
|
|
314
|
+
sendStructuredError(res, {
|
|
315
|
+
statusCode: 404,
|
|
316
|
+
error: 'not_found',
|
|
317
|
+
reason: 'not_found',
|
|
318
|
+
nextAction: `Route /api/v1/onboarding${subPath} not found. Use POST /api/v1/onboarding/run-demo.`,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/** No persistent resources to dispose - included for parity with other routes. */
|
|
322
|
+
export function disposeOnboardingModels() {
|
|
323
|
+
// No-op: handleRunDemo holds no cached models or open handles.
|
|
324
|
+
}
|