create-principles-disciple 1.145.21 → 1.145.22

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 (113) hide show
  1. package/_release/product-identity.json +2 -2
  2. package/console/dist/server/index.js +11 -0
  3. package/console/dist/server/models/ApprovalsConsoleModel.js +10 -11
  4. package/console/dist/server/models/RuleCandidateBindingConsoleModel.d.ts +40 -0
  5. package/console/dist/server/models/RuleCandidateBindingConsoleModel.js +130 -0
  6. package/console/dist/server/models/artifact-principle-resolver.d.ts +2 -2
  7. package/console/dist/server/models/artifact-principle-resolver.js +10 -1
  8. package/console/dist/server/models/principle-id-resolution.d.ts +7 -12
  9. package/console/dist/server/models/principle-id-resolution.js +7 -94
  10. package/console/dist/server/routes/rule-candidate-bindings.d.ts +10 -0
  11. package/console/dist/server/routes/rule-candidate-bindings.js +116 -0
  12. package/console/dist/shared/rule-candidate-binding-contract.d.ts +30 -0
  13. package/console/dist/shared/rule-candidate-binding-contract.js +1 -0
  14. package/console/dist/ui/api.d.ts +9 -0
  15. package/console/dist/ui/api.js +10 -0
  16. package/console/dist/ui/i18n/en.json +25 -0
  17. package/console/dist/ui/i18n/zh-CN.json +25 -0
  18. package/console/dist/ui/pages/focus/FocusPage.js +2 -1
  19. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.d.ts +2 -0
  20. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.js +68 -0
  21. package/console/dist/ui/utils/rule-binding-validators.d.ts +8 -0
  22. package/console/dist/ui/utils/rule-binding-validators.js +46 -0
  23. package/console/dist/web/assets/app.css +6 -0
  24. package/console/dist/web/assets/app.js +30 -30
  25. package/core/dist/principle-tree-ledger.d.ts +24 -4
  26. package/core/dist/principle-tree-ledger.d.ts.map +1 -1
  27. package/core/dist/principle-tree-ledger.js +201 -24
  28. package/core/dist/principle-tree-ledger.js.map +1 -1
  29. package/core/dist/runtime-v2/activation/ledger-identity.d.ts +4 -22
  30. package/core/dist/runtime-v2/activation/ledger-identity.d.ts.map +1 -1
  31. package/core/dist/runtime-v2/activation/ledger-identity.js +54 -0
  32. package/core/dist/runtime-v2/activation/ledger-identity.js.map +1 -1
  33. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +11 -0
  34. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
  35. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +67 -1
  36. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
  37. package/core/dist/runtime-v2/index.d.ts +2 -0
  38. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  39. package/core/dist/runtime-v2/index.js +1 -0
  40. package/core/dist/runtime-v2/index.js.map +1 -1
  41. package/core/dist/runtime-v2/internalization/artificer-runner.d.ts +3 -0
  42. package/core/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
  43. package/core/dist/runtime-v2/internalization/artificer-runner.js +15 -2
  44. package/core/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
  45. package/core/dist/runtime-v2/internalization/candidate-identity.d.ts +17 -0
  46. package/core/dist/runtime-v2/internalization/candidate-identity.d.ts.map +1 -0
  47. package/core/dist/runtime-v2/internalization/candidate-identity.js +56 -0
  48. package/core/dist/runtime-v2/internalization/candidate-identity.js.map +1 -0
  49. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts +2 -0
  50. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  51. package/core/dist/runtime-v2/internalization/evaluator-runner.js +43 -2
  52. package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  53. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +4 -0
  54. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
  55. package/core/dist/runtime-v2/internalization/peer-runner-contracts.js +13 -0
  56. package/core/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
  57. package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts +2 -0
  58. package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
  59. package/core/dist/runtime-v2/internalization/pitask-metadata.js +8 -0
  60. package/core/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
  61. package/core/dist/runtime-v2/internalization/rule-binding-revision.d.ts +28 -0
  62. package/core/dist/runtime-v2/internalization/rule-binding-revision.d.ts.map +1 -0
  63. package/core/dist/runtime-v2/internalization/rule-binding-revision.js +92 -0
  64. package/core/dist/runtime-v2/internalization/rule-binding-revision.js.map +1 -0
  65. package/core/dist/runtime-v2/internalization/scribe-runner.d.ts +7 -1
  66. package/core/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
  67. package/core/dist/runtime-v2/internalization/scribe-runner.js +67 -3
  68. package/core/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
  69. package/core/dist/runtime-v2/principle-tree/ledger-codec.d.ts.map +1 -1
  70. package/core/dist/runtime-v2/principle-tree/ledger-codec.js +3 -6
  71. package/core/dist/runtime-v2/principle-tree/ledger-codec.js.map +1 -1
  72. package/core/dist/runtime-v2/principle-tree/rule-candidate-binding.d.ts +12 -0
  73. package/core/dist/runtime-v2/principle-tree/rule-candidate-binding.d.ts.map +1 -0
  74. package/core/dist/runtime-v2/principle-tree/rule-candidate-binding.js +129 -0
  75. package/core/dist/runtime-v2/principle-tree/rule-candidate-binding.js.map +1 -0
  76. package/core/dist/runtime-v2/store/artifact/sqlite-pi-artifact-store.d.ts.map +1 -1
  77. package/core/dist/runtime-v2/store/artifact/sqlite-pi-artifact-store.js +70 -6
  78. package/core/dist/runtime-v2/store/artifact/sqlite-pi-artifact-store.js.map +1 -1
  79. package/core/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
  80. package/core/dist/runtime-v2/store/sqlite-connection.js +15 -1
  81. package/core/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
  82. package/core/dist/runtime-v2/types/ledger-store.d.ts +3 -0
  83. package/core/dist/runtime-v2/types/ledger-store.d.ts.map +1 -1
  84. package/core/dist/runtime-v2/types/ledger-store.js.map +1 -1
  85. package/core/dist/runtime-v2/types/rule-candidate-binding.d.ts +98 -0
  86. package/core/dist/runtime-v2/types/rule-candidate-binding.d.ts.map +1 -0
  87. package/core/dist/runtime-v2/types/rule-candidate-binding.js +2 -0
  88. package/core/dist/runtime-v2/types/rule-candidate-binding.js.map +1 -0
  89. package/core/dist/telemetry-event.d.ts +2 -2
  90. package/core/dist/telemetry-event.d.ts.map +1 -1
  91. package/core/dist/telemetry-event.js +1 -0
  92. package/core/dist/telemetry-event.js.map +1 -1
  93. package/core/package.json +1 -1
  94. package/host-runtime/dist/internalization-consumer-cycle.js +12 -0
  95. package/host-runtime/dist/internalization-consumer-governance.js +4 -0
  96. package/host-runtime/package.json +1 -1
  97. package/package.json +1 -1
  98. package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
  99. package/pd-cli/dist/commands/runtime-activation.js +4 -0
  100. package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
  101. package/pd-cli/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
  102. package/pd-cli/dist/commands/runtime-internalization-run-once.js +10 -1
  103. package/pd-cli/dist/commands/runtime-internalization-run-once.js.map +1 -1
  104. package/pd-cli/dist/services/demo-story-a-runner.d.ts.map +1 -1
  105. package/pd-cli/dist/services/demo-story-a-runner.js +43 -2
  106. package/pd-cli/dist/services/demo-story-a-runner.js.map +1 -1
  107. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts +8 -30
  108. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  109. package/pd-cli/dist/services/rulehost-pipeline-runner.js +76 -124
  110. package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
  111. package/pd-cli/package.json +1 -1
  112. package/plugin/dist/bundle.js +468 -465
  113. package/release-manager/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "productVersion": "2.3.11",
4
- "sourceCommit": "d2e8da5d5442288872bdb9f73255b6ff4d5cbf55"
3
+ "productVersion": "2.3.12",
4
+ "sourceCommit": "973e331df2bb7b09968731e26825dfe1ee83ad48"
5
5
  }
@@ -22,6 +22,7 @@ import { handleReceiptsRoute, disposeReceiptsModels } from './routes/receipts.js
22
22
  import { handleApprovalsGroupedRoute, disposeApprovalsGroupedModels } from './routes/approvals-grouped.js';
23
23
  import { handleGovernanceRoute, handleGovernanceExperienceRoute, resolveOwnerConfigSnapshot, disposeGovernanceModels } from './routes/governance.js';
24
24
  import { handleOwnerDecisionsRoute } from './routes/owner-decisions.js';
25
+ import { handleRuleCandidateBindingsRoute } from './routes/rule-candidate-bindings.js';
25
26
  import { handleEvidenceOutcomesRoute } from './routes/evidence-outcomes.js';
26
27
  import { handleDesignGateRoute } from './routes/design-gate.js';
27
28
  import { handleOwnerDecisionInboxRoute, handleOwnerDecisionViewRoute, handleOwnerDecisionLibraryRoute, parseOwnerDecisionViewSubPath } from './routes/owner-decision.js';
@@ -485,6 +486,16 @@ function handleRequest(services) {
485
486
  asyncHandler(() => handleGovernanceRoute(req, res, services.workspaceDir))(req, res);
486
487
  return;
487
488
  }
489
+ if (urlPath === '/api/v1/governance/rule-candidate-bindings' || urlPath.startsWith('/api/v1/governance/rule-candidate-bindings/')) {
490
+ const { ownerId, credentialId } = resolveOwnerIdentity(process.env, defaultOwnerIdentityHomeDir());
491
+ asyncHandler(() => handleRuleCandidateBindingsRoute(req, res, {
492
+ workspaceDir: services.workspaceDir,
493
+ subPath: urlPath.slice('/api/v1/governance/rule-candidate-bindings'.length),
494
+ ownerIdentity: services.authConfig.isEnabled() && ownerId !== null && credentialId !== null
495
+ ? { ownerId, credentialId } : null,
496
+ }))(req, res);
497
+ return;
498
+ }
488
499
  // PRI-629: GET/POST /api/v1/governance/owner-decisions — unified Owner Inbox
489
500
  // (read projection + resolution). Identity derived server-side (SPEC §29):
490
501
  // configured owner when registered, else the authenticated console operator.
@@ -2,6 +2,7 @@ import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { SqliteConnection, SqliteApprovalQueueStore, SqliteActivationStateStore, SqlitePIArtifactStore, ActivationDispatcher, PromptWriter, DeferArchiveWriter, RuleHostWriter, createProductionGateDeps, ApprovalCompletionService, ApprovalQueue, mapConfidenceToLabel, isArtifactRevisionOf, detectPromptReplacementTarget, buildOwnerRevisionArtifact, PrincipleTreeLedgerAdapter, MVP_CHANNELS, } from '@principles/core/runtime-v2';
4
4
  import { resolveLedgerPrincipleId } from './principle-id-resolution.js';
5
+ import { createArtifactPrincipleResolutionDeps } from './artifact-principle-resolver.js';
5
6
  import { loadPdConfig, computeFlagsFromLoadResult } from '../config/pd-config-store.js';
6
7
  import { resolveWorkspaceHostToolSemantics, checkPromptArtifactDeliverabilityById, operatorDeclaredBudgetResolution, resolveLivePromptInjectionProjection, runtimeBudgetObservationToResolution, selectConsoleRuntimeBudgetFacts, } from '@principles/host-runtime';
7
8
  import { PROMPT_BUDGET_HOST_KINDS } from '@principles/core/runtime-v2';
@@ -459,17 +460,7 @@ export class ApprovalsConsoleModel {
459
460
  // ids against the ledger and otherwise walks the candidate lineage
460
461
  // (scribe → dreamer seed → candidateId → derivedFromPainIds).
461
462
  const ledger = new PrincipleTreeLedgerAdapter({ stateDir });
462
- const resolution = await resolveLedgerPrincipleId(artifact, {
463
- ledger,
464
- getArtifactById: (id) => piArtifactStore.getArtifactById(id),
465
- getTaskDiagnosticJson: (taskId) => {
466
- const row = connection
467
- .getDb()
468
- .prepare('SELECT diagnostic_json FROM tasks WHERE task_id = ?')
469
- .get(taskId);
470
- return row?.diagnostic_json ?? null;
471
- },
472
- });
463
+ const resolution = await resolveLedgerPrincipleId(artifact, createArtifactPrincipleResolutionDeps(connection, piArtifactStore, this.workspaceDir));
473
464
  if (resolution.status === 'unresolved') {
474
465
  return `ledger_activate_skipped: artifact ${artifactId} — ${resolution.reason}`;
475
466
  }
@@ -653,6 +644,10 @@ export class ApprovalsConsoleModel {
653
644
  // warn later. The dispatcher gate now blocks both, and the existing
654
645
  // post-commit upgrade below keeps its role unchanged.
655
646
  ledgerIdentity: {
647
+ getCandidateKind: (candidateId) => {
648
+ const row = connection.getDb().prepare('SELECT recommendation_kind FROM principle_candidates WHERE candidate_id = ?').get(candidateId);
649
+ return row !== null && typeof row === 'object' && Object.hasOwn(row, 'recommendation_kind') ? Reflect.get(row, 'recommendation_kind') : undefined;
650
+ },
656
651
  ledger: new PrincipleTreeLedgerAdapter({ stateDir: `${this.workspaceDir}/.state` }),
657
652
  getArtifactById: artifactReadModel.getArtifactById,
658
653
  getTaskDiagnosticJson: (taskId) => {
@@ -939,6 +934,10 @@ export class ApprovalsConsoleModel {
939
934
  writers: [new PromptWriter()],
940
935
  approvalQueueStore: queueStore,
941
936
  ledgerIdentity: {
937
+ getCandidateKind: (candidateId) => {
938
+ const row = connection.getDb().prepare('SELECT recommendation_kind FROM principle_candidates WHERE candidate_id = ?').get(candidateId);
939
+ return row !== null && typeof row === 'object' && Object.hasOwn(row, 'recommendation_kind') ? Reflect.get(row, 'recommendation_kind') : undefined;
940
+ },
942
941
  ledger: new PrincipleTreeLedgerAdapter({ stateDir: `${this.workspaceDir}/.state` }),
943
942
  getArtifactById: async (id) => {
944
943
  const rec = await artifactStore.getArtifactById(id);
@@ -0,0 +1,40 @@
1
+ import { reviseRuleBindingOnce } from '@principles/core/runtime-v2';
2
+ import type { RuleCandidateBindingRequest, RuleCandidateBindingMutationResult } from '@principles/core/runtime-v2';
3
+ import type { RuleBindingReviewView, RuleBindingQueueView } from '../../shared/rule-candidate-binding-contract.js';
4
+ import { ActivationsConsoleModel } from './ActivationsConsoleModel.js';
5
+ /** Derived review only. Binding authority stays inside the locked core Ledger writer. */
6
+ export declare class RuleCandidateBindingConsoleModel {
7
+ private readonly workspaceDir;
8
+ constructor(workspaceDir: string);
9
+ private ledger;
10
+ list(): Promise<RuleBindingQueueView>;
11
+ review(candidateId: string, search: string): Promise<{
12
+ ok: true;
13
+ review: RuleBindingReviewView;
14
+ } | {
15
+ ok: false;
16
+ reason: string;
17
+ nextAction: string;
18
+ }>;
19
+ mutate(candidateId: string, request: RuleCandidateBindingRequest, actorId: string): Promise<RuleCandidateBindingMutationResult & {
20
+ revision?: Awaited<ReturnType<typeof reviseRuleBindingOnce>>;
21
+ operations?: Awaited<ReturnType<ActivationsConsoleModel['getActivations']>>;
22
+ operationsScope?: string;
23
+ stopPath?: string;
24
+ nextAction?: string;
25
+ }>;
26
+ revise(candidateId: string, input: {
27
+ expectedBindingRevision: number;
28
+ expectedScribeTaskId: string;
29
+ expectedTaskRevisionEpoch: number;
30
+ requestId: string;
31
+ }): Promise<{
32
+ status: 'refused';
33
+ reason: string;
34
+ nextAction: string;
35
+ } | {
36
+ status: 'revision_started' | 'revision_already_started';
37
+ scribeTaskId: string;
38
+ taskRevisionEpoch: number;
39
+ }>;
40
+ }
@@ -0,0 +1,130 @@
1
+ import path from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import { createRuntimeStateHandle, PrincipleTreeLedgerAdapter, reviseRuleBindingOnce, } from '@principles/core/runtime-v2';
4
+ import { ActivationsConsoleModel } from './ActivationsConsoleModel.js';
5
+ function record(value) {
6
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
7
+ }
8
+ function stringField(value, key) {
9
+ return record(value) && typeof value[key] === 'string' ? value[key] : '';
10
+ }
11
+ /** Derived review only. Binding authority stays inside the locked core Ledger writer. */
12
+ export class RuleCandidateBindingConsoleModel {
13
+ workspaceDir;
14
+ constructor(workspaceDir) {
15
+ this.workspaceDir = workspaceDir;
16
+ }
17
+ ledger(stateManager) {
18
+ return new PrincipleTreeLedgerAdapter({
19
+ stateDir: path.join(this.workspaceDir, '.state'),
20
+ reviewSources: {
21
+ loadCandidateMaterial: async (candidateId) => {
22
+ const candidate = await stateManager.getCandidate(candidateId);
23
+ if (!candidate)
24
+ return null;
25
+ const [task, run, artifact] = await Promise.all([
26
+ stateManager.getTask(candidate.taskId), stateManager.getRun(candidate.sourceRunId),
27
+ stateManager.getArtifact(candidate.artifactId),
28
+ ]);
29
+ const parentTask = task?.taskKind === 'diag_router' && task.inputRef ? await stateManager.getTask(task.inputRef) : null;
30
+ return { candidate, task, run, artifact, parentTask };
31
+ },
32
+ },
33
+ });
34
+ }
35
+ async list() {
36
+ // A fresh workspace with no state.db simply has no binding queue yet;
37
+ // any other storage problem still surfaces as an error (503 at the route).
38
+ if (!existsSync(path.join(this.workspaceDir, '.pd', 'state.db')))
39
+ return { items: [] };
40
+ const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir, readonly: true, bootstrapIfMissing: false });
41
+ try {
42
+ const ledger = this.ledger(handle.stateManager);
43
+ // Split diagnoses commit their rule candidates on diag_router sub-tasks;
44
+ // the queue must surface both provenance shapes, not only direct
45
+ // diagnostician commits.
46
+ const tasks = [
47
+ ...await handle.stateManager.listTasks({ taskKind: 'diagnostician' }),
48
+ ...await handle.stateManager.listTasks({ taskKind: 'diag_router' }),
49
+ ];
50
+ const items = [];
51
+ for (const task of tasks) {
52
+ for (const candidate of await handle.stateManager.getCandidatesByTaskId(task.taskId)) {
53
+ if (candidate.rawRecommendationKind !== 'rule')
54
+ continue;
55
+ const identity = ledger.resolveCandidatePrincipleIdentity(candidate.candidateId, 'rule');
56
+ items.push({ candidateId: candidate.candidateId, text: candidate.description || candidate.title, reason: identity.status === 'unresolved' ? identity.reason : 'bound' });
57
+ }
58
+ }
59
+ return { items };
60
+ }
61
+ finally {
62
+ await handle.close();
63
+ }
64
+ }
65
+ async review(candidateId, search) {
66
+ const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir, readonly: true, bootstrapIfMissing: false });
67
+ try {
68
+ const result = await this.ledger(handle.stateManager).createRuleCandidateBindingReview(candidateId, { search, limit: 20 });
69
+ if (!result.ok)
70
+ return result;
71
+ const { candidateMaterial: material } = result;
72
+ return { ok: true, review: {
73
+ candidateId, candidateText: stringField(material.candidate, 'description') || stringField(material.candidate, 'title'),
74
+ sourcePainId: stringField(Reflect.get(material, 'parentTask'), 'inputRef') || stringField(material.task, 'inputRef'),
75
+ evidence: stringField(material.artifact, 'contentJson') || stringField(material.candidate, 'sourceRecommendationJson'),
76
+ candidateDigest: result.reviewedCandidateDigest,
77
+ currentRevision: result.currentRevision,
78
+ currentBinding: result.currentBinding,
79
+ eligibleTargets: result.eligibleTargets.map(({ principle, reviewedPrincipleDigest }) => ({
80
+ principleId: principle.id, text: principle.text, status: principle.status, version: principle.version, digest: reviewedPrincipleDigest,
81
+ })),
82
+ resumeNotice: '关联后自动启动一次现有配置的正文修订与代码生成、验证;沿用现有调用预算和超时。失败会保留关联与旧工件,不会自动循环,也不会批准或启用规则。',
83
+ } };
84
+ }
85
+ finally {
86
+ await handle.close();
87
+ }
88
+ }
89
+ async mutate(candidateId, request, actorId) {
90
+ const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir });
91
+ try {
92
+ const ledger = this.ledger(handle.stateManager);
93
+ const result = await ledger.mutateRuleCandidateBinding({ candidateId, request, owner: { actorId, source: 'authenticated_console' } });
94
+ if (!result.ok)
95
+ return result;
96
+ if (request.action === 'bind') {
97
+ // Replays also resume the same cause: a crash after Ledger commit must not require another consent.
98
+ try {
99
+ const revision = await reviseRuleBindingOnce({ stateManager: handle.stateManager, ledger: { resolveForCandidate: async (id) => ledger.resolveCandidatePrincipleIdentity(id, 'rule') }, candidateId, expectedBindingRevision: result.binding.revision });
100
+ return { ...result, revision };
101
+ }
102
+ catch {
103
+ return { ...result, revision: { status: 'refused', reason: 'revision_source_unavailable', nextAction: '关联已保存。重试相同请求以恢复同一次修订,或使用恢复入口;不要重新关联。' } };
104
+ }
105
+ }
106
+ // Revoke is independent of present target eligibility and never generates or deactivates anything.
107
+ let operations;
108
+ try {
109
+ operations = await new ActivationsConsoleModel(this.workspaceDir).getActivations();
110
+ }
111
+ catch {
112
+ operations = { status: 'degraded', activations: [], reason: 'activation_source_unavailable', nextAction: '运行记录暂不可读,请重新查看规则运行列表;不能据此认定没有运行中的规则。' };
113
+ }
114
+ return { ...result, operations, operationsScope: 'workspace', stopPath: '/activation', nextAction: '归属已撤回。已运行规则须在规则运行列表另行停用;撤回前已通过最终检查的在途操作仍可能完成。' };
115
+ }
116
+ finally {
117
+ await handle.close();
118
+ }
119
+ }
120
+ async revise(candidateId, input) {
121
+ const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir });
122
+ try {
123
+ const ledger = this.ledger(handle.stateManager);
124
+ return await reviseRuleBindingOnce({ stateManager: handle.stateManager, ledger: { resolveForCandidate: async (id) => ledger.resolveCandidatePrincipleIdentity(id, 'rule') }, candidateId, ...input });
125
+ }
126
+ finally {
127
+ await handle.close();
128
+ }
129
+ }
130
+ }
@@ -1,7 +1,7 @@
1
- import { PrincipleTreeLedgerAdapter } from '@principles/core/runtime-v2';
2
1
  import type { PIArtifactRecord, SqliteConnection } from '@principles/core/runtime-v2';
2
+ import { type LedgerResolutionDeps } from './principle-id-resolution.js';
3
3
  export interface ArtifactPrincipleResolutionDeps {
4
- readonly ledger: PrincipleTreeLedgerAdapter;
4
+ readonly ledger: LedgerResolutionDeps['ledger'];
5
5
  readonly getArtifactById: (artifactId: string) => Promise<PIArtifactRecord | null>;
6
6
  /** Read one column from the tasks table (same workspace as the caller). */
7
7
  readonly getTaskDiagnosticJson: (taskId: string) => string | null;
@@ -20,8 +20,17 @@ function isRecord(value) {
20
20
  * per model call, not per artifact.
21
21
  */
22
22
  export function createArtifactPrincipleResolutionDeps(conn, artifactStore, workspaceDir) {
23
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
23
24
  return {
24
- ledger: new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') }),
25
+ ledger: {
26
+ isAvailable: () => adapter.isAvailable(),
27
+ hasPrinciple: (id) => adapter.hasPrinciple(id),
28
+ listForCandidate: (id) => adapter.listForCandidate(id),
29
+ resolveForCandidate: async (id) => {
30
+ const row = conn.getDb().prepare('SELECT recommendation_kind FROM principle_candidates WHERE candidate_id = ?').get(id);
31
+ return adapter.resolveCandidatePrincipleIdentity(id, isRecord(row) ? row.recommendation_kind : undefined);
32
+ },
33
+ },
25
34
  getArtifactById: (artifactId) => artifactStore.getArtifactById(artifactId),
26
35
  getTaskDiagnosticJson: (taskId) => {
27
36
  const row = conn
@@ -1,10 +1,10 @@
1
- import type { PIArtifactRecord, PIArtifactSnapshot } from '@principles/core/runtime-v2';
1
+ import type { CandidatePrincipleIdentityResolution, PIArtifactRecord, PIArtifactSnapshot } from '@principles/core/runtime-v2';
2
2
  import type { PrincipleTreeLedgerAdapter } from '@principles/core/runtime-v2';
3
3
  /**
4
4
  * PRI-768 v5 follow-up (audit findings F3/F4): resolve the LEDGER principle id
5
5
  * behind an approval artifact.
6
6
  *
7
- * F4: `extractPrincipleId`'s last fallback returns `principleDraft.title`, and
7
+ * F4: `resolveLedgerActivationId`'s last fallback returns `principleDraft.title`, and
8
8
  * `ledger.activatePrinciple(<title>)` then always fails ("Cannot update missing
9
9
  * principle") because ledger keys are UUIDs, never titles. A candidate id must
10
10
  * be validated against the ledger before it is used as an update key.
@@ -32,19 +32,14 @@ export type LedgerPrincipleResolution = {
32
32
  reason: string;
33
33
  };
34
34
  export interface LedgerResolutionDeps {
35
- ledger: PrincipleTreeLedgerAdapter;
35
+ ledger: Pick<PrincipleTreeLedgerAdapter, 'isAvailable' | 'hasPrinciple' | 'listForCandidate'> & {
36
+ resolveForCandidate?: (candidateId: string) => Promise<CandidatePrincipleIdentityResolution>;
37
+ };
36
38
  getArtifactById: (artifactId: string) => Promise<PIArtifactRecord | null>;
37
39
  /** Read one column from the tasks table; null when the row is missing. */
38
40
  getTaskDiagnosticJson: (taskId: string) => string | null;
39
41
  }
40
42
  export declare function toResolutionSnapshot(record: PIArtifactRecord): PIArtifactSnapshot;
41
- /** Candidate id embedded in a dreamer task id ("dreamer-<uuid>-<channel>"). */
42
- export declare function candidateIdFromDreamerTaskId(taskId: string | null | undefined): string | null;
43
- /**
44
- * Resolve the ledger principle id for an approval artifact. Direct hits
45
- * (sourcePrincipleId column / contentJson ids) are validated against the
46
- * ledger before being trusted; otherwise the candidate lineage is walked and
47
- * the ledger must resolve that candidate to EXACTLY ONE principle (multiple
48
- * matches stay unresolved — ambiguity must not be silently collapsed).
49
- */
43
+ export { candidateIdFromDreamerTaskId } from '@principles/core/runtime-v2';
44
+ /** All Console read models use the activation boundary's fail-closed identity semantics. */
50
45
  export declare function resolveLedgerPrincipleId(artifact: PIArtifactRecord, deps: LedgerResolutionDeps): Promise<LedgerPrincipleResolution>;
@@ -1,4 +1,4 @@
1
- import { extractPrincipleId } from '@principles/core/runtime-v2';
1
+ import { resolveLedgerActivationId } from '@principles/core/runtime-v2';
2
2
  export function toResolutionSnapshot(record) {
3
3
  return {
4
4
  artifactId: record.artifactId,
@@ -13,98 +13,11 @@ export function toResolutionSnapshot(record) {
13
13
  updatedAt: record.updatedAt,
14
14
  };
15
15
  }
16
- const DREAMER_TASK_PREFIX = 'dreamer-';
17
- /** Candidate id embedded in a dreamer task id ("dreamer-<uuid>-<channel>"). */
18
- export function candidateIdFromDreamerTaskId(taskId) {
19
- if (!taskId || !taskId.startsWith(DREAMER_TASK_PREFIX))
20
- return null;
21
- const withoutPrefix = taskId.slice(DREAMER_TASK_PREFIX.length);
22
- const lastDash = withoutPrefix.lastIndexOf('-');
23
- if (lastDash <= 0)
24
- return null;
25
- const candidateId = withoutPrefix.slice(0, lastDash);
26
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(candidateId)
27
- ? candidateId
28
- : null;
29
- }
30
- function isRecord(value) {
31
- return value !== null && typeof value === 'object' && !Array.isArray(value);
32
- }
33
- function readDreamerArtifactId(artifact) {
34
- try {
35
- const parsed = JSON.parse(artifact.contentJson);
36
- if (isRecord(parsed) && Object.hasOwn(parsed, 'sourceTrace')) {
37
- const trace = parsed.sourceTrace;
38
- if (isRecord(trace) && Object.hasOwn(trace, 'dreamerArtifactId')) {
39
- const id = trace.dreamerArtifactId;
40
- if (typeof id === 'string' && id.trim() !== '')
41
- return id;
42
- }
43
- }
44
- }
45
- catch {
46
- // contentJson is untrusted; lineage fallback below still applies.
47
- }
48
- const lineage = artifact.lineageArtifactIds ?? [];
49
- const dreamer = lineage.find((id) => typeof id === 'string' && id.includes('-dreamer-'));
50
- return dreamer ?? null;
51
- }
52
- /**
53
- * Resolve the ledger principle id for an approval artifact. Direct hits
54
- * (sourcePrincipleId column / contentJson ids) are validated against the
55
- * ledger before being trusted; otherwise the candidate lineage is walked and
56
- * the ledger must resolve that candidate to EXACTLY ONE principle (multiple
57
- * matches stay unresolved — ambiguity must not be silently collapsed).
58
- */
16
+ export { candidateIdFromDreamerTaskId } from '@principles/core/runtime-v2';
17
+ /** All Console read models use the activation boundary's fail-closed identity semantics. */
59
18
  export async function resolveLedgerPrincipleId(artifact, deps) {
60
- const direct = extractPrincipleId(toResolutionSnapshot(artifact));
61
- if (direct && deps.ledger.hasPrinciple(direct)) {
62
- return { status: 'resolved', principleId: direct, how: 'direct' };
63
- }
64
- const dreamerArtifactId = readDreamerArtifactId(artifact);
65
- if (!dreamerArtifactId) {
66
- return {
67
- status: 'unresolved',
68
- reason: `no ledger principle for "${direct ?? 'no id'}" and artifact has no dreamer lineage`,
69
- };
70
- }
71
- const dreamerArtifact = await deps.getArtifactById(dreamerArtifactId);
72
- if (!dreamerArtifact) {
73
- return { status: 'unresolved', reason: `dreamer artifact ${dreamerArtifactId} not found` };
74
- }
75
- // The dreamer TASK seed is the authoritative carrier of the candidate id;
76
- // the task-id spelling is only a fallback.
77
- let candidateId = candidateIdFromDreamerTaskId(dreamerArtifact.sourceTaskId);
78
- if (dreamerArtifact.sourceTaskId) {
79
- const diagnosticJson = deps.getTaskDiagnosticJson(dreamerArtifact.sourceTaskId);
80
- if (diagnosticJson) {
81
- try {
82
- const parsed = JSON.parse(diagnosticJson);
83
- if (isRecord(parsed) && Object.hasOwn(parsed, 'candidateId')) {
84
- const value = parsed.candidateId;
85
- if (typeof value === 'string' && value.trim() !== '')
86
- candidateId = value.trim();
87
- }
88
- }
89
- catch {
90
- // Malformed seed JSON — fall back to the task-id derivation above.
91
- }
92
- }
93
- }
94
- if (!candidateId) {
95
- return { status: 'unresolved', reason: 'dreamer lineage carries no candidate id' };
96
- }
97
- const matches = deps.ledger.listForCandidate(candidateId);
98
- if (matches.length === 1) {
99
- const [entry] = matches;
100
- if (entry)
101
- return { status: 'resolved', principleId: entry.id, how: 'candidate_lineage' };
102
- }
103
- if (matches.length === 0) {
104
- return { status: 'unresolved', reason: `ledger has no principle derived from candidate ${candidateId}` };
105
- }
106
- return {
107
- status: 'unresolved',
108
- reason: `candidate ${candidateId} maps to ${matches.length} ledger principles; refusing to guess`,
109
- };
19
+ const result = await resolveLedgerActivationId(toResolutionSnapshot(artifact), deps);
20
+ return result.status === 'resolved'
21
+ ? { status: 'resolved', principleId: result.principleId, how: result.how === 'direct_validated' ? 'direct' : 'candidate_lineage' }
22
+ : result;
110
23
  }
@@ -0,0 +1,10 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export interface RuleCandidateBindingRouteContext {
3
+ workspaceDir: string;
4
+ subPath: string;
5
+ ownerIdentity: {
6
+ ownerId: string;
7
+ credentialId: string;
8
+ } | null;
9
+ }
10
+ export declare function handleRuleCandidateBindingsRoute(req: IncomingMessage, res: ServerResponse, ctx: RuleCandidateBindingRouteContext): Promise<void>;
@@ -0,0 +1,116 @@
1
+ import { RuleCandidateBindingConsoleModel } from '../models/RuleCandidateBindingConsoleModel.js';
2
+ import { readBody } from '../utils/request.js';
3
+ import { sendSuccess, sendError, sendBadRequest, sendMethodNotAllowed } from '../utils/response.js';
4
+ function closed(value, keys) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
6
+ && Object.keys(value).every(key => keys.includes(key));
7
+ }
8
+ function text(value, limit) {
9
+ return typeof value === 'string' && value.trim().length > 0 && value.length <= limit;
10
+ }
11
+ function revision(value) {
12
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
13
+ }
14
+ function digest(value) { return typeof value === 'string' && /^sha256:[a-f0-9]{64}$/.test(value); }
15
+ function bindingBody(raw) {
16
+ const common = ['action', 'expectedRevision', 'reason', 'requestId', 'resumeOnce'];
17
+ if (!closed(raw, [...common, 'principleId', 'expectedCandidateDigest', 'expectedPrincipleDigest'])
18
+ || !revision(raw.expectedRevision) || !text(raw.reason, 2000) || !text(raw.requestId, 200))
19
+ return null;
20
+ if (raw.action === 'bind' && raw.resumeOnce === true && text(raw.principleId, 128)
21
+ && digest(raw.expectedCandidateDigest) && digest(raw.expectedPrincipleDigest)) {
22
+ return { action: 'bind', principleId: raw.principleId, expectedRevision: raw.expectedRevision,
23
+ expectedCandidateDigest: raw.expectedCandidateDigest, expectedPrincipleDigest: raw.expectedPrincipleDigest,
24
+ reason: raw.reason, requestId: raw.requestId, resumeOnce: true };
25
+ }
26
+ if (raw.action === 'revoke' && raw.resumeOnce === false && closed(raw, common)) {
27
+ return { action: 'revoke', expectedRevision: raw.expectedRevision, reason: raw.reason, requestId: raw.requestId, resumeOnce: false };
28
+ }
29
+ return null;
30
+ }
31
+ function failure(res, result) {
32
+ const status = result.reason.includes('unavailable') || result.reason.includes('corrupt') ? 503
33
+ : result.reason === 'candidate_missing' ? 404 : 409;
34
+ // The UI surfaces `message` first (api.ts request()); carrying the reason in
35
+ // it keeps the specific cause — stale revision, digest mismatch, ineligible
36
+ // target — visible to the Owner instead of a generic refusal.
37
+ sendError(res, status, result.reason, `当前材料无法完成此操作(${result.reason})。`, { reason: result.reason, nextAction: result.nextAction });
38
+ }
39
+ export async function handleRuleCandidateBindingsRoute(req, res, ctx) {
40
+ // Auth precedes model construction and body parsing: a refused caller cannot create state.
41
+ if (!ctx.ownerIdentity) {
42
+ sendError(res, 403, 'owner_authentication_required', '请先完成控制台认证并配置 Owner 身份。', { nextAction: '在设置中确认 Owner 身份后重试。' });
43
+ return;
44
+ }
45
+ const model = new RuleCandidateBindingConsoleModel(ctx.workspaceDir);
46
+ const match = /^\/([^/]+)(?:\/(review|revise-once))?$/.exec(ctx.subPath);
47
+ const url = new URL(req.url ?? '/', 'http://localhost');
48
+ try {
49
+ if ((ctx.subPath === '' || ctx.subPath === '/') && req.method === 'GET') {
50
+ sendSuccess(res, await model.list());
51
+ return;
52
+ }
53
+ if (!match?.[1]) {
54
+ sendError(res, 404, 'not_found', '未找到该关联入口。');
55
+ return;
56
+ }
57
+ const candidateId = decodeURIComponent(match[1]);
58
+ if (match[2] === 'review') {
59
+ if (req.method !== 'GET') {
60
+ sendMethodNotAllowed(res);
61
+ return;
62
+ }
63
+ const search = url.searchParams.get('search') ?? '';
64
+ if (search.length > 200) {
65
+ sendBadRequest(res, '搜索内容最多 200 字。');
66
+ return;
67
+ }
68
+ const result = await model.review(candidateId, search);
69
+ if (result.ok)
70
+ sendSuccess(res, result.review);
71
+ else
72
+ failure(res, result);
73
+ return;
74
+ }
75
+ if (req.method !== 'POST') {
76
+ sendMethodNotAllowed(res);
77
+ return;
78
+ }
79
+ let raw;
80
+ try {
81
+ raw = JSON.parse(await readBody(req));
82
+ }
83
+ catch {
84
+ sendBadRequest(res, '请求必须是有效的 JSON。');
85
+ return;
86
+ }
87
+ if (match[2] === 'revise-once') {
88
+ if (!closed(raw, ['expectedBindingRevision', 'expectedScribeTaskId', 'expectedTaskRevisionEpoch', 'requestId'])
89
+ || !revision(raw.expectedBindingRevision) || raw.expectedBindingRevision < 1
90
+ || !revision(raw.expectedTaskRevisionEpoch) || !text(raw.expectedScribeTaskId, 512) || !text(raw.requestId, 200)) {
91
+ sendBadRequest(res, '请提交当前关联、修订任务及版本;不接受身份或批准字段。');
92
+ return;
93
+ }
94
+ const result = await model.revise(candidateId, { expectedBindingRevision: raw.expectedBindingRevision, expectedScribeTaskId: raw.expectedScribeTaskId,
95
+ expectedTaskRevisionEpoch: raw.expectedTaskRevisionEpoch, requestId: raw.requestId });
96
+ if (result.status === 'refused')
97
+ failure(res, result);
98
+ else
99
+ sendSuccess(res, result);
100
+ return;
101
+ }
102
+ const request = bindingBody(raw);
103
+ if (!request) {
104
+ sendBadRequest(res, '关联与撤回必须使用各自完整字段;不接受身份、时间或批准字段。');
105
+ return;
106
+ }
107
+ const result = await model.mutate(candidateId, request, ctx.ownerIdentity.ownerId);
108
+ if (result.ok)
109
+ sendSuccess(res, result);
110
+ else
111
+ failure(res, result);
112
+ }
113
+ catch {
114
+ sendError(res, 503, 'binding_source_unavailable', '关联材料暂不可读。', { nextAction: '恢复当前工作区的数据来源后重新加载;不要假定操作已完成。' });
115
+ }
116
+ }
@@ -0,0 +1,30 @@
1
+ export interface RuleBindingTargetView {
2
+ principleId: string;
3
+ text: string;
4
+ status: string;
5
+ version: number;
6
+ digest: string;
7
+ }
8
+ export interface RuleBindingReviewView {
9
+ candidateId: string;
10
+ candidateText: string;
11
+ sourcePainId: string;
12
+ evidence: string;
13
+ candidateDigest: string;
14
+ currentRevision: number;
15
+ currentBinding: {
16
+ action: 'bind' | 'revoke';
17
+ principleId: string;
18
+ reason: string;
19
+ revision: number;
20
+ } | null;
21
+ eligibleTargets: RuleBindingTargetView[];
22
+ resumeNotice: string;
23
+ }
24
+ export interface RuleBindingQueueView {
25
+ items: {
26
+ candidateId: string;
27
+ text: string;
28
+ reason: string;
29
+ }[];
30
+ }
@@ -1,6 +1,8 @@
1
1
  import type { ApiResponse } from "../types.js";
2
2
  import type { OwnerDecisionsData, OwnerResolutionResultData, OwnerDecisionInboxData, OwnerDecisionLibraryData } from "./utils/validators.js";
3
3
  import type { DesignGateAction, DesignGateDecision, DesignGateDeclaration, DesignGateScope } from "@principles/core/runtime-v2/internalization/design-gate-facts";
4
+ import type { RuleBindingReviewView, RuleBindingQueueView } from '../shared/rule-candidate-binding-contract.js';
5
+ import type { RuleCandidateBindingRequest } from '@principles/core/runtime-v2';
4
6
  import type { GovernanceExperienceSnapshot, OwnerGovernanceView, OwnerDecisionViewCore } from '@principles/core/runtime-v2';
5
7
  import type { OwnerIdentityViewData, OwnerIdentityRegisterData, OwnerIdentityUnregisterData } from "./utils/validators.js";
6
8
  import type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, RecoveryResultData, FailedTaskDetailData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, UpdateRecoveryData, UpdateTransactionData, ApprovalRecordData, PrinciplesListData, CorePrinciplesData, ApprovalsGroupedData, EvidenceChainData, TrajectoryData, IntentSummaryData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, IntentInitResultData, IntentSaveResultData, IntentRawContentData, IntentVersionData, RuleCodeOwnerReviewData, RuleCodeMutationData } from "./utils/validators.js";
@@ -41,6 +43,13 @@ export declare function shouldInvalidateSessionTokenOn401(requestToken: string |
41
43
  declare function request(path: string, options?: RequestInit): Promise<ApiResponse<unknown>>;
42
44
  declare function request<T>(path: string, options: RequestInit | undefined, validate: (value: unknown) => T | null): Promise<ApiResponse<T>>;
43
45
  declare function checkAuth(): Promise<boolean>;
46
+ export declare function fetchRuleBindingQueue(): Promise<ApiResponse<RuleBindingQueueView>>;
47
+ export declare function fetchRuleBindingReview(candidateId: string, search?: string): Promise<ApiResponse<RuleBindingReviewView>>;
48
+ export declare function mutateRuleBinding(candidateId: string, body: RuleCandidateBindingRequest): Promise<ApiResponse<{
49
+ status: string;
50
+ revisionStatus?: string;
51
+ nextAction?: string;
52
+ }>>;
44
53
  declare function fetchWorkspaces(): Promise<ApiResponse<WorkspaceEntryData[]>>;
45
54
  declare function addWorkspace(name: string, path: string): Promise<ApiResponse<WorkspaceEntryData>>;
46
55
  declare function removeWorkspace(name: string): Promise<ApiResponse<RemovedEnvelopeData>>;