create-principles-disciple 1.145.35 → 1.145.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_release/product-identity.json +2 -2
- package/console/dist/server/models/ApprovalsConsoleModel.d.ts +22 -2
- package/console/dist/server/models/ApprovalsConsoleModel.js +56 -16
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.d.ts +8 -0
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +3 -2
- package/console/dist/server/models/approval-review-material.d.ts +15 -0
- package/console/dist/server/models/approval-review-material.js +32 -0
- package/console/dist/server/routes/approvals.js +32 -7
- package/console/dist/ui/api.d.ts +8 -2
- package/console/dist/ui/api.js +8 -4
- package/console/dist/ui/i18n/en.json +98 -7
- package/console/dist/ui/i18n/zh-CN.json +98 -7
- package/console/dist/ui/pages/focus/ApprovalSubjectReading.d.ts +24 -0
- package/console/dist/ui/pages/focus/ApprovalSubjectReading.js +133 -0
- package/console/dist/ui/pages/focus/FocusPage.d.ts +5 -26
- package/console/dist/ui/pages/focus/FocusPage.js +166 -459
- package/console/dist/ui/pages/focus/OwnerWorkbench.d.ts +19 -0
- package/console/dist/ui/pages/focus/OwnerWorkbench.js +75 -0
- package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.d.ts +4 -1
- package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.js +7 -3
- package/console/dist/ui/pages/focus/focus-validation.js +31 -0
- package/console/dist/ui/pages/principles/PrincipleDetailPage.js +110 -86
- package/console/dist/ui/pages/principles/PrinciplesPage.js +39 -30
- package/console/dist/ui/utils/approval-review.d.ts +30 -0
- package/console/dist/ui/utils/approval-review.js +50 -0
- package/console/dist/ui/utils/reading-layout.d.ts +1 -0
- package/console/dist/ui/utils/reading-layout.js +1 -1
- package/console/dist/ui/utils/validators.d.ts +4 -0
- package/console/dist/ui/utils/validators.js +10 -2
- package/console/dist/web/assets/app.css +119 -11
- package/console/dist/web/assets/app.js +40 -30
- package/core/dist/runtime-v2/activation/activation-types.d.ts +12 -2
- package/core/dist/runtime-v2/activation/activation-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/activation-types.js.map +1 -1
- package/core/dist/runtime-v2/activation/approval-queue.d.ts +3 -2
- package/core/dist/runtime-v2/activation/approval-queue.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/approval-queue.js +6 -4
- package/core/dist/runtime-v2/activation/approval-queue.js.map +1 -1
- package/core/dist/runtime-v2/activation/approval-review-guard.d.ts +26 -0
- package/core/dist/runtime-v2/activation/approval-review-guard.d.ts.map +1 -0
- package/core/dist/runtime-v2/activation/approval-review-guard.js +25 -0
- package/core/dist/runtime-v2/activation/approval-review-guard.js.map +1 -0
- package/core/dist/runtime-v2/activation/memory-approval-store.d.ts +6 -2
- package/core/dist/runtime-v2/activation/memory-approval-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/memory-approval-store.js +26 -2
- package/core/dist/runtime-v2/activation/memory-approval-store.js.map +1 -1
- package/core/dist/runtime-v2/activation/sqlite-approval-store.d.ts +6 -2
- package/core/dist/runtime-v2/activation/sqlite-approval-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/sqlite-approval-store.js +59 -2
- package/core/dist/runtime-v2/activation/sqlite-approval-store.js.map +1 -1
- package/core/dist/runtime-v2/diagnostician-association-shadow.d.ts +24 -0
- package/core/dist/runtime-v2/diagnostician-association-shadow.d.ts.map +1 -0
- package/core/dist/runtime-v2/diagnostician-association-shadow.js +84 -0
- package/core/dist/runtime-v2/diagnostician-association-shadow.js.map +1 -0
- package/core/dist/runtime-v2/diagnostician-association-validation.d.ts +18 -0
- package/core/dist/runtime-v2/diagnostician-association-validation.d.ts.map +1 -0
- package/core/dist/runtime-v2/diagnostician-association-validation.js +47 -0
- package/core/dist/runtime-v2/diagnostician-association-validation.js.map +1 -0
- package/core/dist/runtime-v2/diagnostician-output.d.ts +123 -0
- package/core/dist/runtime-v2/diagnostician-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/diagnostician-output.js +41 -0
- package/core/dist/runtime-v2/diagnostician-output.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +2 -0
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +1 -0
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.d.ts +12 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.js +50 -2
- package/core/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/router-context-evidence.d.ts +45 -0
- package/core/dist/runtime-v2/internalization/router-context-evidence.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/router-context-evidence.js +64 -0
- package/core/dist/runtime-v2/internalization/router-context-evidence.js.map +1 -0
- package/core/package.json +1 -1
- package/package.json +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +395 -395
- package/release-manager/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type ApprovalReviewMaterial } from './approval-review-material.js';
|
|
2
|
+
import type { ApprovalListFilter, ApprovalListResult, ApprovalDecisionResult, ApprovalRecord, ApprovalStatus, ApprovalReviewGuard } from '@principles/core/runtime-v2';
|
|
2
3
|
import type { ApprovalWithContext, ActivationDecision } from '@principles/core/runtime-v2';
|
|
3
4
|
import { type PromptInjectionTargetHost } from '@principles/host-runtime';
|
|
4
5
|
type UnsupportedChannelResult = {
|
|
@@ -49,6 +50,12 @@ export type ApproveWithActivationResult = {
|
|
|
49
50
|
} | {
|
|
50
51
|
ok: false;
|
|
51
52
|
error: 'not_found';
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
error: 'stale_review';
|
|
56
|
+
reason: string;
|
|
57
|
+
nextAction: string;
|
|
58
|
+
status?: never;
|
|
52
59
|
} | {
|
|
53
60
|
ok: false;
|
|
54
61
|
error: 'unsupported_channel';
|
|
@@ -113,6 +120,11 @@ export declare class ApprovalsConsoleModel {
|
|
|
113
120
|
listApprovals(filter?: ApprovalListFilter): Promise<ApprovalListResult>;
|
|
114
121
|
getApprovalDetail(approvalId: string): Promise<(ApprovalWithContext & {
|
|
115
122
|
isMvpProven: boolean;
|
|
123
|
+
writeGuardVersion: 'approval-review-v1';
|
|
124
|
+
review: {
|
|
125
|
+
expectedReview: ApprovalReviewGuard;
|
|
126
|
+
material: ApprovalReviewMaterial;
|
|
127
|
+
};
|
|
116
128
|
}) | null>;
|
|
117
129
|
approve(approvalId: string, decidedBy: string, options?: {
|
|
118
130
|
note?: string;
|
|
@@ -120,6 +132,7 @@ export declare class ApprovalsConsoleModel {
|
|
|
120
132
|
intentReviewed?: boolean;
|
|
121
133
|
reviewedArtifactId?: string;
|
|
122
134
|
retryActivation?: boolean;
|
|
135
|
+
expectedReview?: ApprovalReviewGuard;
|
|
123
136
|
}): Promise<ApproveWithActivationResult>;
|
|
124
137
|
/**
|
|
125
138
|
* PD_PROMPT_CAPACITY_V1 R-B2: run the shared host-runtime deliverability
|
|
@@ -175,6 +188,7 @@ export declare class ApprovalsConsoleModel {
|
|
|
175
188
|
editedBy: string;
|
|
176
189
|
newArtifactId: string;
|
|
177
190
|
editReason: string;
|
|
191
|
+
expectedReview?: ApprovalReviewGuard;
|
|
178
192
|
}): Promise<{
|
|
179
193
|
ok: true;
|
|
180
194
|
record: ApprovalRecord;
|
|
@@ -183,9 +197,15 @@ export declare class ApprovalsConsoleModel {
|
|
|
183
197
|
error: 'not_found' | 'already_decided' | 'artifact_not_found' | 'artifact_not_validated' | 'artifact_lineage_mismatch';
|
|
184
198
|
status?: ApprovalStatus;
|
|
185
199
|
reason?: string;
|
|
200
|
+
} | {
|
|
201
|
+
ok: false;
|
|
202
|
+
error: 'stale_review';
|
|
203
|
+
reason: string;
|
|
204
|
+
nextAction: string;
|
|
205
|
+
status?: never;
|
|
186
206
|
}>;
|
|
187
207
|
private dispatchActivationAfterApproval;
|
|
188
|
-
reject(approvalId: string, decidedBy: string, reason: string): Promise<ChannelGuardedDecisionResult>;
|
|
208
|
+
reject(approvalId: string, decidedBy: string, reason: string, expectedReview?: ApprovalReviewGuard): Promise<ChannelGuardedDecisionResult>;
|
|
189
209
|
/**
|
|
190
210
|
* EP002-R4 follow-up: reopen a terminal approval so the Owner can decide
|
|
191
211
|
* again in the Console. The concrete gap: an APPROVED activation was
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { Value } from '@sinclair/typebox/value';
|
|
2
|
+
import { approvalReviewMaterial } from './approval-review-material.js';
|
|
1
3
|
import * as fs from 'node:fs';
|
|
2
4
|
import * as path from 'node:path';
|
|
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';
|
|
5
|
+
import { SqliteConnection, SqliteApprovalQueueStore, SqliteActivationStateStore, SqlitePIArtifactStore, ActivationDispatcher, PromptWriter, DeferArchiveWriter, RuleHostWriter, createProductionGateDeps, ApprovalCompletionService, ApprovalQueue, mapConfidenceToLabel, isArtifactRevisionOf, detectPromptReplacementTarget, buildOwnerRevisionArtifact, PrincipleTreeLedgerAdapter, MVP_CHANNELS, computeArtifactDigest, } from '@principles/core/runtime-v2';
|
|
4
6
|
import { resolveLedgerPrincipleId } from './principle-id-resolution.js';
|
|
5
7
|
import { createArtifactPrincipleResolutionDeps } from './artifact-principle-resolver.js';
|
|
6
8
|
import { loadPdConfig, computeFlagsFromLoadResult } from '../config/pd-config-store.js';
|
|
@@ -94,17 +96,39 @@ export class ApprovalsConsoleModel {
|
|
|
94
96
|
};
|
|
95
97
|
}
|
|
96
98
|
async getApprovalDetail(approvalId) {
|
|
97
|
-
if (!stateDbExists(this.workspaceDir))
|
|
99
|
+
if (!stateDbExists(this.workspaceDir))
|
|
98
100
|
return null;
|
|
101
|
+
const { queue, connection } = this.createReadContext();
|
|
102
|
+
const db = connection.getDb();
|
|
103
|
+
db.exec('BEGIN');
|
|
104
|
+
try {
|
|
105
|
+
const record = await queue.getById(approvalId);
|
|
106
|
+
if (!record) {
|
|
107
|
+
db.exec('COMMIT');
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const artifact = await new SqlitePIArtifactStore(connection).getArtifactById(record.artifactId);
|
|
111
|
+
const common = { schemaVersion: 'approval-review-v1', approvalId, artifactId: record.artifactId, channel: record.channel };
|
|
112
|
+
const expectedReview = artifact
|
|
113
|
+
? { ...common, artifactState: 'present', artifactDigest: computeArtifactDigest(artifact) }
|
|
114
|
+
: { ...common, artifactState: 'missing' };
|
|
115
|
+
const material = approvalReviewMaterial(record.artifactId, record.channel, artifact?.contentJson ?? null);
|
|
116
|
+
db.exec('COMMIT');
|
|
117
|
+
return { ...record, confidenceLabel: mapConfidenceToLabel(record.confidence), isMvpProven: MVP_PROVEN_CHANNELS.has(record.channel),
|
|
118
|
+
writeGuardVersion: 'approval-review-v1', review: { expectedReview, material } };
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
try {
|
|
122
|
+
db.exec('ROLLBACK');
|
|
123
|
+
}
|
|
124
|
+
catch { /* Preserve the original read failure. */ }
|
|
125
|
+
if (isMissingTableError(error))
|
|
126
|
+
return null;
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
connection.close();
|
|
99
131
|
}
|
|
100
|
-
const record = await this.readSafeGetById(approvalId);
|
|
101
|
-
if (!record)
|
|
102
|
-
return null;
|
|
103
|
-
return {
|
|
104
|
-
...record,
|
|
105
|
-
confidenceLabel: mapConfidenceToLabel(record.confidence),
|
|
106
|
-
isMvpProven: MVP_PROVEN_CHANNELS.has(record.channel),
|
|
107
|
-
};
|
|
108
132
|
}
|
|
109
133
|
async approve(approvalId, decidedBy, options) {
|
|
110
134
|
const note = options?.note;
|
|
@@ -112,9 +136,12 @@ export class ApprovalsConsoleModel {
|
|
|
112
136
|
if (!stateDbExists(this.workspaceDir)) {
|
|
113
137
|
return { ok: false, error: 'not_found' };
|
|
114
138
|
}
|
|
115
|
-
const
|
|
139
|
+
const reviewed = options?.expectedReview ? await this.getApprovalDetail(approvalId) : null;
|
|
140
|
+
const existing = options?.expectedReview ? reviewed : await this.readSafeGetById(approvalId);
|
|
116
141
|
if (!existing)
|
|
117
142
|
return { ok: false, error: 'not_found' };
|
|
143
|
+
if (options?.expectedReview && !Value.Equal(reviewed?.review.expectedReview, options?.expectedReview))
|
|
144
|
+
return { ok: false, error: 'stale_review', reason: 'The reviewed approval material changed.', nextAction: 'Reload this approval before deciding again.' };
|
|
118
145
|
if (!MVP_PROVEN_CHANNELS.has(existing.channel)) {
|
|
119
146
|
return { ok: false, error: 'unsupported_channel', channel: existing.channel };
|
|
120
147
|
}
|
|
@@ -169,7 +196,7 @@ export class ApprovalsConsoleModel {
|
|
|
169
196
|
try {
|
|
170
197
|
approvalResult = existing.channel === 'prompt' && existing.status === 'approved' && options?.retryActivation === true
|
|
171
198
|
? { ok: true, record: existing }
|
|
172
|
-
: await writeQueue.approve(approvalId, decidedBy, note);
|
|
199
|
+
: await writeQueue.approve(approvalId, decidedBy, note, options?.expectedReview);
|
|
173
200
|
}
|
|
174
201
|
finally {
|
|
175
202
|
try {
|
|
@@ -178,6 +205,8 @@ export class ApprovalsConsoleModel {
|
|
|
178
205
|
catch { /* best-effort */ }
|
|
179
206
|
}
|
|
180
207
|
if (!approvalResult.ok) {
|
|
208
|
+
if (approvalResult.error === 'stale_review')
|
|
209
|
+
return approvalResult;
|
|
181
210
|
if (approvalResult.error === 'already_decided') {
|
|
182
211
|
return { ok: false, error: 'already_decided', status: approvalResult.status };
|
|
183
212
|
}
|
|
@@ -508,9 +537,12 @@ export class ApprovalsConsoleModel {
|
|
|
508
537
|
if (!stateDbExists(this.workspaceDir)) {
|
|
509
538
|
return { ok: false, error: 'not_found' };
|
|
510
539
|
}
|
|
511
|
-
const
|
|
540
|
+
const reviewed = input.expectedReview ? await this.getApprovalDetail(approvalId) : null;
|
|
541
|
+
const existing = input.expectedReview ? reviewed : await this.readSafeGetById(approvalId);
|
|
512
542
|
if (!existing)
|
|
513
543
|
return { ok: false, error: 'not_found' };
|
|
544
|
+
if (input.expectedReview && !Value.Equal(reviewed?.review.expectedReview, input.expectedReview))
|
|
545
|
+
return { ok: false, error: 'stale_review', reason: 'The reviewed approval material changed.', nextAction: 'Reload this approval before deciding again.' };
|
|
514
546
|
if (existing.status !== 'pending') {
|
|
515
547
|
return { ok: false, error: 'already_decided', status: existing.status };
|
|
516
548
|
}
|
|
@@ -543,6 +575,8 @@ export class ApprovalsConsoleModel {
|
|
|
543
575
|
};
|
|
544
576
|
}
|
|
545
577
|
const editResult = await queue.edit({
|
|
578
|
+
expectedNewArtifactDigest: input.expectedReview ? computeArtifactDigest(newArtifact) : undefined,
|
|
579
|
+
expectedReview: input.expectedReview,
|
|
546
580
|
approvalId,
|
|
547
581
|
editedBy,
|
|
548
582
|
newArtifactId,
|
|
@@ -550,6 +584,8 @@ export class ApprovalsConsoleModel {
|
|
|
550
584
|
now: new Date().toISOString(),
|
|
551
585
|
});
|
|
552
586
|
if (!editResult.ok) {
|
|
587
|
+
if (editResult.error === 'stale_review')
|
|
588
|
+
return editResult;
|
|
553
589
|
if (editResult.error === 'already_decided') {
|
|
554
590
|
return { ok: false, error: 'already_decided', status: editResult.status };
|
|
555
591
|
}
|
|
@@ -702,19 +738,23 @@ export class ApprovalsConsoleModel {
|
|
|
702
738
|
catch { /* best-effort */ }
|
|
703
739
|
}
|
|
704
740
|
}
|
|
705
|
-
|
|
741
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- preserve the existing model contract with an additive optional review guard
|
|
742
|
+
async reject(approvalId, decidedBy, reason, expectedReview) {
|
|
706
743
|
if (!stateDbExists(this.workspaceDir)) {
|
|
707
744
|
return { ok: false, error: 'not_found' };
|
|
708
745
|
}
|
|
709
|
-
const
|
|
746
|
+
const reviewed = expectedReview ? await this.getApprovalDetail(approvalId) : null;
|
|
747
|
+
const existing = expectedReview ? reviewed : await this.readSafeGetById(approvalId);
|
|
710
748
|
if (!existing)
|
|
711
749
|
return { ok: false, error: 'not_found' };
|
|
750
|
+
if (expectedReview && !Value.Equal(reviewed?.review.expectedReview, expectedReview))
|
|
751
|
+
return { ok: false, error: 'stale_review', reason: 'The reviewed approval material changed.', nextAction: 'Reload this approval before deciding again.' };
|
|
712
752
|
if (!MVP_PROVEN_CHANNELS.has(existing.channel)) {
|
|
713
753
|
return { ok: false, error: 'unsupported_channel', channel: existing.channel };
|
|
714
754
|
}
|
|
715
755
|
const { queue, connection } = this.createWriteContext();
|
|
716
756
|
try {
|
|
717
|
-
return await queue.reject(approvalId, decidedBy, reason);
|
|
757
|
+
return await queue.reject(approvalId, decidedBy, reason, expectedReview);
|
|
718
758
|
}
|
|
719
759
|
finally {
|
|
720
760
|
try {
|
|
@@ -30,6 +30,8 @@ export interface ApprovalGroup {
|
|
|
30
30
|
promptBudgetTierFit?: 'fits_all_tiers' | 'model_dependent' | 'exceeds_max_tier' | 'unconfirmed';
|
|
31
31
|
revisionReviewUnavailable?: boolean;
|
|
32
32
|
revisionReview?: {
|
|
33
|
+
artifactId: string;
|
|
34
|
+
artifactDigest: string;
|
|
33
35
|
oldStatement: string;
|
|
34
36
|
newStatement: string;
|
|
35
37
|
intentFields: Record<string, string>;
|
|
@@ -48,6 +50,12 @@ export interface ApprovalGroup {
|
|
|
48
50
|
artifactId: string;
|
|
49
51
|
channel: string;
|
|
50
52
|
createdAt: string;
|
|
53
|
+
/**
|
|
54
|
+
* This record's own revision review is incomplete (the group flag also
|
|
55
|
+
* covers the multi-pending ambiguity, so readers must not use it to block
|
|
56
|
+
* an intact sibling approval).
|
|
57
|
+
*/
|
|
58
|
+
revisionReviewUnavailable?: boolean;
|
|
51
59
|
}[];
|
|
52
60
|
}
|
|
53
61
|
export interface ApprovalsGroupedResponse {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SqliteConnection, SqliteApprovalQueueStore, SqlitePIArtifactStore, ApprovalQueue, RUNTIME_V2_PRINCIPLE_BUDGET, detectPromptReplacementTarget, SqliteActivationStateStore, } from '@principles/core/runtime-v2';
|
|
1
|
+
import { SqliteConnection, SqliteApprovalQueueStore, SqlitePIArtifactStore, ApprovalQueue, RUNTIME_V2_PRINCIPLE_BUDGET, detectPromptReplacementTarget, computeArtifactDigest, SqliteActivationStateStore, } from '@principles/core/runtime-v2';
|
|
2
2
|
import { checkPromptArtifactDeliverability, operatorDeclaredBudgetResolution, resolveLivePromptInjectionProjection, RUNTIME_BUDGET_OBSERVATION_STALE_REASON, runtimeBudgetObservationToResolution, selectConsoleRuntimeBudgetFacts, } from '@principles/host-runtime';
|
|
3
3
|
import { PROMPT_BUDGET_HOST_KINDS } from '@principles/core/runtime-v2';
|
|
4
4
|
import { loadLedger } from '@principles/core/principle-tree-ledger';
|
|
@@ -244,7 +244,7 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
244
244
|
intentFields[key] = value;
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
-
revisionReviews.set(artifact.artifactId, { oldStatement, newStatement, intentFields });
|
|
247
|
+
revisionReviews.set(artifact.artifactId, { artifactId: artifact.artifactId, artifactDigest: computeArtifactDigest(artifact), oldStatement, newStatement, intentFields });
|
|
248
248
|
break;
|
|
249
249
|
}
|
|
250
250
|
}
|
|
@@ -297,6 +297,7 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
297
297
|
status: approval.status === 'approved' || approval.status === 'rejected'
|
|
298
298
|
? approval.status
|
|
299
299
|
: 'pending',
|
|
300
|
+
...(revisionReviewUnavailable.has(approval.artifactId) ? { revisionReviewUnavailable: true } : {}),
|
|
300
301
|
});
|
|
301
302
|
}
|
|
302
303
|
const groups = [];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ApprovalReviewMaterial {
|
|
2
|
+
availability: 'available' | 'missing' | 'unreadable';
|
|
3
|
+
artifactId: string;
|
|
4
|
+
statement: string | null;
|
|
5
|
+
rationale: string | null;
|
|
6
|
+
scope: string[];
|
|
7
|
+
risks: string[] | null;
|
|
8
|
+
targetBehavior: string | null;
|
|
9
|
+
exactBody: {
|
|
10
|
+
kind: 'principle' | 'rule_code';
|
|
11
|
+
text: string;
|
|
12
|
+
} | null;
|
|
13
|
+
}
|
|
14
|
+
/** Whitelisted normative fields only; raw artifact JSON is never returned. */
|
|
15
|
+
export declare function approvalReviewMaterial(artifactId: string, channel: string, contentJson: string | null): ApprovalReviewMaterial;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
function record(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
|
|
2
|
+
function text(value) { return typeof value === 'string' && value.trim() !== '' ? value : null; }
|
|
3
|
+
/** Whitelisted normative fields only; raw artifact JSON is never returned. */
|
|
4
|
+
export function approvalReviewMaterial(artifactId, channel, contentJson) {
|
|
5
|
+
const result = { artifactId, availability: contentJson === null ? 'missing' : 'unreadable', statement: null, rationale: null, scope: [], risks: null, targetBehavior: null, exactBody: null };
|
|
6
|
+
if (contentJson === null)
|
|
7
|
+
return result;
|
|
8
|
+
let parsed;
|
|
9
|
+
try {
|
|
10
|
+
parsed = JSON.parse(contentJson);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
if (!record(parsed))
|
|
16
|
+
return result;
|
|
17
|
+
if (record(parsed.intentContract))
|
|
18
|
+
result.targetBehavior = text(parsed.intentContract.targetBehavior);
|
|
19
|
+
if (Array.isArray(parsed.risks) && parsed.risks.every(item => typeof item === 'string'))
|
|
20
|
+
result.risks = parsed.risks;
|
|
21
|
+
const draft = record(parsed.principleDraft) ? parsed.principleDraft : parsed;
|
|
22
|
+
result.statement = text(draft.statement);
|
|
23
|
+
result.rationale = text(draft.rationale);
|
|
24
|
+
if (Array.isArray(draft.applicability) && draft.applicability.every(item => typeof item === 'string'))
|
|
25
|
+
result.scope = draft.applicability;
|
|
26
|
+
const body = channel === 'code_tool_hook' ? text(parsed.implementationCode) : result.statement;
|
|
27
|
+
if (body !== null) {
|
|
28
|
+
result.exactBody = { kind: channel === 'code_tool_hook' ? 'rule_code' : 'principle', text: body };
|
|
29
|
+
result.availability = 'available';
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MVP_CHANNELS } from '@principles/core/runtime-v2';
|
|
1
|
+
import { isApprovalReviewGuard, MVP_CHANNELS } from '@principles/core/runtime-v2';
|
|
2
2
|
import { ApprovalsConsoleModel } from '../models/ApprovalsConsoleModel.js';
|
|
3
3
|
import { sendSuccess, sendError, sendNotFound, sendBadRequest } from '../utils/response.js';
|
|
4
4
|
import { parseQuery, readBody } from '../utils/request.js';
|
|
@@ -124,6 +124,11 @@ export async function handleApprovalsRoute(req, res, workspaceDir, subPath) {
|
|
|
124
124
|
const parsed = parseJsonBody(body, res);
|
|
125
125
|
if (!parsed)
|
|
126
126
|
return;
|
|
127
|
+
const expectedReview = Object.hasOwn(parsed, 'expectedReview') ? parsed.expectedReview : undefined;
|
|
128
|
+
if (Object.hasOwn(parsed, 'expectedReview') && (!isApprovalReviewGuard(expectedReview) || expectedReview.approvalId !== approvalId || expectedReview.artifactState !== 'present')) {
|
|
129
|
+
sendBadRequest(res, 'expectedReview must identify this approval and its exact reviewed artifact/version');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
127
132
|
if (Object.hasOwn(parsed, 'note') && typeof parsed.note !== 'string') {
|
|
128
133
|
sendBadRequest(res, 'note must be a string');
|
|
129
134
|
return;
|
|
@@ -158,9 +163,13 @@ export async function handleApprovalsRoute(req, res, workspaceDir, subPath) {
|
|
|
158
163
|
intentReviewed: parsed.intentReviewed === true,
|
|
159
164
|
...(typeof parsed.reviewedArtifactId === 'string' ? { reviewedArtifactId: parsed.reviewedArtifactId } : {}),
|
|
160
165
|
retryActivation: parsed.retryActivation === true,
|
|
166
|
+
expectedReview: isApprovalReviewGuard(expectedReview) ? expectedReview : undefined,
|
|
161
167
|
});
|
|
162
168
|
if (!result.ok) {
|
|
163
|
-
if (result.error === '
|
|
169
|
+
if (result.error === 'stale_review') {
|
|
170
|
+
sendError(res, 409, 'stale_review', result.reason, { reason: 'stale_review', nextAction: result.nextAction });
|
|
171
|
+
}
|
|
172
|
+
else if (result.error === 'not_found') {
|
|
164
173
|
sendNotFound(res, 'Approval ' + approvalId + ' not found');
|
|
165
174
|
}
|
|
166
175
|
else if (result.error === 'unsupported_channel') {
|
|
@@ -170,7 +179,7 @@ export async function handleApprovalsRoute(req, res, workspaceDir, subPath) {
|
|
|
170
179
|
sendError(res, 409, result.error, result.reason, { nextAction: result.nextAction });
|
|
171
180
|
}
|
|
172
181
|
else if (result.error === 'activation_failed') {
|
|
173
|
-
sendError(res, 500, 'activation_failed', `Approval was ${result.approvalRolledBack ? 'rolled back to pending' : 'approved but activation failed'}. Reason: ${result.reason}`, { nextAction: result.nextAction ?? 'Inspect or regenerate the rule artifact, verify it is validated, then retry approval.' });
|
|
182
|
+
sendError(res, 500, 'activation_failed', `Approval was ${result.approvalRolledBack ? 'rolled back to pending' : 'approved but activation failed'}. Reason: ${result.reason}`, { reason: 'activation_failed', approvalRolledBack: result.approvalRolledBack, nextAction: result.nextAction ?? 'Inspect or regenerate the rule artifact, verify it is validated, then retry approval.' });
|
|
174
183
|
}
|
|
175
184
|
else if (result.error === 'prompt_capacity_refused') {
|
|
176
185
|
// R-B2: refused BEFORE any governance write — the approval stays
|
|
@@ -224,6 +233,11 @@ export async function handleApprovalsRoute(req, res, workspaceDir, subPath) {
|
|
|
224
233
|
const parsed = parseJsonBody(body, res);
|
|
225
234
|
if (!parsed)
|
|
226
235
|
return;
|
|
236
|
+
const expectedReview = Object.hasOwn(parsed, 'expectedReview') ? parsed.expectedReview : undefined;
|
|
237
|
+
if (Object.hasOwn(parsed, 'expectedReview') && (!isApprovalReviewGuard(expectedReview) || expectedReview.approvalId !== approvalId)) {
|
|
238
|
+
sendBadRequest(res, 'expectedReview must identify this approval and its exact reviewed artifact/version');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
227
241
|
if (!Object.hasOwn(parsed, 'reason') || typeof parsed.reason !== 'string') {
|
|
228
242
|
sendBadRequest(res, 'reason is required and must be a string');
|
|
229
243
|
return;
|
|
@@ -232,9 +246,12 @@ export async function handleApprovalsRoute(req, res, workspaceDir, subPath) {
|
|
|
232
246
|
sendBadRequest(res, 'reason must not be empty');
|
|
233
247
|
return;
|
|
234
248
|
}
|
|
235
|
-
const result = await model.reject(approvalId, 'operator', parsed.reason);
|
|
249
|
+
const result = await model.reject(approvalId, 'operator', parsed.reason, isApprovalReviewGuard(expectedReview) ? expectedReview : undefined);
|
|
236
250
|
if (!result.ok) {
|
|
237
|
-
if (result.error === '
|
|
251
|
+
if (result.error === 'stale_review') {
|
|
252
|
+
sendError(res, 409, 'stale_review', result.reason, { reason: 'stale_review', nextAction: result.nextAction });
|
|
253
|
+
}
|
|
254
|
+
else if (result.error === 'not_found') {
|
|
238
255
|
sendNotFound(res, 'Approval ' + approvalId + ' not found');
|
|
239
256
|
}
|
|
240
257
|
else if (result.error === 'unsupported_channel') {
|
|
@@ -319,6 +336,11 @@ export async function handleApprovalsRoute(req, res, workspaceDir, subPath) {
|
|
|
319
336
|
const parsed = parseJsonBody(body, res);
|
|
320
337
|
if (!parsed)
|
|
321
338
|
return;
|
|
339
|
+
const expectedReview = Object.hasOwn(parsed, 'expectedReview') ? parsed.expectedReview : undefined;
|
|
340
|
+
if (Object.hasOwn(parsed, 'expectedReview') && (!isApprovalReviewGuard(expectedReview) || expectedReview.approvalId !== approvalId || expectedReview.artifactState !== 'present')) {
|
|
341
|
+
sendBadRequest(res, 'expectedReview must identify this approval and its exact reviewed artifact/version');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
322
344
|
if (!Object.hasOwn(parsed, 'newArtifactId') || typeof parsed.newArtifactId !== 'string') {
|
|
323
345
|
sendBadRequest(res, 'newArtifactId is required and must be a string');
|
|
324
346
|
return;
|
|
@@ -331,9 +353,12 @@ export async function handleApprovalsRoute(req, res, workspaceDir, subPath) {
|
|
|
331
353
|
sendBadRequest(res, 'editReason must not be empty');
|
|
332
354
|
return;
|
|
333
355
|
}
|
|
334
|
-
const result = await model.editApproval({ approvalId, editedBy: 'operator', newArtifactId: parsed.newArtifactId, editReason: parsed.editReason });
|
|
356
|
+
const result = await model.editApproval({ expectedReview: isApprovalReviewGuard(expectedReview) ? expectedReview : undefined, approvalId, editedBy: 'operator', newArtifactId: parsed.newArtifactId, editReason: parsed.editReason });
|
|
335
357
|
if (!result.ok) {
|
|
336
|
-
if (result.error === '
|
|
358
|
+
if (result.error === 'stale_review') {
|
|
359
|
+
sendError(res, 409, 'stale_review', result.reason, { reason: 'stale_review', nextAction: result.nextAction });
|
|
360
|
+
}
|
|
361
|
+
else if (result.error === 'not_found') {
|
|
337
362
|
sendNotFound(res, 'Approval ' + approvalId + ' not found');
|
|
338
363
|
}
|
|
339
364
|
else if (result.error === 'already_decided') {
|
package/console/dist/ui/api.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ApprovalReviewGuard, type ApprovalReviewData } from './utils/approval-review.js';
|
|
1
2
|
import type { ApiResponse } from "../types.js";
|
|
2
3
|
import type { OwnerDecisionsData, OwnerResolutionResultData, OwnerDecisionInboxData, OwnerDecisionLibraryData } from "./utils/validators.js";
|
|
3
4
|
import type { DesignGateAction, DesignGateDecision, DesignGateDeclaration, DesignGateScope } from "@principles/core/runtime-v2/internalization/design-gate-facts";
|
|
@@ -210,13 +211,18 @@ declare function fetchEvidenceAudit(selectorType: "principle" | "activation" | "
|
|
|
210
211
|
evidenceId: string;
|
|
211
212
|
};
|
|
212
213
|
}): Promise<ApiResponse<EvidenceAuditData>>;
|
|
214
|
+
export declare function fetchApprovalReview(approvalId: string): Promise<ApiResponse<ApprovalReviewData>>;
|
|
213
215
|
declare function approveApproval(approvalId: string, note?: string, options?: {
|
|
216
|
+
expectedReview?: ApprovalReviewGuard;
|
|
214
217
|
targetHost?: 'openclaw' | 'codex' | 'workbuddy';
|
|
215
218
|
intentReviewed?: boolean;
|
|
216
219
|
reviewedArtifactId?: string;
|
|
217
220
|
}): Promise<ApiResponse<ApprovalRecordData>>;
|
|
218
|
-
declare function rejectApproval(approvalId: string, reason: string): Promise<ApiResponse<ApprovalRecordData>>;
|
|
219
|
-
declare function editApproval(approvalId: string, newArtifactId: string, editReason: string
|
|
221
|
+
declare function rejectApproval(approvalId: string, reason: string, expectedReview?: ApprovalReviewGuard): Promise<ApiResponse<ApprovalRecordData>>;
|
|
222
|
+
declare function editApproval(approvalId: string, newArtifactId: string, editReason: string | {
|
|
223
|
+
reason: string;
|
|
224
|
+
expectedReview: ApprovalReviewGuard;
|
|
225
|
+
}): Promise<ApiResponse<ApprovalRecordData>>;
|
|
220
226
|
declare function createFeedbackReport(input: unknown, diagnostics: unknown): Promise<ApiResponse<FeedbackReportData>>;
|
|
221
227
|
declare function listFeedbackReports(): Promise<ApiResponse<{
|
|
222
228
|
drafts: FeedbackDraftSummaryData[];
|
package/console/dist/ui/api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateApprovalReview } from './utils/approval-review.js';
|
|
1
2
|
import { clearStoredToken, loadStoredToken, storeToken } from "./utils/token-storage.js";
|
|
2
3
|
import { validateBindingReview, validateBindingQueue, validateBindingMutation, validateBindingRevise } from './utils/rule-binding-validators.js';
|
|
3
4
|
import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateRecoveryResult, validateFailedTaskDetail, validateOwnerDecisionsData, validateOwnerResolutionResult, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateUpdateRecovery, validateUpdateTransaction, validateApprovalRecordDirect, validatePrinciplesList, validateCorePrinciples, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, validateOwnerGovernanceView, validateOwnerDecisionViewCore, validateOwnerDecisionInbox, validateOwnerDecisionLibrary, validateGovernanceExperienceSnapshot, validateRuleCodeOwnerReview, validateRuleCodeMutation, validateOwnerIdentityView, validateOwnerIdentityRegister, validateOwnerIdentityUnregister, } from "./utils/validators.js";
|
|
@@ -556,23 +557,26 @@ async function fetchEvidenceAudit(selectorType, id, cursor) {
|
|
|
556
557
|
}
|
|
557
558
|
return request(`/api/v1/receipts/evidence-audit?${query.toString()}`, undefined, validateEvidenceAudit);
|
|
558
559
|
}
|
|
560
|
+
export function fetchApprovalReview(approvalId) {
|
|
561
|
+
return request('/api/v1/approvals/' + encodeURIComponent(approvalId), undefined, validateApprovalReview);
|
|
562
|
+
}
|
|
559
563
|
// ── Approvals ─────────────────────────────────────────────────────────────────
|
|
560
564
|
async function approveApproval(approvalId, note, options) {
|
|
561
565
|
return request('/api/v1/approvals/' + encodeURIComponent(approvalId) + '/approve', {
|
|
562
566
|
method: 'POST',
|
|
563
|
-
body: JSON.stringify({ note, ...(options?.targetHost !== undefined ? { host: options.targetHost } : {}), ...(options?.intentReviewed !== undefined ? { intentReviewed: options.intentReviewed, reviewedArtifactId: options.reviewedArtifactId } : {}) }),
|
|
567
|
+
body: JSON.stringify({ note, ...(options?.expectedReview === undefined ? {} : { expectedReview: options.expectedReview }), ...(options?.targetHost !== undefined ? { host: options.targetHost } : {}), ...(options?.intentReviewed !== undefined ? { intentReviewed: options.intentReviewed, reviewedArtifactId: options.reviewedArtifactId } : {}) }),
|
|
564
568
|
}, validateApprovalRecordDirect);
|
|
565
569
|
}
|
|
566
|
-
async function rejectApproval(approvalId, reason) {
|
|
570
|
+
async function rejectApproval(approvalId, reason, expectedReview) {
|
|
567
571
|
return request('/api/v1/approvals/' + encodeURIComponent(approvalId) + '/reject', {
|
|
568
572
|
method: 'POST',
|
|
569
|
-
body: JSON.stringify({ reason }),
|
|
573
|
+
body: JSON.stringify({ reason, ...(expectedReview === undefined ? {} : { expectedReview }) }),
|
|
570
574
|
}, validateApprovalRecordDirect);
|
|
571
575
|
}
|
|
572
576
|
async function editApproval(approvalId, newArtifactId, editReason) {
|
|
573
577
|
return request('/api/v1/approvals/' + encodeURIComponent(approvalId) + '/edit', {
|
|
574
578
|
method: 'POST',
|
|
575
|
-
body: JSON.stringify({ newArtifactId, editReason }),
|
|
579
|
+
body: JSON.stringify({ newArtifactId, editReason: typeof editReason === 'string' ? editReason : editReason.reason, ...(typeof editReason === 'string' ? {} : { expectedReview: editReason.expectedReview }) }),
|
|
576
580
|
}, validateApprovalRecordDirect);
|
|
577
581
|
}
|
|
578
582
|
// ── MVP seed feedback report drafts (PRI-285) ────────────────────────────────
|
|
@@ -48,6 +48,97 @@
|
|
|
48
48
|
},
|
|
49
49
|
"pages": {
|
|
50
50
|
"focus": {
|
|
51
|
+
"workbench": {
|
|
52
|
+
"textPreview": "Text preview",
|
|
53
|
+
"codePreview": "Rule implementation preview",
|
|
54
|
+
"fullText": "Expand complete text",
|
|
55
|
+
"fullCode": "Expand complete rule implementation",
|
|
56
|
+
"scope": "Applicable scope",
|
|
57
|
+
"compare": "Compare the previous version and retained intent",
|
|
58
|
+
"intentReviewed": "I checked the old and new versions and the intent to retain",
|
|
59
|
+
"promptConsequence": "Approval enters the existing prompt activation flow. Delivery still depends on host and capacity.",
|
|
60
|
+
"ruleConsequence": "Accepting an execution proposal is not promotion to live. Observation and promotion remain separate decisions.",
|
|
61
|
+
"approveOne": "Approve this revision",
|
|
62
|
+
"rejectOne": "Reject this revision",
|
|
63
|
+
"replaceOne": "Replace pending revision",
|
|
64
|
+
"replacementId": "Validated replacement artifact identifier",
|
|
65
|
+
"reason": "Reason for this decision",
|
|
66
|
+
"confirm": "Confirm submission",
|
|
67
|
+
"cancel": "Cancel",
|
|
68
|
+
"resultUnknown": "The submission result is unconfirmed. Read the current state before deciding again; do not resubmit.",
|
|
69
|
+
"notRecorded": "This decision is not complete. Review the refreshed material.",
|
|
70
|
+
"approved": "Approval is complete. Check activation and delivery in runtime status.",
|
|
71
|
+
"rejected": "This revision was rejected. Other subjects were not processed.",
|
|
72
|
+
"activationFailed": "Approval is complete, but activation failed. Use runtime recovery; do not approve again.",
|
|
73
|
+
"approvedRuntimeUnknown": "Approval was recorded. Activation is incomplete or unconfirmed. Check runtime status.",
|
|
74
|
+
"replaced": "The pending revision was replaced. Review the current text before deciding.",
|
|
75
|
+
"versionNeedsReview": "Revision {{index}} to review",
|
|
76
|
+
"title": "Needs your attention",
|
|
77
|
+
"subtitle": "Choose one item, understand the proposal and its consequences, then continue.",
|
|
78
|
+
"list": "Work items",
|
|
79
|
+
"count": "{{count}} items",
|
|
80
|
+
"uncertain": "Some sources could not be read. This list may be incomplete.",
|
|
81
|
+
"empty": "No work items are currently confirmed.",
|
|
82
|
+
"back": "Back to work items",
|
|
83
|
+
"next": "View next item",
|
|
84
|
+
"readingEnd": "This is the end of this item. Return to the list or review the next one.",
|
|
85
|
+
"completed": "Review outcome",
|
|
86
|
+
"notCurrent": "This item is no longer in the current list",
|
|
87
|
+
"startTitle": "Start with one item",
|
|
88
|
+
"startBody": "Choose an item to read its material, check the concerns, and see the available actions in one place.",
|
|
89
|
+
"notCurrentBody": "It may have been resolved, or its source may be unavailable. Use the freshly read state.",
|
|
90
|
+
"start": "Start review",
|
|
91
|
+
"loading": "Loading this approval’s exact material…",
|
|
92
|
+
"why": "What this is trying to solve",
|
|
93
|
+
"proposal": "What PD proposes to change",
|
|
94
|
+
"decision": "Your decision",
|
|
95
|
+
"proposedBehavior": "Proposed behavior (this draft)",
|
|
96
|
+
"currentApproved": "currently approved",
|
|
97
|
+
"currentRejected": "currently rejected",
|
|
98
|
+
"refusedCurrentState": "This operation was not performed; {{state}}.",
|
|
99
|
+
"currentStateConfirmed": "The record was reloaded: {{state}}. Whether this submission succeeded remains unconfirmed.",
|
|
100
|
+
"runtimeCheckLink": "Check activation and delivery on the runtime page.",
|
|
101
|
+
"sourceUnlinked": "This approval could not be linked to a principle or a readable name. Check the exact text and source first; you can reject it if the material is insufficient.",
|
|
102
|
+
"risksUnknown": "Risk notes for this revision are missing or unreadable. Risks remain unconfirmed.",
|
|
103
|
+
"risksEmpty": "This revision lists no risks; that does not establish safety.",
|
|
104
|
+
"approvalNote": "Approval note (optional)",
|
|
105
|
+
"budgetBasis": "Prompt capacity source and estimate basis",
|
|
106
|
+
"sourcesPending": "Sources unconfirmed",
|
|
107
|
+
"countExplanation": "How items are counted",
|
|
108
|
+
"archiveReview": "Defer or archive review",
|
|
109
|
+
"archiveConsequence": "Approving defers or archives this proposal; it does not activate a prompt or execution rule.",
|
|
110
|
+
"approveArchive": "Approve defer or archive",
|
|
111
|
+
"beforeDecision": "Before you decide",
|
|
112
|
+
"subjectUnconfirmed": "The complete decision material for this revision is unconfirmed. Check its source first.",
|
|
113
|
+
"revisionChanged": "The pending revision changed. Return to the list and read the new revision and intent comparison before deciding.",
|
|
114
|
+
"scopeUnknown": "Unspecified scope and exceptions remain unknown; they do not mean this applies in every situation.",
|
|
115
|
+
"principleLimits": "Principle-level risks and gaps (not validation of this revision)",
|
|
116
|
+
"reasonAndScope": "Rationale and consequences",
|
|
117
|
+
"materialUnavailable": "The material for this item is unavailable.",
|
|
118
|
+
"exactMaterialMissing": "No text matching this approval and revision was found. Check the material first.",
|
|
119
|
+
"versionLock": "You can read the material. The current approval service does not yet guarantee that the submitted version is the one you reviewed, so approve, reject, and replace actions are unavailable here.",
|
|
120
|
+
"openPrinciple": "View principle details and existing records",
|
|
121
|
+
"subjectDetails": "View approval and revision identifiers",
|
|
122
|
+
"permissionsUnknown": "Approval permissions could not be confirmed. Reading remains available; decision actions are locked.",
|
|
123
|
+
"taskDecision": "Current proposal decision",
|
|
124
|
+
"revisionDecision": "Proposal revision decision",
|
|
125
|
+
"validationReview": "Behavior validation review",
|
|
126
|
+
"approvalMaterialReview": "Approval material check",
|
|
127
|
+
"unknownReview": "Item type needs checking",
|
|
128
|
+
"promptReview": "Prompt principle review",
|
|
129
|
+
"ruleReview": "Execution proposal review",
|
|
130
|
+
"approvalSummary": "Check the proposed behavior, scope, and exact revision.",
|
|
131
|
+
"bindingReview": "Rule attribution review",
|
|
132
|
+
"bindingSummary": "Confirm which principle this rule should implement.",
|
|
133
|
+
"decisionRecorded": "The service received the decision. The list is refreshing from current state.",
|
|
134
|
+
"retry": "Read again",
|
|
135
|
+
"runtimeRecords": "Runtime and historical records",
|
|
136
|
+
"rulePromotion": "Rule promotion review",
|
|
137
|
+
"promotionSummary": "This rule is still being observed. Review the safety evidence before continuing observation, rejecting, or promoting it.",
|
|
138
|
+
"openActivation": "View rule runtime and emergency controls",
|
|
139
|
+
"safetyAlert": "{{count}} rules are safety-isolated. Check their runtime state.",
|
|
140
|
+
"approvalsUnavailable": "Some approval material could not be read. Other confirmed items remain available."
|
|
141
|
+
},
|
|
51
142
|
"ruleBinding": {
|
|
52
143
|
"title": "Formal principle attribution for rules",
|
|
53
144
|
"separate": "Attribution only states which principle a rule belongs to; it does not approve the rule, enable it, or replace a development design approval.",
|
|
@@ -522,13 +613,7 @@
|
|
|
522
613
|
},
|
|
523
614
|
"conclusion": {
|
|
524
615
|
"sectionTitle": "Current status",
|
|
525
|
-
"
|
|
526
|
-
"pendingTotal": "{{count}} item(s) waiting on you",
|
|
527
|
-
"actionableUnlocked": "{{count}} of them actionable now",
|
|
528
|
-
"actionableLocked": "{{count}} actionable object(s) — governance actions are locked, see the notice above",
|
|
529
|
-
"countsNote": "Scope: \"waiting on you\" = actionable objects on screen + inbox pending items (decisions / individually-attended blocked / recovery) not already shown; each object counts once; historical unknowns stay explanatory and are not counted.",
|
|
530
|
-
"pendingUncertain": "Pending count cannot be confirmed — decision updates are unavailable.",
|
|
531
|
-
"actionableWithoutTotal": "{{count}} item(s) actionable now",
|
|
616
|
+
"countsNote": "Scope: the count lists the work items in this workbench; blocked and recovery items are listed separately below.",
|
|
532
617
|
"degradedLabel": "Decision updates degraded:"
|
|
533
618
|
},
|
|
534
619
|
"ownerAttention": {
|
|
@@ -756,6 +841,9 @@
|
|
|
756
841
|
"metricLatest": "Last Updated",
|
|
757
842
|
"blockBasis": "Formation Basis",
|
|
758
843
|
"blockBasisFallback": "This principle was distilled from historical behavior calibration. Its detailed source is not yet linked.",
|
|
844
|
+
"contextDisclosure": "Formation and other information",
|
|
845
|
+
"libraryOverview": "Principle overview",
|
|
846
|
+
"openDetails": "View principle details",
|
|
759
847
|
"blockImpact": "Current Impact",
|
|
760
848
|
"blockImpactSeeDecisionView": "Whether this currently shapes behavior is shown on the detail page under current enforcement — this card does not assert impact from lifecycle status alone.",
|
|
761
849
|
"blockImpactActive": "This principle is currently shaping the Agent's subsequent behavior.",
|
|
@@ -812,6 +900,9 @@
|
|
|
812
900
|
"detail": {
|
|
813
901
|
"loading": "Loading…",
|
|
814
902
|
"backToList": "← Back to list",
|
|
903
|
+
"formationDisclosure": "How this principle formed",
|
|
904
|
+
"fullRule": "Read the complete principle",
|
|
905
|
+
"extraContext": "Other source notes and reflection questions",
|
|
815
906
|
"conclusion": "Conclusion",
|
|
816
907
|
"policyNote": "This is a behavioral policy for future similar scenarios. You can approve, modify, reject, or park it.",
|
|
817
908
|
"modifyWording": "Modify Wording",
|