create-principles-disciple 1.118.0 → 1.119.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.
Files changed (133) hide show
  1. package/console/dist/server/index.js +17 -1
  2. package/console/dist/server/models/ActivationsConsoleModel.d.ts +77 -0
  3. package/console/dist/server/models/ActivationsConsoleModel.js +322 -4
  4. package/console/dist/server/models/ReceiptsConsoleModel.d.ts +42 -0
  5. package/console/dist/server/models/ReceiptsConsoleModel.js +160 -0
  6. package/console/dist/server/routes/activations.d.ts +6 -1
  7. package/console/dist/server/routes/activations.js +142 -1
  8. package/console/dist/server/routes/receipts.d.ts +8 -0
  9. package/console/dist/server/routes/receipts.js +63 -0
  10. package/console/dist/ui/App.js +6 -3
  11. package/console/dist/ui/api.d.ts +40 -3
  12. package/console/dist/ui/api.js +94 -3
  13. package/console/dist/ui/i18n/en.json +75 -0
  14. package/console/dist/ui/i18n/zh-CN.json +75 -0
  15. package/console/dist/ui/pages/activation/ActivationPage.js +114 -5
  16. package/console/dist/ui/pages/activation/ActivationValidators.js +3 -0
  17. package/console/dist/ui/pages/focus/FocusPage.js +16 -3
  18. package/console/dist/ui/pages/principles/PrincipleDetailPage.js +17 -3
  19. package/console/dist/ui/utils/validators.d.ts +100 -0
  20. package/console/dist/ui/utils/validators.js +133 -0
  21. package/console/dist/web/assets/app.css +34 -0
  22. package/console/dist/web/assets/app.js +704 -14
  23. package/console/package.json +2 -0
  24. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +8 -2
  25. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
  26. package/core/dist/runtime-v2/activation/__tests__/openclaw-promotion-checks.test.d.ts +2 -0
  27. package/core/dist/runtime-v2/activation/__tests__/openclaw-promotion-checks.test.d.ts.map +1 -0
  28. package/core/dist/runtime-v2/activation/__tests__/openclaw-promotion-checks.test.js +91 -0
  29. package/core/dist/runtime-v2/activation/__tests__/openclaw-promotion-checks.test.js.map +1 -0
  30. package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-evaluator.test.d.ts +2 -0
  31. package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-evaluator.test.d.ts.map +1 -0
  32. package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-evaluator.test.js +83 -0
  33. package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-evaluator.test.js.map +1 -0
  34. package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-reader.test.d.ts +2 -0
  35. package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-reader.test.d.ts.map +1 -0
  36. package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-reader.test.js +48 -0
  37. package/core/dist/runtime-v2/activation/__tests__/promotion-readiness-reader.test.js.map +1 -0
  38. package/core/dist/runtime-v2/activation/__tests__/rulecode-owner-decision-service.test.d.ts +2 -0
  39. package/core/dist/runtime-v2/activation/__tests__/rulecode-owner-decision-service.test.d.ts.map +1 -0
  40. package/core/dist/runtime-v2/activation/__tests__/rulecode-owner-decision-service.test.js +141 -0
  41. package/core/dist/runtime-v2/activation/__tests__/rulecode-owner-decision-service.test.js.map +1 -0
  42. package/core/dist/runtime-v2/activation/__tests__/rulecode-safety-circuit-breaker.test.d.ts +2 -0
  43. package/core/dist/runtime-v2/activation/__tests__/rulecode-safety-circuit-breaker.test.d.ts.map +1 -0
  44. package/core/dist/runtime-v2/activation/__tests__/rulecode-safety-circuit-breaker.test.js +31 -0
  45. package/core/dist/runtime-v2/activation/__tests__/rulecode-safety-circuit-breaker.test.js.map +1 -0
  46. package/core/dist/runtime-v2/activation/__tests__/sqlite-activation-safety-store.test.d.ts +2 -0
  47. package/core/dist/runtime-v2/activation/__tests__/sqlite-activation-safety-store.test.d.ts.map +1 -0
  48. package/core/dist/runtime-v2/activation/__tests__/sqlite-activation-safety-store.test.js +311 -0
  49. package/core/dist/runtime-v2/activation/__tests__/sqlite-activation-safety-store.test.js.map +1 -0
  50. package/core/dist/runtime-v2/activation/activation-control-types.d.ts +61 -0
  51. package/core/dist/runtime-v2/activation/activation-control-types.d.ts.map +1 -0
  52. package/core/dist/runtime-v2/activation/activation-control-types.js +2 -0
  53. package/core/dist/runtime-v2/activation/activation-control-types.js.map +1 -0
  54. package/core/dist/runtime-v2/activation/index.d.ts +14 -0
  55. package/core/dist/runtime-v2/activation/index.d.ts.map +1 -1
  56. package/core/dist/runtime-v2/activation/index.js +7 -0
  57. package/core/dist/runtime-v2/activation/index.js.map +1 -1
  58. package/core/dist/runtime-v2/activation/openclaw-promotion-checks.d.ts +30 -0
  59. package/core/dist/runtime-v2/activation/openclaw-promotion-checks.d.ts.map +1 -0
  60. package/core/dist/runtime-v2/activation/openclaw-promotion-checks.js +124 -0
  61. package/core/dist/runtime-v2/activation/openclaw-promotion-checks.js.map +1 -0
  62. package/core/dist/runtime-v2/activation/promotion-readiness-evaluator.d.ts +17 -0
  63. package/core/dist/runtime-v2/activation/promotion-readiness-evaluator.d.ts.map +1 -0
  64. package/core/dist/runtime-v2/activation/promotion-readiness-evaluator.js +55 -0
  65. package/core/dist/runtime-v2/activation/promotion-readiness-evaluator.js.map +1 -0
  66. package/core/dist/runtime-v2/activation/promotion-readiness-reader.d.ts +21 -0
  67. package/core/dist/runtime-v2/activation/promotion-readiness-reader.d.ts.map +1 -0
  68. package/core/dist/runtime-v2/activation/promotion-readiness-reader.js +44 -0
  69. package/core/dist/runtime-v2/activation/promotion-readiness-reader.js.map +1 -0
  70. package/core/dist/runtime-v2/activation/rulecode-owner-decision-service.d.ts +104 -0
  71. package/core/dist/runtime-v2/activation/rulecode-owner-decision-service.d.ts.map +1 -0
  72. package/core/dist/runtime-v2/activation/rulecode-owner-decision-service.js +86 -0
  73. package/core/dist/runtime-v2/activation/rulecode-owner-decision-service.js.map +1 -0
  74. package/core/dist/runtime-v2/activation/rulecode-safety-circuit-breaker.d.ts +25 -0
  75. package/core/dist/runtime-v2/activation/rulecode-safety-circuit-breaker.d.ts.map +1 -0
  76. package/core/dist/runtime-v2/activation/rulecode-safety-circuit-breaker.js +23 -0
  77. package/core/dist/runtime-v2/activation/rulecode-safety-circuit-breaker.js.map +1 -0
  78. package/core/dist/runtime-v2/activation/rulecode-shadow-summary.d.ts +14 -0
  79. package/core/dist/runtime-v2/activation/rulecode-shadow-summary.d.ts.map +1 -0
  80. package/core/dist/runtime-v2/activation/rulecode-shadow-summary.js +57 -0
  81. package/core/dist/runtime-v2/activation/rulecode-shadow-summary.js.map +1 -0
  82. package/core/dist/runtime-v2/activation/sqlite-activation-safety-store.d.ts +40 -0
  83. package/core/dist/runtime-v2/activation/sqlite-activation-safety-store.d.ts.map +1 -0
  84. package/core/dist/runtime-v2/activation/sqlite-activation-safety-store.js +461 -0
  85. package/core/dist/runtime-v2/activation/sqlite-activation-safety-store.js.map +1 -0
  86. package/core/dist/runtime-v2/activation/sqlite-activation-state-store.d.ts.map +1 -1
  87. package/core/dist/runtime-v2/activation/sqlite-activation-state-store.js +24 -6
  88. package/core/dist/runtime-v2/activation/sqlite-activation-state-store.js.map +1 -1
  89. package/core/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js +19 -4
  90. package/core/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js.map +1 -1
  91. package/core/dist/runtime-v2/activation/writers/rule-host-writer.d.ts.map +1 -1
  92. package/core/dist/runtime-v2/activation/writers/rule-host-writer.js +10 -0
  93. package/core/dist/runtime-v2/activation/writers/rule-host-writer.js.map +1 -1
  94. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +13 -0
  95. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -1
  96. package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +15 -2
  97. package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
  98. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
  99. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +2 -0
  100. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
  101. package/core/dist/runtime-v2/index.d.ts +2 -2
  102. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  103. package/core/dist/runtime-v2/index.js +1 -1
  104. package/core/dist/runtime-v2/index.js.map +1 -1
  105. package/core/dist/runtime-v2/internalization/__tests__/legacy-rule-contract-scanner.test.js +20 -1
  106. package/core/dist/runtime-v2/internalization/__tests__/legacy-rule-contract-scanner.test.js.map +1 -1
  107. package/core/dist/runtime-v2/internalization/legacy-rule-contract-scanner.d.ts +4 -4
  108. package/core/dist/runtime-v2/internalization/legacy-rule-contract-scanner.d.ts.map +1 -1
  109. package/core/dist/runtime-v2/internalization/legacy-rule-contract-scanner.js +92 -5
  110. package/core/dist/runtime-v2/internalization/legacy-rule-contract-scanner.js.map +1 -1
  111. package/core/dist/runtime-v2/store/sqlite-connection-schema-version.test.js +11 -11
  112. package/core/dist/runtime-v2/store/sqlite-connection-schema-version.test.js.map +1 -1
  113. package/core/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
  114. package/core/dist/runtime-v2/store/sqlite-connection.js +111 -0
  115. package/core/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
  116. package/host-runtime/dist/host-liveness-contract.d.ts +3 -0
  117. package/host-runtime/dist/host-liveness-contract.js +18 -0
  118. package/host-runtime/dist/index.d.ts +1 -0
  119. package/host-runtime/dist/index.js +1 -0
  120. package/host-runtime/dist/legacy-rule-contract-symbols.js +88 -1
  121. package/host-runtime/dist/production-rulehost-gate.js +33 -16
  122. package/package.json +2 -2
  123. package/pd-cli/dist/commands/__tests__/runtime-activation-nextaction.test.js +3 -3
  124. package/pd-cli/dist/commands/__tests__/runtime-activation-nextaction.test.js.map +1 -1
  125. package/pd-cli/dist/commands/runtime-activation.d.ts +15 -3
  126. package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
  127. package/pd-cli/dist/commands/runtime-activation.js +160 -49
  128. package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
  129. package/plugin/dist/bundle.js +730 -565
  130. package/plugin/dist/core/rule-host.d.ts +0 -19
  131. package/plugin/dist/core/rulecode-safety-circuit.d.ts +13 -0
  132. package/plugin/openclaw.plugin.json +1 -1
  133. package/plugin/package.json +1 -1
@@ -117,6 +117,13 @@
117
117
  "partialFailure": "Action completed, but {{failedCount}}/{{totalCount}} record(s) failed: {{reason}}",
118
118
  "unknownFailure": "Check the service logs.",
119
119
  "onboardingTitle": "How PD Works",
120
+ "ruleCodeOwnerQueue": "RuleCode Owner decisions",
121
+ "ruleCodePending": "Rules awaiting live decision",
122
+ "ruleCodePendingCount": "{{count}} shadow rule(s) require review of evidence and scope.",
123
+ "ruleCodeUnavailable": "RuleCode review data is unavailable",
124
+ "ruleCodeUnavailableNextAction": "Open Activations or retry after the activation service recovers.",
125
+ "ruleCodeAlerts": "RuleCode safety alerts",
126
+ "ruleCodeAlertCount": "{{count}} rule(s) are safety-isolated and fail-open.",
120
127
  "onboardingStep1": "Observe deviations — PD watches for behavior that diverges from owner expectations",
121
128
  "onboardingStep2": "Capture evidence — Pain signals are recorded with bounded context",
122
129
  "onboardingStep3": "Generate candidates — Principle candidates are proposed for owner review",
@@ -419,6 +426,20 @@
419
426
  "reflectionQ1": "Does this principle describe behavior I want to see repeatedly?",
420
427
  "reflectionQ2": "Do I understand which channel it will activate through?",
421
428
  "reflectionQ3": "If it's wrong, do I know how to roll it back?",
429
+ "receipts": {
430
+ "title": "Receipt History",
431
+ "headline": "Your principle is influencing agent behavior",
432
+ "counts": "{{effectCount}} effect(s) · {{presenceCount}} injection(s)",
433
+ "empty": "No recent records — this principle has not been triggered at runtime yet.",
434
+ "note": "\"Effect\" = block / auto-correction / agent self-report; \"injection\" = the principle entered task context (gray). Self-reports are probabilistic agent claims, listed separately from deterministic events.",
435
+ "unavailableReason": "Receipt history is unavailable right now.",
436
+ "kind": {
437
+ "rule_blocked": "Block",
438
+ "auto_correct_applied": "Auto-correct",
439
+ "self_reported": "Agent self-report",
440
+ "prompt_injected": "Context injection"
441
+ }
442
+ },
422
443
  "trajectory": "View full trajectory when tracing is needed",
423
444
  "stageEvidence": "Evidence",
424
445
  "stageDiagnosis": "Diagnosis",
@@ -587,6 +608,60 @@
587
608
  "undoDisable": "Disable action cancelled",
588
609
  "viewDebt": "View in Principle Debt",
589
610
  "loadError": "Unable to load activation data. You can try refreshing the page.",
611
+ "receiptsLabel": "Receipts",
612
+ "receiptsValue": "{{effect}} effect(s) · {{presence}} injection(s)",
613
+ "receiptsUnavailable": "Receipt history unavailable: {{reason}}",
614
+ "ownerDecisionTitle": "Owner Live Decision",
615
+ "ownerDecisionRecorded": "RuleCode decision recorded",
616
+ "ownerDecisionLoading": "Loading safety evidence…",
617
+ "safetyIsolation": "Safety Isolation",
618
+ "runtimeCapability": "Runtime {{version}} · shadow evidence {{supported}}",
619
+ "supported": "supported",
620
+ "unsupported": "unsupported",
621
+ "hardSafetyChecks": "Promotion safety checks",
622
+ "live24Hours": "Live monitoring · 24 hours",
623
+ "live7Days": "Live monitoring · 7 days",
624
+ "matched": "Matched",
625
+ "blocked": "Blocked",
626
+ "unhealthy": "Unhealthy",
627
+ "circuitTrips": "Circuit trips",
628
+ "behaviorDrift": "Behavior drift · approved block rate {{approved}} · live block rate {{live}} · delta {{delta}}",
629
+ "ownerDecisionHistory": "Owner and safety decision history",
630
+ "toolDistribution": "Tool distribution",
631
+ "representativeSamples": "Redacted representative samples",
632
+ "circuitConditions": "Circuit breaker isolates on protected-control matches, health failures, 3 consecutive errors, 5 broad consecutive blocks, >80% outside-scope blocking in 20 calls, or a failed neutral probe. It never auto-reactivates.",
633
+ "worstImpact": "Worst plausible impact: matching calls to {{tools}} can be blocked; PD control and recovery surfaces remain outside RuleCode authority.",
634
+ "eligibleEvaluations": "Eligible",
635
+ "wouldBlock": "Would block",
636
+ "wouldAllow": "Would allow",
637
+ "requireApproval": "Would require approval",
638
+ "autoCorrect": "Would auto-correct",
639
+ "neutralControl": "Neutral controls",
640
+ "shadowWindow": "Shadow window: {{first}} — {{last}}",
641
+ "overrideReason": "Why is limited evidence acceptable?",
642
+ "overrideRareBehavior": "Rare behavior",
643
+ "overrideControlledRollout": "Controlled rollout",
644
+ "overrideOwnerAccepts": "Owner accepts limited evidence",
645
+ "evaluationErrors": "Errors",
646
+ "notCollected": "Not collected",
647
+ "scopeArtifactCode": "Scope, artifact and implementation",
648
+ "ownerReviewNote": "Owner review note",
649
+ "continueObserving": "Continue observing",
650
+ "rejectAfterShadow": "Reject and deactivate",
651
+ "confirmLive": "Confirm live enforcement",
652
+ "emergencyDeactivate": "Emergency deactivate",
653
+ "recoverToShadow": "Recover to a new Shadow",
654
+ "releaseGlobalPause": "Release global pause (do not restore rules)",
655
+ "globalPauseReleased": "Global pause latch released; isolated rules remain stopped",
656
+ "rolloutDisabled": "Owner decision rollout is disabled; emergency stop remains available.",
657
+ "pauseAll": "Pause all Live RuleCode",
658
+ "pauseAllSucceeded": "All live RuleCode enforcement paused",
659
+ "pendingRules": "Rules awaiting live decision",
660
+ "pendingRulesDescription": "Shadow Observation has produced a separate Owner Live Decision queue.",
661
+ "safetyAlerts": "Safety alerts",
662
+ "safetyAlertsDescription": "These rules are isolated and fail-open; they never return live automatically.",
663
+ "legacyUnknown": "Historical live activation / decision maker unknown. Review by {{due}}; PD will not fabricate Owner identity.",
664
+ "sevenDays": "within 7 days",
590
665
  "footer": "This page shows activation facts — which principles are in effect and through which channels."
591
666
  },
592
667
  "approvals": {
@@ -117,6 +117,13 @@
117
117
  "partialFailure": "操作完成,但 {{failedCount}}/{{totalCount}} 条记录失败:{{reason}}",
118
118
  "unknownFailure": "请检查服务日志。",
119
119
  "onboardingTitle": "PD 的工作方式",
120
+ "ruleCodeOwnerQueue": "RuleCode 拥有者决策",
121
+ "ruleCodePending": "待上线规则",
122
+ "ruleCodePendingCount": "{{count}} 条 Shadow 规则需要评审证据与范围。",
123
+ "ruleCodeUnavailable": "RuleCode 评审数据暂不可用",
124
+ "ruleCodeUnavailableNextAction": "请打开激活页面查看详情,或在激活服务恢复后重试。",
125
+ "ruleCodeAlerts": "RuleCode 安全告警",
126
+ "ruleCodeAlertCount": "{{count}} 条规则已安全隔离并 fail-open。",
120
127
  "onboardingStep1": "观察偏差 — PD 监控偏离拥有者期望的行为",
121
128
  "onboardingStep2": "捕获证据 — 行为偏差信号以有界上下文记录",
122
129
  "onboardingStep3": "生成候选 — 提出原则候选供拥有者审查",
@@ -419,6 +426,20 @@
419
426
  "reflectionQ1": "这条原则是否描述了我希望反复出现的行为?",
420
427
  "reflectionQ2": "我是否理解它将在哪些通道激活?",
421
428
  "reflectionQ3": "如果它错了,我是否知道如何回滚?",
429
+ "receipts": {
430
+ "title": "生效履历",
431
+ "headline": "你的原则正在影响智能体行为",
432
+ "counts": "生效 {{effectCount}} 次 · 注入 {{presenceCount}} 次",
433
+ "empty": "最近没有记录——这条原则尚未在运行中被触发。",
434
+ "note": "「生效」= 拦截/自动纠正/智能体自述遵循;「注入」= 原则进入了任务上下文(灰色)。智能体自述为概率性自报,与确定性事件分列。",
435
+ "unavailableReason": "暂无法读取生效履历。",
436
+ "kind": {
437
+ "rule_blocked": "拦截",
438
+ "auto_correct_applied": "自动纠正",
439
+ "self_reported": "智能体自述",
440
+ "prompt_injected": "注入上下文"
441
+ }
442
+ },
422
443
  "trajectory": "需要追溯时,查看完整轨迹",
423
444
  "stageEvidence": "证据",
424
445
  "stageDiagnosis": "诊断",
@@ -587,6 +608,60 @@
587
608
  "undoDisable": "停用操作已撤销",
588
609
  "viewDebt": "在原则债务中查看",
589
610
  "loadError": "无法加载生效数据。你可以尝试刷新页面。",
611
+ "receiptsLabel": "生效履历",
612
+ "receiptsValue": "生效 {{effect}} 次 · 注入 {{presence}} 次",
613
+ "receiptsUnavailable": "生效履历暂不可用:{{reason}}",
614
+ "ownerDecisionTitle": "拥有者上线决策",
615
+ "ownerDecisionRecorded": "RuleCode 决策已记录",
616
+ "ownerDecisionLoading": "正在加载安全证据…",
617
+ "safetyIsolation": "安全隔离",
618
+ "runtimeCapability": "运行时 {{version}} · 影子证据{{supported}}",
619
+ "supported": "受支持",
620
+ "unsupported": "不受支持",
621
+ "hardSafetyChecks": "上线安全检查",
622
+ "live24Hours": "Live 监控 · 24 小时",
623
+ "live7Days": "Live 监控 · 7 天",
624
+ "matched": "匹配",
625
+ "blocked": "拦截",
626
+ "unhealthy": "异常",
627
+ "circuitTrips": "熔断次数",
628
+ "behaviorDrift": "行为漂移 · 批准时拦截率 {{approved}} · 实际拦截率 {{live}} · 差值 {{delta}}",
629
+ "ownerDecisionHistory": "拥有者与安全决策历史",
630
+ "toolDistribution": "工具分布",
631
+ "representativeSamples": "脱敏代表样本",
632
+ "circuitConditions": "熔断器会在命中受保护控制面、运行异常、连续 3 次错误、跨 3 个工具连续 5 次拦截、最近 20 次中范围外拦截超过 80%,或中性探针失败时隔离规则;绝不会自动恢复上线。",
633
+ "worstImpact": "最坏影响:对 {{tools}} 的匹配调用可能被拦截;PD 控制与恢复入口不受 RuleCode 管辖。",
634
+ "eligibleEvaluations": "符合条件的评估",
635
+ "wouldBlock": "本会拦截",
636
+ "wouldAllow": "本会放行",
637
+ "requireApproval": "本会要求审批",
638
+ "autoCorrect": "本会自动纠正",
639
+ "neutralControl": "中性对照样本",
640
+ "shadowWindow": "影子观察窗口:{{first}} — {{last}}",
641
+ "overrideReason": "为何有限证据仍可接受?",
642
+ "overrideRareBehavior": "低频行为",
643
+ "overrideControlledRollout": "受控上线",
644
+ "overrideOwnerAccepts": "拥有者接受有限证据",
645
+ "evaluationErrors": "异常",
646
+ "notCollected": "未采集",
647
+ "scopeArtifactCode": "范围、产物与实现代码",
648
+ "ownerReviewNote": "拥有者评审备注",
649
+ "continueObserving": "继续观察",
650
+ "rejectAfterShadow": "拒绝并停用",
651
+ "confirmLive": "确认上线",
652
+ "emergencyDeactivate": "紧急停用",
653
+ "recoverToShadow": "恢复到新 Shadow",
654
+ "releaseGlobalPause": "解除全局暂停(不恢复规则)",
655
+ "globalPauseReleased": "全局暂停已解除;隔离规则仍保持停用",
656
+ "rolloutDisabled": "拥有者决策功能尚未开放;紧急止损仍然可用。",
657
+ "pauseAll": "暂停全部 Live RuleCode",
658
+ "pauseAllSucceeded": "全部 Live RuleCode 已暂停",
659
+ "pendingRules": "待上线规则",
660
+ "pendingRulesDescription": "Shadow Observation 已形成独立的拥有者上线决策队列。",
661
+ "safetyAlerts": "安全告警",
662
+ "safetyAlertsDescription": "这些规则已隔离并 fail-open;它们不会自动恢复上线。",
663
+ "legacyUnknown": "历史上线 / 决策人未知。请在 {{due}} 完成复核;PD 不会伪造拥有者身份。",
664
+ "sevenDays": "7 天内",
590
665
  "footer": "本页展示激活事实——哪些原则在生效、通过哪些通道。"
591
666
  },
592
667
  "approvals": {
@@ -8,7 +8,7 @@ import { PageLoading } from "../../components/layout/page-loading.js";
8
8
  import { SectionTitle } from "../../components/layout/section-title.js";
9
9
  import { ShinyText } from "../../components/ui/shiny-text.js";
10
10
  import { Button } from "../../components/ui/button.js";
11
- import { fetchAllActivations, disableActivation, fetchLifecycleMetrics, } from "../../api.js";
11
+ import { fetchAllActivations, disableActivation, fetchLifecycleMetrics, fetchReceiptCounts, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, } from "../../api.js";
12
12
  import { validateActivationsData, validateLifecycleMetricsData, isReversibleChannel, } from "./ActivationValidators.js";
13
13
  import { enumLabel } from "../../utils/enum-labels.js";
14
14
  // ── Channel label helper ─────────────────────────────────────────────────────
@@ -20,7 +20,64 @@ function CapabilityBoundaryDeclaration() {
20
20
  const { t } = useTranslation();
21
21
  return (_jsx("div", { className: "px-[18px] py-[14px] bg-panel border border-line rounded-[6px] text-ink-3 text-[13px] leading-relaxed", role: "note", "aria-label": t("pages.activation.boundaryLabel"), children: t("pages.activation.boundaryText") }));
22
22
  }
23
- function ActivationFactCard({ record, lifecycleData, onDisable, disabling, }) {
23
+ function RuleCodeOwnerPanel({ record, onChanged }) {
24
+ const { t } = useTranslation();
25
+ const [review, setReview] = useState(null);
26
+ const [error, setError] = useState(null);
27
+ const [busy, setBusy] = useState(false);
28
+ const [note, setNote] = useState('');
29
+ const [overrideReason, setOverrideReason] = useState('rare_behavior');
30
+ const load = useCallback(async () => { const result = await fetchRuleCodeOwnerReview(record.activationId); if (result.success) {
31
+ setReview(result.data);
32
+ setError(null);
33
+ }
34
+ else
35
+ setError(result.error); }, [record.activationId]);
36
+ useEffect(() => { void load(); }, [load]);
37
+ const decide = async (action) => {
38
+ if (!review)
39
+ return;
40
+ setBusy(true);
41
+ const reasonCode = action === 'promote' && review.readiness.status === 'evidence_insufficient'
42
+ ? overrideReason
43
+ : action;
44
+ const body = { idempotencyKey: `${action}-${crypto.randomUUID()}`, reasonCode, note };
45
+ if (action === 'promote')
46
+ Object.assign(body, { confirmed: true, artifactId: review.artifact.artifactId, artifactDigest: review.artifact.digest, controlVersion: review.controlState?.version });
47
+ if (action === 'recover-to-shadow')
48
+ Object.assign(body, { controlVersion: review.controlState?.version });
49
+ const result = await ruleCodeDecision(record.activationId, action, body);
50
+ setBusy(false);
51
+ if (result.success) {
52
+ toast.success(t('pages.activation.ownerDecisionRecorded'));
53
+ onChanged();
54
+ await load();
55
+ }
56
+ else {
57
+ setError(result.error);
58
+ if (result.nextAction)
59
+ toast.info(result.nextAction);
60
+ }
61
+ };
62
+ const releasePause = async () => { if (!review?.globalPause)
63
+ return; setBusy(true); const result = await releaseRuleCodePause(review.globalPause.pauseId, { idempotencyKey: `release-${crypto.randomUUID()}`, reasonCode: 'owner_releases_global_latch', expectedVersion: review.globalPause.version, note }); setBusy(false); if (result.success) {
64
+ toast.success(t('pages.activation.globalPauseReleased'));
65
+ onChanged();
66
+ await load();
67
+ }
68
+ else
69
+ setError(result.error); };
70
+ if (error)
71
+ return _jsx("div", { className: "mt-3 border-l-2 border-danger px-3 text-[12px] text-danger", children: error });
72
+ if (!review)
73
+ return _jsx("div", { className: "mt-3 text-[12px] text-ink-4", children: t('pages.activation.ownerDecisionLoading') });
74
+ const summary = review.readiness.evidenceSnapshot.shadowSummary;
75
+ const value = (number) => number === null ? t('pages.activation.notCollected') : String(number);
76
+ const affectedTools = Array.isArray(review.artifact.content?.affectedTools) ? review.artifact.content.affectedTools.filter((item) => typeof item === 'string') : [];
77
+ const renderWindow = (title, window) => _jsxs("div", { className: "rounded border border-line p-2", children: [_jsx("div", { className: "font-medium text-ink", children: title }), _jsxs("div", { className: "mt-1 grid grid-cols-2 gap-1 md:grid-cols-5", children: [_jsxs("span", { children: [t('pages.activation.eligibleEvaluations'), ": ", value(window.eligible)] }), _jsxs("span", { children: [t('pages.activation.matched'), ": ", value(window.matched)] }), _jsxs("span", { children: [t('pages.activation.blocked'), ": ", value(window.blocked)] }), _jsxs("span", { children: [t('pages.activation.unhealthy'), ": ", value(window.unhealthy)] }), _jsxs("span", { children: [t('pages.activation.circuitTrips'), ": ", window.circuitTrips] })] }), _jsxs("div", { className: "mt-1 text-ink-4", children: [t('pages.activation.toolDistribution'), ": ", window.toolDistribution === null ? t('pages.activation.notCollected') : Object.entries(window.toolDistribution).map(([tool, count]) => `${tool} ${count}`).join(', ') || '—'] })] });
78
+ return _jsxs("div", { className: "mt-4 rounded-[5px] border border-gov/25 bg-surface/70 p-4", "data-testid": `owner-review-${record.activationId}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("strong", { className: "text-[13px] text-ink", children: t('pages.activation.ownerDecisionTitle') }), _jsx("span", { className: "font-mono text-[11px] text-gov", children: review.readiness.status }), review.controlState?.enforcement === 'safety_isolated' && _jsx("span", { className: "text-danger text-[11px]", children: t('pages.activation.safetyIsolation') })] }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2 text-[12px] text-ink-3 md:grid-cols-4", children: [_jsxs("span", { children: [t('pages.activation.eligibleEvaluations'), ": ", value(summary.observed)] }), _jsxs("span", { children: [t('pages.activation.matched'), ": ", value(summary.matched)] }), _jsxs("span", { children: [t('pages.activation.wouldBlock'), ": ", value(summary.wouldBlock)] }), _jsxs("span", { children: [t('pages.activation.wouldAllow'), ": ", value(summary.wouldAllow)] }), _jsxs("span", { children: [t('pages.activation.requireApproval'), ": ", value(summary.requireApproval)] }), _jsxs("span", { children: [t('pages.activation.autoCorrect'), ": ", value(summary.autoCorrect)] }), _jsxs("span", { children: [t('pages.activation.neutralControl'), ": ", value(summary.neutralControl)] }), _jsxs("span", { children: [t('pages.activation.evaluationErrors'), ": ", value(summary.errors)] })] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-4", children: t('pages.activation.shadowWindow', { first: summary.firstObservedAt ?? t('pages.activation.notCollected'), last: summary.lastObservedAt ?? t('pages.activation.notCollected') }) }), _jsx("div", { className: "mt-2 text-[12px] text-ink-3", children: t('pages.activation.runtimeCapability', { version: review.runtimeCapability.hostRuntimeVersion, supported: t(review.runtimeCapability.shadowEvidence ? 'pages.activation.supported' : 'pages.activation.unsupported') }) }), _jsx("div", { className: "mt-2 text-[12px] text-amber", children: t('pages.activation.worstImpact', { tools: affectedTools.join(', ') || t('pages.activation.notCollected') }) }), _jsxs("div", { className: "mt-3 text-[12px] text-ink-3", children: [_jsx("div", { className: "mb-1 font-medium text-ink", children: t('pages.activation.hardSafetyChecks') }), review.readiness.evidenceSnapshot.safetyGateResults.map(check => _jsxs("div", { className: check.status === 'passed' ? 'text-green' : 'text-danger', children: [check.checkId, ": ", check.status, check.reasonCode ? ` · ${check.reasonCode}` : ''] }, check.checkId))] }), review.readiness.failedChecks.length > 0 && _jsx("ul", { className: "mt-2 text-[12px] text-danger", children: review.readiness.failedChecks.map(check => _jsxs("li", { children: [check.checkId, ": ", check.reasonCode] }, check.checkId)) }), _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.scopeArtifactCode') }), _jsx("pre", { className: "mt-2 max-h-64 overflow-auto whitespace-pre-wrap text-ink-3", children: JSON.stringify(review.artifact.content, null, 2) })] }), _jsxs("div", { className: "mt-3 grid gap-2 text-[11px] text-ink-3 md:grid-cols-2", children: [renderWindow(t('pages.activation.live24Hours'), review.liveMetrics.last24Hours), renderWindow(t('pages.activation.live7Days'), review.liveMetrics.last7Days)] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-3", children: t('pages.activation.behaviorDrift', { approved: value(review.behaviorDrift.approvedBlockRate), live: value(review.behaviorDrift.liveBlockRate), delta: value(review.behaviorDrift.delta) }) }), review.liveMetrics.representativeSamples.length > 0 && _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.representativeSamples') }), review.liveMetrics.representativeSamples.map((sample, index) => _jsxs("div", { className: "font-mono text-ink-3", children: [sample.toolName, " \u00B7 ", sample.decision, " \u00B7 ", sample.pathCategory] }, `${sample.toolName}-${index}`))] }), review.decisions.length > 0 && _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.ownerDecisionHistory') }), review.decisions.map(decision => _jsxs("div", { className: "mt-1 border-l border-line pl-2 text-ink-3", children: [_jsx("span", { className: "font-mono", children: decision.decidedAt }), " \u00B7 ", decision.decision, " \u00B7 ", decision.principalKind, ":", decision.actorId, " \u00B7 ", decision.authenticationMethod, " \u00B7 ", decision.reasonCode, decision.note ? ` · ${decision.note}` : ''] }, decision.decisionId))] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-4", children: t('pages.activation.circuitConditions') }), review.readiness.status === 'evidence_insufficient' && _jsxs("label", { className: "mt-3 block text-[12px] text-ink-3", children: [t('pages.activation.overrideReason'), _jsxs("select", { value: overrideReason, onChange: event => setOverrideReason(event.target.value), className: "mt-1 w-full rounded border border-line bg-panel px-3 py-2 text-ink", children: [_jsx("option", { value: "rare_behavior", children: t('pages.activation.overrideRareBehavior') }), _jsx("option", { value: "controlled_rollout", children: t('pages.activation.overrideControlledRollout') }), _jsx("option", { value: "owner_accepts_limited_evidence", children: t('pages.activation.overrideOwnerAccepts') })] })] }), _jsx("input", { value: note, onChange: event => setNote(event.target.value), placeholder: t('pages.activation.ownerReviewNote'), className: "mt-3 w-full rounded border border-line bg-panel px-3 py-2 text-[12px] text-ink" }), _jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [review.globalPause?.status === 'paused' && _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void releasePause(), children: t('pages.activation.releaseGlobalPause') }), record.action === 'code_tool_hook_shadow_activate' && _jsxs(_Fragment, { children: [_jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, onClick: () => void decide('continue-observing'), children: t('pages.activation.continueObserving') }), _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void decide('reject-after-shadow'), children: t('pages.activation.rejectAfterShadow') }), _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled || (review.readiness.status !== 'ready' && review.readiness.status !== 'evidence_insufficient') || (review.readiness.status === 'evidence_insufficient' && note.trim().length === 0), onClick: () => void decide('promote'), children: t('pages.activation.confirmLive') })] }), (record.action === 'code_tool_hook_live_activate' || review.controlState?.enforcement === 'safety_isolated') && _jsx(Button, { disabled: busy, variant: "destructive", onClick: () => void decide('emergency-deactivate'), children: t('pages.activation.emergencyDeactivate') }), review.controlState?.enforcement === 'safety_isolated' && _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void decide('recover-to-shadow'), children: t('pages.activation.recoverToShadow') })] }), !review.ownerDecisionEnabled && _jsx("div", { className: "mt-2 text-[11px] text-amber", children: t('pages.activation.rolloutDisabled') })] });
79
+ }
80
+ function ActivationFactCard({ record, lifecycleData, receiptCount, onDisable, disabling, onChanged, }) {
24
81
  const { t } = useTranslation();
25
82
  const navigate = useNavigate();
26
83
  const [showConfirm, setShowConfirm] = useState(false);
@@ -38,17 +95,22 @@ function ActivationFactCard({ record, lifecycleData, onDisable, disabling, }) {
38
95
  params.set("principleId", record.principleId);
39
96
  }
40
97
  navigate(`/report-problem?${params.toString()}`);
41
- }, children: t("pages.reportProblem.entryFeedback") }), _jsxs("div", { className: "mt-[14px] mb-2", children: [_jsx("span", { className: "text-ink-2 text-[13px]", children: t("pages.activation.principleLabel") }), " ", _jsx(Link, { to: `/principles/${record.principleId}`, className: "text-gov text-[13px] hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `principle-link-${record.principleId}`, title: record.principleId, children: record.action }), _jsx("span", { className: "ml-2 font-mono text-[11px] text-ink-4", children: record.principleId })] }), _jsxs("div", { className: "text-ink-3 text-[13px] leading-relaxed space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.actionLabel") }), " ", _jsx("span", { className: "text-ink-2", children: record.action })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.targetLabel") }), " ", _jsx("span", { className: "text-ink-2 font-mono text-[12px]", children: record.targetRef })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.activatedAtLabel") }), " ", neverActivated ? (_jsx("span", { className: "text-amber", children: t("pages.activation.neverActivated") })) : (_jsx("span", { className: "text-ink-2 font-mono text-[12px] tabular-nums", children: new Date(record.activatedAt).toLocaleString() }))] })] }), lifecycleData !== undefined && (_jsxs("details", { className: "mt-4 group", children: [_jsx("summary", { className: "text-gov text-[13px] cursor-pointer hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("pages.activation.lifecycleToggle") }), _jsx("div", { className: "mt-2 pl-4 border-l-2 border-line", children: lifecycleData === null ? (_jsx("div", { className: "text-ink-4 text-[13px] py-2", children: t("pages.activation.lifecycleUnavailable") })) : lifecycleData.adherence.insufficientData ? (_jsx("div", { className: "text-ink-4 text-[13px] py-2", children: t("pages.activation.insufficientData") })) : (_jsxs("div", { className: "py-2 space-y-2", children: [_jsxs("div", { className: "text-ink-2 text-[13px]", children: [_jsx("span", { className: "font-mono font-semibold text-ink tabular-nums", children: lifecycleData.adherence.rate !== null
98
+ }, children: t("pages.reportProblem.entryFeedback") }), _jsxs("div", { className: "mt-[14px] mb-2", children: [_jsx("span", { className: "text-ink-2 text-[13px]", children: t("pages.activation.principleLabel") }), " ", _jsx(Link, { to: `/principles/${record.principleId}`, className: "text-gov text-[13px] hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `principle-link-${record.principleId}`, title: record.principleId, children: record.action }), _jsx("span", { className: "ml-2 font-mono text-[11px] text-ink-4", children: record.principleId })] }), record.legacyDecisionUnknown && _jsx("div", { className: "mt-3 border-l-2 border-amber px-3 text-[12px] text-amber", children: t('pages.activation.legacyUnknown', { due: record.ownerReviewDueAt ? new Date(record.ownerReviewDueAt).toLocaleString() : t('pages.activation.sevenDays') }) }), _jsxs("div", { className: "text-ink-3 text-[13px] leading-relaxed space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.actionLabel") }), " ", _jsx("span", { className: "text-ink-2", children: record.action })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.targetLabel") }), " ", _jsx("span", { className: "text-ink-2 font-mono text-[12px]", children: record.targetRef })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.activatedAtLabel") }), " ", neverActivated ? (_jsx("span", { className: "text-amber", children: t("pages.activation.neverActivated") })) : (_jsx("span", { className: "text-ink-2 font-mono text-[12px] tabular-nums", children: new Date(record.activatedAt).toLocaleString() }))] }), receiptCount !== undefined && (_jsxs("div", { "data-testid": `activation-receipts-${record.activationId}`, children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.receiptsLabel") }), " ", _jsx("span", { className: "text-ink-2 font-mono text-[12px] tabular-nums", children: t("pages.activation.receiptsValue", {
99
+ effect: receiptCount?.effectCount ?? 0,
100
+ presence: receiptCount?.presenceCount ?? 0,
101
+ }) })] }))] }), lifecycleData !== undefined && (_jsxs("details", { className: "mt-4 group", children: [_jsx("summary", { className: "text-gov text-[13px] cursor-pointer hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("pages.activation.lifecycleToggle") }), _jsx("div", { className: "mt-2 pl-4 border-l-2 border-line", children: lifecycleData === null ? (_jsx("div", { className: "text-ink-4 text-[13px] py-2", children: t("pages.activation.lifecycleUnavailable") })) : lifecycleData.adherence.insufficientData ? (_jsx("div", { className: "text-ink-4 text-[13px] py-2", children: t("pages.activation.insufficientData") })) : (_jsxs("div", { className: "py-2 space-y-2", children: [_jsxs("div", { className: "text-ink-2 text-[13px]", children: [_jsx("span", { className: "font-mono font-semibold text-ink tabular-nums", children: lifecycleData.adherence.rate !== null
42
102
  ? `${(lifecycleData.adherence.rate * 100).toFixed(0)}%`
43
103
  : "—" }), " ", _jsx("span", { className: "text-ink-3", children: t("pages.activation.adherenceLabel") }), _jsx("div", { className: "text-ink-4 text-[11px] mt-1 font-mono", children: t("pages.activation.lifecycleNote") })] }), lifecycleData.ruleMetrics.length > 0 && (_jsx("div", { className: "space-y-1", children: lifecycleData.ruleMetrics.map((rm) => (_jsxs("div", { className: "text-ink-3 text-[12px] font-mono", children: [_jsx("span", { className: "text-ink-2", children: rm.ruleId }), ":", " ", rm.triggered, " ", t("pages.activation.triggered"), " ", rm.lastTriggeredAt && (_jsxs("span", { className: "text-ink-4", children: ["(", new Date(rm.lastTriggeredAt).toLocaleString(), ")"] }))] }, rm.ruleId))) }))] })) })] })), record.status === "active" && reversible && (_jsx("div", { className: "mt-4", children: !showConfirm ? (_jsx("button", { onClick: () => setShowConfirm(true), disabled: disabling, className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `disable-btn-${record.activationId}`, children: t("pages.activation.disableAction") })) : (_jsxs("div", { className: "flex items-center gap-3 px-[14px] py-[10px] bg-surface border border-amber/25 rounded-[4px]", role: "alert", "data-testid": `confirm-bar-${record.activationId}`, children: [_jsx("span", { className: "text-ink-2 text-[13px] flex-1", children: t("pages.activation.confirmDisable") }), _jsx("button", { onClick: () => {
44
104
  onDisable(record);
45
105
  setShowConfirm(false);
46
- }, disabled: disabling, className: "inline-flex items-center border border-danger/40 text-danger bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-danger/5 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `confirm-disable-${record.activationId}`, children: disabling ? t("pages.activation.disabling") : t("common.confirm") }), _jsx("button", { onClick: () => setShowConfirm(false), className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("common.cancel") })] })) }))] }));
106
+ }, disabled: disabling, className: "inline-flex items-center border border-danger/40 text-danger bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-danger/5 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `confirm-disable-${record.activationId}`, children: disabling ? t("pages.activation.disabling") : t("common.confirm") }), _jsx("button", { onClick: () => setShowConfirm(false), className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("common.cancel") })] })) })), record.channel === 'code_tool_hook' && record.status === 'active' && _jsx(RuleCodeOwnerPanel, { record: record, onChanged: onChanged })] }));
47
107
  }
48
108
  export function ActivationPage() {
49
109
  const { t } = useTranslation();
50
110
  const [activationsData, setActivationsData] = useState(null);
51
111
  const [lifecycleCache, setLifecycleCache] = useState({});
112
+ const [receiptCounts, setReceiptCounts] = useState(null);
113
+ const [receiptDegraded, setReceiptDegraded] = useState(null);
52
114
  const [loadingState, setLoadingState] = useState("loading");
53
115
  const [errorMessage, setErrorMessage] = useState(null);
54
116
  const [disablingIds, setDisablingIds] = useState(new Set());
@@ -57,6 +119,8 @@ export function ActivationPage() {
57
119
  setLoadingState("loading");
58
120
  setErrorMessage(null);
59
121
  setDegradedNote(null);
122
+ setReceiptCounts(null);
123
+ setReceiptDegraded(null);
60
124
  const result = await fetchAllActivations();
61
125
  if (!result.success) {
62
126
  setLoadingState("error");
@@ -73,6 +137,36 @@ export function ActivationPage() {
73
137
  if (validated.reason) {
74
138
  setDegradedNote(validated.reason);
75
139
  }
140
+ // PRI-533: per-principle receipt counts for the activation cards.
141
+ // Counts are supplementary evidence — a degraded/failed counts API never
142
+ // blocks the page; the cards omit the row and the page surfaces the
143
+ // degraded reason once (ERR-002: no silent fallback).
144
+ try {
145
+ const rResult = await fetchReceiptCounts();
146
+ if (!rResult.success) {
147
+ setReceiptDegraded({
148
+ reason: rResult.error,
149
+ ...(rResult.nextAction === undefined ? {} : { nextAction: rResult.nextAction }),
150
+ });
151
+ }
152
+ else if (rResult.data.status === "ok") {
153
+ const byId = {};
154
+ for (const entry of rResult.data.counts) {
155
+ byId[entry.principleId] = entry;
156
+ }
157
+ setReceiptCounts(byId);
158
+ }
159
+ else {
160
+ setReceiptDegraded({
161
+ reason: rResult.data.reason ?? "unknown",
162
+ ...(rResult.data.nextAction === undefined ? {} : { nextAction: rResult.data.nextAction }),
163
+ });
164
+ }
165
+ }
166
+ catch (err) {
167
+ // EP-03: counts are optional — degrade with reason, never block the page.
168
+ setReceiptDegraded({ reason: err instanceof Error ? err.message : String(err) });
169
+ }
76
170
  // Pre-fetch lifecycle metrics for active principles with rules
77
171
  // (only for principles that are active, to populate the expandable section)
78
172
  const activePrinciples = new Set(validated.activations
@@ -136,6 +230,18 @@ export function ActivationPage() {
136
230
  return next;
137
231
  });
138
232
  }, [t]);
233
+ const handleEmergencyPause = useCallback(async () => {
234
+ const result = await pauseAllRuleCode({ idempotencyKey: `pause-${crypto.randomUUID()}`, reasonCode: 'owner_emergency_pause' });
235
+ if (result.success) {
236
+ toast.success(t('pages.activation.pauseAllSucceeded'));
237
+ await loadData();
238
+ }
239
+ else {
240
+ toast.error(result.error);
241
+ if (result.nextAction)
242
+ toast.info(result.nextAction);
243
+ }
244
+ }, [loadData, t]);
139
245
  // ── Loading state ────────────────────────────────────────────────────────
140
246
  if (loadingState === "loading") {
141
247
  return (_jsx(PageShell, { children: _jsx(PageLoading, { cardCount: 4, label: t("common.loading") }) }));
@@ -147,9 +253,12 @@ export function ActivationPage() {
147
253
  // ── Loaded state ─────────────────────────────────────────────────────────
148
254
  const activations = activationsData?.activations ?? [];
149
255
  const activeActivations = activations.filter((a) => a.status === "active");
256
+ const pendingRuleCode = activeActivations.filter(record => record.action === 'code_tool_hook_shadow_activate' && record.enforcement !== 'safety_isolated');
257
+ const safetyAlerts = activeActivations.filter(record => record.channel === 'code_tool_hook' && record.enforcement === 'safety_isolated');
258
+ const ordinaryActive = activeActivations.filter(record => !pendingRuleCode.includes(record) && !safetyAlerts.includes(record));
150
259
  const inactiveActivations = activations.filter((a) => a.status === "inactive");
151
260
  const neverActivatedCount = activations.filter((a) => a.activatedAt === null).length;
152
- return (_jsx(PageShell, { children: _jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [_jsx("div", { className: "font-mono text-[12px] tracking-[0.14em] text-ink-3 uppercase mb-3", children: t("pages.activation.eyebrow") }), _jsx(ShinyText, { as: "h1", className: "text-[29px] font-semibold tracking-tight text-ink mb-2", duration: 4.5, brightness: 0.5, disabled: activeActivations.length === 0, children: t("pages.activation.title") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[760px] leading-relaxed mb-7", children: t("pages.activation.subtitle") }), _jsxs("div", { className: "text-sm leading-relaxed text-ink-2 mb-7 px-[18px] py-[14px] bg-panel border border-line rounded-[6px]", role: "status", "aria-live": "polite", children: [_jsx("span", { className: "font-mono font-semibold text-ink tabular-nums", children: activeActivations.length }), " ", _jsx("span", { className: "text-ink-3", children: t("pages.activation.summaryActive") }), " ", "/", " ", _jsx("span", { className: "font-mono font-semibold text-ink tabular-nums", children: inactiveActivations.length }), " ", _jsx("span", { className: "text-ink-3", children: t("pages.activation.summaryInactive") }), neverActivatedCount > 0 && (_jsxs(_Fragment, { children: [" ", "/", " ", _jsx("span", { className: "font-mono font-semibold text-amber tabular-nums", children: neverActivatedCount }), " ", _jsx("span", { className: "text-amber", children: t("pages.activation.summaryNeverActivated") })] }))] }), _jsx(CapabilityBoundaryDeclaration, {}), degradedNote && (_jsx("div", { className: "mt-4 text-ink-4 text-[13px] bg-surface/60 border-l-2 border-amber px-3 py-2", children: degradedNote })), _jsxs("section", { className: "mt-8", "aria-labelledby": "section-active", children: [_jsx(SectionTitle, { id: "section-active", children: t("pages.activation.sectionActive") }), activeActivations.length > 0 ? (_jsx("div", { className: "space-y-[14px]", children: activeActivations.map((record) => (_jsx(ActivationFactCard, { record: record, lifecycleData: lifecycleCache[record.principleId], onDisable: handleDisable, disabling: disablingIds.has(record.activationId) }, record.activationId))) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.activation.emptyActive") }))] }), inactiveActivations.length > 0 && (_jsxs("section", { className: "mt-8", "aria-labelledby": "section-inactive", children: [_jsx(SectionTitle, { id: "section-inactive", children: t("pages.activation.sectionInactive") }), _jsx("div", { className: "space-y-[14px]", children: inactiveActivations.map((record) => (_jsx(ActivationFactCard, { record: record, lifecycleData: lifecycleCache[record.principleId], onDisable: handleDisable, disabling: disablingIds.has(record.activationId) }, record.activationId))) })] })), neverActivatedCount > 0 && (_jsxs("section", { className: "mt-8", "aria-labelledby": "section-never-activated", children: [_jsx(SectionTitle, { id: "section-never-activated", children: t("pages.activation.sectionNeverActivated") }), _jsx("div", { className: "space-y-[10px]", children: activations
261
+ return (_jsx(PageShell, { children: _jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [_jsx("div", { className: "font-mono text-[12px] tracking-[0.14em] text-ink-3 uppercase mb-3", children: t("pages.activation.eyebrow") }), _jsx(ShinyText, { as: "h1", className: "text-[29px] font-semibold tracking-tight text-ink mb-2", duration: 4.5, brightness: 0.5, disabled: activeActivations.length === 0, children: t("pages.activation.title") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[760px] leading-relaxed mb-7", children: t("pages.activation.subtitle") }), _jsxs("div", { className: "text-sm leading-relaxed text-ink-2 mb-7 px-[18px] py-[14px] bg-panel border border-line rounded-[6px]", role: "status", "aria-live": "polite", children: [_jsx("span", { className: "font-mono font-semibold text-ink tabular-nums", children: activeActivations.length }), " ", _jsx("span", { className: "text-ink-3", children: t("pages.activation.summaryActive") }), " ", "/", " ", _jsx("span", { className: "font-mono font-semibold text-ink tabular-nums", children: inactiveActivations.length }), " ", _jsx("span", { className: "text-ink-3", children: t("pages.activation.summaryInactive") }), neverActivatedCount > 0 && (_jsxs(_Fragment, { children: [" ", "/", " ", _jsx("span", { className: "font-mono font-semibold text-amber tabular-nums", children: neverActivatedCount }), " ", _jsx("span", { className: "text-amber", children: t("pages.activation.summaryNeverActivated") })] }))] }), _jsx(CapabilityBoundaryDeclaration, {}), activeActivations.some(record => record.action === 'code_tool_hook_live_activate') && _jsx("div", { className: "mt-4 flex justify-end", children: _jsx(Button, { variant: "destructive", onClick: () => void handleEmergencyPause(), children: t('pages.activation.pauseAll') }) }), degradedNote && (_jsx("div", { className: "mt-4 text-ink-4 text-[13px] bg-surface/60 border-l-2 border-amber px-3 py-2", children: degradedNote })), receiptDegraded && (_jsxs("div", { className: "mt-4 text-ink-4 text-[13px] bg-surface/60 border-l-2 border-amber px-3 py-2", "data-testid": "receipt-counts-degraded", children: [t("pages.activation.receiptsUnavailable", { reason: receiptDegraded.reason }), receiptDegraded.nextAction && (_jsx("span", { className: "mt-1 block font-mono text-[12px]", children: receiptDegraded.nextAction }))] })), pendingRuleCode.length > 0 && _jsxs("section", { className: "mt-8", "aria-labelledby": "section-pending-rulecode", children: [_jsx(SectionTitle, { id: "section-pending-rulecode", children: t('pages.activation.pendingRules') }), _jsx("p", { className: "mb-3 text-[12px] text-ink-3", children: t('pages.activation.pendingRulesDescription') }), _jsx("div", { className: "space-y-[14px]", children: pendingRuleCode.map(record => _jsx(ActivationFactCard, { record: record, lifecycleData: lifecycleCache[record.principleId], receiptCount: undefined, onDisable: handleDisable, disabling: disablingIds.has(record.activationId), onChanged: () => void loadData() }, record.activationId)) })] }), safetyAlerts.length > 0 && _jsxs("section", { className: "mt-8", "aria-labelledby": "section-safety-alerts", children: [_jsx(SectionTitle, { id: "section-safety-alerts", children: t('pages.activation.safetyAlerts') }), _jsx("p", { className: "mb-3 text-[12px] text-danger", children: t('pages.activation.safetyAlertsDescription') }), _jsx("div", { className: "space-y-[14px]", children: safetyAlerts.map(record => _jsx(ActivationFactCard, { record: record, lifecycleData: lifecycleCache[record.principleId], receiptCount: undefined, onDisable: handleDisable, disabling: disablingIds.has(record.activationId), onChanged: () => void loadData() }, record.activationId)) })] }), _jsxs("section", { className: "mt-8", "aria-labelledby": "section-active", children: [_jsx(SectionTitle, { id: "section-active", children: t("pages.activation.sectionActive") }), ordinaryActive.length > 0 ? (_jsx("div", { className: "space-y-[14px]", children: ordinaryActive.map((record) => (_jsx(ActivationFactCard, { record: record, lifecycleData: lifecycleCache[record.principleId], receiptCount: receiptCounts !== null ? (Object.hasOwn(receiptCounts, record.principleId) ? receiptCounts[record.principleId] : null) : undefined, onDisable: handleDisable, disabling: disablingIds.has(record.activationId), onChanged: () => void loadData() }, record.activationId))) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.activation.emptyActive") }))] }), inactiveActivations.length > 0 && (_jsxs("section", { className: "mt-8", "aria-labelledby": "section-inactive", children: [_jsx(SectionTitle, { id: "section-inactive", children: t("pages.activation.sectionInactive") }), _jsx("div", { className: "space-y-[14px]", children: inactiveActivations.map((record) => (_jsx(ActivationFactCard, { record: record, lifecycleData: lifecycleCache[record.principleId], receiptCount: receiptCounts !== null ? (Object.hasOwn(receiptCounts, record.principleId) ? receiptCounts[record.principleId] : null) : undefined, onDisable: handleDisable, disabling: disablingIds.has(record.activationId), onChanged: () => void loadData() }, record.activationId))) })] })), neverActivatedCount > 0 && (_jsxs("section", { className: "mt-8", "aria-labelledby": "section-never-activated", children: [_jsx(SectionTitle, { id: "section-never-activated", children: t("pages.activation.sectionNeverActivated") }), _jsx("div", { className: "space-y-[10px]", children: activations
153
262
  .filter((a) => a.activatedAt === null)
154
263
  .map((record) => (_jsxs("article", { className: "relative pl-[22px] py-[14px] pr-[18px] bg-panel border border-amber/20 border-l-[3px] border-l-amber rounded-[6px]", children: [_jsxs("div", { className: "text-ink-2 text-sm leading-relaxed", children: [_jsx("span", { className: "font-medium text-ink", children: t("pages.activation.neverActivatedCard") }), " ", "\u2014 ", record.action, " \u00B7 ", getChannelLabel(record.channel, t), _jsx("span", { className: "ml-2 font-mono text-[11px] text-ink-4", children: record.principleId })] }), _jsxs(Link, { to: "/debt", className: "inline-flex items-center mt-2 text-gov text-[13px] hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: [t("pages.activation.viewDebt"), " \u2192"] })] }, record.activationId))) })] })), _jsx("footer", { className: "mt-12 pt-6 border-t border-line text-ink-3 text-[13px]", children: t("pages.activation.footer") })] }) }));
155
264
  }
@@ -53,6 +53,9 @@ export function validateActivationRecord(raw) {
53
53
  targetRef: targetRef,
54
54
  activatedAt: activatedAt,
55
55
  status: narrowStatus,
56
+ ...(Object.hasOwn(raw, "enforcement") && (raw.enforcement === "eligible" || raw.enforcement === "safety_isolated") ? { enforcement: raw.enforcement } : {}),
57
+ ...(Object.hasOwn(raw, "legacyDecisionUnknown") && raw.legacyDecisionUnknown === true ? { legacyDecisionUnknown: true } : {}),
58
+ ...(Object.hasOwn(raw, "ownerReviewDueAt") && typeof raw.ownerReviewDueAt === "string" ? { ownerReviewDueAt: raw.ownerReviewDueAt } : {}),
56
59
  };
57
60
  }
58
61
  export function validateActivationsData(raw) {
@@ -8,7 +8,7 @@ import { PageLoading } from "../../components/layout/page-loading.js";
8
8
  import { SectionTitle } from "../../components/layout/section-title.js";
9
9
  import { DailyThoughtCard } from "../../components/focus/daily-thought-card.js";
10
10
  import { ShinyText } from "../../components/ui/shiny-text.js";
11
- import { fetchGovernanceQueue, fetchApprovalsGrouped, approveApproval, rejectApproval, editApproval, } from "../../api.js";
11
+ import { fetchGovernanceQueue, fetchApprovalsGrouped, approveApproval, rejectApproval, editApproval, fetchAllActivations, } from "../../api.js";
12
12
  export function summarizeDecisionResults(results) {
13
13
  const failures = results.filter((result) => !result.success);
14
14
  const first = failures[0];
@@ -358,13 +358,17 @@ export function FocusPage() {
358
358
  const [loadingState, setLoadingState] = useState("loading");
359
359
  const [errorMessage, setErrorMessage] = useState(null);
360
360
  const [groupedErrorReason, setGroupedErrorReason] = useState(null);
361
+ const [ruleCodeItems, setRuleCodeItems] = useState([]);
362
+ const [ruleCodeErrorReason, setRuleCodeErrorReason] = useState(null);
361
363
  const loadData = useCallback(async () => {
362
364
  setLoadingState("loading");
363
365
  setErrorMessage(null);
364
366
  setGroupedErrorReason(null);
365
- const [queueResult, groupedResult] = await Promise.all([
367
+ setRuleCodeErrorReason(null);
368
+ const [queueResult, groupedResult, activationsResult] = await Promise.all([
366
369
  fetchGovernanceQueue(),
367
370
  fetchApprovalsGrouped(),
371
+ fetchAllActivations(),
368
372
  ]);
369
373
  // Queue data is already validated by the API layer (validateGovernanceQueue)
370
374
  if (!queueResult.success) {
@@ -385,6 +389,13 @@ export function FocusPage() {
385
389
  setGroupedErrorReason("Approvals data has unexpected shape");
386
390
  }
387
391
  }
392
+ if (activationsResult.success) {
393
+ setRuleCodeItems(activationsResult.data.activations.filter((item) => item.channel === "code_tool_hook" && item.status === "active"));
394
+ }
395
+ else {
396
+ setRuleCodeItems([]);
397
+ setRuleCodeErrorReason(activationsResult.error);
398
+ }
388
399
  setLoadingState("loaded");
389
400
  }, []);
390
401
  useEffect(() => {
@@ -412,6 +423,8 @@ export function FocusPage() {
412
423
  const evidenceCount = queueData?.evidenceInProgressCount ?? 0;
413
424
  const gateBlocksToday = queueData?.gateBlocksToday ?? 0;
414
425
  const approvalDataUnavailable = (groupedData === null || groupedData.groups.length === 0) && pendingCount > 0;
426
+ const ruleCodePending = ruleCodeItems.filter(item => item.action === 'code_tool_hook_shadow_activate' && item.enforcement !== 'safety_isolated');
427
+ const ruleCodeAlerts = ruleCodeItems.filter(item => item.enforcement === 'safety_isolated');
415
428
  // Map codes to i18n text
416
429
  const stateReason = getStateReasonText(stateReasonCode, t, pendingCount);
417
430
  const nextAction = getNextActionText(nextActionCode, t);
@@ -421,7 +434,7 @@ export function FocusPage() {
421
434
  ? "bg-amber/10 text-amber border border-amber/20"
422
435
  : governanceState === "in_progress"
423
436
  ? "bg-green/10 text-green border border-green/20"
424
- : "text-ink-4 border border-line bg-surface/80"}`, role: "status", children: t(`pages.focus.stateLabel.${governanceState}`) }) }), _jsx("div", { className: "text-ink-2 text-[13px] leading-relaxed mt-1", children: stateReason }), _jsxs("div", { className: "text-ink-4 text-[13px] leading-relaxed mt-1", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.nextActionLabel") }), " ", nextAction] })] }), _jsx(ProseSummary, { pendingCount: pendingCount, deviationCount: deviationCount, stagnationCount: stagnationCount, evidenceCount: evidenceCount }), _jsx(DailyThoughtCard, {}), _jsx(FeedbackStratification, { gateBlocksToday: gateBlocksToday, inProgressCount: evidenceCount, pendingCount: pendingCount }), governanceState === "none" && pendingCount === 0 && deviationCount === 0 && stagnationCount === 0 && (stateReasonCode === "state_db_missing" ? (_jsx(ZeroStateDbMissing, {})) : (_jsxs(_Fragment, { children: [_jsx(ZeroStateHealthy, {}), _jsx("div", { className: "mt-4", children: _jsx(OnboardingGuide, {}) })] }))), governanceState === "in_progress" && inProgressSummary && (_jsx(InProgressGuide, { summary: inProgressSummary })), degradedSignals && degradedSignals.length > 0 && (_jsx(DegradedSummary, { signals: degradedSignals })), _jsxs("section", { className: "mt-8", "aria-labelledby": "section-pending", children: [_jsx(SectionTitle, { id: "section-pending", children: t("pages.focus.sectionPending") }), pendingGroups.length > 0 ? (_jsx("div", { className: "space-y-[14px]", children: pendingGroups.map((group) => (_jsx(PendingReviewCard, { group: group, onDecisionApplied: loadData }, group.principleId))) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: approvalDataUnavailable
437
+ : "text-ink-4 border border-line bg-surface/80"}`, role: "status", children: t(`pages.focus.stateLabel.${governanceState}`) }) }), _jsx("div", { className: "text-ink-2 text-[13px] leading-relaxed mt-1", children: stateReason }), _jsxs("div", { className: "text-ink-4 text-[13px] leading-relaxed mt-1", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.nextActionLabel") }), " ", nextAction] })] }), _jsx(ProseSummary, { pendingCount: pendingCount, deviationCount: deviationCount, stagnationCount: stagnationCount, evidenceCount: evidenceCount }), _jsx(DailyThoughtCard, {}), _jsx(FeedbackStratification, { gateBlocksToday: gateBlocksToday, inProgressCount: evidenceCount, pendingCount: pendingCount }), governanceState === "none" && pendingCount === 0 && deviationCount === 0 && stagnationCount === 0 && (stateReasonCode === "state_db_missing" ? (_jsx(ZeroStateDbMissing, {})) : (_jsxs(_Fragment, { children: [_jsx(ZeroStateHealthy, {}), _jsx("div", { className: "mt-4", children: _jsx(OnboardingGuide, {}) })] }))), governanceState === "in_progress" && inProgressSummary && (_jsx(InProgressGuide, { summary: inProgressSummary })), degradedSignals && degradedSignals.length > 0 && (_jsx(DegradedSummary, { signals: degradedSignals })), (ruleCodeErrorReason || ruleCodePending.length > 0 || ruleCodeAlerts.length > 0) && (_jsxs("section", { className: "mt-8", "aria-labelledby": "section-rulecode-owner", children: [_jsx(SectionTitle, { id: "section-rulecode-owner", children: t("pages.focus.ruleCodeOwnerQueue") }), ruleCodeErrorReason && (_jsxs("div", { className: "mb-3 rounded border border-amber/30 bg-panel p-4 text-[12px] text-ink-3", children: [_jsx("div", { className: "font-medium text-amber", children: t("pages.focus.ruleCodeUnavailable") }), _jsx("div", { className: "mt-1 font-mono", children: ruleCodeErrorReason }), _jsx("div", { className: "mt-1", children: t("pages.focus.ruleCodeUnavailableNextAction") })] })), _jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [ruleCodePending.length > 0 && (_jsxs(Link, { to: "/activation", className: "rounded border border-gov/30 bg-panel p-4 hover:border-gov", children: [_jsx("div", { className: "font-medium text-ink", children: t("pages.focus.ruleCodePending") }), _jsx("div", { className: "mt-1 text-[12px] text-ink-3", children: t("pages.focus.ruleCodePendingCount", { count: ruleCodePending.length }) })] })), ruleCodeAlerts.length > 0 && (_jsxs(Link, { to: "/activation", className: "rounded border border-danger/30 bg-panel p-4 hover:border-danger", children: [_jsx("div", { className: "font-medium text-danger", children: t("pages.focus.ruleCodeAlerts") }), _jsx("div", { className: "mt-1 text-[12px] text-ink-3", children: t("pages.focus.ruleCodeAlertCount", { count: ruleCodeAlerts.length }) })] }))] })] })), _jsxs("section", { className: "mt-8", "aria-labelledby": "section-pending", children: [_jsx(SectionTitle, { id: "section-pending", children: t("pages.focus.sectionPending") }), pendingGroups.length > 0 ? (_jsx("div", { className: "space-y-[14px]", children: pendingGroups.map((group) => (_jsx(PendingReviewCard, { group: group, onDecisionApplied: loadData }, group.principleId))) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: approvalDataUnavailable
425
438
  ? (groupedErrorReason
426
439
  ? `${t("pages.focus.loadError")} (${groupedErrorReason})`
427
440
  : t("pages.focus.loadError"))