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
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Receipts Console Model — PRI-533 (SPEC §5.4).
3
+ *
4
+ * Read-side model for the principle receipt ledger (principle_applications,
5
+ * written by the plugin when principle_receipt_ledger is enabled). Serves:
6
+ * - per-principle receipt history (effect/presence counts + bounded timeline)
7
+ * - per-principle counts for the activations page columns
8
+ *
9
+ * Degradation rules (rc-9): state.db missing, table missing (pre-PRI-531
10
+ * workspace), or the ledger flag disabled → status degraded with reason +
11
+ * nextAction. Never throws to the route.
12
+ *
13
+ * rc-1: SQL rows are typed unknown and narrowed with typeof per column.
14
+ */
15
+ import * as fs from 'node:fs';
16
+ import * as path from 'node:path';
17
+ import { SqliteConnection } from '@principles/core/runtime-v2';
18
+ import { computeFlagsFromLoadResult, loadPdConfig } from '../config/pd-config-store.js';
19
+ const KINDS = new Set(['rule_blocked', 'auto_correct_applied', 'self_reported', 'prompt_injected']);
20
+ const LEVELS = new Set(['effect', 'presence']);
21
+ const TIMELINE_LIMIT = 50;
22
+ function asNullableString(value) {
23
+ return typeof value === 'string' && value.length > 0 ? value : null;
24
+ }
25
+ function toEvent(row) {
26
+ if (typeof row.kind !== 'string' || !KINDS.has(row.kind))
27
+ return null;
28
+ if (typeof row.level !== 'string' || !LEVELS.has(row.level))
29
+ return null;
30
+ if (typeof row.created_at !== 'string')
31
+ return null;
32
+ return {
33
+ kind: row.kind,
34
+ level: row.level,
35
+ sessionId: asNullableString(row.session_id),
36
+ toolName: asNullableString(row.tool_name),
37
+ filePath: asNullableString(row.file_path),
38
+ digest: asNullableString(row.digest),
39
+ createdAt: row.created_at,
40
+ };
41
+ }
42
+ function isMissingTableError(err) {
43
+ return err instanceof Error && err.message.includes('no such table');
44
+ }
45
+ export class ReceiptsConsoleModel {
46
+ workspaceDir;
47
+ constructor(workspaceDir) {
48
+ this.workspaceDir = workspaceDir;
49
+ }
50
+ async getPrincipleReceipts(principleId) {
51
+ const guard = this.precheck();
52
+ if (guard) {
53
+ return { ...guard, principleId, effectCount: 0, presenceCount: 0, lastEffectAt: null, events: [] };
54
+ }
55
+ const conn = new SqliteConnection({ workspaceDir: this.workspaceDir, readonly: true, bootstrapIfMissing: false });
56
+ try {
57
+ const rows = conn.getDb().prepare('SELECT kind, level, session_id, tool_name, file_path, digest, created_at FROM principle_applications WHERE principle_id = ? ORDER BY created_at DESC, id DESC LIMIT ?').all(principleId, TIMELINE_LIMIT);
58
+ const events = [];
59
+ for (const row of Array.isArray(rows) ? rows : []) {
60
+ const event = toEvent(row);
61
+ if (event)
62
+ events.push(event);
63
+ }
64
+ const counts = conn.getDb().prepare("SELECT level, COUNT(*) AS n, MAX(created_at) AS last_at FROM principle_applications WHERE principle_id = ? GROUP BY level").all(principleId);
65
+ let effectCount = 0;
66
+ let presenceCount = 0;
67
+ let lastEffectAt = null;
68
+ for (const row of Array.isArray(counts) ? counts : []) {
69
+ const rec = row;
70
+ if (rec.level === 'effect') {
71
+ effectCount = typeof rec.n === 'number' ? rec.n : 0;
72
+ lastEffectAt = asNullableString(rec.last_at);
73
+ }
74
+ else if (rec.level === 'presence') {
75
+ presenceCount = typeof rec.n === 'number' ? rec.n : 0;
76
+ }
77
+ }
78
+ return { status: 'ok', principleId, effectCount, presenceCount, lastEffectAt, events };
79
+ }
80
+ catch (err) {
81
+ if (isMissingTableError(err)) {
82
+ return {
83
+ status: 'degraded',
84
+ reason: 'principle_applications table not found — ledger not initialized on this workspace',
85
+ nextAction: 'Update the plugin to a version including PRI-531 and enable principle_receipt_ledger',
86
+ principleId, effectCount: 0, presenceCount: 0, lastEffectAt: null, events: [],
87
+ };
88
+ }
89
+ throw err;
90
+ }
91
+ finally {
92
+ conn.close();
93
+ }
94
+ }
95
+ async getReceiptCounts() {
96
+ const guard = this.precheck();
97
+ if (guard) {
98
+ return { status: guard.status, reason: guard.reason, nextAction: guard.nextAction, counts: [] };
99
+ }
100
+ const conn = new SqliteConnection({ workspaceDir: this.workspaceDir, readonly: true, bootstrapIfMissing: false });
101
+ try {
102
+ const rows = conn.getDb().prepare(`SELECT principle_id,
103
+ SUM(CASE WHEN level = 'effect' THEN 1 ELSE 0 END) AS effect_count,
104
+ SUM(CASE WHEN level = 'presence' THEN 1 ELSE 0 END) AS presence_count,
105
+ MAX(CASE WHEN level = 'effect' THEN created_at END) AS last_effect_at
106
+ FROM principle_applications GROUP BY principle_id`).all();
107
+ const counts = [];
108
+ for (const row of Array.isArray(rows) ? rows : []) {
109
+ const rec = row;
110
+ const principleId = asNullableString(rec.principle_id);
111
+ if (!principleId)
112
+ continue;
113
+ counts.push({
114
+ principleId,
115
+ effectCount: typeof rec.effect_count === 'number' ? rec.effect_count : 0,
116
+ presenceCount: typeof rec.presence_count === 'number' ? rec.presence_count : 0,
117
+ lastEffectAt: asNullableString(rec.last_effect_at),
118
+ });
119
+ }
120
+ return { status: 'ok', counts };
121
+ }
122
+ catch (err) {
123
+ if (isMissingTableError(err)) {
124
+ return {
125
+ status: 'degraded',
126
+ reason: 'principle_applications table not found — ledger not initialized on this workspace',
127
+ nextAction: 'Update the plugin to a version including PRI-531 and enable principle_receipt_ledger',
128
+ counts: [],
129
+ };
130
+ }
131
+ throw err;
132
+ }
133
+ finally {
134
+ conn.close();
135
+ }
136
+ }
137
+ /**
138
+ * Shared preconditions: state.db exists + ledger flag enabled. Returns a
139
+ * degraded partial response when a precondition fails (rc-9), else null.
140
+ */
141
+ precheck() {
142
+ const stateDbPath = path.join(this.workspaceDir, '.pd', 'state.db');
143
+ if (!fs.existsSync(stateDbPath)) {
144
+ return {
145
+ status: 'degraded',
146
+ reason: 'state.db not found — workspace may not be initialized',
147
+ nextAction: 'Run pd runtime diagnostics to check workspace state',
148
+ };
149
+ }
150
+ const flags = computeFlagsFromLoadResult(loadPdConfig(this.workspaceDir));
151
+ if (flags.flags.principle_receipt_ledger?.enabled !== true) {
152
+ return {
153
+ status: 'degraded',
154
+ reason: 'principle_receipt_ledger flag is disabled — no receipt history is being recorded',
155
+ nextAction: 'Enable the flag in .pd/config.yaml: features.principle_receipt_ledger.enabled = true',
156
+ };
157
+ }
158
+ return null;
159
+ }
160
+ }
@@ -1,3 +1,8 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
- export declare function handleActivationsRoute(req: IncomingMessage, res: ServerResponse, workspaceDir: string, subPath: string): Promise<void>;
2
+ import type { OwnerPromotionActor } from '@principles/core/runtime-v2';
3
+ export interface ActivationsRouteAuthority {
4
+ ownerActor: OwnerPromotionActor | null;
5
+ breakGlassActor: OwnerPromotionActor;
6
+ }
7
+ export declare function handleActivationsRoute(req: IncomingMessage, res: ServerResponse, workspaceDir: string, subPath: string, authority?: ActivationsRouteAuthority): Promise<void>;
3
8
  export declare function disposeActivationsModels(): void;
@@ -29,9 +29,58 @@ function validateDisableRequest(body) {
29
29
  }
30
30
  return { ok: true, confirmed: true };
31
31
  }
32
+ function mutationBody(body) {
33
+ if (!isRecord(body))
34
+ throw new Error('Request body must be a JSON object');
35
+ for (const field of ['idempotencyKey', 'reasonCode']) {
36
+ if (!Object.hasOwn(body, field) || typeof body[field] !== 'string' || body[field].trim().length === 0)
37
+ throw new Error(`Missing or invalid required field: ${field}`);
38
+ }
39
+ const { idempotencyKey, reasonCode } = body;
40
+ if (typeof idempotencyKey !== 'string' || typeof reasonCode !== 'string')
41
+ throw new Error('Required mutation fields are invalid');
42
+ const result = { idempotencyKey, reasonCode };
43
+ if (Object.hasOwn(body, 'note')) {
44
+ if (typeof body.note !== 'string')
45
+ throw new Error('Field "note" must be a string');
46
+ result.note = body.note;
47
+ }
48
+ if (Object.hasOwn(body, 'confirmed')) {
49
+ if (typeof body.confirmed !== 'boolean')
50
+ throw new Error('Field "confirmed" must be boolean');
51
+ result.confirmed = body.confirmed;
52
+ }
53
+ for (const field of ['artifactId', 'artifactDigest']) {
54
+ if (Object.hasOwn(body, field)) {
55
+ if (typeof body[field] !== 'string')
56
+ throw new Error(`Field "${field}" must be a string`);
57
+ result[field] = body[field];
58
+ }
59
+ }
60
+ for (const field of ['controlVersion', 'expectedVersion']) {
61
+ if (Object.hasOwn(body, field)) {
62
+ if (typeof body[field] !== 'number' || !Number.isInteger(body[field]))
63
+ throw new Error(`Field "${field}" must be an integer`);
64
+ result[field] = body[field];
65
+ }
66
+ }
67
+ return result;
68
+ }
69
+ async function readJson(req) {
70
+ const chunks = [];
71
+ let totalSize = 0;
72
+ for await (const chunk of req) {
73
+ const buf = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
74
+ totalSize += buf.length;
75
+ if (totalSize > MAX_BODY_SIZE)
76
+ throw new Error('payload_too_large');
77
+ chunks.push(buf);
78
+ }
79
+ return JSON.parse(Buffer.concat(chunks).toString('utf-8'));
80
+ }
32
81
  // ── Route handler ────────────────────────────────────────────────────────────
33
82
  /* eslint-disable @typescript-eslint/max-params */
34
- export async function handleActivationsRoute(req, res, workspaceDir, subPath) {
83
+ export async function handleActivationsRoute(req, res, workspaceDir, subPath, authority) {
35
84
  // GET /api/v1/activations
36
85
  if (req.method === 'GET' && (subPath === '' || subPath === '/')) {
37
86
  const model = getModel(workspaceDir);
@@ -45,6 +94,98 @@ export async function handleActivationsRoute(req, res, workspaceDir, subPath) {
45
94
  }
46
95
  return;
47
96
  }
97
+ const mutationMatch = /^\/([^/]+)\/(continue-observing|promote|reject-after-shadow|emergency-deactivate|recover-to-shadow)$/.exec(subPath);
98
+ const pauseMatch = subPath === '/emergency-pause';
99
+ const releaseMatch = /^\/emergency-pause\/([^/]+)\/release$/.exec(subPath);
100
+ if (req.method === 'POST' && (mutationMatch || pauseMatch || releaseMatch)) {
101
+ let body;
102
+ try {
103
+ body = mutationBody(await readJson(req));
104
+ }
105
+ catch (err) {
106
+ const message = err instanceof Error ? err.message : String(err);
107
+ sendError(res, message === 'payload_too_large' ? 413 : 400, 'validation_error', message, { nextAction: 'Refresh the review and submit a valid bounded request.' });
108
+ return;
109
+ }
110
+ const [, , matchedOperation] = mutationMatch ?? [];
111
+ const operation = matchedOperation ?? (pauseMatch ? 'emergency-pause' : 'release');
112
+ const governance = operation === 'continue-observing' || operation === 'promote' || operation === 'reject-after-shadow' || operation === 'recover-to-shadow' || operation === 'release';
113
+ const actor = governance ? authority?.ownerActor : (authority?.ownerActor ?? authority?.breakGlassActor);
114
+ if (!actor) {
115
+ sendError(res, 403, 'owner_authentication_required', 'This governance decision requires the configured Owner identity.', { nextAction: 'Configure PD_OWNER_ID and PD_OWNER_CREDENTIAL_ID, then authenticate with the Console token.' });
116
+ return;
117
+ }
118
+ const input = { actor, idempotencyKey: body.idempotencyKey, reasonCode: body.reasonCode, ...(body.note === undefined ? {} : { note: body.note }) };
119
+ const model = getModel(workspaceDir);
120
+ try {
121
+ if (pauseMatch) {
122
+ sendSuccess(res, await model.pauseAllRuleCode(input));
123
+ return;
124
+ }
125
+ if (releaseMatch) {
126
+ const [, rawPauseId] = releaseMatch;
127
+ if (!rawPauseId || body.expectedVersion === undefined)
128
+ throw new Error('Pause ID and expectedVersion are required');
129
+ sendSuccess(res, await model.releaseRuleCodePause(decodeURIComponent(rawPauseId), body.expectedVersion, input));
130
+ return;
131
+ }
132
+ const [, rawId] = mutationMatch ?? [];
133
+ if (!rawId)
134
+ throw new Error('Activation ID is required');
135
+ const activationId = decodeURIComponent(rawId);
136
+ if (operation === 'continue-observing')
137
+ sendSuccess(res, await model.continueObserving(activationId, input));
138
+ else if (operation === 'reject-after-shadow' || operation === 'emergency-deactivate')
139
+ sendSuccess(res, await model.deactivateRuleCode(activationId, operation === 'reject-after-shadow' ? 'reject_after_shadow' : 'emergency_deactivate', input));
140
+ else if (operation === 'recover-to-shadow') {
141
+ if (body.controlVersion === undefined)
142
+ throw new Error('Recovery requires controlVersion');
143
+ sendSuccess(res, await model.recoverRuleCodeToShadow(activationId, body.controlVersion, input));
144
+ }
145
+ else {
146
+ if (!body.artifactId || !body.artifactDigest || body.controlVersion === undefined || body.confirmed !== true)
147
+ throw new Error('Promotion requires artifactId, artifactDigest, controlVersion, and confirmed=true');
148
+ const result = await model.promoteRuleCode(activationId, { artifactId: body.artifactId, artifactDigest: body.artifactDigest, controlVersion: body.controlVersion, confirmed: body.confirmed }, input);
149
+ if (!result.ok) {
150
+ sendError(res, 409, result.reasonCode, result.summary, { failedChecks: result.failedChecks, nextAction: result.nextAction });
151
+ return;
152
+ }
153
+ sendSuccess(res, result);
154
+ }
155
+ }
156
+ catch (err) {
157
+ const message = err instanceof Error ? err.message : String(err);
158
+ sendError(res, message.includes('expected version') || message.includes('requires exactly one') ? 409 : 500, 'owner_decision_failed', message, { nextAction: 'Refresh the Owner review; no partial decision was applied.' });
159
+ }
160
+ return;
161
+ }
162
+ const ownerReviewMatch = /^\/([^/]+)\/owner-review$/.exec(subPath);
163
+ if (req.method === 'GET' && ownerReviewMatch) {
164
+ const [, rawId] = ownerReviewMatch;
165
+ if (!rawId) {
166
+ sendError(res, 400, 'invalid_id', 'Activation ID is missing');
167
+ return;
168
+ }
169
+ let activationId;
170
+ try {
171
+ activationId = decodeURIComponent(rawId);
172
+ }
173
+ catch {
174
+ sendError(res, 400, 'invalid_id', 'Activation ID contains invalid URI encoding');
175
+ return;
176
+ }
177
+ try {
178
+ sendSuccess(res, await getModel(workspaceDir).getOwnerReview(activationId, authority?.ownerActor !== null && authority?.ownerActor !== undefined));
179
+ }
180
+ catch (err) {
181
+ const message = err instanceof Error ? err.message : String(err);
182
+ if (message.includes('requires exactly one') || message.includes('not found'))
183
+ sendNotFound(res, message);
184
+ else
185
+ sendError(res, 500, 'owner_review_error', message, { nextAction: 'Keep the rule in shadow and inspect workspace integrity.' });
186
+ }
187
+ return;
188
+ }
48
189
  // POST /api/v1/activations/:id/disable
49
190
  const disableExec = /^\/([^/]+)\/disable$/.exec(subPath);
50
191
  if (req.method === 'POST' && disableExec) {
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Receipts API routes — PRI-533.
3
+ * GET /api/v1/receipts/counts → per-principle receipt counts
4
+ * GET /api/v1/receipts/principles/:principleId → per-principle receipt history
5
+ */
6
+ import type { IncomingMessage, ServerResponse } from 'node:http';
7
+ export declare function handleReceiptsRoute(req: IncomingMessage, res: ServerResponse, workspaceDir: string, subPath: string): Promise<void>;
8
+ export declare function disposeReceiptsModels(): void;
@@ -0,0 +1,63 @@
1
+ import { ReceiptsConsoleModel } from '../models/ReceiptsConsoleModel.js';
2
+ import { sendSuccess, sendError, sendNotFound } from '../utils/response.js';
3
+ const models = new Map();
4
+ function getModel(workspaceDir) {
5
+ let model = models.get(workspaceDir);
6
+ if (!model) {
7
+ model = new ReceiptsConsoleModel(workspaceDir);
8
+ models.set(workspaceDir, model);
9
+ }
10
+ return model;
11
+ }
12
+ /* eslint-disable @typescript-eslint/max-params */
13
+ export async function handleReceiptsRoute(req, res, workspaceDir, subPath) {
14
+ if (req.method !== 'GET') {
15
+ sendError(res, 405, 'method_not_allowed', 'Only GET is supported on /api/v1/receipts');
16
+ return;
17
+ }
18
+ if (subPath === '/counts' || subPath === '/counts/') {
19
+ const model = getModel(workspaceDir);
20
+ try {
21
+ const result = await model.getReceiptCounts();
22
+ sendSuccess(res, result);
23
+ }
24
+ catch (err) {
25
+ const message = err instanceof Error ? err.message : String(err);
26
+ sendError(res, 500, 'receipts_counts_error', message);
27
+ }
28
+ return;
29
+ }
30
+ // No regex here: ESLint's prefer-regexp-exec demands .exec(), while the
31
+ // Mimosa write gate flags `.exec(`-shaped calls — plain slicing satisfies both.
32
+ if (subPath.startsWith('/principles/')) {
33
+ const rawId = subPath.slice('/principles/'.length);
34
+ if (rawId.length === 0 || rawId.includes('/')) {
35
+ sendNotFound(res, `Route /api/v1/receipts${subPath} not found`);
36
+ return;
37
+ }
38
+ let principleId;
39
+ try {
40
+ principleId = decodeURIComponent(rawId);
41
+ }
42
+ catch {
43
+ sendError(res, 400, 'invalid_id', 'Principle ID contains invalid URI encoding');
44
+ return;
45
+ }
46
+ const model = getModel(workspaceDir);
47
+ try {
48
+ const result = await model.getPrincipleReceipts(principleId);
49
+ sendSuccess(res, result);
50
+ }
51
+ catch (err) {
52
+ const message = err instanceof Error ? err.message : String(err);
53
+ sendError(res, 500, 'receipts_error', message);
54
+ }
55
+ return;
56
+ }
57
+ sendNotFound(res, `Route /api/v1/receipts${subPath} not found`);
58
+ }
59
+ export function disposeReceiptsModels() {
60
+ // Models hold no persistent resources (request-scoped readonly connections
61
+ // are closed in finally blocks) — clearing the per-workspace map suffices.
62
+ models.clear();
63
+ }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect, useCallback } from "react";
3
- import { HashRouter, Routes, Route, Navigate, useNavigate } from "react-router-dom";
3
+ import { HashRouter, Routes, Route, Navigate, useLocation, useNavigate } from "react-router-dom";
4
4
  import { I18nextProvider } from "react-i18next";
5
5
  import i18n from "./i18n/index.js";
6
6
  import { ThemeProvider } from "./components/theme-provider.js";
@@ -33,7 +33,7 @@ import { getOnboardingState } from "./utils/onboarding-state.js";
33
33
  const IS_DEV = import.meta.env.DEV;
34
34
  function AuthRoutes() {
35
35
  const [authed, setAuthed] = useState(null);
36
- const [showSplash, setShowSplash] = useState(true);
36
+ const [showSplash, setShowSplash] = useState(() => window.location.hash.startsWith("#/splash"));
37
37
  // Onboarding redirect needs workspaceId (for onboarding-state lookup) and
38
38
  // feature flags (to gate the redirect). Both are fetched after auth.
39
39
  const [currentWorkspaceId, setCurrentWorkspaceId] = useState("default");
@@ -42,6 +42,7 @@ function AuthRoutes() {
42
42
  // the onboarding redirect so state isn't saved under a fake "default" key.
43
43
  const [workspaceReady, setWorkspaceReady] = useState(false);
44
44
  const navigate = useNavigate();
45
+ const location = useLocation();
45
46
  const verifyAuth = useCallback(async () => {
46
47
  const valid = await checkAuth();
47
48
  setAuthed(valid);
@@ -110,6 +111,8 @@ function AuthRoutes() {
110
111
  useEffect(() => {
111
112
  if (authed !== true || featureFlags === null || showSplash)
112
113
  return;
114
+ if (!['/', '/login', '/splash'].includes(location.pathname))
115
+ return;
113
116
  const flagEnabled = featureFlags?.new_user_onboarding?.enabled === true;
114
117
  // P2-C: if workspace fetch failed, don't force onboarding under "default" key.
115
118
  if (flagEnabled && workspaceReady) {
@@ -119,7 +122,7 @@ function AuthRoutes() {
119
122
  else {
120
123
  navigate('/focus', { replace: true });
121
124
  }
122
- }, [authed, featureFlags, showSplash, currentWorkspaceId, workspaceReady, navigate]);
125
+ }, [authed, featureFlags, showSplash, currentWorkspaceId, workspaceReady, location.pathname, navigate]);
123
126
  const handleAuthSuccess = useCallback(() => {
124
127
  setAuthed(true);
125
128
  // Post-auth redirect is handled by the authed + featureFlags effect above,
@@ -1,7 +1,7 @@
1
1
  import type { ApiResponse } from "../types.js";
2
2
  import { listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm } from "./utils/signal-keywords-api.js";
3
3
  import type { OwnerGovernanceView } from '@principles/core/runtime-v2';
4
- import type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, TrajectoryData, IntentSummaryData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, IntentInitResultData, IntentSaveResultData, IntentRawContentData, IntentVersionData } from "./utils/validators.js";
4
+ import type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, TrajectoryData, IntentSummaryData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, IntentInitResultData, IntentSaveResultData, IntentRawContentData, IntentVersionData, RuleCodeOwnerReviewData, RuleCodeMutationData } from "./utils/validators.js";
5
5
  declare function getToken(): string | null;
6
6
  declare function setToken(token: string): void;
7
7
  declare function clearToken(): void;
@@ -30,6 +30,39 @@ declare function fetchPrincipleGovernance(principleId: string): Promise<ApiRespo
30
30
  declare function archivePrinciple(principleId: string): Promise<ApiResponse<unknown>>;
31
31
  declare function unarchivePrinciple(principleId: string): Promise<ApiResponse<unknown>>;
32
32
  declare function fetchPrincipleTrajectory(principleId: string): Promise<ApiResponse<TrajectoryData>>;
33
+ export interface ReceiptEventData {
34
+ kind: "rule_blocked" | "auto_correct_applied" | "self_reported" | "prompt_injected";
35
+ level: "effect" | "presence";
36
+ sessionId: string | null;
37
+ toolName: string | null;
38
+ filePath: string | null;
39
+ digest: string | null;
40
+ createdAt: string;
41
+ }
42
+ export interface PrincipleReceiptsData {
43
+ status: "ok" | "degraded";
44
+ reason?: string;
45
+ nextAction?: string;
46
+ principleId: string;
47
+ effectCount: number;
48
+ presenceCount: number;
49
+ lastEffectAt: string | null;
50
+ events: ReceiptEventData[];
51
+ }
52
+ export interface ReceiptCountEntryData {
53
+ principleId: string;
54
+ effectCount: number;
55
+ presenceCount: number;
56
+ lastEffectAt: string | null;
57
+ }
58
+ export interface ReceiptCountsData {
59
+ status: "ok" | "degraded";
60
+ reason?: string;
61
+ nextAction?: string;
62
+ counts: ReceiptCountEntryData[];
63
+ }
64
+ declare function fetchPrincipleReceipts(principleId: string): Promise<ApiResponse<PrincipleReceiptsData>>;
65
+ declare function fetchReceiptCounts(): Promise<ApiResponse<ReceiptCountsData>>;
33
66
  declare function approveApproval(approvalId: string, note?: string): Promise<ApiResponse<ApprovalRecordData>>;
34
67
  declare function rejectApproval(approvalId: string, reason: string): Promise<ApiResponse<ApprovalRecordData>>;
35
68
  declare function editApproval(approvalId: string, newArtifactId: string, editReason: string): Promise<ApiResponse<ApprovalRecordData>>;
@@ -99,6 +132,10 @@ declare function fetchGovernanceQueue(): Promise<ApiResponse<GovernanceQueueData
99
132
  declare function fetchApprovalsGrouped(): Promise<ApiResponse<ApprovalsGroupedData>>;
100
133
  declare function fetchAllActivations(): Promise<ApiResponse<ActivationsData>>;
101
134
  declare function disableActivation(activationId: string): Promise<ApiResponse<DisableActivationData>>;
135
+ declare function fetchRuleCodeOwnerReview(activationId: string): Promise<ApiResponse<RuleCodeOwnerReviewData>>;
136
+ declare function ruleCodeDecision(activationId: string, action: 'continue-observing' | 'reject-after-shadow' | 'emergency-deactivate' | 'promote' | 'recover-to-shadow', body: Record<string, unknown>): Promise<ApiResponse<RuleCodeMutationData>>;
137
+ declare function pauseAllRuleCode(body: Record<string, unknown>): Promise<ApiResponse<RuleCodeMutationData>>;
138
+ declare function releaseRuleCodePause(pauseId: string, body: Record<string, unknown>): Promise<ApiResponse<RuleCodeMutationData>>;
102
139
  declare function fetchLifecycleMetrics(principleId: string): Promise<ApiResponse<LifecycleMetricsData>>;
103
140
  declare function fetchUpdateStatus(): Promise<ApiResponse<UpdateStatusData>>;
104
141
  declare function fetchUpdateHistory(): Promise<ApiResponse<UpdateHistoryData>>;
@@ -158,8 +195,8 @@ export interface FollowUpPayload {
158
195
  candidateId?: string;
159
196
  }
160
197
  declare function dispatchFollowUp(decisionId: string, payload: FollowUpPayload): Promise<ApiResponse<FollowUpResponseData>>;
161
- export { request, listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport, fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
162
- export type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, EvidenceChainRecordData, EvidenceChainStateData, TrajectoryData, TrajectoryStageData, IntentSummaryData, IntentSectionsData, IntentDocWarningData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, LinkCandidateFollowUpData, GuideRulehostFollowUpData, GeneratePatchProposalFollowUpData, IntentVersionEntry, IntentVersionData, } from "./utils/validators.js";
198
+ export { request, listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, fetchPrincipleReceipts, fetchReceiptCounts, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport, fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
199
+ export type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, RuleCodeOwnerReviewData, RuleCodeMutationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, EvidenceChainRecordData, EvidenceChainStateData, TrajectoryData, TrajectoryStageData, IntentSummaryData, IntentSectionsData, IntentDocWarningData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, LinkCandidateFollowUpData, GuideRulehostFollowUpData, GeneratePatchProposalFollowUpData, IntentVersionEntry, IntentVersionData, } from "./utils/validators.js";
163
200
  export type { SignalKeyword, PendingSignalTerm } from "./utils/signal-keywords-types.js";
164
201
  export type { ActivationRecordData as ActivationRecord } from "./utils/validators.js";
165
202
  export type { ApprovalRecordData as ApprovalRecord } from "./utils/validators.js";
@@ -1,5 +1,5 @@
1
1
  import { listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, } from "./utils/signal-keywords-api.js";
2
- import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateRollbackResult, validateApprovalRecordDirect, validatePrinciplesList, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, validateOwnerGovernanceView, } from "./utils/validators.js";
2
+ import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateRollbackResult, validateApprovalRecordDirect, validatePrinciplesList, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, validateOwnerGovernanceView, validateRuleCodeOwnerReview, validateRuleCodeMutation, } from "./utils/validators.js";
3
3
  // ── Auth ──────────────────────────────────────────────────────────────────────
4
4
  function getToken() {
5
5
  return sessionStorage.getItem("pd_token");
@@ -136,6 +136,86 @@ async function unarchivePrinciple(principleId) {
136
136
  async function fetchPrincipleTrajectory(principleId) {
137
137
  return request(`/api/principles/${encodeURIComponent(principleId)}/trajectory`, undefined, validateTrajectoryData);
138
138
  }
139
+ const RECEIPT_KINDS = new Set(["rule_blocked", "auto_correct_applied", "self_reported", "prompt_injected"]);
140
+ function validateReceiptEvent(value) {
141
+ if (typeof value !== "object" || value === null)
142
+ return null;
143
+ const rec = value;
144
+ if (typeof rec.kind !== "string" || !RECEIPT_KINDS.has(rec.kind))
145
+ return null;
146
+ if (rec.level !== "effect" && rec.level !== "presence")
147
+ return null;
148
+ if (typeof rec.createdAt !== "string")
149
+ return null;
150
+ const asStr = (v) => (typeof v === "string" && v.length > 0 ? v : null);
151
+ return {
152
+ kind: rec.kind,
153
+ level: rec.level,
154
+ sessionId: asStr(rec.sessionId),
155
+ toolName: asStr(rec.toolName),
156
+ filePath: asStr(rec.filePath),
157
+ digest: asStr(rec.digest),
158
+ createdAt: rec.createdAt,
159
+ };
160
+ }
161
+ function validatePrincipleReceipts(data) {
162
+ if (typeof data !== "object" || data === null)
163
+ return null;
164
+ const rec = data;
165
+ if (typeof rec.principleId !== "string")
166
+ return null;
167
+ const events = [];
168
+ if (Array.isArray(rec.events)) {
169
+ for (const item of rec.events) {
170
+ const event = validateReceiptEvent(item);
171
+ if (event)
172
+ events.push(event);
173
+ }
174
+ }
175
+ return {
176
+ status: rec.status === "ok" ? "ok" : "degraded",
177
+ reason: typeof rec.reason === "string" ? rec.reason : undefined,
178
+ nextAction: typeof rec.nextAction === "string" ? rec.nextAction : undefined,
179
+ principleId: rec.principleId,
180
+ effectCount: typeof rec.effectCount === "number" ? rec.effectCount : 0,
181
+ presenceCount: typeof rec.presenceCount === "number" ? rec.presenceCount : 0,
182
+ lastEffectAt: typeof rec.lastEffectAt === "string" ? rec.lastEffectAt : null,
183
+ events,
184
+ };
185
+ }
186
+ function validateReceiptCounts(data) {
187
+ if (typeof data !== "object" || data === null)
188
+ return null;
189
+ const rec = data;
190
+ const counts = [];
191
+ if (Array.isArray(rec.counts)) {
192
+ for (const item of rec.counts) {
193
+ if (typeof item !== "object" || item === null)
194
+ continue;
195
+ const entry = item;
196
+ if (typeof entry.principleId !== "string")
197
+ continue;
198
+ counts.push({
199
+ principleId: entry.principleId,
200
+ effectCount: typeof entry.effectCount === "number" ? entry.effectCount : 0,
201
+ presenceCount: typeof entry.presenceCount === "number" ? entry.presenceCount : 0,
202
+ lastEffectAt: typeof entry.lastEffectAt === "string" ? entry.lastEffectAt : null,
203
+ });
204
+ }
205
+ }
206
+ return {
207
+ status: rec.status === "ok" ? "ok" : "degraded",
208
+ reason: typeof rec.reason === "string" ? rec.reason : undefined,
209
+ nextAction: typeof rec.nextAction === "string" ? rec.nextAction : undefined,
210
+ counts,
211
+ };
212
+ }
213
+ async function fetchPrincipleReceipts(principleId) {
214
+ return request(`/api/v1/receipts/principles/${encodeURIComponent(principleId)}`, undefined, validatePrincipleReceipts);
215
+ }
216
+ async function fetchReceiptCounts() {
217
+ return request("/api/v1/receipts/counts", undefined, validateReceiptCounts);
218
+ }
139
219
  // ── Approvals ─────────────────────────────────────────────────────────────────
140
220
  async function approveApproval(approvalId, note) {
141
221
  return request('/api/v1/approvals/' + encodeURIComponent(approvalId) + '/approve', {
@@ -314,6 +394,17 @@ async function disableActivation(activationId) {
314
394
  body: JSON.stringify({ confirmed: true }),
315
395
  }, validateDisableActivation);
316
396
  }
397
+ async function fetchRuleCodeOwnerReview(activationId) {
398
+ return request(`/api/v1/activations/${encodeURIComponent(activationId)}/owner-review`, undefined, validateRuleCodeOwnerReview);
399
+ }
400
+ async function mutateRuleCode(path, body) {
401
+ return request(path, { method: 'POST', body: JSON.stringify(body) }, validateRuleCodeMutation);
402
+ }
403
+ function ruleCodeDecision(activationId, action, body) {
404
+ return mutateRuleCode(`/api/v1/activations/${encodeURIComponent(activationId)}/${action}`, body);
405
+ }
406
+ function pauseAllRuleCode(body) { return mutateRuleCode('/api/v1/activations/emergency-pause', body); }
407
+ function releaseRuleCodePause(pauseId, body) { return mutateRuleCode(`/api/v1/activations/emergency-pause/${encodeURIComponent(pauseId)}/release`, body); }
317
408
  async function fetchLifecycleMetrics(principleId) {
318
409
  return request(`/api/v1/lifecycle/principles/${encodeURIComponent(principleId)}`, undefined, validateLifecycleMetrics);
319
410
  }
@@ -405,6 +496,6 @@ export { request,
405
496
  // signal-keywords (UI stubs, see signal-keywords-api.ts)
406
497
  listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm,
407
498
  // auth
408
- getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport,
499
+ getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, fetchPrincipleReceipts, fetchReceiptCounts, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport,
409
500
  // Feedback submit ladder (Slice 3, spec §8)
410
- fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
501
+ fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };