create-principles-disciple 1.130.2 → 1.131.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/console/dist/server/models/ActivationsConsoleModel.js +16 -2
- package/console/dist/server/models/ApprovalsConsoleModel.js +25 -1
- package/console/dist/server/routes/update.js +55 -23
- package/console/dist/server/update/mutation-controller.d.ts +90 -0
- package/console/dist/server/update/mutation-controller.js +89 -0
- package/core/dist/runtime-v2/__tests__/replay-production-parity.test.d.ts +13 -0
- package/core/dist/runtime-v2/__tests__/replay-production-parity.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/__tests__/replay-production-parity.test.js +133 -0
- package/core/dist/runtime-v2/__tests__/replay-production-parity.test.js.map +1 -0
- package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js +36 -0
- package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js.map +1 -1
- package/core/dist/runtime-v2/activation/activation-dispatcher.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/activation-dispatcher.js +4 -0
- package/core/dist/runtime-v2/activation/activation-dispatcher.js.map +1 -1
- package/core/dist/runtime-v2/activation/activation-types.d.ts +11 -1
- 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/production-gate-deps.d.ts +18 -1
- package/core/dist/runtime-v2/activation/production-gate-deps.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/production-gate-deps.js +5 -1
- package/core/dist/runtime-v2/activation/production-gate-deps.js.map +1 -1
- package/core/dist/runtime-v2/activation/writers/rule-host-writer.d.ts +18 -0
- 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 +36 -1
- package/core/dist/runtime-v2/activation/writers/rule-host-writer.js.map +1 -1
- package/core/dist/runtime-v2/golden-trace-replay-validator.d.ts +8 -0
- package/core/dist/runtime-v2/golden-trace-replay-validator.d.ts.map +1 -1
- package/core/dist/runtime-v2/golden-trace-replay-validator.js +9 -3
- package/core/dist/runtime-v2/golden-trace-replay-validator.js.map +1 -1
- package/core/dist/runtime-v2/golden-trace.d.ts +9 -0
- package/core/dist/runtime-v2/golden-trace.d.ts.map +1 -1
- package/core/dist/runtime-v2/golden-trace.js +7 -1
- package/core/dist/runtime-v2/golden-trace.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +5 -1
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +3 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/rule-reliability-validation.test.d.ts +12 -0
- package/core/dist/runtime-v2/internalization/__tests__/rule-reliability-validation.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/__tests__/rule-reliability-validation.test.js +256 -0
- package/core/dist/runtime-v2/internalization/__tests__/rule-reliability-validation.test.js.map +1 -0
- package/core/dist/runtime-v2/internalization/__tests__/tool-semantic-registry.test.d.ts +11 -0
- package/core/dist/runtime-v2/internalization/__tests__/tool-semantic-registry.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/__tests__/tool-semantic-registry.test.js +133 -0
- package/core/dist/runtime-v2/internalization/__tests__/tool-semantic-registry.test.js.map +1 -0
- package/core/dist/runtime-v2/internalization/evaluator-output.d.ts +10 -0
- package/core/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-output.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 -0
- package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/index.d.ts +5 -1
- package/core/dist/runtime-v2/internalization/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/index.js +3 -1
- package/core/dist/runtime-v2/internalization/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/refiner-rulehost-gate.d.ts +21 -0
- package/core/dist/runtime-v2/internalization/refiner-rulehost-gate.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/refiner-rulehost-gate.js +30 -9
- package/core/dist/runtime-v2/internalization/refiner-rulehost-gate.js.map +1 -1
- package/core/dist/runtime-v2/internalization/refiner-sandbox-wrapper.d.ts +18 -0
- package/core/dist/runtime-v2/internalization/refiner-sandbox-wrapper.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/refiner-sandbox-wrapper.js +11 -3
- package/core/dist/runtime-v2/internalization/refiner-sandbox-wrapper.js.map +1 -1
- package/core/dist/runtime-v2/internalization/rule-context-v2.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/rule-context-v2.js +6 -26
- package/core/dist/runtime-v2/internalization/rule-context-v2.js.map +1 -1
- package/core/dist/runtime-v2/internalization/rule-host-contracts.d.ts +10 -1
- 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 +21 -0
- 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 +28 -1
- package/core/dist/runtime-v2/internalization/rule-host-input-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/rule-reliability-validation.d.ts +67 -0
- package/core/dist/runtime-v2/internalization/rule-reliability-validation.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/rule-reliability-validation.js +178 -0
- package/core/dist/runtime-v2/internalization/rule-reliability-validation.js.map +1 -0
- package/core/dist/runtime-v2/internalization/tool-semantic-baseline.d.ts +26 -0
- package/core/dist/runtime-v2/internalization/tool-semantic-baseline.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/tool-semantic-baseline.js +57 -0
- package/core/dist/runtime-v2/internalization/tool-semantic-baseline.js.map +1 -0
- package/core/dist/runtime-v2/internalization/tool-semantic-registry.d.ts +76 -0
- package/core/dist/runtime-v2/internalization/tool-semantic-registry.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/tool-semantic-registry.js +113 -0
- package/core/dist/runtime-v2/internalization/tool-semantic-registry.js.map +1 -0
- package/core/package.json +1 -1
- package/host-runtime/dist/host-tool-declaration.d.ts +57 -0
- package/host-runtime/dist/host-tool-declaration.js +161 -0
- package/host-runtime/dist/host-tool-semantic-resolver.d.ts +47 -0
- package/host-runtime/dist/host-tool-semantic-resolver.js +73 -0
- package/host-runtime/dist/index.d.ts +5 -1
- package/host-runtime/dist/index.js +5 -0
- package/host-runtime/dist/internalization-consumer-cycle.d.ts +9 -0
- package/host-runtime/dist/internalization-consumer-cycle.js +9 -3
- package/host-runtime/dist/internalization-consumer-governance.d.ts +9 -3
- package/host-runtime/dist/internalization-consumer-governance.js +13 -5
- package/host-runtime/dist/production-rulehost-gate.d.ts +10 -0
- package/host-runtime/dist/production-rulehost-gate.js +11 -4
- package/host-runtime/package.json +1 -1
- package/package.json +2 -2
- package/pd-cli/dist/commands/rulecode.d.ts +11 -0
- package/pd-cli/dist/commands/rulecode.d.ts.map +1 -1
- package/pd-cli/dist/commands/rulecode.js +4 -1
- package/pd-cli/dist/commands/rulecode.js.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.js +83 -3
- package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
- package/pd-cli/dist/services/workspace-tool-semantics.d.ts +19 -0
- package/pd-cli/dist/services/workspace-tool-semantics.d.ts.map +1 -0
- package/pd-cli/dist/services/workspace-tool-semantics.js +20 -0
- package/pd-cli/dist/services/workspace-tool-semantics.js.map +1 -0
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +450 -450
- package/plugin/dist/constants/tool-semantics.d.ts +36 -0
- package/plugin/dist/governance-audit.js +143 -143
- package/plugin/dist/rulehost-evidence.js +162 -162
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SqliteConnection, SqliteActivationStateStore, SqlitePIArtifactStore, extractPrincipleId, extractEvidenceRefs, PromotionReadinessReader, RuleHostWriter, createProductionGateDeps, SqliteActivationSafetyStore, isFeatureEnabled, RuleCodeOwnerDecisionService, collectOpenClawPromotionChecks, summarizeRuleCodeShadowEvents, buildPromotionEvidenceSnapshot, } from '@principles/core/runtime-v2';
|
|
2
|
-
import { OPENCLAW_HOST_LIVENESS_CONTRACT } from '@principles/host-runtime';
|
|
2
|
+
import { OPENCLAW_HOST_LIVENESS_CONTRACT, resolveWorkspaceHostToolSemantics } from '@principles/host-runtime';
|
|
3
3
|
import { loadPdConfig, computeFlagsFromLoadResult } from '../config/pd-config-store.js';
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
5
|
import * as path from 'node:path';
|
|
@@ -355,7 +355,21 @@ export class ActivationsConsoleModel {
|
|
|
355
355
|
const decisions = await safetyStore.listDecisions(activationId);
|
|
356
356
|
const telemetry = readRuleCodeTelemetry(this.workspaceDir, activationId, decisions);
|
|
357
357
|
const flags = computeFlagsFromLoadResult(loadPdConfig(this.workspaceDir));
|
|
358
|
-
|
|
358
|
+
// PRI-634-F R3 (SPEC P1-1): promotion readiness evaluates through the
|
|
359
|
+
// SAME workspace host-declaration resolver as CLI/Approvals — the bare
|
|
360
|
+
// baseline must never serve as an existence proof here. Unresolvable
|
|
361
|
+
// provenance degrades to the legacy readiness behavior (read-model
|
|
362
|
+
// probe, not a mutation), consistent with the CLI readiness writer.
|
|
363
|
+
const toolSemantics = resolveWorkspaceHostToolSemantics(this.workspaceDir);
|
|
364
|
+
const writer = new RuleHostWriter({
|
|
365
|
+
gateDeps: createProductionGateDeps({
|
|
366
|
+
projectDir: this.workspaceDir,
|
|
367
|
+
...(toolSemantics.ok ? { toolSemantics: toolSemantics.registry } : {}),
|
|
368
|
+
}),
|
|
369
|
+
featureFlagProbe: id => isFeatureEnabled(flags, id),
|
|
370
|
+
projectDir: this.workspaceDir,
|
|
371
|
+
...(toolSemantics.ok ? { toolSemantics: toolSemantics.registry } : {}),
|
|
372
|
+
});
|
|
359
373
|
const reader = new PromotionReadinessReader({
|
|
360
374
|
listCodeToolHookActivations: () => activationStore.listCodeToolHookActivations(true),
|
|
361
375
|
getArtifactById: id => artifactStore.getArtifactById(id),
|
|
@@ -2,6 +2,7 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { SqliteConnection, SqliteApprovalQueueStore, SqliteActivationStateStore, SqlitePIArtifactStore, ActivationDispatcher, PromptWriter, DeferArchiveWriter, RuleHostWriter, createProductionGateDeps, ApprovalCompletionService, ApprovalQueue, mapConfidenceToLabel, isArtifactRevisionOf, extractPrincipleId, PrincipleTreeLedgerAdapter, MVP_CHANNELS, } from '@principles/core/runtime-v2';
|
|
4
4
|
import { loadPdConfig, computeFlagsFromLoadResult } from '../config/pd-config-store.js';
|
|
5
|
+
import { resolveWorkspaceHostToolSemantics } from '@principles/host-runtime';
|
|
5
6
|
const MVP_PROVEN_CHANNELS = new Set(MVP_CHANNELS);
|
|
6
7
|
const EMPTY_STATS = { pending: 0, approved: 0, rejected: 0, cancelled: 0 };
|
|
7
8
|
function isMissingTableError(err) {
|
|
@@ -338,14 +339,37 @@ export class ApprovalsConsoleModel {
|
|
|
338
339
|
};
|
|
339
340
|
const activationStateStore = new SqliteActivationStateStore(connection);
|
|
340
341
|
const approvalQueueStore = new SqliteApprovalQueueStore(connection);
|
|
342
|
+
// PRI-634-F R3 (SPEC P1-1): the Console resolves tool semantics through
|
|
343
|
+
// the SAME workspace host-declaration resolver as the CLI — never guess
|
|
344
|
+
// a host, never validate against the bare baseline. A code_tool_hook
|
|
345
|
+
// approval whose provenance is unresolvable refuses BEFORE dispatch
|
|
346
|
+
// (fail loud, same reason string as the CLI); e2e seed environments
|
|
347
|
+
// must persist a host declaration like any real host would.
|
|
348
|
+
const toolSemantics = resolveWorkspaceHostToolSemantics(this.workspaceDir);
|
|
349
|
+
if (existing.channel === 'code_tool_hook' && !toolSemantics.ok) {
|
|
350
|
+
return {
|
|
351
|
+
decision: 'refused',
|
|
352
|
+
reason: toolSemantics.reason,
|
|
353
|
+
nextAction: toolSemantics.nextAction,
|
|
354
|
+
channel: existing.channel,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
341
357
|
// Wire all three MVP-Core writers, including RuleHostWriter for code_tool_hook.
|
|
342
358
|
// This fixes the P0 breakpoint where code_tool_hook approvals could not activate.
|
|
343
359
|
const dispatcher = new ActivationDispatcher(artifactReadModel, activationStateStore, {
|
|
344
360
|
writers: [
|
|
345
361
|
new PromptWriter(),
|
|
346
362
|
new RuleHostWriter({
|
|
347
|
-
gateDeps: createProductionGateDeps(
|
|
363
|
+
gateDeps: createProductionGateDeps({
|
|
364
|
+
projectDir: this.workspaceDir,
|
|
365
|
+
...(toolSemantics.ok ? { toolSemantics: toolSemantics.registry } : {}),
|
|
366
|
+
}),
|
|
348
367
|
featureFlagProbe: (flagId) => pdFlags.flags[flagId]?.enabled === true,
|
|
368
|
+
projectDir: this.workspaceDir,
|
|
369
|
+
...(toolSemantics.ok ? { toolSemantics: toolSemantics.registry } : {}),
|
|
370
|
+
// Provenance unresolvable → writer refuses at canActivate with
|
|
371
|
+
// the resolver reason (dispatcher maps it to a refused
|
|
372
|
+
// decision). Ordering note above: artifact-schema reasons win.
|
|
349
373
|
}),
|
|
350
374
|
new DeferArchiveWriter(),
|
|
351
375
|
],
|
|
@@ -10,6 +10,7 @@ import { migrateLegacyExtensionBackups, reservePdBackupDestination, resolvePdBac
|
|
|
10
10
|
import { resolveExtensionsDir, resolveUpdateLayout, resolvePluginDir, readCurrentVersion, } from '../utils/installed-layout.js';
|
|
11
11
|
import { ActivationCompatibilityReadModel } from '@principles/core/runtime-v2';
|
|
12
12
|
import { collectFileDepLinkSpecs } from '../utils/update-links.js';
|
|
13
|
+
import { updateMutationController, LEGACY_MUTATION_AUTHORITY, } from '../update/mutation-controller.js';
|
|
13
14
|
/**
|
|
14
15
|
* Legacy rule contract preflight (2026-08-19): refuse to swap the runtime
|
|
15
16
|
* while an ACTIVE owner-approved rule still depends on a RuleHost contract
|
|
@@ -1081,10 +1082,19 @@ async function doInlineFullUpdate(workspaceDir) {
|
|
|
1081
1082
|
// ---------------------------------------------------------------------------
|
|
1082
1083
|
// Route handler
|
|
1083
1084
|
// ---------------------------------------------------------------------------
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1085
|
+
/**
|
|
1086
|
+
* PRI-659: the four mutation kinds below are the legacy console updater's
|
|
1087
|
+
* implementations, registered into the MutationController (ADR-0023/0024
|
|
1088
|
+
* migration boundary). handleUpdateRoute is now a thin dispatcher: it maps
|
|
1089
|
+
* the URL subPath to a mutation kind and lets the controller resolve the
|
|
1090
|
+
* authority. The implementation stays here verbatim (replace-then-delete —
|
|
1091
|
+
* no third updater, no logic duplication); when ReleaseManager matures it
|
|
1092
|
+
* registers under `release-manager` for the same kinds and this route layer
|
|
1093
|
+
* needs no further change.
|
|
1094
|
+
*/
|
|
1095
|
+
function legacyCheckMutation(req, res, ctx) {
|
|
1096
|
+
return (async () => {
|
|
1097
|
+
const pluginDir = resolvePluginDir(ctx.workspaceDir);
|
|
1088
1098
|
if (req.method !== 'GET') {
|
|
1089
1099
|
sendMethodNotAllowed(res);
|
|
1090
1100
|
return;
|
|
@@ -1111,10 +1121,11 @@ export async function handleUpdateRoute(req, res, workspaceDir, subPath) {
|
|
|
1111
1121
|
catch (err) {
|
|
1112
1122
|
sendError(res, 500, 'update_check_error', err instanceof Error ? err.message : 'Unknown error');
|
|
1113
1123
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1124
|
+
})();
|
|
1125
|
+
}
|
|
1126
|
+
function legacyApplyMutation(req, res, ctx) {
|
|
1127
|
+
return (async () => {
|
|
1128
|
+
const pluginDir = resolvePluginDir(ctx.workspaceDir);
|
|
1118
1129
|
if (req.method !== 'POST') {
|
|
1119
1130
|
sendMethodNotAllowed(res);
|
|
1120
1131
|
return;
|
|
@@ -1146,7 +1157,7 @@ export async function handleUpdateRoute(req, res, workspaceDir, subPath) {
|
|
|
1146
1157
|
return;
|
|
1147
1158
|
}
|
|
1148
1159
|
// Path traversal validation
|
|
1149
|
-
if (!validatePathInWorkspace(targetDir, workspaceDir)) {
|
|
1160
|
+
if (!validatePathInWorkspace(targetDir, ctx.workspaceDir)) {
|
|
1150
1161
|
sendBadRequest(res, 'targetDir must be within workspace or extensions directory');
|
|
1151
1162
|
return;
|
|
1152
1163
|
}
|
|
@@ -1154,7 +1165,7 @@ export async function handleUpdateRoute(req, res, workspaceDir, subPath) {
|
|
|
1154
1165
|
targetDir,
|
|
1155
1166
|
mergeStrategy,
|
|
1156
1167
|
createBackup,
|
|
1157
|
-
}, workspaceDir);
|
|
1168
|
+
}, ctx.workspaceDir);
|
|
1158
1169
|
sendSuccess(res, result);
|
|
1159
1170
|
}
|
|
1160
1171
|
catch (err) {
|
|
@@ -1164,10 +1175,11 @@ export async function handleUpdateRoute(req, res, workspaceDir, subPath) {
|
|
|
1164
1175
|
}
|
|
1165
1176
|
sendError(res, 500, 'update_apply_error', err instanceof Error ? err.message : 'Unknown error');
|
|
1166
1177
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1178
|
+
})();
|
|
1179
|
+
}
|
|
1180
|
+
function legacyRollbackMutation(req, res, ctx) {
|
|
1181
|
+
return (async () => {
|
|
1182
|
+
const pluginDir = resolvePluginDir(ctx.workspaceDir);
|
|
1171
1183
|
if (req.method !== 'POST') {
|
|
1172
1184
|
sendMethodNotAllowed(res);
|
|
1173
1185
|
return;
|
|
@@ -1189,15 +1201,15 @@ export async function handleUpdateRoute(req, res, workspaceDir, subPath) {
|
|
|
1189
1201
|
return;
|
|
1190
1202
|
}
|
|
1191
1203
|
// Path traversal validation
|
|
1192
|
-
if (!validatePathInWorkspace(targetDir, workspaceDir)) {
|
|
1204
|
+
if (!validatePathInWorkspace(targetDir, ctx.workspaceDir)) {
|
|
1193
1205
|
sendBadRequest(res, 'targetDir must be within workspace or extensions directory');
|
|
1194
1206
|
return;
|
|
1195
1207
|
}
|
|
1196
|
-
if (!validatePathInWorkspace(backupDir, workspaceDir)) {
|
|
1208
|
+
if (!validatePathInWorkspace(backupDir, ctx.workspaceDir)) {
|
|
1197
1209
|
sendBadRequest(res, 'backupDir must be within the workspace, extensions directory, or PD backups directory');
|
|
1198
1210
|
return;
|
|
1199
1211
|
}
|
|
1200
|
-
const result = await doRollbackUpdate({ targetDir, backupDir }, workspaceDir);
|
|
1212
|
+
const result = await doRollbackUpdate({ targetDir, backupDir }, ctx.workspaceDir);
|
|
1201
1213
|
sendSuccess(res, result);
|
|
1202
1214
|
}
|
|
1203
1215
|
catch (err) {
|
|
@@ -1207,22 +1219,42 @@ export async function handleUpdateRoute(req, res, workspaceDir, subPath) {
|
|
|
1207
1219
|
}
|
|
1208
1220
|
sendError(res, 500, 'update_rollback_error', err instanceof Error ? err.message : 'Unknown error');
|
|
1209
1221
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1222
|
+
})();
|
|
1223
|
+
}
|
|
1224
|
+
function legacyApplyFullMutation(req, res, ctx) {
|
|
1225
|
+
return (async () => {
|
|
1214
1226
|
if (req.method !== 'POST') {
|
|
1215
1227
|
sendMethodNotAllowed(res);
|
|
1216
1228
|
return;
|
|
1217
1229
|
}
|
|
1218
1230
|
try {
|
|
1219
|
-
const result = await doInlineFullUpdate(workspaceDir);
|
|
1231
|
+
const result = await doInlineFullUpdate(ctx.workspaceDir);
|
|
1220
1232
|
sendSuccess(res, result);
|
|
1221
1233
|
}
|
|
1222
1234
|
catch (err) {
|
|
1223
1235
|
sendError(res, 500, 'update_apply_full_error', err instanceof Error ? err.message : 'Unknown error');
|
|
1224
1236
|
}
|
|
1237
|
+
})();
|
|
1238
|
+
}
|
|
1239
|
+
// PRI-659: register the legacy authority (ADR-0024 D-1 fallback authority)
|
|
1240
|
+
// for every mutation kind. Registration is the ONLY coupling between this
|
|
1241
|
+
// implementation and the controller — no other module needs to know where
|
|
1242
|
+
// the legacy implementation lives.
|
|
1243
|
+
updateMutationController.register('check', { name: LEGACY_MUTATION_AUTHORITY, handler: legacyCheckMutation });
|
|
1244
|
+
updateMutationController.register('apply', { name: LEGACY_MUTATION_AUTHORITY, handler: legacyApplyMutation });
|
|
1245
|
+
updateMutationController.register('apply-full', { name: LEGACY_MUTATION_AUTHORITY, handler: legacyApplyFullMutation });
|
|
1246
|
+
updateMutationController.register('rollback', { name: LEGACY_MUTATION_AUTHORITY, handler: legacyRollbackMutation });
|
|
1247
|
+
const UPDATE_MUTATION_KINDS = new Map([
|
|
1248
|
+
['/check', 'check'],
|
|
1249
|
+
['/apply', 'apply'],
|
|
1250
|
+
['/apply-full', 'apply-full'],
|
|
1251
|
+
['/rollback', 'rollback'],
|
|
1252
|
+
]);
|
|
1253
|
+
export async function handleUpdateRoute(req, res, workspaceDir, subPath) {
|
|
1254
|
+
const kind = UPDATE_MUTATION_KINDS.get(subPath);
|
|
1255
|
+
if (kind === undefined) {
|
|
1256
|
+
sendNotFound(res, `Update route not found: ${subPath}`);
|
|
1225
1257
|
return;
|
|
1226
1258
|
}
|
|
1227
|
-
|
|
1259
|
+
await updateMutationController.dispatch(req, res, { workspaceDir }, kind);
|
|
1228
1260
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation Controller — PRI-659 migration boundary.
|
|
3
|
+
*
|
|
4
|
+
* Governed by:
|
|
5
|
+
* - ADR-0023 (PD Installation Architecture Decisions) — runtime is written
|
|
6
|
+
* only by sanctioned authorities.
|
|
7
|
+
* - ADR-0024 (PD Runtime Mutation Governance, D-1) — the Console Web updater
|
|
8
|
+
* must converge into a trigger/presentation layer over ReleaseManager; it
|
|
9
|
+
* must not remain an independent mutation authority long-term.
|
|
10
|
+
*
|
|
11
|
+
* Role:
|
|
12
|
+
* The controller is the SINGLE routing point between the HTTP surface
|
|
13
|
+
* (`/api/update/*`) and whichever authority actually performs the mutation.
|
|
14
|
+
* It owns:
|
|
15
|
+
* - the mutation-kind registry (check / apply / apply-full / rollback),
|
|
16
|
+
* - authority resolution with an explicit preferred authority
|
|
17
|
+
* (`release-manager`, ADR-0024 D-1) and a safe fallback
|
|
18
|
+
* (`legacy-console-updater`) until ReleaseManager leaves shadow mode,
|
|
19
|
+
* - observability: every dispatched response carries the
|
|
20
|
+
* `X-PD-Mutation-Authority` header so operators can see which
|
|
21
|
+
* authority served a mutation.
|
|
22
|
+
*
|
|
23
|
+
* Non-goals (deliberate):
|
|
24
|
+
* - The controller performs NO file mutation itself. It only routes. It is
|
|
25
|
+
* therefore not a new mutation authority (asserted by migration tests).
|
|
26
|
+
* - It does not wrap, copy, or replace the legacy updater implementation.
|
|
27
|
+
* The legacy implementation stays in `routes/update.ts` and registers
|
|
28
|
+
* itself here. When ReleaseManager matures, it registers under
|
|
29
|
+
* `RELEASE_MANAGER_AUTHORITY` for the same kinds and the route layer
|
|
30
|
+
* needs no further change.
|
|
31
|
+
*/
|
|
32
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
33
|
+
/** Authority that exists today: the inline console updater (routes/update.ts). */
|
|
34
|
+
export declare const LEGACY_MUTATION_AUTHORITY: 'legacy-console-updater';
|
|
35
|
+
/** Authority designated by ADR-0024 D-1 as the long-term mutation authority. */
|
|
36
|
+
export declare const RELEASE_MANAGER_AUTHORITY: 'release-manager';
|
|
37
|
+
/**
|
|
38
|
+
* Preferred authority for every mutation kind. Until ReleaseManager exits
|
|
39
|
+
* shadow mode (its apply()/rollback() still refuse with
|
|
40
|
+
* `shadow_mode_read_only`) nothing registers under this name and resolution
|
|
41
|
+
* falls back to the legacy authority — preserving current capability
|
|
42
|
+
* (replace-then-delete, not delete-then-rebuild).
|
|
43
|
+
*/
|
|
44
|
+
export declare const PREFERRED_MUTATION_AUTHORITY: "release-manager";
|
|
45
|
+
export type MutationKind = 'check' | 'apply' | 'apply-full' | 'rollback';
|
|
46
|
+
export declare const MUTATION_KINDS: readonly MutationKind[];
|
|
47
|
+
export interface MutationContext {
|
|
48
|
+
readonly workspaceDir: string;
|
|
49
|
+
}
|
|
50
|
+
export type MutationHandler = (req: IncomingMessage, res: ServerResponse, ctx: MutationContext) => Promise<void>;
|
|
51
|
+
export interface MutationAuthority {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly handler: MutationHandler;
|
|
54
|
+
}
|
|
55
|
+
export interface MutationGovernanceInfo {
|
|
56
|
+
readonly active: string;
|
|
57
|
+
readonly preferred: string;
|
|
58
|
+
readonly fallback: boolean;
|
|
59
|
+
readonly available: readonly string[];
|
|
60
|
+
}
|
|
61
|
+
export interface ResolvedAuthority {
|
|
62
|
+
readonly authority: MutationAuthority;
|
|
63
|
+
readonly fallback: boolean;
|
|
64
|
+
}
|
|
65
|
+
export declare class MutationController {
|
|
66
|
+
private readonly authorities;
|
|
67
|
+
register(kind: MutationKind, authority: MutationAuthority): void;
|
|
68
|
+
unregister(kind: MutationKind, name: string): boolean;
|
|
69
|
+
hasAuthority(kind: MutationKind, name: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve which authority serves `kind`: the preferred authority when
|
|
72
|
+
* registered, otherwise the legacy authority as an explicit fallback.
|
|
73
|
+
* Throws when nothing is registered — an unregistered kind must fail loud,
|
|
74
|
+
* not silently mutate.
|
|
75
|
+
*/
|
|
76
|
+
resolveAuthority(kind: MutationKind): ResolvedAuthority;
|
|
77
|
+
/**
|
|
78
|
+
* Route one mutation request. Response body contract is owned by the
|
|
79
|
+
* authority handler; the controller only annotates the response with the
|
|
80
|
+
* resolved authority so governance state is observable per request.
|
|
81
|
+
*/
|
|
82
|
+
dispatch(req: IncomingMessage, res: ServerResponse, ctx: MutationContext, kind: MutationKind): Promise<void>;
|
|
83
|
+
/** Governance snapshot for observability and migration tests. */
|
|
84
|
+
describeGovernance(): Record<MutationKind, MutationGovernanceInfo>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Process-wide controller for the `/api/update/*` surface. The legacy
|
|
88
|
+
* updater registers its handlers at module load of `routes/update.ts`.
|
|
89
|
+
*/
|
|
90
|
+
export declare const updateMutationController: MutationController;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** Authority that exists today: the inline console updater (routes/update.ts). */
|
|
2
|
+
export const LEGACY_MUTATION_AUTHORITY = 'legacy-console-updater';
|
|
3
|
+
/** Authority designated by ADR-0024 D-1 as the long-term mutation authority. */
|
|
4
|
+
export const RELEASE_MANAGER_AUTHORITY = 'release-manager';
|
|
5
|
+
/**
|
|
6
|
+
* Preferred authority for every mutation kind. Until ReleaseManager exits
|
|
7
|
+
* shadow mode (its apply()/rollback() still refuse with
|
|
8
|
+
* `shadow_mode_read_only`) nothing registers under this name and resolution
|
|
9
|
+
* falls back to the legacy authority — preserving current capability
|
|
10
|
+
* (replace-then-delete, not delete-then-rebuild).
|
|
11
|
+
*/
|
|
12
|
+
export const PREFERRED_MUTATION_AUTHORITY = RELEASE_MANAGER_AUTHORITY;
|
|
13
|
+
export const MUTATION_KINDS = ['check', 'apply', 'apply-full', 'rollback'];
|
|
14
|
+
export class MutationController {
|
|
15
|
+
authorities = new Map();
|
|
16
|
+
register(kind, authority) {
|
|
17
|
+
let table = this.authorities.get(kind);
|
|
18
|
+
if (table === undefined) {
|
|
19
|
+
table = new Map();
|
|
20
|
+
this.authorities.set(kind, table);
|
|
21
|
+
}
|
|
22
|
+
table.set(authority.name, authority);
|
|
23
|
+
}
|
|
24
|
+
unregister(kind, name) {
|
|
25
|
+
return this.authorities.get(kind)?.delete(name) ?? false;
|
|
26
|
+
}
|
|
27
|
+
hasAuthority(kind, name) {
|
|
28
|
+
return this.authorities.get(kind)?.has(name) ?? false;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve which authority serves `kind`: the preferred authority when
|
|
32
|
+
* registered, otherwise the legacy authority as an explicit fallback.
|
|
33
|
+
* Throws when nothing is registered — an unregistered kind must fail loud,
|
|
34
|
+
* not silently mutate.
|
|
35
|
+
*/
|
|
36
|
+
resolveAuthority(kind) {
|
|
37
|
+
const table = this.authorities.get(kind);
|
|
38
|
+
if (table === undefined || table.size === 0) {
|
|
39
|
+
throw new Error(`No mutation authority registered for kind: ${kind}`);
|
|
40
|
+
}
|
|
41
|
+
const preferred = table.get(PREFERRED_MUTATION_AUTHORITY);
|
|
42
|
+
if (preferred !== undefined) {
|
|
43
|
+
return { authority: preferred, fallback: false };
|
|
44
|
+
}
|
|
45
|
+
const legacy = table.get(LEGACY_MUTATION_AUTHORITY);
|
|
46
|
+
if (legacy !== undefined) {
|
|
47
|
+
return { authority: legacy, fallback: true };
|
|
48
|
+
}
|
|
49
|
+
// Multiple non-preferred, non-legacy authorities: refuse to guess.
|
|
50
|
+
throw new Error(`No preferred (${PREFERRED_MUTATION_AUTHORITY}) or legacy (${LEGACY_MUTATION_AUTHORITY}) authority registered for kind: ${kind}`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Route one mutation request. Response body contract is owned by the
|
|
54
|
+
* authority handler; the controller only annotates the response with the
|
|
55
|
+
* resolved authority so governance state is observable per request.
|
|
56
|
+
*/
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- (req, res) mirrors the Node handler shape used across this server
|
|
58
|
+
async dispatch(req, res, ctx, kind) {
|
|
59
|
+
const { authority, fallback } = this.resolveAuthority(kind);
|
|
60
|
+
const headerValue = fallback
|
|
61
|
+
? `${authority.name} (preferred: ${PREFERRED_MUTATION_AUTHORITY} not yet available)`
|
|
62
|
+
: authority.name;
|
|
63
|
+
res.setHeader('X-PD-Mutation-Authority', headerValue);
|
|
64
|
+
await authority.handler(req, res, ctx);
|
|
65
|
+
}
|
|
66
|
+
/** Governance snapshot for observability and migration tests. */
|
|
67
|
+
describeGovernance() {
|
|
68
|
+
const snapshot = {};
|
|
69
|
+
for (const kind of MUTATION_KINDS) {
|
|
70
|
+
const table = this.authorities.get(kind);
|
|
71
|
+
const available = table !== undefined ? [...table.keys()] : [];
|
|
72
|
+
let info;
|
|
73
|
+
if (table === undefined || table.size === 0) {
|
|
74
|
+
info = { active: 'none', preferred: PREFERRED_MUTATION_AUTHORITY, fallback: false, available };
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const { authority, fallback } = this.resolveAuthority(kind);
|
|
78
|
+
info = { active: authority.name, preferred: PREFERRED_MUTATION_AUTHORITY, fallback, available };
|
|
79
|
+
}
|
|
80
|
+
snapshot[kind] = info;
|
|
81
|
+
}
|
|
82
|
+
return snapshot;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Process-wide controller for the `/api/update/*` surface. The legacy
|
|
87
|
+
* updater registers its handlers at module load of `routes/update.ts`.
|
|
88
|
+
*/
|
|
89
|
+
export const updateMutationController = new MutationController();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replay / Production Input Parity tests — PRI-634-F Phase 2
|
|
3
|
+
*
|
|
4
|
+
* SPEC §12 Phase 2 acceptance: for the same {tool, params, workspace}, the
|
|
5
|
+
* replay input builder and the production action builder produce identical
|
|
6
|
+
* canonicalKind / normalizedPath / paramsSummary.
|
|
7
|
+
*
|
|
8
|
+
* Baseline defect this file pins (see docs/pri-634-f-baseline-report.md §1.3):
|
|
9
|
+
* replay builders never passed isBashTool/isWriteTool hints, so bash command
|
|
10
|
+
* extraction and write-tool synthetic paths never fired on the replay side.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=replay-production-parity.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-production-parity.test.d.ts","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/replay-production-parity.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replay / Production Input Parity tests — PRI-634-F Phase 2
|
|
3
|
+
*
|
|
4
|
+
* SPEC §12 Phase 2 acceptance: for the same {tool, params, workspace}, the
|
|
5
|
+
* replay input builder and the production action builder produce identical
|
|
6
|
+
* canonicalKind / normalizedPath / paramsSummary.
|
|
7
|
+
*
|
|
8
|
+
* Baseline defect this file pins (see docs/pri-634-f-baseline-report.md §1.3):
|
|
9
|
+
* replay builders never passed isBashTool/isWriteTool hints, so bash command
|
|
10
|
+
* extraction and write-tool synthetic paths never fired on the replay side.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, expect, it } from 'vitest';
|
|
13
|
+
import { buildRuleHostAction } from '../internalization/rule-host-input-builder.js';
|
|
14
|
+
import { createSyntheticRuleHostInput } from '../golden-trace.js';
|
|
15
|
+
import { evaluateInRefinerSandbox } from '../internalization/refiner-sandbox-wrapper.js';
|
|
16
|
+
import { buildToolSemanticRegistry } from '../internalization/tool-semantic-registry.js';
|
|
17
|
+
const PROJECT_DIR = '/workspace/project';
|
|
18
|
+
function registry() {
|
|
19
|
+
const built = buildToolSemanticRegistry([
|
|
20
|
+
{ rawToolName: 'shell', canonicalKind: 'execute' },
|
|
21
|
+
{ rawToolName: 'delete_file', canonicalKind: 'write' },
|
|
22
|
+
]);
|
|
23
|
+
if (!built.ok)
|
|
24
|
+
throw new Error(built.errors.join('; '));
|
|
25
|
+
return built.registry;
|
|
26
|
+
}
|
|
27
|
+
const FIXTURES = [
|
|
28
|
+
{ label: 'write tool with absolute path', toolName: 'write_file', params: { file_path: '/workspace/project/src/a.ts', content: 'x' } },
|
|
29
|
+
{ label: 'write tool with relative path', toolName: 'edit', params: { path: 'src/a.ts', old_string: 'a', new_string: 'b' } },
|
|
30
|
+
{ label: 'bash tool, mutation target', toolName: 'bash', params: { command: 'rm -rf /workspace/project/build' } },
|
|
31
|
+
{ label: 'bash tool, no file target (full command)', toolName: 'bash', params: { command: 'npm test' } },
|
|
32
|
+
{ label: 'generic LLM alias execute_command', toolName: 'execute_command', params: { command: 'python deploy.py' } },
|
|
33
|
+
{ label: 'host alias shell (was other before host layer)', toolName: 'shell', params: { command: 'grep -r TODO .' } },
|
|
34
|
+
{ label: 'write tool without any path (synthetic <tool:X>)', toolName: 'delete_file', params: {} },
|
|
35
|
+
{ label: 'write tool with pathless write', toolName: 'insert', params: { content: 'data' } },
|
|
36
|
+
{ label: 'read tool with path', toolName: 'read_file', params: { file_path: '/workspace/project/README.md' } },
|
|
37
|
+
{ label: 'unknown tool (resolve other, no extraction)', toolName: 'mystery_tool', params: { path: '/workspace/project/x' } },
|
|
38
|
+
];
|
|
39
|
+
describe('replay/production input parity (SPEC §12 Phase 2)', () => {
|
|
40
|
+
for (const fixture of FIXTURES) {
|
|
41
|
+
it(`replay input === production action: ${fixture.label}`, () => {
|
|
42
|
+
const toolSemantics = registry();
|
|
43
|
+
const canonicalKind = toolSemantics.resolve(fixture.toolName);
|
|
44
|
+
// Production side: the gate resolves the kind from the registry, then
|
|
45
|
+
// hands it to the shared builder (hooks/gate.ts + production-rulehost-gate.ts).
|
|
46
|
+
const productionAction = buildRuleHostAction(fixture.toolName, fixture.params, PROJECT_DIR, { canonicalKind });
|
|
47
|
+
// Replay side: the synthetic input builder resolves through the SAME
|
|
48
|
+
// registry (refiner-sandbox-wrapper + golden-trace-replay-validator).
|
|
49
|
+
const replayInput = createSyntheticRuleHostInput({ toolName: fixture.toolName, params: fixture.params }, {}, { projectDir: PROJECT_DIR, toolSemantics });
|
|
50
|
+
expect(replayInput.action.toolName).toBe(productionAction.toolName);
|
|
51
|
+
expect(replayInput.action.canonicalKind).toBe(productionAction.canonicalKind);
|
|
52
|
+
expect(replayInput.action.normalizedPath).toBe(productionAction.normalizedPath);
|
|
53
|
+
expect(replayInput.action.paramsSummary).toEqual(productionAction.paramsSummary);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
it('registry-driven replay now extracts bash command paths (baseline defect pinned)', () => {
|
|
57
|
+
const toolSemantics = registry();
|
|
58
|
+
const input = createSyntheticRuleHostInput({ toolName: 'bash', params: { command: 'rm -rf /workspace/project/build' } }, {}, { projectDir: PROJECT_DIR, toolSemantics });
|
|
59
|
+
// Baseline: hints absent → extraction skipped → normalizedPath null.
|
|
60
|
+
expect(input.action.normalizedPath).toBe('build');
|
|
61
|
+
expect(input.action.canonicalKind).toBe('execute');
|
|
62
|
+
});
|
|
63
|
+
it('legacy replay behavior is unchanged without a registry (back-compat)', () => {
|
|
64
|
+
const input = createSyntheticRuleHostInput({ toolName: 'bash', params: { command: 'rm -rf /workspace/project/build' } }, {}, { projectDir: PROJECT_DIR });
|
|
65
|
+
// PRI-439 legacy semantics: projectDir present but no hints → bash command
|
|
66
|
+
// extraction never fires; normalizePathPure(null) === '' (not a path).
|
|
67
|
+
expect(input.action.normalizedPath).toBe('');
|
|
68
|
+
expect(input.action.canonicalKind).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('activation-gate sandbox parity (refiner-sandbox-wrapper)', () => {
|
|
72
|
+
it('a rule reading input.action sees the bash command path when toolSemantics is provided', () => {
|
|
73
|
+
const toolSemantics = registry();
|
|
74
|
+
const trace = {
|
|
75
|
+
traceId: 'trace-bash-parity',
|
|
76
|
+
version: 1,
|
|
77
|
+
createdAt: '2026-09-03T00:00:00.000Z',
|
|
78
|
+
cases: [
|
|
79
|
+
{
|
|
80
|
+
caseId: 'negative-1',
|
|
81
|
+
kind: 'negative',
|
|
82
|
+
toolName: 'bash',
|
|
83
|
+
params: { command: 'rm -rf /workspace/project/build' },
|
|
84
|
+
expectedDecision: 'block',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
caseId: 'positive-1',
|
|
88
|
+
kind: 'positive',
|
|
89
|
+
toolName: 'bash',
|
|
90
|
+
params: { command: 'echo ok' },
|
|
91
|
+
expectedDecision: 'allow',
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
// The rule matches on the extracted normalizedPath — under the baseline
|
|
96
|
+
// (registry absent) it would never match because normalizedPath was null.
|
|
97
|
+
// evaluateCode is a plain closure: the wrapper uses `code` only for static
|
|
98
|
+
// checks, so the behavioral contract under test is the INPUT it receives.
|
|
99
|
+
const code = [
|
|
100
|
+
'const evaluate = (input) => {',
|
|
101
|
+
' if (input.action.normalizedPath === "build") {',
|
|
102
|
+
' return { decision: "block", matched: true, reason: "destructive build wipe" };',
|
|
103
|
+
' }',
|
|
104
|
+
' return { decision: "allow", matched: false, reason: "not the protected path" };',
|
|
105
|
+
'};',
|
|
106
|
+
].join('\n');
|
|
107
|
+
const result = evaluateInRefinerSandbox(code, trace, {
|
|
108
|
+
evaluateCode: (input) => {
|
|
109
|
+
if (input.action.normalizedPath === 'build') {
|
|
110
|
+
return { decision: 'block', matched: true, reason: 'destructive build wipe' };
|
|
111
|
+
}
|
|
112
|
+
return { decision: 'allow', matched: false, reason: 'not the protected path' };
|
|
113
|
+
},
|
|
114
|
+
toolSemantics,
|
|
115
|
+
projectDir: PROJECT_DIR,
|
|
116
|
+
});
|
|
117
|
+
expect(result.success).toBe(true);
|
|
118
|
+
expect(result.failedCases).toHaveLength(0);
|
|
119
|
+
// Negative control: without the registry+root the same closure cannot
|
|
120
|
+
// match — pinning WHY the parity plumbing is required on the replay side.
|
|
121
|
+
const baselineResult = evaluateInRefinerSandbox(code, trace, {
|
|
122
|
+
evaluateCode: (input) => {
|
|
123
|
+
if (input.action.normalizedPath === 'build') {
|
|
124
|
+
return { decision: 'block', matched: true, reason: 'destructive build wipe' };
|
|
125
|
+
}
|
|
126
|
+
return { decision: 'allow', matched: false, reason: 'not the protected path' };
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
expect(baselineResult.success).toBe(false);
|
|
130
|
+
expect(baselineResult.failedCases[0]?.caseId).toBe('negative-1');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=replay-production-parity.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-production-parity.test.js","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/replay-production-parity.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -673,4 +673,40 @@ describe('activation type helpers', () => {
|
|
|
673
673
|
expect(HIGH_RISK_CHANNEL_MAP.code_tool_hook).toBe('high');
|
|
674
674
|
});
|
|
675
675
|
});
|
|
676
|
+
// ── PRI-634-F R2: structured failure passthrough (review P2) ────────────────
|
|
677
|
+
describe('ActivationDispatcher — structured reliability failure passthrough', () => {
|
|
678
|
+
it('a refused canActivate with a structured failure surfaces it on the refused decision', async () => {
|
|
679
|
+
const artifactStore = new MemoryArtifactReadModel();
|
|
680
|
+
artifactStore.addArtifact(makePrincipleArtifact());
|
|
681
|
+
const failingWriter = {
|
|
682
|
+
channel: 'code_tool_hook',
|
|
683
|
+
canActivate: async () => ({
|
|
684
|
+
ok: false,
|
|
685
|
+
reason: 'rule_reliability_tool_not_host_dispatchable:adapter — execute_command…',
|
|
686
|
+
riskLevel: 'high',
|
|
687
|
+
failure: {
|
|
688
|
+
layer: 'adapter',
|
|
689
|
+
reasonCode: 'tool_not_host_dispatchable',
|
|
690
|
+
evidence: 'affectedTools semantically classifiable but NOT dispatched by this host: execute_command',
|
|
691
|
+
nextAction: 'regenerate the rule against host-declared tool names',
|
|
692
|
+
},
|
|
693
|
+
}),
|
|
694
|
+
activate: async () => { throw new Error('must not be reached'); },
|
|
695
|
+
};
|
|
696
|
+
const dispatcher = new ActivationDispatcher(artifactStore, new MemoryActivationStateStore(), {
|
|
697
|
+
writers: [failingWriter],
|
|
698
|
+
// code_tool_hook is high-risk: the approval store must exist so the
|
|
699
|
+
// flow reaches the writer's canActivate (the refusal under test)
|
|
700
|
+
// instead of the earlier requires_approval refusal.
|
|
701
|
+
approvalQueueStore: { enqueue: async () => ({ approvalId: 'apr-1', requestedAt: '2026-05-17T00:00:00.000Z' }) },
|
|
702
|
+
});
|
|
703
|
+
const decision = await dispatcher.dispatch(makeDispatchInput({ channel: 'code_tool_hook', confirm: true }));
|
|
704
|
+
expect(decision.decision).toBe('refused');
|
|
705
|
+
if (decision.decision !== 'refused')
|
|
706
|
+
return;
|
|
707
|
+
expect(decision.failure?.layer).toBe('adapter');
|
|
708
|
+
expect(decision.failure?.reasonCode).toBe('tool_not_host_dispatchable');
|
|
709
|
+
expect(decision.failure?.nextAction).toContain('host-declared');
|
|
710
|
+
});
|
|
711
|
+
});
|
|
676
712
|
//# sourceMappingURL=activation-dispatcher.test.js.map
|