@sema-agent/client-core 0.105.0 → 0.107.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 +301 -0
- package/README.md +40 -21
- package/dist/adapt/arms.js +10 -1
- package/dist/adapter/activeRunSelfHeal.d.ts +21 -2
- package/dist/adapter/activeRunSelfHeal.js +129 -28
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +0 -5
- package/dist/adapter/downstream/eventToSdkMessage.js +20 -19
- package/dist/adapter/downstream/terminalToSdkResult.js +2 -3
- package/dist/adapter/downstream/wiringManifestView.d.ts +1 -3
- package/dist/adapter/downstream/wiringManifestView.js +0 -1
- package/dist/adapter/runStream.js +3 -4
- package/dist/approvalReachWire.d.ts +1 -0
- package/dist/approvalReachWire.js +4 -1
- package/dist/approvalSeat.d.ts +21 -0
- package/dist/approvalSeat.js +73 -0
- package/dist/assistantInbox.d.ts +2 -0
- package/dist/assistantInbox.js +3 -0
- package/dist/checkpointRowVerdict.d.ts +29 -0
- package/dist/checkpointRowVerdict.js +98 -0
- package/dist/coreValuePorts.d.ts +12 -1
- package/dist/coreValuePorts.js +10 -0
- package/dist/decideFailureNote.js +3 -1
- package/dist/decideReceipt.d.ts +9 -0
- package/dist/decideReceipt.js +29 -1
- package/dist/declared/supportFloorDeclared.js +2 -2
- package/dist/deviceRebind.d.ts +7 -0
- package/dist/deviceRebind.js +39 -5
- package/dist/displayUntrusted.d.ts +5 -3
- package/dist/displayUntrusted.js +50 -41
- package/dist/engineAddress.d.ts +3 -0
- package/dist/engineAddress.js +25 -0
- package/dist/engineDirectory.js +9 -17
- package/dist/engineErrorCodes.d.ts +8 -0
- package/dist/engineErrorCodes.js +8 -0
- package/dist/engineNoticeCodes.d.ts +38 -3
- package/dist/engineNoticeCodes.js +167 -11
- package/dist/engineRowRef.js +1 -1
- package/dist/errorSupplementRoles.js +4 -1
- package/dist/eventLeg.d.ts +2 -2
- package/dist/eventLeg.js +2 -1
- package/dist/executionLaneCapability.js +3 -2
- package/dist/executionLaneWords.d.ts +1 -0
- package/dist/executionLaneWords.js +3 -0
- package/dist/fleet/fleetProjection.js +26 -5
- package/dist/generated/engineFactTables.d.ts +2 -1
- package/dist/generated/engineFactTables.js +3 -1
- package/dist/generated/engineNoticeTables.js +28 -7
- package/dist/generated/sdkFactTables.d.ts +3 -0
- package/dist/generated/sdkFactTables.js +15 -0
- package/dist/generated/settingsSchemaFns.d.ts +11 -0
- package/dist/generated/settingsSchemaFns.js +2 -1
- package/dist/generated/settingsSchemaSafetyMergeSpec.js +1 -1
- package/dist/generated/settingsSchemaSettingsSources.d.ts +1 -1
- package/dist/generated/settingsSchemaSettingsSources.js +22 -2
- package/dist/hitl/approvalLiveFace.d.ts +17 -0
- package/dist/hitl/approvalLiveFace.js +35 -0
- package/dist/hitl/approvalQueueRow.d.ts +21 -0
- package/dist/hitl/approvalQueueRow.js +69 -0
- package/dist/hitl/approvalResolution.d.ts +5 -1
- package/dist/hitl/approvalResolution.js +18 -1
- package/dist/hitl/approvalsFeed.d.ts +5 -4
- package/dist/hitl/approvalsFeed.js +39 -6
- package/dist/hitl/askParkRowRouting.d.ts +4 -3
- package/dist/hitl/askParkRowRouting.js +27 -3
- package/dist/hitl/automaticAnswerVerdict.js +13 -10
- package/dist/hitl/crashConverged.d.ts +5 -1
- package/dist/hitl/decisionConflict.js +2 -1
- package/dist/hitl/decisionRefusal.js +1 -1
- package/dist/hitl/decisionSend.d.ts +3 -0
- package/dist/hitl/decisionSend.js +32 -6
- package/dist/hitl/frameRouter.js +4 -5
- package/dist/hitl/hitlBridge.d.ts +6 -2
- package/dist/hitl/hitlBridge.js +53 -7
- package/dist/hitl/livePendingAsk.d.ts +5 -0
- package/dist/hitl/livePendingAsk.js +10 -3
- package/dist/hitl/parkOwnership.js +4 -5
- package/dist/hitl/parkResolver.js +9 -5
- package/dist/hitl/parkRowBirthWait.d.ts +7 -0
- package/dist/hitl/parkRowBirthWait.js +1 -1
- package/dist/hitl/parkRowDelegation.d.ts +6 -0
- package/dist/hitl/parkRowDelegation.js +33 -0
- package/dist/hitl/persistedRulesWire.d.ts +15 -3
- package/dist/hitl/persistedRulesWire.js +102 -20
- package/dist/hitl/planReviewCard.d.ts +2 -0
- package/dist/hitl/planReviewCard.js +21 -0
- package/dist/hitl/planReviewWire.d.ts +2 -2
- package/dist/hitl/planReviewWire.js +105 -38
- package/dist/hitl/sessionAllowRoute.d.ts +7 -0
- package/dist/hitl/sessionAllowRoute.js +49 -0
- package/dist/hitl/sessionPolicyWire.d.ts +1 -7
- package/dist/hitl/sessionPolicyWire.js +3 -43
- package/dist/hitl/standingRows.js +10 -10
- package/dist/hitl/subagentSource.d.ts +1 -0
- package/dist/hitl/subagentSource.js +13 -0
- package/dist/hitl/toolApprovalWire.d.ts +3 -2
- package/dist/hitl/toolApprovalWire.js +100 -44
- package/dist/hostSettingsSource.d.ts +4 -1
- package/dist/hostSettingsSource.js +11 -6
- package/dist/hostTurnSettings.d.ts +4 -5
- package/dist/hostTurnSettings.js +80 -19
- package/dist/i18n/messages.en.d.ts +41 -12
- package/dist/i18n/messages.en.js +42 -13
- package/dist/i18n/messages.zh.js +45 -13
- package/dist/index.d.ts +22 -2
- package/dist/index.js +12 -2
- package/dist/injectionLaneNotice.js +2 -1
- package/dist/liveInitToolFace.js +0 -1
- package/dist/liveModelCatalog.d.ts +15 -0
- package/dist/liveModelCatalog.js +40 -0
- package/dist/mcpWireCaps.js +1 -1
- package/dist/modelCapabilityProbe.js +66 -33
- package/dist/noticePresentation.js +2 -0
- package/dist/ownKey.d.ts +0 -1
- package/dist/ownKey.js +0 -2
- package/dist/permissionsDeclaration.d.ts +2 -2
- package/dist/permissionsDeclaration.js +12 -14
- package/dist/projectSeed.d.ts +39 -0
- package/dist/projectSeed.js +80 -0
- package/dist/promptDelivery.js +1 -1
- package/dist/replayBody.d.ts +7 -6
- package/dist/replayBody.js +62 -7
- package/dist/request/taskRequest.js +3 -4
- package/dist/ruleCompileVerdict.d.ts +0 -2
- package/dist/ruleCompileVerdict.js +0 -31
- package/dist/ruleRefusalSentence.js +0 -3
- package/dist/runReattachPolicy.d.ts +3 -0
- package/dist/runReattachPolicy.js +15 -2
- package/dist/runTerminal.js +30 -8
- package/dist/sandboxWire.d.ts +0 -3
- package/dist/sandboxWire.js +0 -9
- package/dist/sdkWireTransit.d.ts +1 -1
- package/dist/seam.d.ts +14 -7
- package/dist/seam.js +12 -0
- package/dist/seatContract.d.ts +58 -7
- package/dist/seatContract.js +135 -4
- package/dist/serverSupportRange.d.ts +18 -0
- package/dist/serverSupportRange.js +25 -0
- package/dist/sessionEngine.d.ts +156 -0
- package/dist/sessionEngine.js +464 -0
- package/dist/sessionMap.d.ts +11 -1
- package/dist/sessionMap.js +35 -9
- package/dist/sessionMemorySync.d.ts +31 -0
- package/dist/sessionMemorySync.js +95 -0
- package/dist/sessionRowTag.d.ts +4 -0
- package/dist/sessionRowTag.js +18 -0
- package/dist/settingsLayerAdmission.d.ts +1 -0
- package/dist/settingsLayerAdmission.js +4 -1
- package/dist/toolResult.js +1 -1
- package/dist/wireRefusalCopy.d.ts +6 -1
- package/dist/wireRefusalCopy.js +9 -7
- package/docs/INTEGRATION-CLIENTS.md +1393 -18
- package/package.json +5 -5
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { displayUntrusted, quoteUntrustedForSentence } from '../displayUntrusted.js';
|
|
2
2
|
import { engineCapTrue } from '../engineCapsCache.js';
|
|
3
3
|
import { CORE_RULE_BEHAVIORS } from '../generated/engineFactTables.js';
|
|
4
|
-
import { wireFailureShapeOf } from '../wireFailureShape.js';
|
|
4
|
+
import { slotReadOf, wireFailureShapeOf } from '../wireFailureShape.js';
|
|
5
5
|
import { CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED } from '../engineErrorCodes.js';
|
|
6
6
|
import { hostLog } from '../host.js';
|
|
7
7
|
import { msg, renderMessage } from '../i18n/translate.js';
|
|
8
|
-
import { guardedRead, readOwnDataValue } from '../ownKey.js';
|
|
8
|
+
import { appendOwnElement, guardedRead, readOwnDataValue } from '../ownKey.js';
|
|
9
9
|
import { observedPermissionRulesWrite, permissionRulesWriteAvailable } from '../permissionRulesWriteCapability.js';
|
|
10
10
|
import { isToolApprovalRespondReplayed, readToolApprovalRespondAck } from './toolApprovalWire.js';
|
|
11
11
|
export function persistedRulesLaneAvailable(baseUrl) {
|
|
@@ -17,27 +17,109 @@ export function persistedRulesGovernanceAvailable(baseUrl) {
|
|
|
17
17
|
export function persistedRulesWriteAvailable(baseUrl) {
|
|
18
18
|
return permissionRulesWriteAvailable(observedPermissionRulesWrite(baseUrl ?? ''));
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
const NO_INDETERMINATE = Object.freeze([]);
|
|
21
|
+
const INDETERMINATE_DETAIL_SHOWN_MAX = 400;
|
|
22
|
+
function ownArrayItems(v) {
|
|
23
|
+
try {
|
|
24
|
+
if (typeof v !== 'object' || v === null || !Array.isArray(v))
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const len = slotReadOf(v, 'length');
|
|
31
|
+
if (len.presence !== 'present' || typeof len.value !== 'number' || !Number.isSafeInteger(len.value) || len.value < 0)
|
|
32
|
+
return undefined;
|
|
33
|
+
const out = [];
|
|
34
|
+
for (let i = 0; i < len.value; i++) {
|
|
35
|
+
const item = slotReadOf(v, String(i));
|
|
36
|
+
if (item.presence !== 'present')
|
|
37
|
+
return undefined;
|
|
38
|
+
appendOwnElement(out, item.value);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function ownNonEmpty(o, key) {
|
|
43
|
+
const r = slotReadOf(o, key);
|
|
44
|
+
return r.presence === 'present' && typeof r.value === 'string' && r.value !== '' ? r.value : undefined;
|
|
45
|
+
}
|
|
46
|
+
function indeterminateRowOf(row) {
|
|
47
|
+
const k = slotReadOf(row, 'key');
|
|
48
|
+
const keyObj = k.presence === 'present' && typeof k.value === 'object' && k.value !== null ? k.value : undefined;
|
|
49
|
+
const behavior = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'behavior');
|
|
50
|
+
const rule = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'rule');
|
|
51
|
+
const scope = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'scope');
|
|
52
|
+
const reason = ownNonEmpty(row, 'reason');
|
|
53
|
+
const d = slotReadOf(row, 'detail');
|
|
54
|
+
const shown = d.presence === 'present' && typeof d.value === 'string' ? displayUntrusted(d.value, { max: INDETERMINATE_DETAIL_SHOWN_MAX }) : undefined;
|
|
55
|
+
return Object.freeze({
|
|
56
|
+
...(behavior !== undefined && rule !== undefined && scope !== undefined ? { key: Object.freeze({ behavior, rule, scope }) } : {}),
|
|
57
|
+
...(reason !== undefined ? { reason } : {}),
|
|
58
|
+
...(shown !== undefined && shown.trim() !== '' ? { detail: shown } : {}),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const RECEIPT_ROW_ARMS = {
|
|
62
|
+
persisted: (acc) => {
|
|
63
|
+
acc.persisted += 1;
|
|
64
|
+
},
|
|
65
|
+
'no-op': (acc) => {
|
|
66
|
+
acc.deduped += 1;
|
|
67
|
+
},
|
|
68
|
+
indeterminate: (acc, row) => {
|
|
69
|
+
appendOwnElement(acc.indeterminate, indeterminateRowOf(row));
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
function receiptCountsOf(rows) {
|
|
73
|
+
const acc = { persisted: 0, deduped: 0, indeterminate: [] };
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
if (typeof row !== 'object' || row === null)
|
|
76
|
+
return null;
|
|
77
|
+
const o = slotReadOf(row, 'outcome');
|
|
78
|
+
const word = o.presence === 'present' ? o.value : undefined;
|
|
79
|
+
if (typeof word !== 'string' || !Object.hasOwn(RECEIPT_ROW_ARMS, word))
|
|
80
|
+
return null;
|
|
81
|
+
RECEIPT_ROW_ARMS[word](acc, row);
|
|
82
|
+
}
|
|
83
|
+
return { persisted: acc.persisted, deduped: acc.deduped, indeterminate: acc.indeterminate.length === 0 ? NO_INDETERMINATE : Object.freeze(acc.indeterminate) };
|
|
84
|
+
}
|
|
85
|
+
function legacyEnvelopeCountsOf(result) {
|
|
86
|
+
if (typeof result !== 'object' || result === null)
|
|
87
|
+
return null;
|
|
88
|
+
const m = slotReadOf(result, 'members');
|
|
89
|
+
const rows = m.presence === 'present' ? ownArrayItems(m.value) : undefined;
|
|
90
|
+
if (rows === undefined)
|
|
91
|
+
return null;
|
|
92
|
+
let persisted = 0;
|
|
93
|
+
let deduped = 0;
|
|
94
|
+
for (const row of rows) {
|
|
95
|
+
if (typeof row !== 'object' || row === null)
|
|
96
|
+
continue;
|
|
97
|
+
const st = slotReadOf(row, 'status');
|
|
98
|
+
const status = st.presence === 'present' ? st.value : undefined;
|
|
99
|
+
if (status === 'persisted')
|
|
100
|
+
persisted += 1;
|
|
101
|
+
else if (status === 'deduped')
|
|
102
|
+
deduped += 1;
|
|
103
|
+
}
|
|
104
|
+
return { persisted, deduped, indeterminate: NO_INDETERMINATE };
|
|
105
|
+
}
|
|
106
|
+
export function readCcImportRedeemCounts(body) {
|
|
107
|
+
try {
|
|
108
|
+
if (typeof body !== 'object' || body === null)
|
|
109
|
+
return null;
|
|
110
|
+
const top = slotReadOf(body, 'members');
|
|
111
|
+
if (top.presence !== 'absent') {
|
|
112
|
+
if (top.presence !== 'present')
|
|
113
|
+
return null;
|
|
114
|
+
const rows = ownArrayItems(top.value);
|
|
115
|
+
return rows === undefined ? null : receiptCountsOf(rows);
|
|
31
116
|
}
|
|
32
|
-
|
|
117
|
+
const legacy = slotReadOf(body, 'result');
|
|
118
|
+
return legacy.presence === 'present' ? legacyEnvelopeCountsOf(legacy.value) : null;
|
|
33
119
|
}
|
|
34
|
-
|
|
35
|
-
return
|
|
36
|
-
persisted: Array.isArray(o.persisted) ? o.persisted.length : 0,
|
|
37
|
-
deduped: Array.isArray(o.deduped) ? o.deduped.length : 0,
|
|
38
|
-
};
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
39
122
|
}
|
|
40
|
-
return null;
|
|
41
123
|
}
|
|
42
124
|
export function classifyRulesFailure(e, context) {
|
|
43
125
|
const { status, errorCode, message, retryAfterSec, committed } = wireFailureShapeOf(e);
|
|
@@ -6,6 +6,8 @@ export declare function planReviewCardNoteRef(facts: PlanReviewCardFacts): Messa
|
|
|
6
6
|
export declare function planReviewUnreadableNoteRef(): MessageRef;
|
|
7
7
|
export declare function planReviewCardNote(facts: PlanReviewCardFacts, lang?: UiLanguage): string | undefined;
|
|
8
8
|
export declare function planReviewAskAgainKey(facts: PlanReviewCardFacts | undefined): MessageKey;
|
|
9
|
+
export type PlanReviewDecisionReason = 'card_missing' | 'card_withholds' | 'gate_moved' | 'gate_untrusted';
|
|
10
|
+
export declare function planReviewDecisionReasonRef(reason: PlanReviewDecisionReason, card?: PlanReviewCardFacts): MessageRef;
|
|
9
11
|
export interface PlanReviewLedgerSource {
|
|
10
12
|
readonly runRow: (taskId: string, signal: AbortSignal) => Promise<object>;
|
|
11
13
|
readonly frames: (taskId: string, signal: AbortSignal) => AsyncIterable<object>;
|
|
@@ -46,6 +46,27 @@ export function planReviewAskAgainKey(facts) {
|
|
|
46
46
|
return body.cause === 'not_carried' ? 'approval.planReview.askAgain.notCarried' : 'approval.planReview.askAgain.unknown';
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
+
export function planReviewDecisionReasonRef(reason, card) {
|
|
50
|
+
switch (reason) {
|
|
51
|
+
case 'card_missing':
|
|
52
|
+
return msg('approval.planReview.decision.cardMissing');
|
|
53
|
+
case 'card_withholds': {
|
|
54
|
+
let ref;
|
|
55
|
+
try {
|
|
56
|
+
const read = card === undefined ? undefined : planReviewCardFactsFrom(card);
|
|
57
|
+
ref = read === undefined ? undefined : planReviewCardNoteRef(read);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
ref = undefined;
|
|
61
|
+
}
|
|
62
|
+
return ref ?? planReviewUnreadableNoteRef();
|
|
63
|
+
}
|
|
64
|
+
case 'gate_moved':
|
|
65
|
+
return msg('approval.planReview.decision.gateMoved');
|
|
66
|
+
case 'gate_untrusted':
|
|
67
|
+
return msg('approval.planReview.decision.gateUntrusted');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
49
70
|
const PLAN_REVIEW_LEDGER_BUDGET_MS = 3_000;
|
|
50
71
|
const planReviewFactsFlights = new Map();
|
|
51
72
|
export function _resetPlanReviewFactsFlightsForTest() {
|
|
@@ -100,7 +100,7 @@ export interface PlanReviewDeliveryOptions {
|
|
|
100
100
|
}
|
|
101
101
|
export interface DecidePlanReviewOptions extends PlanReviewDeliveryOptions {
|
|
102
102
|
reason?: string;
|
|
103
|
-
|
|
103
|
+
card?: PlanReviewCardFacts;
|
|
104
104
|
}
|
|
105
105
|
export interface PlanReviewDecisionHandle {
|
|
106
106
|
settle(): void;
|
|
@@ -124,7 +124,7 @@ export interface ReopenPlanReviewSharedOpts {
|
|
|
124
124
|
}
|
|
125
125
|
export interface ReopenPlanReviewFreshOpts extends ReopenPlanReviewSharedOpts {
|
|
126
126
|
mintFreshQuestionId?: true;
|
|
127
|
-
deliverDecision?: (taskId: string, decision: Parameters<typeof decidePlanReview>[1], reason
|
|
127
|
+
deliverDecision?: (taskId: string, decision: Parameters<typeof decidePlanReview>[1], reason: string | undefined, card: PlanReviewCardFacts) => void | Promise<void>;
|
|
128
128
|
wire?: PlanReviewWire;
|
|
129
129
|
onOutcome?: PlanReviewDeliveryOptions['onOutcome'];
|
|
130
130
|
}
|
|
@@ -2,22 +2,21 @@ import { publishQuestionFrame, publishQuestionFrameFor, registerLocalQuestionRes
|
|
|
2
2
|
import { hostLog } from '../host.js';
|
|
3
3
|
import { abortableSleep, settleWithinDeadline } from '../abortableSleep.js';
|
|
4
4
|
import { clientHasVerbs, engineWireFor, noAnswerKindOf, sendOnce } from '../engineWireFor.js';
|
|
5
|
-
import { engineCapTrue } from '../engineCapsCache.js';
|
|
6
5
|
import { engineWireTarget } from '../engineWireTarget.js';
|
|
7
6
|
import { enqueuePlanReviewOutcome } from '../notifications.js';
|
|
8
7
|
import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
|
|
9
8
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
10
|
-
import { wireFailureTextsOf } from '../wireFailureShape.js';
|
|
9
|
+
import { readThrownExtra, wireFailureTextsOf } from '../wireFailureShape.js';
|
|
11
10
|
import { appendOwnElement, readOwnDataNonEmptyString, readOwnDataValue } from '../ownKey.js';
|
|
12
11
|
import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError, resumeContextUnavailableContent, resumeContextUnavailableFromError } from '../resumeRefusalCopy.js';
|
|
13
|
-
import { REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
|
|
12
|
+
import { APPROVAL_STALE, REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
|
|
14
13
|
import { refusalDeliveryOf } from './decisionRefusal.js';
|
|
15
14
|
import { directFailureVerdictOf, directSafetyStopOf } from './approvalResolution.js';
|
|
16
15
|
import { __resetDecisionLedgerForTests, decisionInFlightOf, decisionKeyInFlight, decisionLedgerKeysOf, decisionOpsWithKey, decisionReadMark, dropDecisionOp, latestDecisionHandedMark, markLatestDecisionNotApplied, openDecisionOp, planDecisionKey, readMarkOf, settleDecisionOp, settleDecisionOpsOnGateLeft, } from './decisionLedger.js';
|
|
17
16
|
import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
|
|
18
17
|
import { freshPlanReviewQuestionId, planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
|
|
19
18
|
import { isPlanReviewPark, mintPlanReviewCardFacts, notePlanReviewCardIssued, pausedCheckpointIdOf, planReviewCardFactsFrom, planReviewFactsCurrent, registeredPlanReviewCardFacts, samePlanReviewCardFacts, unknownBody, _resetPlanReviewRunsForTest, } from '../planReviewCardFacts.js';
|
|
20
|
-
import { planReviewAskAgainKey, planReviewCardNoteRef, planReviewUnreadableNoteRef, takePlanReviewFacts, _resetPlanReviewFactsFlightsForTest, } from './planReviewCard.js';
|
|
19
|
+
import { planReviewAskAgainKey, planReviewCardNoteRef, planReviewDecisionReasonRef, planReviewUnreadableNoteRef, takePlanReviewFacts, _resetPlanReviewFactsFlightsForTest, } from './planReviewCard.js';
|
|
21
20
|
import { EN_MESSAGES } from '../i18n/messages.en.js';
|
|
22
21
|
import { msg, renderMessage, DATA_FACE_LANGUAGE } from '../i18n/translate.js';
|
|
23
22
|
import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
|
|
@@ -183,6 +182,7 @@ const armedPlanReviews = new Map();
|
|
|
183
182
|
const offeredModeByQuestionId = new Map();
|
|
184
183
|
const OFFERED_MODE_LEDGER_MAX = 64;
|
|
185
184
|
export function _resetArmedPlanReviewsForTest() {
|
|
185
|
+
engineRefusedGateIds.clear();
|
|
186
186
|
armedPlanReviews.clear();
|
|
187
187
|
offeredModeByQuestionId.clear();
|
|
188
188
|
planReviewDismissalsByKey.clear();
|
|
@@ -519,7 +519,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
519
519
|
const cardDelivery = cardReason !== undefined ? { ...delivery, reason: cardReason } : delivery;
|
|
520
520
|
notePlanReviewAnswered(questionId);
|
|
521
521
|
hostLog('debug', `planReviewWire: arm responder delivering ${decided} for task ${taskId} (path=arm)`);
|
|
522
|
-
void decidePlanReviewFor(taskId, decided, modeAfter, cardDelivery,
|
|
522
|
+
void decidePlanReviewFor(taskId, decided, modeAfter, cardDelivery, facts);
|
|
523
523
|
return { ok: true };
|
|
524
524
|
};
|
|
525
525
|
unregister = registerLocalQuestionResponder(questionId, responder);
|
|
@@ -534,7 +534,7 @@ export function planReviewOutcomeTail(taskId, decision, effect, cause) {
|
|
|
534
534
|
switch (effect) {
|
|
535
535
|
case 'took_effect':
|
|
536
536
|
return decision === 'approve'
|
|
537
|
-
? `
|
|
537
|
+
? `The result of the resumed task ${JSON.stringify(taskId)} arrives in its completion notification; when it arrives, give the user a brief report of what was done.`
|
|
538
538
|
: 'Briefly acknowledge to the user that the plan was rejected and continue planning.';
|
|
539
539
|
case 'advanced':
|
|
540
540
|
return `Tell the user the ${decision} went through and that another approval is now waiting for them; do not report the task as finished.`;
|
|
@@ -543,9 +543,9 @@ export function planReviewOutcomeTail(taskId, decision, effect, cause) {
|
|
|
543
543
|
case 'not_sent':
|
|
544
544
|
return `Nothing was sent to the engine. Do not tell the user the plan was ${decision === 'approve' ? 'approved' : 'rejected'}; the plan is still waiting for a decision.`;
|
|
545
545
|
case 'not_applied':
|
|
546
|
-
return `The ${decision} did not take effect. Do not tell the user the plan was ${decision === 'approve' ? 'approved' : 'rejected'};
|
|
546
|
+
return `The ${decision} did not take effect. Do not tell the user the plan was ${decision === 'approve' ? 'approved' : 'rejected'}; find out where task ${JSON.stringify(taskId)} stands (the background task list or its next notification shows it) before saying anything about the plan.`;
|
|
547
547
|
case 'unconfirmed':
|
|
548
|
-
return `Do not state the result as fact: tell the user the ${decision} was submitted but its effect could not be confirmed;
|
|
548
|
+
return `Do not state the result as fact: tell the user the ${decision} was submitted but its effect could not be confirmed; the background task list or the next notification of task ${JSON.stringify(taskId)} shows where it stands.`;
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
551
|
function planReviewDecisionTimedOutOutcome(decision) {
|
|
@@ -632,12 +632,39 @@ export function latestPlanReviewDecisionMark(taskId) {
|
|
|
632
632
|
export function __resetPlanReviewDecisionLatchForTests() {
|
|
633
633
|
__resetDecisionLedgerForTests('plan_review');
|
|
634
634
|
}
|
|
635
|
+
const engineRefusedGateIds = new Set();
|
|
636
|
+
const ENGINE_REFUSED_GATE_IDS_MAX = 64;
|
|
637
|
+
function noteEngineRefusedGate(id) {
|
|
638
|
+
if (engineRefusedGateIds.has(id))
|
|
639
|
+
return;
|
|
640
|
+
engineRefusedGateIds.add(id);
|
|
641
|
+
while (engineRefusedGateIds.size > ENGINE_REFUSED_GATE_IDS_MAX) {
|
|
642
|
+
const oldest = engineRefusedGateIds.values().next();
|
|
643
|
+
if (oldest.done === true)
|
|
644
|
+
break;
|
|
645
|
+
engineRefusedGateIds.delete(oldest.value);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function planReviewDecisionReasonSegment(reason, card) {
|
|
649
|
+
if (reason === undefined)
|
|
650
|
+
return '';
|
|
651
|
+
try {
|
|
652
|
+
return ` ${renderMessage(planReviewDecisionReasonRef(reason, card), DATA_FACE_LANGUAGE)}`;
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
return '';
|
|
656
|
+
}
|
|
657
|
+
}
|
|
635
658
|
export async function decidePlanReview(taskId, decision, permissionModeAfter, opts) {
|
|
636
|
-
return decidePlanReviewFor(taskId, decision, permissionModeAfter, opts,
|
|
659
|
+
return decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, decisionCardOf(taskId, opts));
|
|
637
660
|
}
|
|
638
|
-
function
|
|
661
|
+
function decisionCardOf(taskId, opts) {
|
|
639
662
|
try {
|
|
640
|
-
|
|
663
|
+
if (opts === null || typeof opts !== 'object')
|
|
664
|
+
return undefined;
|
|
665
|
+
const raw = readOwnDataValue(opts, 'card');
|
|
666
|
+
const facts = raw !== null && typeof raw === 'object' ? planReviewCardFactsFrom(raw) : undefined;
|
|
667
|
+
return facts !== undefined && facts.taskId === taskId ? facts : undefined;
|
|
641
668
|
}
|
|
642
669
|
catch {
|
|
643
670
|
return undefined;
|
|
@@ -650,6 +677,8 @@ function planReviewSettledVerdict(r) {
|
|
|
650
677
|
return status !== 'completed' && !gateMoved ? 'still_parked' : 'unconfirmed';
|
|
651
678
|
if (status === 'completed')
|
|
652
679
|
return 'took_effect';
|
|
680
|
+
if (status === 'running' && r.decision === 'reject')
|
|
681
|
+
return 'took_effect';
|
|
653
682
|
const newGateHasCard = () => {
|
|
654
683
|
const leg = durableDecideLegOf({ gateKind: r.gateKindAfter ?? null });
|
|
655
684
|
return leg === 'approval' || leg === 'plan_review';
|
|
@@ -691,7 +720,7 @@ function askAgainReasonOf(card, opts) {
|
|
|
691
720
|
return renderMessage(msg('approval.planReview.askAgain.unknown'), DATA_FACE_LANGUAGE);
|
|
692
721
|
}
|
|
693
722
|
}
|
|
694
|
-
async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts,
|
|
723
|
+
async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, card) {
|
|
695
724
|
if (typeof taskId === 'string' && planReviewOpsOf(taskId).some((op) => op.phase === 'posting')) {
|
|
696
725
|
hostLog('error', `planReviewWire: decidePlanReview(${decision}) for task ${taskId} REFUSED — a decision for this task is already in flight (duplicate delivery latched)`);
|
|
697
726
|
return;
|
|
@@ -724,7 +753,7 @@ async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts,
|
|
|
724
753
|
try {
|
|
725
754
|
const wireDecision = decision === 'ask_again' ? 'reject' : decision;
|
|
726
755
|
const wireOpts = decision === 'ask_again' ? { ...opts, reason: askAgainReasonOf(card, opts) } : opts;
|
|
727
|
-
await decidePlanReviewInner(taskId, wireDecision, permissionModeAfter, dispatchNo, releaseLatch, settle, wireOpts,
|
|
756
|
+
await decidePlanReviewInner(taskId, wireDecision, permissionModeAfter, dispatchNo, releaseLatch, settle, wireOpts, card, decision);
|
|
728
757
|
}
|
|
729
758
|
finally {
|
|
730
759
|
releaseLatch();
|
|
@@ -745,8 +774,11 @@ function planReviewClientsFor(resolved) {
|
|
|
745
774
|
repull: () => resolved.make({ timeoutMs: 15_000 }),
|
|
746
775
|
};
|
|
747
776
|
}
|
|
748
|
-
async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, settle, opts,
|
|
777
|
+
async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, settle, opts, card, semanticDecision) {
|
|
749
778
|
let outcome;
|
|
779
|
+
let decisionReason;
|
|
780
|
+
const gateBefore = card?.checkpointId;
|
|
781
|
+
const approvalWithheld = decision !== 'approve' ? undefined : card === undefined ? 'card_missing' : card.body.kind !== 'shown' || card.checkpointId === undefined ? 'card_withholds' : undefined;
|
|
750
782
|
let effect = 'unconfirmed';
|
|
751
783
|
let stillParkedCause;
|
|
752
784
|
let statusSeen;
|
|
@@ -758,6 +790,12 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
758
790
|
effect = 'not_sent';
|
|
759
791
|
outcome = `The plan_review ${decision} could NOT be sent: the post-approval permission mode given with it is not valid (it must be "default" or "acceptEdits"). Tell the user plainly that the decision did not go through; the plan is still waiting.`;
|
|
760
792
|
}
|
|
793
|
+
else if (approvalWithheld !== undefined) {
|
|
794
|
+
hostLog('debug', `planReviewWire: not-sent decision=${decision} reason=${approvalWithheld}`);
|
|
795
|
+
effect = 'not_sent';
|
|
796
|
+
decisionReason = approvalWithheld;
|
|
797
|
+
outcome = renderMessage(msg('approval.planReview.decision.notSent', { decision }), DATA_FACE_LANGUAGE);
|
|
798
|
+
}
|
|
761
799
|
else if (resolved.kind === 'no-target') {
|
|
762
800
|
hostLog('error', `planReviewWire: ${decision} NOT sent — engineWireTarget() unavailable`);
|
|
763
801
|
effect = 'not_sent';
|
|
@@ -777,11 +815,24 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
777
815
|
}
|
|
778
816
|
else {
|
|
779
817
|
const client = clients.decide;
|
|
818
|
+
const checkpointId = gateBefore !== undefined && !engineRefusedGateIds.has(gateBefore) ? gateBefore : undefined;
|
|
819
|
+
const repullRow = async () => {
|
|
820
|
+
const probe = clients.repull();
|
|
821
|
+
if (!probe)
|
|
822
|
+
return undefined;
|
|
823
|
+
if (wire !== undefined) {
|
|
824
|
+
const got = await settleWithinDeadline(PLAN_REVIEW_INJECTED_REPULL_TIMEOUT_MS, (signal) => probe.runs.get(taskId, { signal }));
|
|
825
|
+
if (got.kind === 'error')
|
|
826
|
+
throw new Error(got.detail);
|
|
827
|
+
if (got.kind === 'deadline')
|
|
828
|
+
throw new Error(`post-decide status re-pull passed its ${PLAN_REVIEW_INJECTED_REPULL_TIMEOUT_MS} ms deadline`);
|
|
829
|
+
return got.value;
|
|
830
|
+
}
|
|
831
|
+
return (await probe.runs.get(taskId));
|
|
832
|
+
};
|
|
780
833
|
try {
|
|
781
834
|
const mode = isPlanReviewModeAfter(permissionModeAfter) ? permissionModeAfter : undefined;
|
|
782
835
|
const reason = denyReasonForWire(opts?.reason, `plan-review ${taskId}`);
|
|
783
|
-
const capsBase = resolved.kind === 'target' ? resolved.target.baseUrl : resolved.capsBaseUrl;
|
|
784
|
-
const checkpointId = gateBefore !== undefined && engineCapTrue(capsBase, 'planReviewCheckpointId') ? gateBefore : undefined;
|
|
785
836
|
const reqBody = { decision };
|
|
786
837
|
if (reason !== undefined)
|
|
787
838
|
reqBody.reason = reason;
|
|
@@ -812,20 +863,8 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
812
863
|
let gateAfter;
|
|
813
864
|
let gateKindAfter;
|
|
814
865
|
try {
|
|
815
|
-
const
|
|
816
|
-
if (
|
|
817
|
-
let post;
|
|
818
|
-
if (wire !== undefined) {
|
|
819
|
-
const got = await settleWithinDeadline(PLAN_REVIEW_INJECTED_REPULL_TIMEOUT_MS, (signal) => probe.runs.get(taskId, { signal }));
|
|
820
|
-
if (got.kind === 'error')
|
|
821
|
-
throw new Error(got.detail);
|
|
822
|
-
if (got.kind === 'deadline')
|
|
823
|
-
throw new Error(`post-decide status re-pull passed its ${PLAN_REVIEW_INJECTED_REPULL_TIMEOUT_MS} ms deadline`);
|
|
824
|
-
post = got.value;
|
|
825
|
-
}
|
|
826
|
-
else {
|
|
827
|
-
post = (await probe.runs.get(taskId));
|
|
828
|
-
}
|
|
866
|
+
const post = await repullRow();
|
|
867
|
+
if (post !== undefined) {
|
|
829
868
|
postStatus = bodyWordOf(post, 'status');
|
|
830
869
|
gateAfter = pausedCheckpointIdOf(post?.result);
|
|
831
870
|
gateKindAfter = pausedGateKindOf(post?.result);
|
|
@@ -837,7 +876,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
837
876
|
const bodyStatus = bodyWordOf(body, 'status');
|
|
838
877
|
const effective = postStatus ?? bodyStatus;
|
|
839
878
|
statusSeen = effective;
|
|
840
|
-
effect = planReviewSettledVerdict({ postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
|
|
879
|
+
effect = planReviewSettledVerdict({ decision, postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
|
|
841
880
|
const code = bodyWordOf(body, 'errorCode');
|
|
842
881
|
if (effect === 'still_parked' && postStatus === 'needs_review' && !bodySaysReparked(body)) {
|
|
843
882
|
outcome =
|
|
@@ -855,7 +894,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
855
894
|
}
|
|
856
895
|
else if (effect === 'took_effect') {
|
|
857
896
|
outcome =
|
|
858
|
-
`The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — final status: ${effective} ` +
|
|
897
|
+
`The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — ${effective === 'completed' ? 'final status' : 'status'}: ${effective} ` +
|
|
859
898
|
`(${postStatus !== undefined ? 'post-decide re-checked: task left the review gate' : "from the decision's own reply; the post-decide re-check was unavailable"}).`;
|
|
860
899
|
}
|
|
861
900
|
else if (effective === 'needs_review' && (gateBefore === undefined || gateAfter === undefined || postStatus !== 'needs_review')) {
|
|
@@ -898,6 +937,36 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
898
937
|
`This ${decision} was not applied: the engine no longer has a pending plan review for this task — an earlier decision most likely already went through (or the review expired). ` +
|
|
899
938
|
'This is NOT a rejection of the plan and NOT a failure of the task; the answer just given simply had nothing left to act on.';
|
|
900
939
|
}
|
|
940
|
+
else if (status === 409 && refusedCode === APPROVAL_STALE) {
|
|
941
|
+
let untrusted = false;
|
|
942
|
+
if (checkpointId !== undefined) {
|
|
943
|
+
const bag = readThrownExtra(e);
|
|
944
|
+
const noCurrentPending = bag.state === 'object' &&
|
|
945
|
+
(() => {
|
|
946
|
+
try {
|
|
947
|
+
return Object.getOwnPropertyDescriptor(bag.value, 'currentPending') === undefined;
|
|
948
|
+
}
|
|
949
|
+
catch {
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
})();
|
|
953
|
+
if (noCurrentPending) {
|
|
954
|
+
try {
|
|
955
|
+
const row = await repullRow();
|
|
956
|
+
untrusted = row !== undefined && pausedCheckpointIdOf(row?.result) === checkpointId;
|
|
957
|
+
}
|
|
958
|
+
catch {
|
|
959
|
+
untrusted = false;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
if (untrusted && checkpointId !== undefined)
|
|
964
|
+
noteEngineRefusedGate(checkpointId);
|
|
965
|
+
hostLog('debug', `planReviewWire: approval-stale decision=${decision} reason=${untrusted ? 'gate_untrusted' : 'gate_moved'}`);
|
|
966
|
+
effect = 'not_applied';
|
|
967
|
+
decisionReason = untrusted ? 'gate_untrusted' : 'gate_moved';
|
|
968
|
+
outcome = renderMessage(msg('approval.planReview.decision.refused', { decision }), DATA_FACE_LANGUAGE);
|
|
969
|
+
}
|
|
901
970
|
else if (checkpointUnsupportedVersionFromError(e) !== null) {
|
|
902
971
|
const cu = checkpointUnsupportedVersionFromError(e);
|
|
903
972
|
hostLog('debug', `planReviewWire: ${decision} → 409 checkpoint.unsupported_version (the server cannot continue this parked review in its stored form; still pending)`);
|
|
@@ -939,6 +1008,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
939
1008
|
}
|
|
940
1009
|
}
|
|
941
1010
|
}
|
|
1011
|
+
outcome = `${outcome}${planReviewDecisionReasonSegment(decisionReason, card)}`;
|
|
942
1012
|
const tail = planReviewOutcomeTail(taskId, decision, effect, stillParkedCause);
|
|
943
1013
|
const prompt = `<plan-review-outcome>\n${outcome}\n${tail}\n</plan-review-outcome>`;
|
|
944
1014
|
const onOutcome = opts?.onOutcome;
|
|
@@ -1087,10 +1157,10 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
|
|
|
1087
1157
|
? { ...(opts.wire !== undefined ? { wire: opts.wire } : {}), ...(opts.onOutcome !== undefined ? { onOutcome: opts.onOutcome } : {}) }
|
|
1088
1158
|
: undefined;
|
|
1089
1159
|
const deliver = opts?.deliverDecision ??
|
|
1090
|
-
((tid, decision, reason) => {
|
|
1160
|
+
((tid, decision, reason, card) => {
|
|
1091
1161
|
hostLog('debug', `planReviewWire: reopen responder delivering ${decision} for task ${tid} (path=reopen-default)`);
|
|
1092
1162
|
const withReason = reason === undefined ? delivery : { ...delivery, reason };
|
|
1093
|
-
void decidePlanReviewFor(tid, decision, undefined, withReason,
|
|
1163
|
+
void decidePlanReviewFor(tid, decision, undefined, withReason, card);
|
|
1094
1164
|
});
|
|
1095
1165
|
const activeKey = `${sessionKey}\u0000${canonicalId}`;
|
|
1096
1166
|
let unregister = () => { };
|
|
@@ -1116,10 +1186,7 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
|
|
|
1116
1186
|
notePlanReviewDecisiveAnswerFor(sessionKey, taskId);
|
|
1117
1187
|
notePlanReviewAnsweredFor(sessionKey, questionId);
|
|
1118
1188
|
try {
|
|
1119
|
-
|
|
1120
|
-
await deliver(taskId, decided);
|
|
1121
|
-
else
|
|
1122
|
-
await deliver(taskId, decided, cardReason);
|
|
1189
|
+
await deliver(taskId, decided, cardReason, facts);
|
|
1123
1190
|
}
|
|
1124
1191
|
catch (e) {
|
|
1125
1192
|
hostLog('error', `planReviewWire: reopen decision delivery threw for task ${taskId}: ${String(e)}`);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type SessionAllowLeg = 'frame' | 'v1' | 'generic';
|
|
2
|
+
export declare function sessionAllowCarried(leg: SessionAllowLeg, approvalId: unknown): boolean;
|
|
3
|
+
export type SessionAllowWhy = 'given' | 'route_cannot_carry' | 'evidence_unreadable' | 'card_unreadable';
|
|
4
|
+
export type CardUnreadableEvidence = readonly string[] | 'absent' | 'unreadable';
|
|
5
|
+
export declare function readCardUnreadableEvidence(card: object): CardUnreadableEvidence;
|
|
6
|
+
export declare function sessionAllowCardReadable(card: object | null | undefined): Exclude<SessionAllowWhy, 'route_cannot_carry'>;
|
|
7
|
+
export declare function sessionAllowEligible(card: object | null | undefined): SessionAllowWhy;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function sessionAllowCarried(leg, approvalId) {
|
|
2
|
+
if (leg === 'frame')
|
|
3
|
+
return true;
|
|
4
|
+
if (leg === 'v1')
|
|
5
|
+
return typeof approvalId === 'string' && approvalId !== '';
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
export function readCardUnreadableEvidence(card) {
|
|
9
|
+
try {
|
|
10
|
+
if (!('unreadableEvidence' in card))
|
|
11
|
+
return 'absent';
|
|
12
|
+
const d = Object.getOwnPropertyDescriptor(card, 'unreadableEvidence');
|
|
13
|
+
const list = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
|
|
14
|
+
return Array.isArray(list) && list.every((k) => typeof k === 'string') ? list : 'unreadable';
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return 'unreadable';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function sessionAllowCardReadable(card) {
|
|
21
|
+
if (typeof card !== 'object' || card === null)
|
|
22
|
+
return 'card_unreadable';
|
|
23
|
+
try {
|
|
24
|
+
if ('cardPayloadUnreadable' in card)
|
|
25
|
+
return 'card_unreadable';
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return 'card_unreadable';
|
|
29
|
+
}
|
|
30
|
+
const evidence = readCardUnreadableEvidence(card);
|
|
31
|
+
if (evidence === 'unreadable')
|
|
32
|
+
return 'card_unreadable';
|
|
33
|
+
return evidence !== 'absent' && evidence.length > 0 ? 'evidence_unreadable' : 'given';
|
|
34
|
+
}
|
|
35
|
+
export function sessionAllowEligible(card) {
|
|
36
|
+
if (typeof card !== 'object' || card === null)
|
|
37
|
+
return 'card_unreadable';
|
|
38
|
+
let carried;
|
|
39
|
+
try {
|
|
40
|
+
const d = Object.getOwnPropertyDescriptor(card, 'allowSessionCarried');
|
|
41
|
+
carried = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return 'card_unreadable';
|
|
45
|
+
}
|
|
46
|
+
if (carried !== true)
|
|
47
|
+
return 'route_cannot_carry';
|
|
48
|
+
return sessionAllowCardReadable(card);
|
|
49
|
+
}
|
|
@@ -128,17 +128,11 @@ export interface SessionPolicyInertEntryRefusal {
|
|
|
128
128
|
export declare function tightenSessionPolicy(facade: SessionPolicyFacade, req: SessionPolicyTightenRequest, opts?: {
|
|
129
129
|
signal?: AbortSignal;
|
|
130
130
|
}): Promise<SessionPolicyTightenOutcome>;
|
|
131
|
-
export interface SessionPolicyTightenFacts {
|
|
132
|
-
readonly agentsStartedBeforeFirstRule?: number | undefined;
|
|
133
|
-
readonly workflowsRunning?: number | undefined;
|
|
134
|
-
readonly untrackedBackgroundItems?: number | undefined;
|
|
135
|
-
readonly inFlightUnreadable?: true | undefined;
|
|
136
|
-
}
|
|
137
131
|
export interface SessionPolicyDeploymentFacts {
|
|
138
132
|
readonly operator?: 'present' | 'absent';
|
|
139
133
|
readonly identityAttach?: 'available' | 'unavailable';
|
|
140
134
|
}
|
|
141
|
-
export declare function sessionPolicyTightenNotice(outcome: SessionPolicyTightenOutcome, lang?: UiLanguage,
|
|
135
|
+
export declare function sessionPolicyTightenNotice(outcome: SessionPolicyTightenOutcome, lang?: UiLanguage, deployment?: SessionPolicyDeploymentFacts): string;
|
|
142
136
|
export declare function sessionPolicyReadNotice(outcome: Exclude<SessionPolicyReadOutcome, {
|
|
143
137
|
kind: 'present';
|
|
144
138
|
}> | {
|
|
@@ -414,29 +414,6 @@ export async function tightenSessionPolicy(facade, req, opts) {
|
|
|
414
414
|
return { kind: 'conflict', rev: second.retryAtRev };
|
|
415
415
|
}
|
|
416
416
|
const earlierGrammarNote = (lang) => renderMessage(msg('rules.sessionPolicy.tighten.earlierGrammar'), lang);
|
|
417
|
-
function factSlot(facts, key) {
|
|
418
|
-
try {
|
|
419
|
-
return ownRead(facts, key);
|
|
420
|
-
}
|
|
421
|
-
catch {
|
|
422
|
-
return undefined;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
function factCount(facts, key) {
|
|
426
|
-
const n = factSlot(facts, key);
|
|
427
|
-
return typeof n === 'number' && Number.isSafeInteger(n) && n >= 1 ? n : undefined;
|
|
428
|
-
}
|
|
429
|
-
function firstRuleWorkFactsOf(facts) {
|
|
430
|
-
const none = { agents: undefined, workflows: undefined, untracked: undefined, unreadable: false };
|
|
431
|
-
if (typeof facts !== 'object' || facts === null)
|
|
432
|
-
return none;
|
|
433
|
-
return {
|
|
434
|
-
agents: factCount(facts, 'agentsStartedBeforeFirstRule'),
|
|
435
|
-
workflows: factCount(facts, 'workflowsRunning'),
|
|
436
|
-
untracked: factCount(facts, 'untrackedBackgroundItems'),
|
|
437
|
-
unreadable: factSlot(facts, 'inFlightUnreadable') === true,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
417
|
function deploymentHasNoOperator(d) {
|
|
441
418
|
try {
|
|
442
419
|
return typeof d === 'object' && d !== null && ownRead(d, 'operator') === 'absent';
|
|
@@ -453,28 +430,11 @@ function deploymentHasNoIdentity(d) {
|
|
|
453
430
|
return false;
|
|
454
431
|
}
|
|
455
432
|
}
|
|
456
|
-
export function sessionPolicyTightenNotice(outcome, lang,
|
|
433
|
+
export function sessionPolicyTightenNotice(outcome, lang, deployment) {
|
|
457
434
|
const say = (key, params) => renderMessage(msg(key, params), lang);
|
|
458
435
|
switch (outcome.kind) {
|
|
459
|
-
case 'written':
|
|
460
|
-
|
|
461
|
-
const w = firstRuleWorkFactsOf(facts);
|
|
462
|
-
const unseen = msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.unseen');
|
|
463
|
-
if (w.unreadable)
|
|
464
|
-
return `${head} ${renderMessage(msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.unreadable'), lang)} ${renderMessage(unseen, lang)}`;
|
|
465
|
-
const parts = [];
|
|
466
|
-
if (w.agents !== undefined)
|
|
467
|
-
appendOwnElement(parts, msg(w.agents === 1 ? 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.agents.one' : 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.agents.many', { n: w.agents }));
|
|
468
|
-
if (w.workflows !== undefined)
|
|
469
|
-
appendOwnElement(parts, msg(w.workflows === 1 ? 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.workflows.one' : 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.workflows.many', { n: w.workflows }));
|
|
470
|
-
if (w.untracked !== undefined)
|
|
471
|
-
appendOwnElement(parts, msg(w.untracked === 1 ? 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.untracked.one' : 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.untracked.many', { n: w.untracked }));
|
|
472
|
-
const [p0, p1, p2] = parts;
|
|
473
|
-
if (p0 === undefined)
|
|
474
|
-
return head;
|
|
475
|
-
const list = p1 === undefined ? p0 : p2 === undefined ? msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.list.two', { a: p0, b: p1 }) : msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.list.three', { a: p0, b: p1, c: p2 });
|
|
476
|
-
return `${head} ${renderMessage(msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.counts', { list }), lang)} ${renderMessage(unseen, lang)}`;
|
|
477
|
-
}
|
|
436
|
+
case 'written':
|
|
437
|
+
return say('rules.sessionPolicy.tighten.written', { rev: outcome.rev });
|
|
478
438
|
case 'conflict':
|
|
479
439
|
return say('rules.sessionPolicy.tighten.conflict');
|
|
480
440
|
case 'loosen_forbidden':
|