@sema-agent/client-core 0.91.0 → 0.93.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/CHANGELOG.md +351 -0
- package/README.md +51 -34
- package/dist/adapt/arms.js +6 -12
- package/dist/adapt/panelTasks.d.ts +0 -2
- package/dist/adapt/panelTasks.js +6 -26
- package/dist/adapt/toolCards.js +6 -4
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +3 -3
- package/dist/adapter/activeRunSelfHeal.d.ts +0 -2
- package/dist/adapter/activeRunSelfHeal.js +5 -27
- package/dist/adapter/downstream/terminalToSdkResult.js +16 -7
- package/dist/adapter/runStream.js +6 -2
- package/dist/agentsWireCaps.js +1 -1
- package/dist/approvalDecidedByCapability.d.ts +16 -0
- package/dist/approvalDecidedByCapability.js +47 -0
- package/dist/approvalsStreamLiveCapability.js +3 -3
- package/dist/askSurvivesStandingAnswer.d.ts +4 -1
- package/dist/askSurvivesStandingAnswer.js +13 -0
- package/dist/autoModeUnavailable.js +3 -2
- package/dist/centerWiringCapability.js +1 -1
- package/dist/cloudConfigWireCaps.js +10 -9
- package/dist/compensations.js +7 -7
- package/dist/controlRouter.d.ts +23 -23
- package/dist/controlRouter.js +114 -49
- package/dist/coreValuePorts.d.ts +4 -2
- package/dist/coreValuePorts.js +5 -0
- package/dist/decideFailureNote.js +4 -2
- package/dist/decideReceipt.d.ts +11 -0
- package/dist/decideReceipt.js +95 -9
- package/dist/detachWire.d.ts +2 -14
- package/dist/detachWire.js +1 -47
- package/dist/deviceExecutorManagementCapability.d.ts +6 -2
- package/dist/deviceExecutorManagementCapability.js +61 -15
- package/dist/displayUntrusted.js +2 -1
- package/dist/effectiveFacts.d.ts +14 -4
- package/dist/effectiveFacts.js +45 -5
- package/dist/engineAgentAbsence.js +2 -1
- package/dist/engineCapReader.js +16 -10
- package/dist/engineCapsCache.d.ts +2 -0
- package/dist/engineCapsCache.js +13 -2
- package/dist/engineConfigDrift.d.ts +20 -0
- package/dist/engineConfigDrift.js +54 -0
- package/dist/engineErrorCodes.d.ts +17 -0
- package/dist/engineErrorCodes.js +45 -0
- package/dist/engineInlineTaskStats.d.ts +1 -0
- package/dist/engineNoticeCodes.d.ts +16 -2
- package/dist/engineNoticeCodes.js +71 -0
- package/dist/engineWireFor.d.ts +22 -0
- package/dist/engineWireFor.js +154 -9
- package/dist/engineWireSdk.d.ts +0 -1
- package/dist/engineWireSdk.js +0 -3
- package/dist/executionLaneCapability.js +4 -2
- package/dist/fileHistoryCaptureCapability.d.ts +1 -1
- package/dist/fileHistoryCaptureCapability.js +1 -6
- package/dist/fileHistoryLegNotice.d.ts +1 -1
- package/dist/fileHistoryLegNotice.js +1 -6
- package/dist/gateOutcome.js +2 -17
- package/dist/gateVocabulary.d.ts +2 -8
- package/dist/gateVocabulary.js +6 -32
- package/dist/generated/engineFactTables.d.ts +17 -1
- package/dist/generated/engineFactTables.js +91 -0
- package/dist/generated/engineNoticeTables.d.ts +1 -1
- package/dist/generated/engineNoticeTables.js +4 -1
- package/dist/generated/settingsSchemaTables.d.ts +8 -0
- package/dist/generated/settingsSchemaTables.js +40 -0
- package/dist/goalStopHook.d.ts +1 -8
- package/dist/goalStopHook.js +2 -58
- package/dist/headlessPermissionModeWire.d.ts +3 -2
- package/dist/headlessPermissionModeWire.js +46 -5
- package/dist/hitl/allowRuleShadow.d.ts +21 -0
- package/dist/hitl/allowRuleShadow.js +167 -0
- package/dist/hitl/askClassPair.d.ts +14 -0
- package/dist/hitl/askClassPair.js +42 -0
- package/dist/hitl/askGateWire.js +1 -0
- package/dist/hitl/askParkRowRouting.js +2 -2
- package/dist/hitl/askSurvivesPosture.d.ts +5 -0
- package/dist/hitl/askSurvivesPosture.js +17 -0
- package/dist/hitl/automaticAnswerVerdict.d.ts +21 -0
- package/dist/hitl/automaticAnswerVerdict.js +143 -0
- package/dist/hitl/crashConverged.d.ts +1 -0
- package/dist/hitl/crashConverged.js +1 -1
- package/dist/hitl/frameRouter.d.ts +2 -0
- package/dist/hitl/frameRouter.js +73 -16
- package/dist/hitl/gateLedger.d.ts +7 -0
- package/dist/hitl/gateLedger.js +37 -5
- package/dist/hitl/hitlBridge.d.ts +1 -0
- package/dist/hitl/hitlBridge.js +6 -1
- package/dist/hitl/hitlHostSurface.d.ts +5 -2
- package/dist/hitl/hitlHostSurface.js +25 -3
- package/dist/hitl/livePendingAsk.js +24 -5
- package/dist/hitl/parkResolver.js +24 -4
- package/dist/hitl/persistedRulesWire.d.ts +4 -1
- package/dist/hitl/persistedRulesWire.js +38 -19
- package/dist/hitl/planReviewWire.d.ts +0 -2
- package/dist/hitl/planReviewWire.js +3 -20
- package/dist/hitl/replayedApproval.d.ts +21 -0
- package/dist/hitl/replayedApproval.js +89 -0
- package/dist/hitl/sessionPolicyDeliverable.js +20 -2
- package/dist/hitl/sessionPolicyWire.d.ts +10 -0
- package/dist/hitl/sessionPolicyWire.js +19 -6
- package/dist/hitl/toolApprovalWire.d.ts +27 -7
- package/dist/hitl/toolApprovalWire.js +153 -94
- package/dist/hookMatcherVerdict.js +2 -1
- package/dist/hooksWireCaps.d.ts +1 -1
- package/dist/hooksWireCaps.js +1 -1
- package/dist/humanTurn.js +3 -4
- package/dist/index.d.ts +21 -6
- package/dist/index.js +17 -4
- package/dist/inheritEnvWire.d.ts +1 -4
- package/dist/inheritEnvWire.js +3 -22
- package/dist/limitsWire.d.ts +0 -6
- package/dist/limitsWire.js +0 -38
- package/dist/mcpLiveness.js +2 -1
- package/dist/mcpProbeCapability.js +1 -1
- package/dist/mcpProbeWire.js +10 -12
- package/dist/mcpReconnect.d.ts +5 -0
- package/dist/mcpReconnect.js +28 -0
- package/dist/memoryComplianceCapability.js +2 -2
- package/dist/memoryOriginCapability.js +2 -2
- package/dist/memoryVerbsWire.d.ts +1 -1
- package/dist/memoryVerbsWire.js +17 -13
- package/dist/model/catalog.js +2 -1
- package/dist/model/providerPresets.d.ts +3 -1
- package/dist/model/tierVocabulary.d.ts +2 -1
- package/dist/model/tierVocabulary.js +3 -8
- package/dist/ownKey.d.ts +2 -0
- package/dist/ownKey.js +15 -0
- package/dist/peerFrames.js +2 -2
- package/dist/peerLaneCapability.js +2 -2
- package/dist/permissionModesCapability.d.ts +16 -0
- package/dist/permissionModesCapability.js +71 -0
- package/dist/permissionRuleIssue.js +10 -7
- package/dist/permissionRulesWriteCapability.js +2 -2
- package/dist/permissionWireCaps.d.ts +2 -0
- package/dist/permissionWireCaps.js +8 -1
- package/dist/permissionsAllowGrantCapability.js +7 -2
- package/dist/permissionsDeclaration.d.ts +7 -1
- package/dist/permissionsDeclaration.js +29 -20
- package/dist/pluginHooksWire.js +2 -11
- package/dist/postureKnob.js +1 -1
- package/dist/promptProfileWireCaps.d.ts +2 -1
- package/dist/protocolSegment.d.ts +11 -0
- package/dist/protocolSegment.js +31 -0
- package/dist/readFacePosture.js +1 -1
- package/dist/request/taskRequest.js +1 -15
- package/dist/resumeRefusalCopy.d.ts +1 -0
- package/dist/resumeRefusalCopy.js +26 -3
- package/dist/retryStatus.js +2 -1
- package/dist/rewindArchiveCapability.js +2 -2
- package/dist/ruleCompileVerdict.d.ts +2 -0
- package/dist/ruleCompileVerdict.js +13 -0
- package/dist/ruleRemovalConsequence.d.ts +2 -1
- package/dist/runCancelContext.d.ts +12 -0
- package/dist/runCancelContext.js +83 -1
- package/dist/runTerminal.d.ts +16 -0
- package/dist/runTerminal.js +32 -3
- package/dist/seam.d.ts +1 -0
- package/dist/seam.js +1 -1
- package/dist/seatContract.d.ts +2 -1
- package/dist/sessionMemoryStatus.d.ts +2 -4
- package/dist/sessionPolicyCapability.js +1 -1
- package/dist/sessionSyncFileHistoryCapability.d.ts +18 -0
- package/dist/sessionSyncFileHistoryCapability.js +50 -0
- package/dist/skillToolListsCapability.d.ts +18 -0
- package/dist/skillToolListsCapability.js +50 -0
- package/dist/sqlEngineCapability.js +2 -9
- package/dist/steerPriorityCapability.d.ts +18 -0
- package/dist/steerPriorityCapability.js +80 -0
- package/dist/steerReceipt.d.ts +9 -0
- package/dist/steerReceipt.js +25 -0
- package/dist/storePostureCapability.js +2 -2
- package/dist/subagent/engineSubagentSteer.js +2 -9
- package/dist/supportFloor.d.ts +1 -0
- package/dist/supportFloor.js +15 -5
- package/dist/taskRequestWords.d.ts +4 -2
- package/dist/taskRequestWords.js +1 -1
- package/dist/toolHistoryBadTurn.js +1 -2
- package/dist/toolResult.d.ts +10 -1
- package/dist/toolResult.js +80 -6
- package/dist/types/engineState.d.ts +2 -12
- package/dist/userInvokedSkillsCapability.d.ts +18 -0
- package/dist/userInvokedSkillsCapability.js +50 -0
- package/dist/webSearchBackendCapability.js +2 -2
- package/dist/webSearchWireCaps.d.ts +1 -1
- package/dist/webSearchWireCaps.js +40 -2
- package/dist/wireFailureShape.d.ts +2 -0
- package/dist/wireFailureShape.js +6 -2
- package/dist/wireRefusalCopy.d.ts +9 -3
- package/dist/wireRefusalCopy.js +41 -4
- package/dist/workflowsJournalCapability.d.ts +18 -0
- package/dist/workflowsJournalCapability.js +50 -0
- package/dist/writeProtectionCapability.d.ts +2 -4
- package/dist/writeProtectionCapability.js +1 -1
- package/docs/INTEGRATION-CLIENTS.md +1516 -18
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { putOwnKey } from '../ownKey.js';
|
|
1
|
+
import { putOwnKey, readOwnDataValue } from '../ownKey.js';
|
|
2
2
|
import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
|
|
3
|
-
import { CHECKPOINT_UNSUPPORTED_VERSION, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
|
|
3
|
+
import { ASK_GONE, CHECKPOINT_UNSUPPORTED_VERSION, DECIDE_WORKFLOW_PARK_FORKED, DECIDE_WORKFLOW_PARKS_UNREADABLE, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
|
|
4
4
|
import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
|
|
5
5
|
import { hostLog } from '../host.js';
|
|
6
6
|
import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
|
|
@@ -58,10 +58,16 @@ function alreadyResolvedFailureVerdict(outcome) {
|
|
|
58
58
|
return false;
|
|
59
59
|
if ('errorCode' in outcome && isDecideParkMovedCode(outcome.errorCode))
|
|
60
60
|
return true;
|
|
61
|
+
if ('errorCode' in outcome && outcome.errorCode === ASK_GONE)
|
|
62
|
+
return true;
|
|
61
63
|
return isAlreadyResolvedGateReason(outcome.reason);
|
|
62
64
|
}
|
|
63
65
|
export function isGateStandingErrorCode(code, retriable) {
|
|
64
|
-
return code === RESUME_POLICY_FOLD_CODE ||
|
|
66
|
+
return (code === RESUME_POLICY_FOLD_CODE ||
|
|
67
|
+
code === 'corrupt' ||
|
|
68
|
+
(code === CHECKPOINT_UNSUPPORTED_VERSION && retriable === true) ||
|
|
69
|
+
code === DECIDE_WORKFLOW_PARKS_UNREADABLE ||
|
|
70
|
+
code === DECIDE_WORKFLOW_PARK_FORKED);
|
|
65
71
|
}
|
|
66
72
|
export function isCodeClassifiedGateFailure(code, retriable) {
|
|
67
73
|
return isGateStandingErrorCode(code, retriable) || code === PARKED_RESUME_STARTUP_FAILED;
|
|
@@ -190,10 +196,18 @@ function isFetchStepNoPending(outcome) {
|
|
|
190
196
|
async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
|
|
191
197
|
const { deps, led, taskId } = ctx;
|
|
192
198
|
if (park.gate === 'fs') {
|
|
199
|
+
let decidedByReading;
|
|
200
|
+
try {
|
|
201
|
+
decidedByReading = readOwnDataValue(deps.approvalLane, 'approvalDecidedBy');
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
decidedByReading = undefined;
|
|
205
|
+
}
|
|
193
206
|
const outcome = await surfaceFsApprovalAndDecide({
|
|
194
207
|
client: deps.client,
|
|
195
208
|
...(onPresented ? { onPresented } : {}),
|
|
196
209
|
...(deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : {}),
|
|
210
|
+
...(decidedByReading !== undefined ? { approvalDecidedBy: decidedByReading } : {}),
|
|
197
211
|
}, taskId, argsByCallOf(led), ctx.signal, gatedCallId);
|
|
198
212
|
const outcomeDenied = 'denied' in outcome ? outcome.denied : undefined;
|
|
199
213
|
if (outcome.kind === 'decided' && outcomeDenied === true && outcome.gatedCallId) {
|
|
@@ -208,6 +222,12 @@ async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
|
|
|
208
222
|
}
|
|
209
223
|
return surfaceGateAndDecide(deps, taskId, argsByCallOf(led), ctx.signal, gatedCallId, onPresented);
|
|
210
224
|
}
|
|
225
|
+
function decidedIdentityCandidate(led, park) {
|
|
226
|
+
const id = park.gatedCallId;
|
|
227
|
+
if (id !== undefined)
|
|
228
|
+
return id;
|
|
229
|
+
return led.lastFsOrShellGatedCallId();
|
|
230
|
+
}
|
|
211
231
|
export async function resolvePark(park, ctx) {
|
|
212
232
|
const { led, taskId } = ctx;
|
|
213
233
|
let outcome;
|
|
@@ -243,7 +263,7 @@ export async function resolvePark(park, ctx) {
|
|
|
243
263
|
}
|
|
244
264
|
else {
|
|
245
265
|
if (park.gate === 'fs')
|
|
246
|
-
candidateGatedCallId = led
|
|
266
|
+
candidateGatedCallId = decidedIdentityCandidate(led, park);
|
|
247
267
|
outcome = await surfaceParkGate(park, ctx, park.gatedCallId, witness);
|
|
248
268
|
}
|
|
249
269
|
const alreadyDecidedById = outcome.kind === 'failed' &&
|
|
@@ -50,6 +50,9 @@ export type RulesFailure = {
|
|
|
50
50
|
} | {
|
|
51
51
|
kind: 'forbidden';
|
|
52
52
|
message: string;
|
|
53
|
+
} | {
|
|
54
|
+
kind: 'operators-unconfigured';
|
|
55
|
+
message: string;
|
|
53
56
|
} | {
|
|
54
57
|
kind: 'body-shape';
|
|
55
58
|
message: string;
|
|
@@ -100,7 +103,7 @@ export type PersistedRuleWriteBehavior = (typeof PERSISTED_RULE_WRITE_BEHAVIORS)
|
|
|
100
103
|
export type PersistedRuleWriteRequest = RuleWriteRequest;
|
|
101
104
|
export type PersistedRuleWriteWireResult = RuleWriteResult;
|
|
102
105
|
export type PersistedRuleLiveness = RemovalLiveness;
|
|
103
|
-
export declare const PERSISTED_RULE_WRITE_REFUSAL_CAUSES: readonly ["unwritable_behavior", "unreadable_identity", "unusable_principal", "client_too_old", "field_refused", "body_shape", "operator_only", "store_absent", "lane_too_old", "route_absent", "not_committed"];
|
|
106
|
+
export declare const PERSISTED_RULE_WRITE_REFUSAL_CAUSES: readonly ["unwritable_behavior", "unreadable_identity", "unusable_principal", "client_too_old", "field_refused", "body_shape", "operator_only", "operators_unconfigured", "store_absent", "lane_too_old", "route_absent", "not_committed"];
|
|
104
107
|
export type PersistedRuleWriteRefusalCause = (typeof PERSISTED_RULE_WRITE_REFUSAL_CAUSES)[number];
|
|
105
108
|
export declare const PERSISTED_RULE_WRITE_UNKNOWN_REASONS: readonly ["indeterminate", "malformed_result", "identity_mismatch", "unattributed_method_refusal", "unclassified"];
|
|
106
109
|
export type PersistedRuleWriteUnknownReason = (typeof PERSISTED_RULE_WRITE_UNKNOWN_REASONS)[number];
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { engineCapTrue } from '../engineCapsCache.js';
|
|
2
|
+
import { CORE_RULE_BEHAVIORS } from '../generated/engineFactTables.js';
|
|
2
3
|
import { wireFailureShapeOf } from '../wireFailureShape.js';
|
|
4
|
+
import { CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED } from '../engineErrorCodes.js';
|
|
3
5
|
import { hostLog } from '../host.js';
|
|
4
6
|
import { observedPermissionRulesWrite, permissionRulesWriteAvailable } from '../permissionRulesWriteCapability.js';
|
|
5
7
|
import { readToolApprovalRespondAck } from './toolApprovalWire.js';
|
|
@@ -7,7 +9,7 @@ export function persistedRulesLaneAvailable(baseUrl) {
|
|
|
7
9
|
return engineCapTrue(baseUrl, 'permissionRules');
|
|
8
10
|
}
|
|
9
11
|
export function persistedRulesGovernanceAvailable(baseUrl) {
|
|
10
|
-
return engineCapTrue(baseUrl, '
|
|
12
|
+
return engineCapTrue(baseUrl, 'permissionRules');
|
|
11
13
|
}
|
|
12
14
|
export function persistedRulesWriteAvailable(baseUrl) {
|
|
13
15
|
return permissionRulesWriteAvailable(observedPermissionRulesWrite(baseUrl ?? ''));
|
|
@@ -66,6 +68,8 @@ export function classifyRulesFailure(e) {
|
|
|
66
68
|
return { kind: 'too-many-candidates', message };
|
|
67
69
|
if (status === 403 && errorCode === 'auth.operator_only')
|
|
68
70
|
return { kind: 'forbidden', message };
|
|
71
|
+
if (errorCode === CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED)
|
|
72
|
+
return { kind: 'operators-unconfigured', message };
|
|
69
73
|
if (status === 405) {
|
|
70
74
|
return errorCode === 'method_not_allowed'
|
|
71
75
|
? { kind: 'method-unsupported', message }
|
|
@@ -73,7 +77,7 @@ export function classifyRulesFailure(e) {
|
|
|
73
77
|
}
|
|
74
78
|
return { kind: 'error', message };
|
|
75
79
|
}
|
|
76
|
-
export const PERSISTED_RULE_BEHAVIORS =
|
|
80
|
+
export const PERSISTED_RULE_BEHAVIORS = CORE_RULE_BEHAVIORS;
|
|
77
81
|
const BEHAVIOR_SET = new Set(PERSISTED_RULE_BEHAVIORS);
|
|
78
82
|
function rowStr(row, key) {
|
|
79
83
|
if (typeof row !== 'object' || row === null || Array.isArray(row))
|
|
@@ -84,6 +88,10 @@ function rowStr(row, key) {
|
|
|
84
88
|
function persistedRuleIdentityRowReadable(row) {
|
|
85
89
|
return rowStr(row, 'rule') !== undefined && rowStr(row, 'scope') !== undefined;
|
|
86
90
|
}
|
|
91
|
+
function persistedRuleIdentityRowWhy(row) {
|
|
92
|
+
const bad = ['rule', 'scope'].filter((k) => rowStr(row, k) === undefined);
|
|
93
|
+
return `${bad.join(' and ')} ${bad.length === 1 ? 'is' : 'are'} missing or not a non-empty string`;
|
|
94
|
+
}
|
|
87
95
|
export function persistedRuleBehaviorOf(rule) {
|
|
88
96
|
const b = rowStr(rule, 'behavior');
|
|
89
97
|
return b !== undefined && BEHAVIOR_SET.has(b) ? b : undefined;
|
|
@@ -144,16 +152,22 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
|
|
|
144
152
|
}
|
|
145
153
|
return { ok: false, failure };
|
|
146
154
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
const pageProblems = [];
|
|
156
|
+
if (typeof res !== 'object' || res === null) {
|
|
157
|
+
pageProblems.push('the page is not an object');
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
if (!Array.isArray(res.rules))
|
|
161
|
+
pageProblems.push('rules is missing or not an array');
|
|
162
|
+
if (typeof res.rev !== 'number' || !Number.isFinite(res.rev))
|
|
163
|
+
pageProblems.push('rev is missing or not a finite number');
|
|
164
|
+
}
|
|
165
|
+
if (pageProblems.length > 0) {
|
|
152
166
|
return {
|
|
153
167
|
ok: false,
|
|
154
168
|
failure: {
|
|
155
169
|
kind: 'error',
|
|
156
|
-
message:
|
|
170
|
+
message: `rule list page was malformed (${pageProblems.join('; ')}) on a 2xx — refusing to certify it as a complete governance list`,
|
|
157
171
|
},
|
|
158
172
|
};
|
|
159
173
|
}
|
|
@@ -172,7 +186,7 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
|
|
|
172
186
|
ok: false,
|
|
173
187
|
failure: {
|
|
174
188
|
kind: 'error',
|
|
175
|
-
message:
|
|
189
|
+
message: `rule list page carried a malformed rule row (${persistedRuleIdentityRowWhy(r)}) — refusing to present a list that hides or garbles live rules`,
|
|
176
190
|
},
|
|
177
191
|
};
|
|
178
192
|
}
|
|
@@ -221,6 +235,7 @@ export const PERSISTED_RULE_WRITE_REFUSAL_CAUSES = Object.freeze([
|
|
|
221
235
|
'field_refused',
|
|
222
236
|
'body_shape',
|
|
223
237
|
'operator_only',
|
|
238
|
+
'operators_unconfigured',
|
|
224
239
|
'store_absent',
|
|
225
240
|
'lane_too_old',
|
|
226
241
|
'route_absent',
|
|
@@ -242,6 +257,8 @@ function writeOutcomeFromFailure(failure) {
|
|
|
242
257
|
return { status: 'refused', cause: 'body_shape', message };
|
|
243
258
|
case 'forbidden':
|
|
244
259
|
return { status: 'refused', cause: 'operator_only', message };
|
|
260
|
+
case 'operators-unconfigured':
|
|
261
|
+
return { status: 'refused', cause: 'operators_unconfigured', message };
|
|
245
262
|
case 'lane-unavailable':
|
|
246
263
|
return { status: 'refused', cause: 'store_absent', message };
|
|
247
264
|
case 'method-unsupported':
|
|
@@ -341,24 +358,26 @@ export async function writePersistedRule(facade, input, opts) {
|
|
|
341
358
|
catch (e) {
|
|
342
359
|
return writeOutcomeFromFailure(classifyRulesFailure(e));
|
|
343
360
|
}
|
|
344
|
-
const malformed = () => ({
|
|
361
|
+
const malformed = (what) => ({
|
|
345
362
|
status: 'unknown',
|
|
346
363
|
why: 'malformed_result',
|
|
347
|
-
message:
|
|
364
|
+
message: `the rule write answered 2xx with a body this client cannot read (${what}) — refusing to certify it as a standing rule; re-list the rules to reconcile`,
|
|
348
365
|
});
|
|
349
366
|
if (res === null || typeof res !== 'object' || Array.isArray(res))
|
|
350
|
-
return malformed();
|
|
367
|
+
return malformed('the body is not an object');
|
|
351
368
|
const snap = certificationSnapshot(res);
|
|
352
369
|
if (snap === null)
|
|
353
|
-
return malformed();
|
|
370
|
+
return malformed('a field of the body is an inherited or accessor property, or threw when read');
|
|
354
371
|
const { status, rev, hasLiveness, liveness, hasRow, row } = snap;
|
|
355
|
-
|
|
356
|
-
|
|
372
|
+
const statusOk = status === 'persisted' || status === 'no-op';
|
|
373
|
+
const revOk = typeof rev === 'number' && Number.isFinite(rev);
|
|
374
|
+
if (!statusOk || !revOk)
|
|
375
|
+
return malformed([...(statusOk ? [] : ['status is not persisted or no-op']), ...(revOk ? [] : ['rev is not a finite number'])].join('; '));
|
|
357
376
|
const wrote = status === 'persisted' ? 'persisted' : 'no_op';
|
|
358
377
|
if (hasLiveness) {
|
|
359
378
|
if (liveness === 'no') {
|
|
360
379
|
if (hasRow)
|
|
361
|
-
return malformed();
|
|
380
|
+
return malformed('liveness and the written row contradict each other');
|
|
362
381
|
return {
|
|
363
382
|
status: 'revoked',
|
|
364
383
|
wrote,
|
|
@@ -368,7 +387,7 @@ export async function writePersistedRule(facade, input, opts) {
|
|
|
368
387
|
}
|
|
369
388
|
if (liveness === 'unknown') {
|
|
370
389
|
if (hasRow)
|
|
371
|
-
return malformed();
|
|
390
|
+
return malformed('liveness and the written row contradict each other');
|
|
372
391
|
return {
|
|
373
392
|
status: 'unverified',
|
|
374
393
|
wrote,
|
|
@@ -377,10 +396,10 @@ export async function writePersistedRule(facade, input, opts) {
|
|
|
377
396
|
};
|
|
378
397
|
}
|
|
379
398
|
if (liveness !== 'yes')
|
|
380
|
-
return malformed();
|
|
399
|
+
return malformed('liveness is not one of yes, no or unknown');
|
|
381
400
|
}
|
|
382
401
|
if (!persistedRuleIdentityRowReadable(row))
|
|
383
|
-
return malformed();
|
|
402
|
+
return malformed(`the written row is unreadable (${persistedRuleIdentityRowWhy(row)})`);
|
|
384
403
|
const landed = row;
|
|
385
404
|
if (landed.behavior !== behavior || landed.rule !== rule || landed.scope !== scope) {
|
|
386
405
|
return {
|
|
@@ -6,8 +6,6 @@ export declare const PLAN_REVIEW_APPROVE_LABEL = "Yes, approve and run the plan"
|
|
|
6
6
|
export declare const PLAN_REVIEW_REJECT_LABEL = "No, reject it (keep planning)";
|
|
7
7
|
export declare const PLAN_REVIEW_APPROVE_AUTO_LABEL = "Yes, and auto-accept edits";
|
|
8
8
|
export declare const PLAN_REVIEW_APPROVE_MANUAL_LABEL = "Yes, and manually approve edits";
|
|
9
|
-
export declare const PLAN_REVIEW_MODE_AFTER_MIN_ENGINE: readonly [number, number, number];
|
|
10
|
-
export declare function versionSupportsPlanReviewModeAfter(v: string | undefined): 'supported' | 'unsupported' | 'unknown';
|
|
11
9
|
export declare function planReviewDecisionFromAnswer(answer: QuestionAnswer | null | undefined, shownLabels?: readonly string[]): 'approve' | 'reject' | 'dismissed';
|
|
12
10
|
export type PlanReviewChoice = {
|
|
13
11
|
decision: 'approve';
|
|
@@ -7,9 +7,8 @@ import { enqueuePlanReviewOutcome } from '../notifications.js';
|
|
|
7
7
|
import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
|
|
8
8
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
9
9
|
import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError } from '../resumeRefusalCopy.js';
|
|
10
|
-
import { REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
|
|
10
|
+
import { REQUEST_FIELD_CONFLICT, isUnavailableFaceCode } from '../engineErrorCodes.js';
|
|
11
11
|
import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
|
|
12
|
-
import { engineCapString } from '../engineCapsCache.js';
|
|
13
12
|
import { planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
|
|
14
13
|
import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, registerArmedGateFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
|
|
15
14
|
import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
@@ -20,20 +19,6 @@ export const PLAN_REVIEW_APPROVE_LABEL = 'Yes, approve and run the plan';
|
|
|
20
19
|
export const PLAN_REVIEW_REJECT_LABEL = 'No, reject it (keep planning)';
|
|
21
20
|
export const PLAN_REVIEW_APPROVE_AUTO_LABEL = 'Yes, and auto-accept edits';
|
|
22
21
|
export const PLAN_REVIEW_APPROVE_MANUAL_LABEL = 'Yes, and manually approve edits';
|
|
23
|
-
export const PLAN_REVIEW_MODE_AFTER_MIN_ENGINE = Object.freeze([7, 86, 0]);
|
|
24
|
-
export function versionSupportsPlanReviewModeAfter(v) {
|
|
25
|
-
const mm = v ? /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(v) : null;
|
|
26
|
-
if (!mm)
|
|
27
|
-
return 'unknown';
|
|
28
|
-
const got = [Number(mm[1]), Number(mm[2]), Number(mm[3])];
|
|
29
|
-
for (let i = 0; i < 3; i++) {
|
|
30
|
-
const g = got[i];
|
|
31
|
-
const need = PLAN_REVIEW_MODE_AFTER_MIN_ENGINE[i];
|
|
32
|
-
if (g !== need)
|
|
33
|
-
return g > need ? 'supported' : 'unsupported';
|
|
34
|
-
}
|
|
35
|
-
return 'supported';
|
|
36
|
-
}
|
|
37
22
|
const APPROVE_LABEL = PLAN_REVIEW_APPROVE_LABEL;
|
|
38
23
|
const REJECT_LABEL = PLAN_REVIEW_REJECT_LABEL;
|
|
39
24
|
const LEGACY_TWO_CHOICE_LABELS = Object.freeze([PLAN_REVIEW_APPROVE_LABEL, PLAN_REVIEW_REJECT_LABEL]);
|
|
@@ -266,9 +251,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
266
251
|
}
|
|
267
252
|
armedPlanReviewTaskIds.add(taskId);
|
|
268
253
|
armedTaskId = taskId;
|
|
269
|
-
const
|
|
270
|
-
const offerModeAfter = opts?.submittedInPlanMode === true &&
|
|
271
|
-
versionSupportsPlanReviewModeAfter(engineCapString(capsBaseUrl, 'version')) === 'supported';
|
|
254
|
+
const offerModeAfter = opts?.submittedInPlanMode === true;
|
|
272
255
|
if (offerModeAfter)
|
|
273
256
|
offerModeAfterByTask.set(taskId, true);
|
|
274
257
|
else
|
|
@@ -648,7 +631,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
648
631
|
else if (typeof status === 'number') {
|
|
649
632
|
const msg = e instanceof Error ? e.message : String(e);
|
|
650
633
|
hostLog('debug', `planReviewWire: ${decision} → ${status} ${msg.slice(0, 200)}`);
|
|
651
|
-
effect = status >= 400 && status < 500 ? 'not_applied' : 'unconfirmed';
|
|
634
|
+
effect = (status >= 400 && status < 500) || (typeof refusedCode === 'string' && isUnavailableFaceCode(refusedCode)) ? 'not_applied' : 'unconfirmed';
|
|
652
635
|
outcome = `${`The plan_review decision failed: HTTP ${status} ${msg}`.trim()}${decideFailureNote(e)}`;
|
|
653
636
|
}
|
|
654
637
|
else if (noAnswer === 'time-limit' || noAnswer === 'deadline') {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ApprovalsListEnvelope, type CrashConvergedRow } from './crashConverged.js';
|
|
2
|
+
export type ReplayedApprovalDisposition = {
|
|
3
|
+
readonly kind: 'live';
|
|
4
|
+
readonly basis: 'live_leg' | 'preamble' | 'pending_list';
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: 'decided';
|
|
7
|
+
readonly cause?: CrashConvergedRow['cause'];
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: 'unknown';
|
|
10
|
+
readonly why: 'unreadable_frame' | 'no_approval_id' | 'not_read' | 'list_unreadable' | 'list_not_reported' | 'list_incomplete';
|
|
11
|
+
};
|
|
12
|
+
export interface ReplayPendingBasis {
|
|
13
|
+
readonly preamble?: ReadonlySet<string>;
|
|
14
|
+
readonly approvals?: {
|
|
15
|
+
readonly read: 'ok';
|
|
16
|
+
readonly body: ApprovalsListEnvelope;
|
|
17
|
+
} | {
|
|
18
|
+
readonly read: 'failed';
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare function replayedApprovalDisposition(frame: object, basis: ReplayPendingBasis): ReplayedApprovalDisposition;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { hasLedgerPositionKey } from './gateLedger.js';
|
|
2
|
+
import { readLivePendingRows } from './livePendingAsk.js';
|
|
3
|
+
import { projectCrashConverged } from './crashConverged.js';
|
|
4
|
+
const live = (basis) => Object.freeze({ kind: 'live', basis });
|
|
5
|
+
const unknown = (why) => Object.freeze({ kind: 'unknown', why });
|
|
6
|
+
const decided = (cause) => Object.freeze(cause !== undefined ? { kind: 'decided', cause } : { kind: 'decided' });
|
|
7
|
+
function approvalIdOf(frame) {
|
|
8
|
+
const v = frame.approvalId;
|
|
9
|
+
return typeof v === 'string' && v !== '' ? v : undefined;
|
|
10
|
+
}
|
|
11
|
+
function crashConvergedCauseOf(body, approvalId) {
|
|
12
|
+
const projection = projectCrashConverged(body);
|
|
13
|
+
if (projection === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
for (const row of [...projection.resumeSafe, ...projection.needsHuman]) {
|
|
16
|
+
if (row.approvalId === approvalId)
|
|
17
|
+
return row.cause;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
export function replayedApprovalDisposition(frame, basis) {
|
|
22
|
+
let approvalId;
|
|
23
|
+
try {
|
|
24
|
+
if (typeof frame !== 'object' || frame === null)
|
|
25
|
+
return unknown('unreadable_frame');
|
|
26
|
+
if (!hasLedgerPositionKey(frame))
|
|
27
|
+
return live('live_leg');
|
|
28
|
+
approvalId = approvalIdOf(frame);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return unknown('unreadable_frame');
|
|
32
|
+
}
|
|
33
|
+
if (approvalId === undefined)
|
|
34
|
+
return unknown('no_approval_id');
|
|
35
|
+
let b;
|
|
36
|
+
let inPreamble = false;
|
|
37
|
+
try {
|
|
38
|
+
b = typeof basis === 'object' && basis !== null ? basis : undefined;
|
|
39
|
+
inPreamble = b?.preamble?.has(approvalId) === true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
inPreamble = false;
|
|
43
|
+
}
|
|
44
|
+
if (inPreamble)
|
|
45
|
+
return live('preamble');
|
|
46
|
+
let approvals;
|
|
47
|
+
try {
|
|
48
|
+
approvals = b?.approvals;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return unknown('not_read');
|
|
52
|
+
}
|
|
53
|
+
if (approvals === undefined || approvals === null)
|
|
54
|
+
return unknown('not_read');
|
|
55
|
+
let body;
|
|
56
|
+
try {
|
|
57
|
+
if (approvals.read !== 'ok')
|
|
58
|
+
return unknown('list_unreadable');
|
|
59
|
+
body = approvals.body;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return unknown('list_unreadable');
|
|
63
|
+
}
|
|
64
|
+
let reading;
|
|
65
|
+
try {
|
|
66
|
+
reading = readLivePendingRows(body);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return unknown('list_unreadable');
|
|
70
|
+
}
|
|
71
|
+
if (reading.kind === 'present' && reading.rows.some((r) => r.approvalId === approvalId))
|
|
72
|
+
return live('pending_list');
|
|
73
|
+
let cause;
|
|
74
|
+
try {
|
|
75
|
+
cause = crashConvergedCauseOf(body, approvalId);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
cause = undefined;
|
|
79
|
+
}
|
|
80
|
+
if (cause !== undefined)
|
|
81
|
+
return decided(cause);
|
|
82
|
+
if (reading.kind === 'not_reported')
|
|
83
|
+
return unknown('list_not_reported');
|
|
84
|
+
if (reading.kind === 'malformed')
|
|
85
|
+
return unknown('list_unreadable');
|
|
86
|
+
if (reading.dropped > 0)
|
|
87
|
+
return unknown('list_incomplete');
|
|
88
|
+
return decided();
|
|
89
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { CONFIG_LEGACY_TOOL_NAME, RULES_LEGACY_TOOL_NAME } from '../engineErrorCodes.js';
|
|
2
2
|
import { rosterMountedNames } from './rosterMountedNames.js';
|
|
3
|
+
import { ruleCompileVerdict } from '../ruleCompileVerdict.js';
|
|
4
|
+
import { protocolSegmentOf } from '../protocolSegment.js';
|
|
3
5
|
import { PREFIXES_IN_PROSE, REFUSED_NAME_CLASSES, REFUSED_NAME_FIX, RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
|
|
4
6
|
import { CORE_PROTOCOL_PREFIXES, CORE_RETIRED_TOOL_CURRENT_NAMES } from '../generated/toolNameTables.js';
|
|
5
7
|
const COVERING_NAMESPACE_PREFIXES = CORE_PROTOCOL_PREFIXES;
|
|
@@ -9,7 +11,7 @@ const WITHHELD_NOTICE = {
|
|
|
9
11
|
qualified_deny: 'Not written to this session’s rule record: that record holds whole tool names and command names only, and this rule has a part in parentheses it cannot carry — recording just the name could block more than this rule does.',
|
|
10
12
|
peer_wide: 'Not written to this session’s rule record: this rule names a server or agent peer without naming one of its tools, and an engine that matches that record by exact tool name would block none of that peer’s tools.',
|
|
11
13
|
wildcard: 'Not written to this session’s rule record: this rule uses a wildcard (*), and an engine that matches that record by exact tool name would block none of the tools it covers.',
|
|
12
|
-
not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text,
|
|
14
|
+
not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, has spaces around the name, is not a single tool name in rule syntax (for example it contains a backslash or a parenthesis, or is longer than a rule can be), or spells its server or agent peer part in a way the engine never uses when naming tools (for example with a dot or a space), so no tool would ever match it.',
|
|
13
15
|
legacy_tool_name: `Not written to this session’s rule record: unless a tool by exactly this name is available, the engine refuses it — it is a retired tool name, or it contains "__" but does not start with ${PREFIXES_IN_PROSE} — and with it in that record every later run of this session would fail at startup until that entry is removed from the record. ${REFUSED_NAME_FIX}`,
|
|
14
16
|
not_in_roster: 'Not written to this session’s rule record: the tool roster supplied for this check has no tool by exactly this name, as a name or an alias, with letter case counted — it may be misspelled, or name a tool this deployment does not provide — so recorded as it is it would block nothing here.',
|
|
15
17
|
};
|
|
@@ -97,7 +99,23 @@ export function sessionPolicyDeliverable(behavior, rules, opts) {
|
|
|
97
99
|
withheld.push({ rule: '', why: 'not_a_tool_name' });
|
|
98
100
|
continue;
|
|
99
101
|
}
|
|
100
|
-
|
|
102
|
+
let why = withheldWhyOf(r);
|
|
103
|
+
if (why === undefined) {
|
|
104
|
+
const grammar = ruleCompileVerdict(r, 'deny');
|
|
105
|
+
if (grammar === 'unknown')
|
|
106
|
+
return unreadable();
|
|
107
|
+
if (grammar === 'refused')
|
|
108
|
+
why = 'not_a_tool_name';
|
|
109
|
+
}
|
|
110
|
+
if (why === undefined && COVERING_NAMESPACE_PREFIXES.some((p) => r.startsWith(p))) {
|
|
111
|
+
const segment = protocolSegmentOf(r);
|
|
112
|
+
if (segment.kind === 'empty' || segment.kind === 'unminted')
|
|
113
|
+
why = 'not_a_tool_name';
|
|
114
|
+
else if (segment.kind === 'unknown')
|
|
115
|
+
return unreadable();
|
|
116
|
+
}
|
|
117
|
+
if (why === undefined && mounted !== undefined && !mounted.has(r))
|
|
118
|
+
why = 'not_in_roster';
|
|
101
119
|
if (why === undefined)
|
|
102
120
|
exact.push(r);
|
|
103
121
|
else {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { SessionPermissionRules, StoredSessionRules } from '@sema-agent/sdk';
|
|
2
2
|
import type { SessionPolicyReading } from '../sessionPolicyCapability.js';
|
|
3
3
|
import type { ToolRosterView } from '../toolRoster.js';
|
|
4
|
+
import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
|
|
5
|
+
export type LegacyToolNameVerdict = Exclude<(typeof CORE_RULE_TOOL_NAME_VERDICTS)[number], 'current'>;
|
|
4
6
|
export interface SessionPolicyFacade {
|
|
5
7
|
getPolicy(sessionId: string, opts?: {
|
|
6
8
|
signal?: AbortSignal;
|
|
@@ -43,6 +45,8 @@ export type SessionPolicyFailure = {
|
|
|
43
45
|
message: string;
|
|
44
46
|
field?: SessionPolicyRemovedEntry['field'];
|
|
45
47
|
name?: string;
|
|
48
|
+
verdict?: LegacyToolNameVerdict;
|
|
49
|
+
currentName?: string;
|
|
46
50
|
} | {
|
|
47
51
|
kind: 'unauthorized';
|
|
48
52
|
message: string;
|
|
@@ -95,6 +99,8 @@ export type SessionPolicyTightenOutcome = {
|
|
|
95
99
|
message: string;
|
|
96
100
|
field?: SessionPolicyRemovedEntry['field'];
|
|
97
101
|
name?: string;
|
|
102
|
+
verdict?: LegacyToolNameVerdict;
|
|
103
|
+
currentName?: string;
|
|
98
104
|
alreadyInRecord?: boolean;
|
|
99
105
|
} | {
|
|
100
106
|
kind: 'unknown';
|
|
@@ -109,6 +115,8 @@ export declare function sessionPolicyReadNotice(outcome: Exclude<SessionPolicyRe
|
|
|
109
115
|
kind: 'present';
|
|
110
116
|
}> | {
|
|
111
117
|
kind: 'no_target';
|
|
118
|
+
} | {
|
|
119
|
+
kind: 'not_started';
|
|
112
120
|
}): string;
|
|
113
121
|
export interface SessionPolicyRemovedEntry {
|
|
114
122
|
readonly field: 'toolDeny' | 'toolAllow';
|
|
@@ -148,6 +156,8 @@ export type SessionPolicyRemovalOutcome = {
|
|
|
148
156
|
message: string;
|
|
149
157
|
field?: SessionPolicyRemovedEntry['field'];
|
|
150
158
|
name?: string;
|
|
159
|
+
verdict?: LegacyToolNameVerdict;
|
|
160
|
+
currentName?: string;
|
|
151
161
|
};
|
|
152
162
|
export type SessionPolicyRosterRequiredWhy = 'absent' | 'unreadable';
|
|
153
163
|
export declare function removeRefusedSessionRules(facade: SessionPolicyFacade, sessionId: string, roster: ToolRosterView | undefined, opts?: {
|
|
@@ -4,6 +4,10 @@ import { isEngineRefusedToolName } from './sessionPolicyDeliverable.js';
|
|
|
4
4
|
import { RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
|
|
5
5
|
import { RULES_LEGACY_TOOL_NAME, isRulesRefusalCode } from '../engineErrorCodes.js';
|
|
6
6
|
import { rosterMountedNames } from './rosterMountedNames.js';
|
|
7
|
+
import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
|
|
8
|
+
function isLegacyToolNameVerdict(v) {
|
|
9
|
+
return typeof v === 'string' && v !== 'current' && CORE_RULE_TOOL_NAME_VERDICTS.includes(v);
|
|
10
|
+
}
|
|
7
11
|
const RULE_FIELD_PRESENCE = {
|
|
8
12
|
toolAllow: true,
|
|
9
13
|
toolDeny: true,
|
|
@@ -14,7 +18,7 @@ const RULE_FIELD_PRESENCE = {
|
|
|
14
18
|
export const SESSION_POLICY_RULE_FIELDS = Object.freeze(Object.keys(RULE_FIELD_PRESENCE));
|
|
15
19
|
const MAX_RULE_ENTRIES = 10_000;
|
|
16
20
|
export function classifySessionPolicyFailure(e) {
|
|
17
|
-
const { status, errorCode, message, bodyField, bodyName } = wireFailureShapeOf(e);
|
|
21
|
+
const { status, errorCode, message, bodyField, bodyName, bodyVerdict, bodyCurrentName } = wireFailureShapeOf(e);
|
|
18
22
|
if (errorCode === undefined || errorCode === '')
|
|
19
23
|
return { kind: 'no-verdict', message };
|
|
20
24
|
if (errorCode === RULES_LEGACY_TOOL_NAME) {
|
|
@@ -23,6 +27,8 @@ export function classifySessionPolicyFailure(e) {
|
|
|
23
27
|
message,
|
|
24
28
|
...(bodyField === 'toolDeny' || bodyField === 'toolAllow' ? { field: bodyField } : {}),
|
|
25
29
|
...(bodyName !== undefined && bodyName !== '' ? { name: bodyName } : {}),
|
|
30
|
+
...(isLegacyToolNameVerdict(bodyVerdict) ? { verdict: bodyVerdict } : {}),
|
|
31
|
+
...(bodyCurrentName !== undefined && bodyCurrentName !== '' ? { currentName: bodyCurrentName } : {}),
|
|
26
32
|
};
|
|
27
33
|
}
|
|
28
34
|
if (isRulesRefusalCode(errorCode) && status !== undefined && status >= 400 && status < 500)
|
|
@@ -227,7 +233,7 @@ function unknownFrom(failure, leg) {
|
|
|
227
233
|
}
|
|
228
234
|
}
|
|
229
235
|
function legacyToolNameOutcome(failure, prior, add) {
|
|
230
|
-
const { field, name } = failure;
|
|
236
|
+
const { field, name, verdict, currentName } = failure;
|
|
231
237
|
let alreadyInRecord;
|
|
232
238
|
if (field !== undefined && name !== undefined) {
|
|
233
239
|
const inPrior = Object.hasOwn(prior, field) ? prior[field] : undefined;
|
|
@@ -242,6 +248,8 @@ function legacyToolNameOutcome(failure, prior, add) {
|
|
|
242
248
|
message: failure.message,
|
|
243
249
|
...(field !== undefined ? { field } : {}),
|
|
244
250
|
...(name !== undefined ? { name } : {}),
|
|
251
|
+
...(verdict !== undefined ? { verdict } : {}),
|
|
252
|
+
...(currentName !== undefined ? { currentName } : {}),
|
|
245
253
|
...(alreadyInRecord !== undefined ? { alreadyInRecord } : {}),
|
|
246
254
|
};
|
|
247
255
|
}
|
|
@@ -320,7 +328,7 @@ export function sessionPolicyTightenNotice(outcome) {
|
|
|
320
328
|
case 'face_unavailable':
|
|
321
329
|
return 'This deployment does not keep per-session tool rules, so nothing was written.';
|
|
322
330
|
case 'session_not_found':
|
|
323
|
-
return 'This session is not readable from here — it may not exist, or it may belong to someone else; nothing was written.';
|
|
331
|
+
return 'This session is not readable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which); nothing was written.';
|
|
324
332
|
case 'session_ownerless':
|
|
325
333
|
return 'This session has no owner, so a rule written for it could never be read back; nothing was written. Attach an identity to the session and try again.';
|
|
326
334
|
case 'unauthorized':
|
|
@@ -346,6 +354,7 @@ export function sessionPolicyTightenNotice(outcome) {
|
|
|
346
354
|
}
|
|
347
355
|
const READ_NOTICE_GENERIC = 'This session’s rule record could not be shown, so what it holds is not known. Try reading it again.';
|
|
348
356
|
const READ_NOTICE_NO_TARGET = 'There is no engine connection for this session, so its rule record cannot be read and what it holds is not known. Look again once it is connected.';
|
|
357
|
+
const READ_NOTICE_NOT_STARTED = 'This session has not begun on the engine yet — nothing has been sent in it — so it has no rule record to read. Send a first message, then look again.';
|
|
349
358
|
function readFailureSentence(kind) {
|
|
350
359
|
switch (kind) {
|
|
351
360
|
case 'face-unavailable':
|
|
@@ -359,7 +368,7 @@ function readFailureSentence(kind) {
|
|
|
359
368
|
case 'session-ownerless':
|
|
360
369
|
return 'This session has no owner, so it has no rule record to read.';
|
|
361
370
|
case 'session-not-found':
|
|
362
|
-
return 'This session is not readable from here — it may not exist, or it may belong to someone else — so its rule record cannot be shown. Check that you are connected to the engine and the session you meant, or continue in a new session.';
|
|
371
|
+
return 'This session is not readable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which) — so its rule record cannot be shown. Check that you are connected to the engine and the session you meant, or continue in a new session.';
|
|
363
372
|
case 'request-rejected':
|
|
364
373
|
return 'The engine rejected the request to read this session’s rule record as malformed, so it could not be read. Sending the same request again will not change that; check which session was asked for.';
|
|
365
374
|
case 'legacy-tool-name':
|
|
@@ -401,6 +410,8 @@ export function sessionPolicyReadNotice(outcome) {
|
|
|
401
410
|
return readMalformedSentence(ownRead(outcome, 'why')) ?? READ_NOTICE_GENERIC;
|
|
402
411
|
if (arm === 'no_target')
|
|
403
412
|
return READ_NOTICE_NO_TARGET;
|
|
413
|
+
if (arm === 'not_started')
|
|
414
|
+
return READ_NOTICE_NOT_STARTED;
|
|
404
415
|
return READ_NOTICE_GENERIC;
|
|
405
416
|
}
|
|
406
417
|
catch {
|
|
@@ -488,6 +499,8 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
|
|
|
488
499
|
message: failure.message,
|
|
489
500
|
...(failure.field !== undefined ? { field: failure.field } : {}),
|
|
490
501
|
...(failure.name !== undefined ? { name: failure.name } : {}),
|
|
502
|
+
...(failure.verdict !== undefined ? { verdict: failure.verdict } : {}),
|
|
503
|
+
...(failure.currentName !== undefined ? { currentName: failure.currentName } : {}),
|
|
491
504
|
},
|
|
492
505
|
};
|
|
493
506
|
}
|
|
@@ -559,7 +572,7 @@ export function sessionPolicyRemovalNotice(outcome) {
|
|
|
559
572
|
case 'face_unavailable':
|
|
560
573
|
return 'This deployment does not keep per-session tool rules, so there is no session rule record to remove entries from — nothing was changed.';
|
|
561
574
|
case 'session_not_found':
|
|
562
|
-
return 'This session is not readable from here — it may not exist, or it may belong to someone else; nothing was changed.';
|
|
575
|
+
return 'This session is not readable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which); nothing was changed.';
|
|
563
576
|
case 'session_ownerless':
|
|
564
577
|
return 'This session has no owner, so it has no session rule record to change; nothing was changed.';
|
|
565
578
|
case 'unauthorized':
|
|
@@ -576,7 +589,7 @@ export function sessionPolicyRemovalNotice(outcome) {
|
|
|
576
589
|
case 'store_corrupt':
|
|
577
590
|
return 'The engine could not establish which version this session’s rules are at and refused to stamp a new one, so nothing was changed. This needs an operator to look at the rule store.';
|
|
578
591
|
case 'session_not_found':
|
|
579
|
-
return 'This session is not writable from here — it may not exist, or it may belong to someone else; nothing was changed.';
|
|
592
|
+
return 'This session is not writable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which); nothing was changed.';
|
|
580
593
|
case 'unauthorized':
|
|
581
594
|
return 'This call could not present a verifiable identity, so nothing was changed.';
|
|
582
595
|
default:
|