create-principles-disciple 1.146.1 → 1.146.2
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 +1 -1
- package/console/dist/server/models/ActivationsConsoleModel.d.ts +3 -0
- package/console/dist/server/models/ActivationsConsoleModel.js +25 -1
- package/console/dist/server/models/ApprovalsConsoleModel.d.ts +5 -0
- package/console/dist/server/models/ApprovalsConsoleModel.js +40 -2
- package/console/dist/server/routes/activations.js +47 -0
- package/console/dist/server/routes/approvals.js +1 -1
- package/console/dist/shared/approved-rule-revision-contract.d.ts +8 -0
- package/console/dist/shared/approved-rule-revision-contract.js +48 -0
- package/console/dist/ui/api.d.ts +3 -0
- package/console/dist/ui/api.js +9 -0
- package/console/dist/ui/i18n/en.json +7 -0
- package/console/dist/ui/i18n/zh-CN.json +7 -0
- package/console/dist/ui/pages/activation/ActivationPage.js +69 -2
- package/console/dist/web/assets/app.css +3 -0
- package/console/dist/web/assets/app.js +31 -31
- package/core/dist/runtime-v2/activation/activation-dispatcher.d.ts +2 -0
- package/core/dist/runtime-v2/activation/activation-dispatcher.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/activation-dispatcher.js +60 -22
- package/core/dist/runtime-v2/activation/activation-dispatcher.js.map +1 -1
- 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/index.d.ts +2 -0
- package/core/dist/runtime-v2/activation/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/index.js +2 -0
- package/core/dist/runtime-v2/activation/index.js.map +1 -1
- package/core/dist/runtime-v2/activation/rule-replacement.d.ts +38 -0
- package/core/dist/runtime-v2/activation/rule-replacement.d.ts.map +1 -0
- package/core/dist/runtime-v2/activation/rule-replacement.js +44 -0
- package/core/dist/runtime-v2/activation/rule-replacement.js.map +1 -0
- package/core/dist/runtime-v2/activation/sqlite-activation-state-store.d.ts +9 -1
- package/core/dist/runtime-v2/activation/sqlite-activation-state-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/sqlite-activation-state-store.js +53 -23
- package/core/dist/runtime-v2/activation/sqlite-activation-state-store.js.map +1 -1
- package/core/dist/runtime-v2/activation/writers/rule-host-writer.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/writers/rule-host-writer.js +15 -2
- package/core/dist/runtime-v2/activation/writers/rule-host-writer.js.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +2 -0
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +5 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +3 -1
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +2 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/approved-rule-revision.d.ts +51 -0
- package/core/dist/runtime-v2/internalization/approved-rule-revision.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/approved-rule-revision.js +211 -0
- package/core/dist/runtime-v2/internalization/approved-rule-revision.js.map +1 -0
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts +1 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js +2 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/bash-risk.d.ts +4 -0
- package/core/dist/runtime-v2/internalization/bash-risk.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/bash-risk.js +42 -0
- package/core/dist/runtime-v2/internalization/bash-risk.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js +4 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +2 -1
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
- package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts +21 -0
- package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/pitask-metadata.js +30 -0
- package/core/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
- package/core/dist/runtime-v2/internalization/rule-host-contracts.d.ts +2 -0
- package/core/dist/runtime-v2/internalization/rule-host-contracts.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/rule-host-input-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/rule-host-input-builder.js +6 -0
- package/core/dist/runtime-v2/internalization/rule-host-input-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.js +32 -0
- package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.js.map +1 -1
- package/core/dist/runtime-v2/proven-channel-baseline.js +1 -1
- package/core/dist/runtime-v2/proven-channel-baseline.js.map +1 -1
- package/core/dist/runtime-v2/store/task/sqlite-approved-rule-revision.d.ts +15 -0
- package/core/dist/runtime-v2/store/task/sqlite-approved-rule-revision.d.ts.map +1 -0
- package/core/dist/runtime-v2/store/task/sqlite-approved-rule-revision.js +39 -0
- package/core/dist/runtime-v2/store/task/sqlite-approved-rule-revision.js.map +1 -0
- package/core/package.json +1 -1
- package/host-runtime/dist/production-rulehost-gate.js +11 -2
- package/package.json +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +477 -472
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/dist/rulehost-evidence.js +52 -49
- package/release-manager/package.json +1 -1
|
@@ -2,6 +2,7 @@ import type { ActivationStatusRecord, PromotionReadinessResult, ActivationContro
|
|
|
2
2
|
import { type PromptInjectionTargetHost } from '@principles/host-runtime';
|
|
3
3
|
import type { PromptActivationInjectionStatusWire, PromptInjectionRouteSummaryWire } from '../../shared/prompt-injection-contract.js';
|
|
4
4
|
import { type GovernanceAuditWriter } from 'principles-disciple/governance-audit';
|
|
5
|
+
import type { ApprovedRuleRevisionRequest } from '../../shared/approved-rule-revision-contract.js';
|
|
5
6
|
/**
|
|
6
7
|
* PRI-491 — owner-observable activation record.
|
|
7
8
|
*
|
|
@@ -151,6 +152,8 @@ export declare class ActivationsConsoleModel {
|
|
|
151
152
|
private readonly workspaceDir;
|
|
152
153
|
private readonly governanceAuditWriter;
|
|
153
154
|
constructor(workspaceDir: string, governanceAuditWriter?: GovernanceAuditWriter);
|
|
155
|
+
previewApprovedRuleRevision(activationId: string): Promise<import("@principles/core/runtime-v2").ApprovedRuleRevisionPreview>;
|
|
156
|
+
reviseApprovedRuleOnce(activationId: string, input: ApprovedRuleRevisionRequest, actor: OwnerPromotionActor): Promise<import("@principles/core/runtime-v2").ApprovedRuleRevisionResult>;
|
|
154
157
|
getActivations(targetHost?: PromptInjectionTargetHost): Promise<ActivationsResponse>;
|
|
155
158
|
getOwnerReview(activationId: string, ownerIdentityConfigured?: boolean, options?: {
|
|
156
159
|
ownerActor?: OwnerPromotionActor;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SqliteConnection, SqliteActivationStateStore, SqlitePIArtifactStore, extractPrincipleId, extractEvidenceRefs, PromotionReadinessReader, RuleHostWriter, createProductionGateDeps, SqliteActivationSafetyStore, isFeatureEnabled, RuleCodeOwnerDecisionService, collectOpenClawPromotionChecks, summarizeRuleCodeShadowEvents, buildPromotionEvidenceSnapshot, } from '@principles/core/runtime-v2';
|
|
1
|
+
import { SqliteConnection, SqliteActivationStateStore, SqlitePIArtifactStore, extractPrincipleId, extractEvidenceRefs, PromotionReadinessReader, RuleHostWriter, createProductionGateDeps, SqliteActivationSafetyStore, isFeatureEnabled, RuleCodeOwnerDecisionService, collectOpenClawPromotionChecks, summarizeRuleCodeShadowEvents, buildPromotionEvidenceSnapshot, createRuntimeStateHandle, PrincipleTreeLedgerAdapter, previewApprovedRuleRevision, reviseApprovedRuleOnce, } from '@principles/core/runtime-v2';
|
|
2
2
|
import { resolvePromotionHostLiveness, resolveWorkspaceHostToolSemantics, readPromptActivationInjectionStatuses, operatorDeclaredBudgetResolution, runtimeBudgetObservationToResolution, selectConsoleRuntimeBudgetFacts, RUNTIME_BUDGET_OBSERVATION_STALE_REASON, RUNTIME_V2_EVENT_LOG_CANDIDATE_DIRS } from '@principles/host-runtime';
|
|
3
3
|
import { PROMPT_BUDGET_HOST_KINDS } from '@principles/core/runtime-v2';
|
|
4
4
|
import { loadPdConfig, computeFlagsFromLoadResult } from '../config/pd-config-store.js';
|
|
@@ -168,6 +168,30 @@ export class ActivationsConsoleModel {
|
|
|
168
168
|
this.workspaceDir = workspaceDir;
|
|
169
169
|
this.governanceAuditWriter = governanceAuditWriter;
|
|
170
170
|
}
|
|
171
|
+
async previewApprovedRuleRevision(activationId) {
|
|
172
|
+
const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir, readonly: true, bootstrapIfMissing: false });
|
|
173
|
+
try {
|
|
174
|
+
const ledger = new PrincipleTreeLedgerAdapter({ stateDir: path.join(this.workspaceDir, '.state') });
|
|
175
|
+
return await previewApprovedRuleRevision({ stateManager: handle.stateManager,
|
|
176
|
+
ledger: { resolveForCandidate: async (id) => ledger.resolveCandidatePrincipleIdentity(id, 'rule'),
|
|
177
|
+
withLockedRuleCandidateIdentity: (id, operation) => ledger.withLockedRuleCandidateIdentity(id, operation) }, activationId });
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await handle.close();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async reviseApprovedRuleOnce(activationId, input, actor) {
|
|
184
|
+
const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir, bootstrapIfMissing: false });
|
|
185
|
+
try {
|
|
186
|
+
const ledger = new PrincipleTreeLedgerAdapter({ stateDir: path.join(this.workspaceDir, '.state') });
|
|
187
|
+
return await reviseApprovedRuleOnce({ stateManager: handle.stateManager,
|
|
188
|
+
ledger: { resolveForCandidate: async (id) => ledger.resolveCandidatePrincipleIdentity(id, 'rule'),
|
|
189
|
+
withLockedRuleCandidateIdentity: (id, operation) => ledger.withLockedRuleCandidateIdentity(id, operation) }, activationId, ...input, actor });
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
await handle.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
171
195
|
async getActivations(targetHost) {
|
|
172
196
|
const stateDbPath = path.join(this.workspaceDir, '.pd', 'state.db');
|
|
173
197
|
if (!fs.existsSync(stateDbPath)) {
|
|
@@ -71,6 +71,11 @@ export type ApproveWithActivationResult = {
|
|
|
71
71
|
error: 'prompt_replacement_refused';
|
|
72
72
|
reason: string;
|
|
73
73
|
nextAction: string;
|
|
74
|
+
} | {
|
|
75
|
+
ok: false;
|
|
76
|
+
error: 'rule_replacement_refused';
|
|
77
|
+
reason: string;
|
|
78
|
+
nextAction: string;
|
|
74
79
|
}
|
|
75
80
|
/**
|
|
76
81
|
* PD_PROMPT_CAPACITY_V1 R-B2: the prompt-channel write gate refused BEFORE
|
|
@@ -2,7 +2,7 @@ import { Value } from '@sinclair/typebox/value';
|
|
|
2
2
|
import { approvalReviewMaterial } from './approval-review-material.js';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import * as path from 'node:path';
|
|
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';
|
|
5
|
+
import { SqliteConnection, SqliteApprovalQueueStore, SqliteActivationStateStore, SqlitePIArtifactStore, ActivationDispatcher, PromptWriter, DeferArchiveWriter, RuleHostWriter, createProductionGateDeps, ApprovalCompletionService, ApprovalQueue, mapConfidenceToLabel, isArtifactRevisionOf, detectPromptReplacementTarget, detectRuleReplacementTarget, buildOwnerRevisionArtifact, PrincipleTreeLedgerAdapter, MVP_CHANNELS, computeArtifactDigest, } from '@principles/core/runtime-v2';
|
|
6
6
|
import { resolveLedgerPrincipleId } from './principle-id-resolution.js';
|
|
7
7
|
import { createArtifactPrincipleResolutionDeps } from './artifact-principle-resolver.js';
|
|
8
8
|
import { loadPdConfig, computeFlagsFromLoadResult } from '../config/pd-config-store.js';
|
|
@@ -191,6 +191,39 @@ export class ApprovalsConsoleModel {
|
|
|
191
191
|
catch { /* best-effort */ }
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
+
// SPEC-RULECODE-APPROVED-REVISION-001 §B: approving a revised RuleCode
|
|
195
|
+
// artifact replaces its superseded shadow in the SAME transaction as the
|
|
196
|
+
// new activation (no stop-old-then-start-new window). Detection is
|
|
197
|
+
// fail-closed on ambiguity and refuses to shadow an already-live rule.
|
|
198
|
+
if (existing.channel === 'code_tool_hook' && (existing.status === 'pending' || (existing.status === 'approved' && options?.retryActivation === true))) {
|
|
199
|
+
const { connection } = this.createReadContext();
|
|
200
|
+
try {
|
|
201
|
+
const ruleArtifactStore = new SqlitePIArtifactStore(connection);
|
|
202
|
+
const detection = await detectRuleReplacementTarget({
|
|
203
|
+
approvalArtifactId: existing.artifactId,
|
|
204
|
+
getArtifactById: async (id) => ruleArtifactStore.getArtifactById(id),
|
|
205
|
+
listRuleActivations: async () => new SqliteActivationStateStore(connection).listCodeToolHookActivations(),
|
|
206
|
+
});
|
|
207
|
+
if (!detection.ok) {
|
|
208
|
+
// An unreadable NEW artifact is not a replacement decision: the
|
|
209
|
+
// activation path already reports it with a precise reason and the
|
|
210
|
+
// established status code. Only replacement-specific refusals stop here.
|
|
211
|
+
if (!detection.error.startsWith('rule_replacement_artifact_unreadable')) {
|
|
212
|
+
return { ok: false, error: 'rule_replacement_refused', reason: detection.error, nextAction: detection.nextAction };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
supersedeActivationId = detection.target?.supersededActivationId;
|
|
217
|
+
supersedeArtifactId = detection.target?.supersededArtifactId;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
try {
|
|
222
|
+
connection.close();
|
|
223
|
+
}
|
|
224
|
+
catch { /* best-effort */ }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
194
227
|
const { queue: writeQueue, connection: writeConnection } = this.createWriteContext();
|
|
195
228
|
let approvalResult;
|
|
196
229
|
try {
|
|
@@ -236,7 +269,12 @@ export class ApprovalsConsoleModel {
|
|
|
236
269
|
}
|
|
237
270
|
}
|
|
238
271
|
catch { /* best-effort rollback */ }
|
|
239
|
-
|
|
272
|
+
// Carry the dispatcher's nextAction (it names the concrete cause for a
|
|
273
|
+
// replacement failure); without it the Owner only sees a stable code.
|
|
274
|
+
const activationNextAction = 'nextAction' in activation && typeof activation.nextAction === 'string'
|
|
275
|
+
? activation.nextAction : undefined;
|
|
276
|
+
return { ok: false, error: 'activation_failed', reason: detail, approvalRolledBack,
|
|
277
|
+
...(activationNextAction !== undefined ? { nextAction: activationNextAction } : {}) };
|
|
240
278
|
}
|
|
241
279
|
// P1 #4 fix: when activation is skipped (feature flag disabled), surface
|
|
242
280
|
// a clear warning so the owner knows behavior did NOT change. The approval
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ActivationsConsoleModel } from '../models/ActivationsConsoleModel.js';
|
|
2
2
|
import { ApprovalsConsoleModel } from '../models/ApprovalsConsoleModel.js';
|
|
3
3
|
import { sendSuccess, sendError, sendNotFound } from '../utils/response.js';
|
|
4
|
+
import { validateApprovedRuleRevisionRequest } from '../../shared/approved-rule-revision-contract.js';
|
|
4
5
|
const MAX_BODY_SIZE = 1024 * 1024; // 1 MB
|
|
5
6
|
const models = new Map();
|
|
6
7
|
const approvalsModels = new Map();
|
|
@@ -116,6 +117,52 @@ export async function handleActivationsRoute(req, res, workspaceDir, subPath, au
|
|
|
116
117
|
}
|
|
117
118
|
return;
|
|
118
119
|
}
|
|
120
|
+
const revisionMatch = /^\/([^/]+)\/(rule-revision-preview|revise-rule-once)$/.exec(subPath);
|
|
121
|
+
if (revisionMatch && ((req.method === 'GET' && revisionMatch[2] === 'rule-revision-preview')
|
|
122
|
+
|| (req.method === 'POST' && revisionMatch[2] === 'revise-rule-once'))) {
|
|
123
|
+
const actor = authority?.ownerActor;
|
|
124
|
+
if (!actor) {
|
|
125
|
+
sendError(res, 403, 'owner_authentication_required', 'This revision requires the configured Owner identity.', { nextAction: 'Authenticate as the configured Owner in the Console.' });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const [, rawId] = revisionMatch;
|
|
129
|
+
if (!rawId) {
|
|
130
|
+
sendError(res, 400, 'invalid_id', 'Activation ID is missing');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
let activationId;
|
|
134
|
+
try {
|
|
135
|
+
activationId = decodeURIComponent(rawId);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
sendError(res, 400, 'invalid_id', 'Activation ID contains invalid URI encoding');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let input;
|
|
142
|
+
if (req.method === 'POST') {
|
|
143
|
+
try {
|
|
144
|
+
input = validateApprovedRuleRevisionRequest(await readJson(req));
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
148
|
+
sendError(res, message === 'payload_too_large' ? 413 : 400, 'validation_error', message);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (!input) {
|
|
152
|
+
sendError(res, 400, 'validation_error', 'Revision requires the reviewed artifact, digest, binding/control/task versions, requestId and 1–4000 characters of directed feedback.', { nextAction: 'Refresh the revision preview and describe the required correction.' });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const model = getModel(workspaceDir);
|
|
158
|
+
sendSuccess(res, input ? await model.reviseApprovedRuleOnce(activationId, input, actor) : await model.previewApprovedRuleRevision(activationId));
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
162
|
+
sendError(res, 500, 'rule_revision_unavailable', message, { nextAction: 'Refresh the revision preview; keep the current rule in shadow.' });
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
119
166
|
const mutationMatch = /^\/([^/]+)\/(continue-observing|promote|reject-after-shadow|emergency-deactivate|recover-to-shadow)$/.exec(subPath);
|
|
120
167
|
const pauseMatch = subPath === '/emergency-pause';
|
|
121
168
|
const releaseMatch = /^\/emergency-pause\/([^/]+)\/release$/.exec(subPath);
|
|
@@ -175,7 +175,7 @@ export async function handleApprovalsRoute(req, res, workspaceDir, subPath) {
|
|
|
175
175
|
else if (result.error === 'unsupported_channel') {
|
|
176
176
|
sendError(res, 403, 'unsupported_channel', `Cannot approve unsupported channel. Only MVP proven channels (${MVP_CHANNEL_LIST}) can be approved.`);
|
|
177
177
|
}
|
|
178
|
-
else if (result.error === 'prompt_replacement_refused') {
|
|
178
|
+
else if (result.error === 'prompt_replacement_refused' || result.error === 'rule_replacement_refused') {
|
|
179
179
|
sendError(res, 409, result.error, result.reason, { nextAction: result.nextAction });
|
|
180
180
|
}
|
|
181
181
|
else if (result.error === 'activation_failed') {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { previewApprovedRuleRevision, reviseApprovedRuleOnce } from '@principles/core/runtime-v2';
|
|
2
|
+
/** Wire projections of the core authority; no independent revision state. */
|
|
3
|
+
export type ApprovedRuleRevisionPreview = Awaited<ReturnType<typeof previewApprovedRuleRevision>>;
|
|
4
|
+
export type ApprovedRuleRevisionResult = Awaited<ReturnType<typeof reviseApprovedRuleOnce>>;
|
|
5
|
+
export type ApprovedRuleRevisionRequest = Pick<Parameters<typeof reviseApprovedRuleOnce>[0], 'expectedArtifactId' | 'expectedArtifactDigest' | 'expectedBindingRevision' | 'expectedControlVersion' | 'expectedTaskRevisionEpoch' | 'requestId' | 'feedback'>;
|
|
6
|
+
export declare function validateApprovedRuleRevisionRequest(value: unknown): ApprovedRuleRevisionRequest | null;
|
|
7
|
+
export declare function validateApprovedRuleRevisionPreview(value: unknown): ApprovedRuleRevisionPreview | null;
|
|
8
|
+
export declare function validateApprovedRuleRevisionResult(value: unknown): ApprovedRuleRevisionResult | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
function record(value) {
|
|
2
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function text(value) { return typeof value === 'string' && value.trim().length > 0; }
|
|
5
|
+
function version(value) { return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0; }
|
|
6
|
+
export function validateApprovedRuleRevisionRequest(value) {
|
|
7
|
+
if (!record(value) || !text(value.expectedArtifactId) || !text(value.expectedArtifactDigest)
|
|
8
|
+
|| !/^sha256:[a-f0-9]{64}$/.test(value.expectedArtifactDigest)
|
|
9
|
+
|| !version(value.expectedBindingRevision) || !version(value.expectedControlVersion)
|
|
10
|
+
|| !version(value.expectedTaskRevisionEpoch) || !text(value.requestId) || value.requestId.length > 200
|
|
11
|
+
|| !text(value.feedback) || value.feedback.length > 4000)
|
|
12
|
+
return null;
|
|
13
|
+
return { expectedArtifactId: value.expectedArtifactId, expectedArtifactDigest: value.expectedArtifactDigest,
|
|
14
|
+
expectedBindingRevision: value.expectedBindingRevision, expectedControlVersion: value.expectedControlVersion,
|
|
15
|
+
expectedTaskRevisionEpoch: value.expectedTaskRevisionEpoch, requestId: value.requestId, feedback: value.feedback.trim() };
|
|
16
|
+
}
|
|
17
|
+
function refused(value) {
|
|
18
|
+
return value.status === 'refused' && text(value.reason) && text(value.nextAction)
|
|
19
|
+
? { status: 'refused', reason: value.reason, nextAction: value.nextAction } : null;
|
|
20
|
+
}
|
|
21
|
+
export function validateApprovedRuleRevisionPreview(value) {
|
|
22
|
+
if (!record(value))
|
|
23
|
+
return null;
|
|
24
|
+
if (value.status === 'refused')
|
|
25
|
+
return refused(value);
|
|
26
|
+
if (value.status !== 'ready' || !text(value.activationId) || !text(value.candidateId)
|
|
27
|
+
|| !text(value.principleId) || !text(value.artificerTaskId) || !text(value.expectedArtifactId)
|
|
28
|
+
|| !text(value.expectedArtifactDigest) || !/^sha256:[a-f0-9]{64}$/.test(value.expectedArtifactDigest)
|
|
29
|
+
|| !version(value.expectedBindingRevision) || !version(value.expectedControlVersion)
|
|
30
|
+
|| !version(value.expectedTaskRevisionEpoch))
|
|
31
|
+
return null;
|
|
32
|
+
return { status: 'ready', activationId: value.activationId, candidateId: value.candidateId,
|
|
33
|
+
principleId: value.principleId, artificerTaskId: value.artificerTaskId,
|
|
34
|
+
expectedArtifactId: value.expectedArtifactId, expectedArtifactDigest: value.expectedArtifactDigest,
|
|
35
|
+
expectedBindingRevision: value.expectedBindingRevision, expectedControlVersion: value.expectedControlVersion,
|
|
36
|
+
expectedTaskRevisionEpoch: value.expectedTaskRevisionEpoch };
|
|
37
|
+
}
|
|
38
|
+
export function validateApprovedRuleRevisionResult(value) {
|
|
39
|
+
if (!record(value))
|
|
40
|
+
return null;
|
|
41
|
+
if (value.status === 'refused')
|
|
42
|
+
return refused(value);
|
|
43
|
+
if ((value.status !== 'started' && value.status !== 'already_started') || !text(value.artificerTaskId)
|
|
44
|
+
|| !text(value.causeId) || !version(value.taskRevisionEpoch))
|
|
45
|
+
return null;
|
|
46
|
+
return { status: value.status, artificerTaskId: value.artificerTaskId,
|
|
47
|
+
causeId: value.causeId, taskRevisionEpoch: value.taskRevisionEpoch };
|
|
48
|
+
}
|
package/console/dist/ui/api.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { OwnerDecisionsData, OwnerResolutionResultData, OwnerDecisionInboxD
|
|
|
4
4
|
import type { DesignGateAction, DesignGateDecision, DesignGateDeclaration, DesignGateScope } from "@principles/core/runtime-v2/internalization/design-gate-facts";
|
|
5
5
|
import type { RuleBindingReviewView, RuleBindingQueueView } from '../shared/rule-candidate-binding-contract.js';
|
|
6
6
|
import type { RuleCandidateBindingRequest } from '@principles/core/runtime-v2';
|
|
7
|
+
import type { ApprovedRuleRevisionPreview, ApprovedRuleRevisionRequest, ApprovedRuleRevisionResult } from '../shared/approved-rule-revision-contract.js';
|
|
7
8
|
import type { GovernanceExperienceSnapshot, OwnerGovernanceView, OwnerDecisionViewCore } from '@principles/core/runtime-v2';
|
|
8
9
|
import type { OwnerIdentityViewData, OwnerIdentityRegisterData, OwnerIdentityUnregisterData } from "./utils/validators.js";
|
|
9
10
|
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";
|
|
@@ -311,6 +312,8 @@ declare function fetchApprovalsGrouped(targetHost?: 'openclaw' | 'codex' | 'work
|
|
|
311
312
|
declare function fetchAllActivations(targetHost?: 'openclaw' | 'codex' | 'workbuddy'): Promise<ApiResponse<ActivationsData>>;
|
|
312
313
|
declare function disableActivation(activationId: string): Promise<ApiResponse<DisableActivationData>>;
|
|
313
314
|
declare function fetchRuleCodeOwnerReview(activationId: string): Promise<ApiResponse<RuleCodeOwnerReviewData>>;
|
|
315
|
+
export declare function fetchApprovedRuleRevisionPreview(activationId: string): Promise<ApiResponse<ApprovedRuleRevisionPreview>>;
|
|
316
|
+
export declare function reviseApprovedRuleOnce(activationId: string, input: ApprovedRuleRevisionRequest): Promise<ApiResponse<ApprovedRuleRevisionResult>>;
|
|
314
317
|
export interface ProposeRevisionData {
|
|
315
318
|
ok: boolean;
|
|
316
319
|
alreadyPending?: boolean;
|
package/console/dist/ui/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { validateApprovalReview } from './utils/approval-review.js';
|
|
2
2
|
import { clearStoredToken, loadStoredToken, storeToken } from "./utils/token-storage.js";
|
|
3
|
+
import { validateApprovedRuleRevisionPreview, validateApprovedRuleRevisionResult } from '../shared/approved-rule-revision-contract.js';
|
|
3
4
|
import { validateBindingReview, validateBindingQueue, validateBindingMutation, validateBindingRevise } from './utils/rule-binding-validators.js';
|
|
4
5
|
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";
|
|
5
6
|
// ── Auth ──────────────────────────────────────────────────────────────────────
|
|
@@ -793,6 +794,14 @@ async function disableActivation(activationId) {
|
|
|
793
794
|
async function fetchRuleCodeOwnerReview(activationId) {
|
|
794
795
|
return request(`/api/v1/activations/${encodeURIComponent(activationId)}/owner-review`, undefined, validateRuleCodeOwnerReview);
|
|
795
796
|
}
|
|
797
|
+
export async function fetchApprovedRuleRevisionPreview(activationId) {
|
|
798
|
+
return request(`/api/v1/activations/${encodeURIComponent(activationId)}/rule-revision-preview`, undefined, validateApprovedRuleRevisionPreview);
|
|
799
|
+
}
|
|
800
|
+
export async function reviseApprovedRuleOnce(activationId, input) {
|
|
801
|
+
return request(`/api/v1/activations/${encodeURIComponent(activationId)}/revise-rule-once`, {
|
|
802
|
+
method: 'POST', body: JSON.stringify(input),
|
|
803
|
+
}, validateApprovedRuleRevisionResult);
|
|
804
|
+
}
|
|
796
805
|
async function proposePromptRevision(activationId, statement, targetHost) {
|
|
797
806
|
return request(`/api/v1/activations/${encodeURIComponent(activationId)}/propose-revision`, {
|
|
798
807
|
method: 'POST',
|
|
@@ -1371,6 +1371,13 @@
|
|
|
1371
1371
|
"evaluationErrors": "Errors",
|
|
1372
1372
|
"notCollected": "Not collected",
|
|
1373
1373
|
"scopeArtifactCode": "Scope, artifact and implementation",
|
|
1374
|
+
"ruleRevisionTitle": "Revise this rule for a new review",
|
|
1375
|
+
"ruleRevisionReviewQueue": "View review queue",
|
|
1376
|
+
"ruleRevisionBoundary": "Revise once using this feedback. Existing code and approvals are preserved. New code requires fresh approval and will not activate automatically.",
|
|
1377
|
+
"ruleRevisionFeedback": "Required correction (up to 4000 characters)",
|
|
1378
|
+
"ruleRevisionOnce": "Revise once with feedback",
|
|
1379
|
+
"ruleRevisionStarted": "Revision queued. The result will appear for review; the current rule stays in shadow.",
|
|
1380
|
+
"ruleRevisionAlreadyExists": "This artifact already has a revision; this request did not start a new one.",
|
|
1374
1381
|
"ownerReviewNote": "Owner review note",
|
|
1375
1382
|
"continueObserving": "Continue observing",
|
|
1376
1383
|
"rejectAfterShadow": "Reject and deactivate",
|
|
@@ -1371,6 +1371,13 @@
|
|
|
1371
1371
|
"evaluationErrors": "异常",
|
|
1372
1372
|
"notCollected": "未采集",
|
|
1373
1373
|
"scopeArtifactCode": "范围、产物与实现代码",
|
|
1374
|
+
"ruleRevisionTitle": "修正规则后重新审阅",
|
|
1375
|
+
"ruleRevisionReviewQueue": "查看待审列表",
|
|
1376
|
+
"ruleRevisionBoundary": "只按本次反馈修订一次,保留旧规则和审批记录。新代码需重新审阅批准,不会自动启用。",
|
|
1377
|
+
"ruleRevisionFeedback": "需要修正什么(最多4000字)",
|
|
1378
|
+
"ruleRevisionOnce": "按反馈修订一次",
|
|
1379
|
+
"ruleRevisionStarted": "修订已排队。完成后将在待审列表出现;当前规则保持影子状态。",
|
|
1380
|
+
"ruleRevisionAlreadyExists": "该工件已有修订,本次请求未启动新的修订。",
|
|
1374
1381
|
"ownerReviewNote": "拥有者评审备注",
|
|
1375
1382
|
"continueObserving": "继续观察",
|
|
1376
1383
|
"rejectAfterShadow": "拒绝并停用",
|
|
@@ -8,7 +8,7 @@ import { PageLoading } from "../../components/layout/page-loading.js";
|
|
|
8
8
|
import { SectionTitle } from "../../components/layout/section-title.js";
|
|
9
9
|
import { ShinyText } from "../../components/ui/shiny-text.js";
|
|
10
10
|
import { Button } from "../../components/ui/button.js";
|
|
11
|
-
import { fetchAllActivations, disableActivation, fetchLifecycleMetrics, fetchReceiptCounts, fetchRuleCodeOwnerReview, proposePromptRevision, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, } from "../../api.js";
|
|
11
|
+
import { fetchAllActivations, disableActivation, fetchLifecycleMetrics, fetchReceiptCounts, fetchRuleCodeOwnerReview, fetchApprovedRuleRevisionPreview, reviseApprovedRuleOnce, proposePromptRevision, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, } from "../../api.js";
|
|
12
12
|
import { ReceiptCoverageDisclosure, getReceiptSourceStatusLabelKey } from "../../components/receipts/ReceiptCoverageDisclosure.js";
|
|
13
13
|
import { validateActivationsData, validateLifecycleMetricsData, isReversibleChannel, } from "./ActivationValidators.js";
|
|
14
14
|
import { enumLabel } from "../../utils/enum-labels.js";
|
|
@@ -41,6 +41,73 @@ function CapabilityBoundaryDeclaration() {
|
|
|
41
41
|
const { t } = useTranslation();
|
|
42
42
|
return (_jsx("div", { className: "px-[18px] py-[14px] bg-panel border border-line rounded-[6px] text-ink-3 text-[13px] leading-relaxed", role: "note", "aria-label": t("pages.activation.boundaryLabel"), children: t("pages.activation.boundaryText") }));
|
|
43
43
|
}
|
|
44
|
+
function ApprovedRuleRevisionPanel({ activationId, onChanged }) {
|
|
45
|
+
const { t } = useTranslation();
|
|
46
|
+
const [preview, setPreview] = useState(null);
|
|
47
|
+
const [feedback, setFeedback] = useState('');
|
|
48
|
+
const [error, setError] = useState(null);
|
|
49
|
+
const [busy, setBusy] = useState(false);
|
|
50
|
+
const [submitted, setSubmitted] = useState(false);
|
|
51
|
+
// A concurrent/earlier revision already holds this artifact's budget. That is
|
|
52
|
+
// not this request succeeding, and the feedback just typed was NOT recorded.
|
|
53
|
+
const [alreadyStarted, setAlreadyStarted] = useState(false);
|
|
54
|
+
// Preserve the exact request across a transport retry; a new ID is not a new
|
|
55
|
+
// budget (the server's budget is anchored on the artifact digest + receipt,
|
|
56
|
+
// so editing the feedback and resending cannot spend a second round).
|
|
57
|
+
const [pendingRequestId, setPendingRequestId] = useState(null);
|
|
58
|
+
const load = useCallback(async () => {
|
|
59
|
+
const result = await fetchApprovedRuleRevisionPreview(activationId);
|
|
60
|
+
if (result.success) {
|
|
61
|
+
setPreview(result.data);
|
|
62
|
+
setError(null);
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
setError(result.error);
|
|
66
|
+
}, [activationId]);
|
|
67
|
+
useEffect(() => { void load(); }, [load]);
|
|
68
|
+
const revise = async () => {
|
|
69
|
+
if (preview?.status !== 'ready')
|
|
70
|
+
return;
|
|
71
|
+
const input = {
|
|
72
|
+
expectedArtifactId: preview.expectedArtifactId, expectedArtifactDigest: preview.expectedArtifactDigest,
|
|
73
|
+
expectedBindingRevision: preview.expectedBindingRevision, expectedControlVersion: preview.expectedControlVersion,
|
|
74
|
+
expectedTaskRevisionEpoch: preview.expectedTaskRevisionEpoch,
|
|
75
|
+
requestId: pendingRequestId ?? crypto.randomUUID(), feedback: feedback.trim(),
|
|
76
|
+
};
|
|
77
|
+
setPendingRequestId(input.requestId);
|
|
78
|
+
setBusy(true);
|
|
79
|
+
setError(null);
|
|
80
|
+
const result = await reviseApprovedRuleOnce(activationId, input);
|
|
81
|
+
setBusy(false);
|
|
82
|
+
if (!result.success) {
|
|
83
|
+
setError(result.error);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (result.data.status === 'refused') {
|
|
87
|
+
setPendingRequestId(null);
|
|
88
|
+
// Take the server verdict as the current view: if the refresh below also
|
|
89
|
+
// fails, the stale `ready` form must not stay submittable.
|
|
90
|
+
setPreview(result.data);
|
|
91
|
+
setError(result.data.nextAction);
|
|
92
|
+
await load();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (result.data.status === 'already_started') {
|
|
96
|
+
toast.info(t('pages.activation.ruleRevisionAlreadyExists'));
|
|
97
|
+
setAlreadyStarted(true);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Announce before onChanged(): the parent reload unmounts this panel, and a
|
|
101
|
+
// panel-local confirmation would disappear with it.
|
|
102
|
+
toast.success(t('pages.activation.ruleRevisionStarted'));
|
|
103
|
+
setSubmitted(true);
|
|
104
|
+
onChanged();
|
|
105
|
+
};
|
|
106
|
+
return _jsxs("div", { className: "mt-3 rounded border border-line p-3 text-[12px] text-ink-3", "data-testid": `approved-rule-revision-${activationId}`, children: [_jsx("div", { className: "font-medium text-ink", children: t('pages.activation.ruleRevisionTitle') }), _jsx("p", { className: "mt-1", children: t('pages.activation.ruleRevisionBoundary') }), error && _jsx("p", { role: "alert", className: "mt-2 text-danger", children: error }), alreadyStarted ? _jsxs("div", { className: "mt-2", children: [_jsx("p", { role: "status", children: t('pages.activation.ruleRevisionAlreadyExists') }), _jsx(Link, { to: "/focus", className: "mt-1 inline-block text-gov underline", children: t('pages.activation.ruleRevisionReviewQueue') })] })
|
|
107
|
+
: submitted ? _jsxs("div", { className: "mt-2", children: [_jsx("p", { role: "status", className: "text-green", children: t('pages.activation.ruleRevisionStarted') }), _jsx(Link, { to: "/focus", className: "mt-1 inline-block text-gov underline", children: t('pages.activation.ruleRevisionReviewQueue') })] })
|
|
108
|
+
: preview?.status === 'ready' ? _jsxs(_Fragment, { children: [_jsxs("label", { className: "mt-2 block", children: [t('pages.activation.ruleRevisionFeedback'), _jsx("textarea", { value: feedback, maxLength: 4000, disabled: busy, onChange: event => { setFeedback(event.target.value); setPendingRequestId(null); }, className: "mt-1 min-h-20 w-full rounded border border-line bg-panel px-3 py-2 text-ink" })] }), _jsx(Button, { disabled: busy || feedback.trim().length === 0, onClick: () => void revise(), children: t('pages.activation.ruleRevisionOnce') })] }) : preview?.status === 'refused' ? _jsx("p", { className: "mt-2", children: preview.nextAction })
|
|
109
|
+
: _jsx("p", { className: "mt-2", children: t('pages.activation.ownerDecisionLoading') })] });
|
|
110
|
+
}
|
|
44
111
|
function RuleCodeOwnerPanel({ record, onChanged }) {
|
|
45
112
|
const { t } = useTranslation();
|
|
46
113
|
const [review, setReview] = useState(null);
|
|
@@ -96,7 +163,7 @@ function RuleCodeOwnerPanel({ record, onChanged }) {
|
|
|
96
163
|
const value = (number) => number === null ? t('pages.activation.notCollected') : String(number);
|
|
97
164
|
const affectedTools = Array.isArray(review.artifact.content?.affectedTools) ? review.artifact.content.affectedTools.filter((item) => typeof item === 'string') : [];
|
|
98
165
|
const renderWindow = (title, window) => _jsxs("div", { className: "rounded border border-line p-2", children: [_jsx("div", { className: "font-medium text-ink", children: title }), _jsxs("div", { className: "mt-1 grid grid-cols-2 gap-1 md:grid-cols-5", children: [_jsxs("span", { children: [t('pages.activation.eligibleEvaluations'), ": ", value(window.eligible)] }), _jsxs("span", { children: [t('pages.activation.matched'), ": ", value(window.matched)] }), _jsxs("span", { children: [t('pages.activation.blocked'), ": ", value(window.blocked)] }), _jsxs("span", { children: [t('pages.activation.unhealthy'), ": ", value(window.unhealthy)] }), _jsxs("span", { children: [t('pages.activation.circuitTrips'), ": ", window.circuitTrips] })] }), _jsxs("div", { className: "mt-1 text-ink-4", children: [t('pages.activation.toolDistribution'), ": ", window.toolDistribution === null ? t('pages.activation.notCollected') : Object.entries(window.toolDistribution).map(([tool, count]) => `${tool} ${count}`).join(', ') || '—'] })] });
|
|
99
|
-
return _jsxs("div", { className: "mt-4 rounded-[5px] border border-gov/25 bg-surface/70 p-4", "data-testid": `owner-review-${record.activationId}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("strong", { className: "text-[13px] text-ink", children: t('pages.activation.ownerDecisionTitle') }), _jsx("span", { className: "font-mono text-[11px] text-gov", children: review.readiness.status }), review.controlState?.enforcement === 'safety_isolated' && _jsx("span", { className: "text-danger text-[11px]", children: t('pages.activation.safetyIsolation') })] }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2 text-[12px] text-ink-3 md:grid-cols-4", children: [_jsxs("span", { children: [t('pages.activation.eligibleEvaluations'), ": ", value(summary.observed)] }), _jsxs("span", { children: [t('pages.activation.matched'), ": ", value(summary.matched)] }), _jsxs("span", { children: [t('pages.activation.wouldBlock'), ": ", value(summary.wouldBlock)] }), _jsxs("span", { children: [t('pages.activation.wouldAllow'), ": ", value(summary.wouldAllow)] }), _jsxs("span", { children: [t('pages.activation.requireApproval'), ": ", value(summary.requireApproval)] }), _jsxs("span", { children: [t('pages.activation.autoCorrect'), ": ", value(summary.autoCorrect)] }), _jsxs("span", { children: [t('pages.activation.neutralControl'), ": ", value(summary.neutralControl)] }), _jsxs("span", { children: [t('pages.activation.evaluationErrors'), ": ", value(summary.errors)] })] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-4", children: t('pages.activation.shadowWindow', { first: summary.firstObservedAt ?? t('pages.activation.notCollected'), last: summary.lastObservedAt ?? t('pages.activation.notCollected') }) }), _jsx("div", { className: "mt-2 text-[12px] text-ink-3", children: t('pages.activation.runtimeCapability', { version: review.runtimeCapability.hostRuntimeVersion, supported: t(review.runtimeCapability.shadowEvidence ? 'pages.activation.supported' : 'pages.activation.unsupported') }) }), _jsx("div", { className: "mt-2 text-[12px] text-amber", children: t('pages.activation.worstImpact', { tools: affectedTools.join(', ') || t('pages.activation.notCollected') }) }), _jsxs("div", { className: "mt-3 text-[12px] text-ink-3", children: [_jsx("div", { className: "mb-1 font-medium text-ink", children: t('pages.activation.hardSafetyChecks') }), review.readiness.evidenceSnapshot.safetyGateResults.map(check => _jsxs("div", { className: check.status === 'passed' ? 'text-green' : 'text-danger', children: [t(`pages.activation.checks.${check.checkId}`, { defaultValue: check.checkId }), ": ", t(`pages.activation.checkStatus.${check.status}`, { defaultValue: check.status }), check.reasonCode ? ` · ${t(`pages.activation.reasonCodes.${check.reasonCode}`, { defaultValue: check.reasonCode })}` : ''] }, check.checkId))] }), review.readiness.failedChecks.length > 0 && _jsx("ul", { className: "mt-2 text-[12px] text-danger", children: review.readiness.failedChecks.map(check => _jsxs("li", { children: [t(`pages.activation.checks.${check.checkId}`, { defaultValue: check.checkId }), ": ", t(`pages.activation.reasonCodes.${check.reasonCode}`, { defaultValue: check.reasonCode })] }, check.checkId)) }), _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.scopeArtifactCode') }), _jsx("pre", { className: "mt-2 max-h-64 overflow-auto whitespace-pre-wrap text-ink-3", children: JSON.stringify(review.artifact.content, null, 2) })] }), _jsxs("div", { className: "mt-3 grid gap-2 text-[11px] text-ink-3 md:grid-cols-2", children: [renderWindow(t('pages.activation.live24Hours'), review.liveMetrics.last24Hours), renderWindow(t('pages.activation.live7Days'), review.liveMetrics.last7Days)] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-3", children: t('pages.activation.behaviorDrift', { approved: value(review.behaviorDrift.approvedBlockRate), live: value(review.behaviorDrift.liveBlockRate), delta: value(review.behaviorDrift.delta) }) }), review.liveMetrics.representativeSamples.length > 0 && _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.representativeSamples') }), review.liveMetrics.representativeSamples.map((sample, index) => _jsxs("div", { className: "font-mono text-ink-3", children: [sample.toolName, " \u00B7 ", sample.decision, " \u00B7 ", sample.pathCategory] }, `${sample.toolName}-${index}`))] }), review.decisions.length > 0 && _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.ownerDecisionHistory') }), review.decisions.map(decision => _jsxs("div", { className: "mt-1 border-l border-line pl-2 text-ink-3", children: [_jsx("span", { className: "font-mono", children: decision.decidedAt }), " \u00B7 ", decision.decision, " \u00B7 ", decision.principalKind, ":", decision.actorId, " \u00B7 ", decision.authenticationMethod, " \u00B7 ", decision.reasonCode, decision.note ? ` · ${decision.note}` : ''] }, decision.decisionId))] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-4", children: t('pages.activation.circuitConditions') }), review.readiness.status === 'evidence_insufficient' && _jsxs("label", { className: "mt-3 block text-[12px] text-ink-3", children: [t('pages.activation.overrideReason'), _jsxs("select", { value: overrideReason, onChange: event => setOverrideReason(event.target.value), className: "mt-1 w-full rounded border border-line bg-panel px-3 py-2 text-ink", children: [_jsx("option", { value: "rare_behavior", children: t('pages.activation.overrideRareBehavior') }), _jsx("option", { value: "controlled_rollout", children: t('pages.activation.overrideControlledRollout') }), _jsx("option", { value: "owner_accepts_limited_evidence", children: t('pages.activation.overrideOwnerAccepts') })] })] }), _jsx("input", { value: note, onChange: event => setNote(event.target.value), placeholder: t('pages.activation.ownerReviewNote'), className: "mt-3 w-full rounded border border-line bg-panel px-3 py-2 text-[12px] text-ink" }), _jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [review.globalPause?.status === 'paused' && _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void releasePause(), children: t('pages.activation.releaseGlobalPause') }), record.action === 'code_tool_hook_shadow_activate' && _jsxs(_Fragment, { children: [_jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, onClick: () => void decide('continue-observing'), children: t('pages.activation.continueObserving') }), _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void decide('reject-after-shadow'), children: t('pages.activation.rejectAfterShadow') }), _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled || (review.readiness.status !== 'ready' && review.readiness.status !== 'evidence_insufficient') || (review.readiness.status === 'evidence_insufficient' && note.trim().length === 0), onClick: () => void decide('promote'), children: t('pages.activation.confirmLive') })] }), (record.action === 'code_tool_hook_live_activate' || review.controlState?.enforcement === 'safety_isolated') && _jsx(Button, { disabled: busy, variant: "destructive", onClick: () => void decide('emergency-deactivate'), children: t('pages.activation.emergencyDeactivate') }), review.controlState?.enforcement === 'safety_isolated' && _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void decide('recover-to-shadow'), children: t('pages.activation.recoverToShadow') })] }), !review.ownerDecisionEnabled && _jsx("div", { className: "mt-2 text-[11px] text-amber", children: t('pages.activation.rolloutDisabled') })] });
|
|
166
|
+
return _jsxs("div", { className: "mt-4 rounded-[5px] border border-gov/25 bg-surface/70 p-4", "data-testid": `owner-review-${record.activationId}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("strong", { className: "text-[13px] text-ink", children: t('pages.activation.ownerDecisionTitle') }), _jsx("span", { className: "font-mono text-[11px] text-gov", children: review.readiness.status }), review.controlState?.enforcement === 'safety_isolated' && _jsx("span", { className: "text-danger text-[11px]", children: t('pages.activation.safetyIsolation') })] }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2 text-[12px] text-ink-3 md:grid-cols-4", children: [_jsxs("span", { children: [t('pages.activation.eligibleEvaluations'), ": ", value(summary.observed)] }), _jsxs("span", { children: [t('pages.activation.matched'), ": ", value(summary.matched)] }), _jsxs("span", { children: [t('pages.activation.wouldBlock'), ": ", value(summary.wouldBlock)] }), _jsxs("span", { children: [t('pages.activation.wouldAllow'), ": ", value(summary.wouldAllow)] }), _jsxs("span", { children: [t('pages.activation.requireApproval'), ": ", value(summary.requireApproval)] }), _jsxs("span", { children: [t('pages.activation.autoCorrect'), ": ", value(summary.autoCorrect)] }), _jsxs("span", { children: [t('pages.activation.neutralControl'), ": ", value(summary.neutralControl)] }), _jsxs("span", { children: [t('pages.activation.evaluationErrors'), ": ", value(summary.errors)] })] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-4", children: t('pages.activation.shadowWindow', { first: summary.firstObservedAt ?? t('pages.activation.notCollected'), last: summary.lastObservedAt ?? t('pages.activation.notCollected') }) }), _jsx("div", { className: "mt-2 text-[12px] text-ink-3", children: t('pages.activation.runtimeCapability', { version: review.runtimeCapability.hostRuntimeVersion, supported: t(review.runtimeCapability.shadowEvidence ? 'pages.activation.supported' : 'pages.activation.unsupported') }) }), _jsx("div", { className: "mt-2 text-[12px] text-amber", children: t('pages.activation.worstImpact', { tools: affectedTools.join(', ') || t('pages.activation.notCollected') }) }), _jsxs("div", { className: "mt-3 text-[12px] text-ink-3", children: [_jsx("div", { className: "mb-1 font-medium text-ink", children: t('pages.activation.hardSafetyChecks') }), review.readiness.evidenceSnapshot.safetyGateResults.map(check => _jsxs("div", { className: check.status === 'passed' ? 'text-green' : 'text-danger', children: [t(`pages.activation.checks.${check.checkId}`, { defaultValue: check.checkId }), ": ", t(`pages.activation.checkStatus.${check.status}`, { defaultValue: check.status }), check.reasonCode ? ` · ${t(`pages.activation.reasonCodes.${check.reasonCode}`, { defaultValue: check.reasonCode })}` : ''] }, check.checkId))] }), review.readiness.failedChecks.length > 0 && _jsx("ul", { className: "mt-2 text-[12px] text-danger", children: review.readiness.failedChecks.map(check => _jsxs("li", { children: [t(`pages.activation.checks.${check.checkId}`, { defaultValue: check.checkId }), ": ", t(`pages.activation.reasonCodes.${check.reasonCode}`, { defaultValue: check.reasonCode })] }, check.checkId)) }), _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.scopeArtifactCode') }), _jsx("pre", { className: "mt-2 max-h-64 overflow-auto whitespace-pre-wrap text-ink-3", children: JSON.stringify(review.artifact.content, null, 2) })] }), _jsxs("div", { className: "mt-3 grid gap-2 text-[11px] text-ink-3 md:grid-cols-2", children: [renderWindow(t('pages.activation.live24Hours'), review.liveMetrics.last24Hours), renderWindow(t('pages.activation.live7Days'), review.liveMetrics.last7Days)] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-3", children: t('pages.activation.behaviorDrift', { approved: value(review.behaviorDrift.approvedBlockRate), live: value(review.behaviorDrift.liveBlockRate), delta: value(review.behaviorDrift.delta) }) }), review.liveMetrics.representativeSamples.length > 0 && _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.representativeSamples') }), review.liveMetrics.representativeSamples.map((sample, index) => _jsxs("div", { className: "font-mono text-ink-3", children: [sample.toolName, " \u00B7 ", sample.decision, " \u00B7 ", sample.pathCategory] }, `${sample.toolName}-${index}`))] }), review.decisions.length > 0 && _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.ownerDecisionHistory') }), review.decisions.map(decision => _jsxs("div", { className: "mt-1 border-l border-line pl-2 text-ink-3", children: [_jsx("span", { className: "font-mono", children: decision.decidedAt }), " \u00B7 ", decision.decision, " \u00B7 ", decision.principalKind, ":", decision.actorId, " \u00B7 ", decision.authenticationMethod, " \u00B7 ", decision.reasonCode, decision.note ? ` · ${decision.note}` : ''] }, decision.decisionId))] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-4", children: t('pages.activation.circuitConditions') }), record.action === 'code_tool_hook_shadow_activate' && _jsx(ApprovedRuleRevisionPanel, { activationId: record.activationId, onChanged: onChanged }), review.readiness.status === 'evidence_insufficient' && _jsxs("label", { className: "mt-3 block text-[12px] text-ink-3", children: [t('pages.activation.overrideReason'), _jsxs("select", { value: overrideReason, onChange: event => setOverrideReason(event.target.value), className: "mt-1 w-full rounded border border-line bg-panel px-3 py-2 text-ink", children: [_jsx("option", { value: "rare_behavior", children: t('pages.activation.overrideRareBehavior') }), _jsx("option", { value: "controlled_rollout", children: t('pages.activation.overrideControlledRollout') }), _jsx("option", { value: "owner_accepts_limited_evidence", children: t('pages.activation.overrideOwnerAccepts') })] })] }), _jsx("input", { value: note, onChange: event => setNote(event.target.value), placeholder: t('pages.activation.ownerReviewNote'), className: "mt-3 w-full rounded border border-line bg-panel px-3 py-2 text-[12px] text-ink" }), _jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [review.globalPause?.status === 'paused' && _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void releasePause(), children: t('pages.activation.releaseGlobalPause') }), record.action === 'code_tool_hook_shadow_activate' && _jsxs(_Fragment, { children: [_jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, onClick: () => void decide('continue-observing'), children: t('pages.activation.continueObserving') }), _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void decide('reject-after-shadow'), children: t('pages.activation.rejectAfterShadow') }), _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled || (review.readiness.status !== 'ready' && review.readiness.status !== 'evidence_insufficient') || (review.readiness.status === 'evidence_insufficient' && note.trim().length === 0), onClick: () => void decide('promote'), children: t('pages.activation.confirmLive') })] }), (record.action === 'code_tool_hook_live_activate' || review.controlState?.enforcement === 'safety_isolated') && _jsx(Button, { disabled: busy, variant: "destructive", onClick: () => void decide('emergency-deactivate'), children: t('pages.activation.emergencyDeactivate') }), review.controlState?.enforcement === 'safety_isolated' && _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void decide('recover-to-shadow'), children: t('pages.activation.recoverToShadow') })] }), !review.ownerDecisionEnabled && _jsx("div", { className: "mt-2 text-[11px] text-amber", children: t('pages.activation.rolloutDisabled') })] });
|
|
100
167
|
}
|
|
101
168
|
function ActivationFactCard({ record, lifecycleData, receiptCount, onDisable, disabling, onChanged, }) {
|
|
102
169
|
const { t, i18n } = useTranslation();
|