@sema-agent/server 7.118.0 → 7.120.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/MIGRATION.md +139 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +26 -13
- package/deploy/sema-up/README.md +2 -0
- package/deploy/sema-up/chart/templates/minio.yaml +2 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
- package/deploy/sema-up/chart/templates/server.yaml +1 -0
- package/deploy/sema-up/chart/values.yaml +7 -1
- package/deploy/sema-up/cluster-up.sh +20 -8
- package/deploy/sema-up/kube-up.sh +29 -16
- package/deploy/sema-up/lib/buckets.sh +21 -0
- package/deploy/sema-up/lib/health.sh +33 -0
- package/deploy/sema-up/sema-up.sh +14 -9
- package/deploy/sema-up/smoke.sh +16 -6
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
- package/dist/approval-ask-audit-store.js +2 -7
- package/dist/approval-card.d.ts +5 -5
- package/dist/approval.d.ts +51 -5
- package/dist/approval.js +76 -13
- package/dist/audit.d.ts +12 -1
- package/dist/audit.js +62 -2
- package/dist/auto-mode-face.d.ts +2 -3
- package/dist/bake-runner/main.js +1 -6
- package/dist/bake-runner/protocol.d.ts +18 -0
- package/dist/bake-runner/protocol.js +22 -2
- package/dist/bake-runner/runner.d.ts +12 -8
- package/dist/bake-runner/runner.js +5 -9
- package/dist/boot/config-center.js +6 -6
- package/dist/boot/coordinators.js +4 -1
- package/dist/boot/device-lane.d.ts +3 -3
- package/dist/boot/device-lane.js +1 -2
- package/dist/boot/execution-env.js +3 -2
- package/dist/boot/fatal-exit.d.ts +7 -1
- package/dist/boot/fatal-exit.js +2 -1
- package/dist/boot/leader.d.ts +4 -4
- package/dist/boot/leader.js +1 -2
- package/dist/boot/parked-revive-gate.d.ts +5 -1
- package/dist/boot/parked-revive-gate.js +8 -2
- package/dist/boot/resolve-spec.js +7 -4
- package/dist/boot/runner-deps.d.ts +4 -3
- package/dist/boot/runner-deps.js +7 -3
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-07-capability-layer.js +5 -1
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.js +12 -7
- package/dist/capabilities/builtin-tools.d.ts +31 -8
- package/dist/capabilities/builtin-tools.js +22 -1
- package/dist/capabilities/center-plugins.d.ts +2 -0
- package/dist/capabilities/center-plugins.js +24 -6
- package/dist/capabilities/code-review-council.js +8 -11
- package/dist/capabilities/scenarios.d.ts +42 -13
- package/dist/capabilities/scenarios.js +36 -9
- package/dist/capabilities/send-user-file-tool.d.ts +6 -3
- package/dist/capabilities/send-user-file-tool.js +1 -1
- package/dist/config-catalog.js +5 -5
- package/dist/config-lkg.d.ts +33 -5
- package/dist/config-lkg.js +55 -11
- package/dist/config-types.d.ts +8 -1
- package/dist/config.js +11 -11
- package/dist/deployment-governance.d.ts +8 -3
- package/dist/deployment-governance.js +7 -4
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +4 -0
- package/dist/device-ws-hub.js +17 -9
- package/dist/device-ws-protocol.d.ts +15 -0
- package/dist/host-decision.d.ts +52 -27
- package/dist/host-decision.js +14 -7
- package/dist/http/admission.js +6 -6
- package/dist/http/resume-legs.js +6 -1
- package/dist/http/routes/approvals-assistant.d.ts +25 -3
- package/dist/http/routes/approvals-assistant.js +9 -5
- package/dist/http/routes/capabilities.js +5 -3
- package/dist/http/routes/devices.js +3 -3
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +13 -6
- package/dist/http/routes/runs.js +11 -5
- package/dist/http/routes/sessions.js +20 -16
- package/dist/http/wire-types.d.ts +12 -13
- package/dist/images/manifest.d.ts +7 -1
- package/dist/images/manifest.js +18 -16
- package/dist/list-entry-display.d.ts +14 -0
- package/dist/list-entry-display.js +14 -0
- package/dist/memory-store-refusal.d.ts +7 -12
- package/dist/memory-store-refusal.js +4 -5
- package/dist/namespaced-rule-name.d.ts +4 -3
- package/dist/namespaced-rule-name.js +2 -6
- package/dist/observability/fail-open.d.ts +9 -1
- package/dist/observability/fail-open.js +9 -1
- package/dist/operator-ask.d.ts +7 -6
- package/dist/plugins/approval-exemption-store.d.ts +4 -0
- package/dist/plugins/approval-exemption-store.js +1 -0
- package/dist/plugins/blob-backend.d.ts +13 -0
- package/dist/plugins/blob-backend.js +26 -0
- package/dist/plugins/local-session-store.d.ts +78 -47
- package/dist/plugins/local-session-store.js +152 -122
- package/dist/plugins/one-time-migrations.d.ts +14 -0
- package/dist/plugins/one-time-migrations.js +90 -3
- package/dist/plugins/permission-rule-store-file.js +2 -6
- package/dist/plugins/permission-rule-store-sql.d.ts +64 -6
- package/dist/plugins/permission-rule-store-sql.js +166 -59
- package/dist/plugins/remote-env-device.d.ts +12 -1
- package/dist/plugins/remote-env-device.js +13 -5
- package/dist/plugins/scheduler-support.d.ts +8 -4
- package/dist/plugins/scheduler-support.js +5 -5
- package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
- package/dist/plugins/snapshot-bucket-presence.js +40 -0
- package/dist/plugins/store-backend.d.ts +17 -6
- package/dist/plugins/store-backend.js +46 -16
- package/dist/plugins/store-open-wait.d.ts +33 -0
- package/dist/plugins/store-open-wait.js +73 -0
- package/dist/rule-entry-list.d.ts +0 -1
- package/dist/rule-entry-list.js +5 -10
- package/dist/rules-consent.d.ts +12 -3
- package/dist/rules-consent.js +5 -4
- package/dist/run-local.js +6 -3
- package/dist/security.d.ts +5 -3
- package/dist/security.js +4 -0
- package/dist/sema-config-dir.d.ts +19 -0
- package/dist/sema-config-dir.js +33 -0
- package/dist/session-policy-wire.js +10 -14
- package/dist/spec-fields.d.ts +7 -2
- package/dist/store-posture.d.ts +14 -6
- package/dist/store-posture.js +7 -4
- package/dist/task-settings.d.ts +30 -58
- package/dist/task-settings.js +6 -18
- package/dist/tool-approval.d.ts +97 -43
- package/dist/tool-approval.js +120 -31
- package/dist/trace/engine-notice-wire.d.ts +6 -4
- package/dist/trace/engine-notice-wire.js +16 -9
- package/dist/trace/redact.d.ts +6 -0
- package/dist/trace/redact.js +9 -9
- package/dist/trace/self-minted-wire.d.ts +15 -1
- package/dist/trace/self-minted-wire.js +2 -0
- package/dist/user-input-ledger.d.ts +19 -15
- package/dist/user-input-ledger.js +12 -20
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +56 -14
- package/package.json +2 -2
package/dist/tool-approval.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { contentKindOf } from "./approval-content-kind.js";
|
|
2
|
-
import {
|
|
2
|
+
import { heldAskOf, liveSettledBy, readDecidedBy, retiredDecisionKeyFacts, DECIDED_BY_OUTSIDE_CLOSED_SET, POLICY_CANNOT_ALLOW_PERSON_ASK } from "./host-decision.js";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
-
import { uuidv7, classifyAsk, foldArbitrationRow, screenAskClassMembers, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
|
|
4
|
+
import { uuidv7, classifyAsk, foldArbitrationRow, policyCannotAnswer, screenAskClassMembers, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
|
|
5
5
|
import { precheckCardRuleText } from "./rules-consent.js";
|
|
6
6
|
import { redactDeep, redactSecrets, redactSecretsWithArms } from "./trace/redact.js";
|
|
7
7
|
import { hasBidiControls } from "./text-bidi.js";
|
|
@@ -15,6 +15,8 @@ import { settleByPolicy } from "./approval-policy-settlement.js";
|
|
|
15
15
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
16
16
|
import { governanceOriginOf } from "./runtime-governance.js";
|
|
17
17
|
import { answerGrantsBeyondCall, grantBindingOf } from "./standing-answer-grant.js";
|
|
18
|
+
import { operatorAsk } from "./operator-ask.js";
|
|
19
|
+
import { APPROVAL_EXEMPTED_EVENT } from "./plugins/approval-exemption-store.js";
|
|
18
20
|
const defaultLogger = createLogger();
|
|
19
21
|
export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
|
|
20
22
|
export function isApprovalGateKind(k) {
|
|
@@ -54,7 +56,7 @@ function durableRuleMaterial(material) {
|
|
|
54
56
|
export const ASK_DECISION_CONSUMER_ABSENT = "absent";
|
|
55
57
|
const MAX_INFLIGHT_TERMINAL_CLAIMS = 2;
|
|
56
58
|
const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
|
|
57
|
-
const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2_000;
|
|
59
|
+
export const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2_000;
|
|
58
60
|
const DURABLE_CALL_TIMEOUT_MS = 5_000;
|
|
59
61
|
const TERMINAL_LADDER_WALL_MS = DURABLE_CALL_TIMEOUT_MS + DURABLE_CONVERGE_READ_TIMEOUT_MS + DURABLE_CONVERGE_BACKOFF_MS.reduce((a, b) => a + b, 0);
|
|
60
62
|
const TERMINAL_CLAIM_CAP_POLL_MS = 50;
|
|
@@ -85,43 +87,65 @@ function sessionAllowCategory(toolName) {
|
|
|
85
87
|
function sessionAllowKey(owner, sessionId, category, binding) {
|
|
86
88
|
return JSON.stringify([owner, sessionId, category, binding.kind === "bound" ? binding.digest : null]);
|
|
87
89
|
}
|
|
88
|
-
function askStandingAnswerFacts(req, governanceForced) {
|
|
90
|
+
function askStandingAnswerFacts(req, governanceForced, deploymentListAsks) {
|
|
91
|
+
const classes = screenAskClassMembers(req.askClass, req.classes)?.classes;
|
|
89
92
|
return {
|
|
90
93
|
governanceForced,
|
|
91
94
|
requiresRealApproval: req.requiresRealApproval === true,
|
|
92
95
|
mandated: req.mandated === true,
|
|
93
96
|
grantBinding: grantBindingOf(req),
|
|
94
|
-
classes
|
|
97
|
+
classes,
|
|
98
|
+
deploymentListQuestion: carriesDeploymentListQuestion(deploymentListAsks, classes),
|
|
95
99
|
};
|
|
96
100
|
}
|
|
97
|
-
|
|
101
|
+
const NO_GATE_FACTS = { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined, mandateMarks: {} };
|
|
102
|
+
const DEPLOYMENT_LIST_ASK_CLASSES = new Set(classifyAsk(operatorAsk("APPROVAL_REQUIRE", ""), NO_GATE_FACTS).classes);
|
|
103
|
+
export function carriesDeploymentListQuestion(deploymentListAsks, classes) {
|
|
104
|
+
return deploymentListAsks && classes !== undefined && classes.some((c) => DEPLOYMENT_LIST_ASK_CLASSES.has(c));
|
|
105
|
+
}
|
|
106
|
+
export function policyAskStandingAnswerFacts(req, ask, deploymentListQuestion) {
|
|
98
107
|
return {
|
|
99
108
|
governanceForced: false,
|
|
100
109
|
requiresRealApproval: ask.requiresRealApproval === true,
|
|
101
110
|
mandated: ask.mandated === true,
|
|
102
111
|
grantBinding: grantBindingOf(req),
|
|
103
|
-
classes: [...classifyAsk(ask,
|
|
112
|
+
classes: [...classifyAsk(ask, NO_GATE_FACTS).classes],
|
|
113
|
+
deploymentListQuestion,
|
|
104
114
|
};
|
|
105
115
|
}
|
|
106
|
-
export function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
facts.grantBinding.kind
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
export function standingAnswerHome(facts) {
|
|
117
|
+
if (facts.governanceForced || facts.requiresRealApproval || facts.mandated || facts.classes === undefined)
|
|
118
|
+
return "none";
|
|
119
|
+
if (facts.deploymentListQuestion) {
|
|
120
|
+
return facts.grantBinding.kind === "none" && facts.classes.every((c) => DEPLOYMENT_LIST_ASK_CLASSES.has(c)) ? "server_store" : "none";
|
|
121
|
+
}
|
|
122
|
+
return facts.grantBinding.kind !== "unreadable" && foldArbitrationRow(new Set(facts.classes)).ruleMayClear ? "seat" : "none";
|
|
113
123
|
}
|
|
114
124
|
export function standingAnswerDurablyRecordable(facts) {
|
|
115
|
-
|
|
125
|
+
const home = standingAnswerHome(facts);
|
|
126
|
+
switch (home) {
|
|
127
|
+
case "none":
|
|
128
|
+
return false;
|
|
129
|
+
case "seat":
|
|
130
|
+
return facts.grantBinding.kind === "none";
|
|
131
|
+
case "server_store":
|
|
132
|
+
return true;
|
|
133
|
+
default: {
|
|
134
|
+
const unhandled = home;
|
|
135
|
+
throw new Error(`unhandled standing-answer home: ${String(unhandled)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
116
138
|
}
|
|
117
|
-
export function parkRowStandingAnswerFacts(pending, gate, governance) {
|
|
139
|
+
export function parkRowStandingAnswerFacts(pending, gate, governance, deploymentListAsks) {
|
|
118
140
|
const riskDescriptor = "riskDescriptor" in gate ? gate.riskDescriptor : undefined;
|
|
141
|
+
const classes = screenAskClassMembers(pending.askClass, pending.classes)?.classes;
|
|
119
142
|
return {
|
|
120
143
|
governanceForced: governanceOriginOf({ riskDescriptor }, governance) === true,
|
|
121
144
|
requiresRealApproval: readParkRowTrueSeat(pending, "requiresRealApproval"),
|
|
122
145
|
mandated: readParkRowTrueSeat(pending, "mandated") || mandatedFromSeat(readRuleOffersAbsenceSeat(pending)),
|
|
123
146
|
grantBinding: grantBindingOf(pending),
|
|
124
|
-
classes
|
|
147
|
+
classes,
|
|
148
|
+
deploymentListQuestion: carriesDeploymentListQuestion(deploymentListAsks, classes),
|
|
125
149
|
};
|
|
126
150
|
}
|
|
127
151
|
function pendingEntryStandingAnswerFacts(entry) {
|
|
@@ -131,6 +155,7 @@ function pendingEntryStandingAnswerFacts(entry) {
|
|
|
131
155
|
mandated: entry.mandated === true,
|
|
132
156
|
grantBinding: entry.grantBinding,
|
|
133
157
|
classes: entry.classes,
|
|
158
|
+
deploymentListQuestion: entry.deploymentListQuestion === true,
|
|
134
159
|
};
|
|
135
160
|
}
|
|
136
161
|
function mandatedFromSeat(seat) {
|
|
@@ -151,6 +176,11 @@ export const READ_ROOT_GRANT_ALLOW_ONLY = 'readRootGrant is only meaningful with
|
|
|
151
176
|
export const READ_ROOT_GRANT_SHAPE = `readRootGrant must be the card's readRootCandidate.dir sent back byte for byte — a non-empty string of at most ${READ_ROOT_CANDIDATE_DIR_MAX} characters`;
|
|
152
177
|
export const PERSIST_RULE_SCOPE_REFUSED = "this endpoint does not accept a rule scope — the server mints it from where the approval happened (send neither `scope` nor `persistRule.scope`)";
|
|
153
178
|
export function parseToolApprovalResponse(body) {
|
|
179
|
+
const retired = retiredDecisionKeyFacts(body);
|
|
180
|
+
const parsed = parseToolApprovalFields(body, retired);
|
|
181
|
+
return parsed.ok || retired === undefined ? parsed : { ...parsed, ...retired.extra };
|
|
182
|
+
}
|
|
183
|
+
function parseToolApprovalFields(body, retired) {
|
|
154
184
|
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
155
185
|
return { ok: false, error: "body must be an object" };
|
|
156
186
|
const d = body.decision;
|
|
@@ -193,8 +223,8 @@ export function parseToolApprovalResponse(body) {
|
|
|
193
223
|
}
|
|
194
224
|
note = rawNote;
|
|
195
225
|
}
|
|
196
|
-
if (
|
|
197
|
-
return { ok: false, error:
|
|
226
|
+
if (retired !== undefined)
|
|
227
|
+
return { ok: false, error: retired.error };
|
|
198
228
|
const decidedByReading = readDecidedBy(body.decidedBy, false);
|
|
199
229
|
let decidedBy;
|
|
200
230
|
switch (decidedByReading.kind) {
|
|
@@ -386,6 +416,7 @@ export class ToolApprovalCoordinator {
|
|
|
386
416
|
parkedRedeem;
|
|
387
417
|
askAudit;
|
|
388
418
|
sessionGrants;
|
|
419
|
+
deploymentListAsks;
|
|
389
420
|
shutdownDraining = false;
|
|
390
421
|
asksInFlight = 0;
|
|
391
422
|
parkCommits;
|
|
@@ -407,6 +438,10 @@ export class ToolApprovalCoordinator {
|
|
|
407
438
|
this.parkTombstonePerPrincipalMax = opts?.parkTombstonePerPrincipalMax ?? DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX;
|
|
408
439
|
this.askAudit = opts?.askAudit;
|
|
409
440
|
this.sessionGrants = opts?.sessionGrants;
|
|
441
|
+
if (opts?.deploymentListAsks !== undefined && opts.sessionGrants === undefined) {
|
|
442
|
+
throw new Error("ToolApprovalCoordinator: deploymentListAsks needs sessionGrants — the deployment list's standing answers live in the session exemption store");
|
|
443
|
+
}
|
|
444
|
+
this.deploymentListAsks = opts?.deploymentListAsks;
|
|
410
445
|
this.parkCommits = opts?.parkCommits;
|
|
411
446
|
this.parkCommits?.onParkCommitted((calls) => this.noteParkCommitted(calls));
|
|
412
447
|
}
|
|
@@ -758,9 +793,19 @@ export class ToolApprovalCoordinator {
|
|
|
758
793
|
return (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
|
|
759
794
|
}
|
|
760
795
|
async standingGrantSettles(origin, req, governanceForced) {
|
|
761
|
-
const facts = askStandingAnswerFacts(req, governanceForced);
|
|
762
|
-
|
|
763
|
-
|
|
796
|
+
const facts = askStandingAnswerFacts(req, governanceForced, this.deploymentListAsks?.(req) === true);
|
|
797
|
+
const home = standingAnswerHome(facts);
|
|
798
|
+
switch (home) {
|
|
799
|
+
case "seat":
|
|
800
|
+
break;
|
|
801
|
+
case "none":
|
|
802
|
+
case "server_store":
|
|
803
|
+
return false;
|
|
804
|
+
default: {
|
|
805
|
+
const unhandled = home;
|
|
806
|
+
throw new Error(`unhandled standing-answer home: ${String(unhandled)}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
764
809
|
const sessionId = origin.sessionId;
|
|
765
810
|
if (!sessionId)
|
|
766
811
|
return false;
|
|
@@ -771,7 +816,7 @@ export class ToolApprovalCoordinator {
|
|
|
771
816
|
if (!(await this.sessionGrantUncontestedByRow(origin, req)))
|
|
772
817
|
return false;
|
|
773
818
|
if (durable)
|
|
774
|
-
defaultLogger.info(
|
|
819
|
+
defaultLogger.info(APPROVAL_EXEMPTED_EVENT, { sessionId, toolName: req.toolName, seat: "approval_seat" });
|
|
775
820
|
return true;
|
|
776
821
|
}
|
|
777
822
|
async durableGrantHas(sessionId, canonicalToolName) {
|
|
@@ -1052,6 +1097,7 @@ export class ToolApprovalCoordinator {
|
|
|
1052
1097
|
if (signal?.aborted)
|
|
1053
1098
|
return false;
|
|
1054
1099
|
const governanceForced = this.governanceForcedOf(origin, req);
|
|
1100
|
+
const deploymentListAsks = this.deploymentListAsks?.(req) === true;
|
|
1055
1101
|
if (await this.standingGrantSettles(origin, req, governanceForced))
|
|
1056
1102
|
return true;
|
|
1057
1103
|
const aliveCtxs = ctxs.filter((c) => !c.abortSignal?.aborted);
|
|
@@ -1107,7 +1153,7 @@ export class ToolApprovalCoordinator {
|
|
|
1107
1153
|
...(governanceForced ? { governanceForced: true } : {}),
|
|
1108
1154
|
...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
1109
1155
|
...(req.mandated === true ? { mandated: true } : {}),
|
|
1110
|
-
...(
|
|
1156
|
+
...(standingAnswerHome(askStandingAnswerFacts(req, governanceForced, deploymentListAsks)) === "none" ? { rememberRefused: true } : {}),
|
|
1111
1157
|
...(typeof req.boundGrantDigest === "string" ? { boundGrantDigest: req.boundGrantDigest, ...(req.boundGrants !== undefined ? { boundGrants: req.boundGrants } : {}) } : {}),
|
|
1112
1158
|
...(noRulingCause !== undefined ? { classifierNoRuling: noRulingCause } : {}),
|
|
1113
1159
|
...(mandateWord !== undefined ? { mandate: mandateWord } : {}),
|
|
@@ -1637,13 +1683,15 @@ export class ToolApprovalCoordinator {
|
|
|
1637
1683
|
}
|
|
1638
1684
|
const entryGrantBinding = grantBindingOf(req);
|
|
1639
1685
|
const entryClasses = askClassPair?.classes;
|
|
1640
|
-
const
|
|
1686
|
+
const entryDeploymentListQuestion = carriesDeploymentListQuestion(deploymentListAsks, entryClasses);
|
|
1687
|
+
const rememberRefused = standingAnswerHome(pendingEntryStandingAnswerFacts({
|
|
1641
1688
|
...(effectiveGovernanceForced ? { governanceForced: true } : {}),
|
|
1642
1689
|
...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
1643
1690
|
...(req.mandated === true ? { mandated: true } : {}),
|
|
1644
1691
|
grantBinding: entryGrantBinding,
|
|
1645
1692
|
classes: entryClasses,
|
|
1646
|
-
|
|
1693
|
+
...(entryDeploymentListQuestion ? { deploymentListQuestion: true } : {}),
|
|
1694
|
+
})) === "none";
|
|
1647
1695
|
if (rememberRefused)
|
|
1648
1696
|
frame.rememberRefused = true;
|
|
1649
1697
|
else
|
|
@@ -1665,6 +1713,7 @@ export class ToolApprovalCoordinator {
|
|
|
1665
1713
|
toolName: req.toolName,
|
|
1666
1714
|
grantBinding: entryGrantBinding,
|
|
1667
1715
|
...(entryClasses !== undefined ? { classes: entryClasses } : {}),
|
|
1716
|
+
...(entryDeploymentListQuestion ? { deploymentListQuestion: true } : {}),
|
|
1668
1717
|
registeredAtMs: Date.now(),
|
|
1669
1718
|
expiresAtMs: persistedExpiresAtMs,
|
|
1670
1719
|
...(ruleLaneMaterial !== undefined ? { ruleLane: ruleLaneMaterial } : {}),
|
|
@@ -1794,7 +1843,7 @@ export class ToolApprovalCoordinator {
|
|
|
1794
1843
|
respond(id, principal, body, httpReq) {
|
|
1795
1844
|
const parsed = parseToolApprovalResponse(body);
|
|
1796
1845
|
if (!parsed.ok)
|
|
1797
|
-
return { status: 400, body: { error: parsed.error, errorCode: "request.body_shape" } };
|
|
1846
|
+
return { status: 400, body: { error: parsed.error, errorCode: "request.body_shape", ...(parsed.field !== undefined ? { field: parsed.field } : {}) } };
|
|
1798
1847
|
const entry = this.pending.get(id);
|
|
1799
1848
|
if (!entry || (entry.owner !== null && entry.owner !== principal)) {
|
|
1800
1849
|
return this.respondLate(id, principal, parsed, httpReq);
|
|
@@ -1802,17 +1851,43 @@ export class ToolApprovalCoordinator {
|
|
|
1802
1851
|
const preRule = this.precheckEditedRuleBeforeDecision(entry, parsed);
|
|
1803
1852
|
if (preRule !== undefined)
|
|
1804
1853
|
return preRule;
|
|
1805
|
-
if (
|
|
1854
|
+
if (parsed.value !== "deny" && policyCannotAnswer(heldAskOf(entry.requiresRealApproval === true), parsed.decidedBy)) {
|
|
1806
1855
|
return { status: 400, body: { error: POLICY_CANNOT_ALLOW_PERSON_ASK, errorCode: "request.body_shape" } };
|
|
1807
1856
|
}
|
|
1808
1857
|
const ruleEligibility = { eligible: true };
|
|
1809
|
-
const
|
|
1858
|
+
const decided = this.askStore && entry.askId !== undefined && entry.batchId !== undefined
|
|
1810
1859
|
? this.respondWithCas(this.askStore, entry.askId, entry.batchId, id, entry, parsed, ruleEligibility)
|
|
1811
1860
|
: this.finishRespond(id, entry, parsed);
|
|
1861
|
+
const settled = this.recordDeploymentListAnswer(decided, entry, parsed, principal, ruleEligibility);
|
|
1812
1862
|
if (parsed.persistRule === undefined)
|
|
1813
1863
|
return settled;
|
|
1814
1864
|
return this.persistRuleAfterDecision(settled, entry, parsed.persistRule, parsed.updatedInput !== undefined, ruleEligibility);
|
|
1815
1865
|
}
|
|
1866
|
+
recordDeploymentListAnswer(decided, entry, parsed, principal, eligibility) {
|
|
1867
|
+
const store = this.sessionGrants;
|
|
1868
|
+
const sessionId = entry.sessionId;
|
|
1869
|
+
if (parsed.value !== "allow_session" || !sessionId || store === undefined)
|
|
1870
|
+
return decided;
|
|
1871
|
+
if (standingAnswerHome(pendingEntryStandingAnswerFacts(entry)) !== "server_store")
|
|
1872
|
+
return decided;
|
|
1873
|
+
return (async () => {
|
|
1874
|
+
const result = await decided;
|
|
1875
|
+
if (result.status !== 200)
|
|
1876
|
+
return result;
|
|
1877
|
+
if (!eligibility.eligible)
|
|
1878
|
+
return { ...result, body: { ...result.body, rememberApplied: false } };
|
|
1879
|
+
let rememberApplied = false;
|
|
1880
|
+
try {
|
|
1881
|
+
await this.withStoreDeadline(store.grant(sessionId, entry.toolName, principal ?? entry.owner), "sessionGrants.grant(deployment-list)", DURABLE_CALL_TIMEOUT_MS);
|
|
1882
|
+
rememberApplied = true;
|
|
1883
|
+
defaultLogger.info("approval_exemption_granted", { sessionId, toolName: entry.toolName, grantedBy: principal ?? entry.owner });
|
|
1884
|
+
}
|
|
1885
|
+
catch (err) {
|
|
1886
|
+
defaultLogger.warn("approval_exemption_grant_failed", { sessionId, toolName: entry.toolName, error: String(err).slice(0, 200) });
|
|
1887
|
+
}
|
|
1888
|
+
return { ...result, body: { ...result.body, rememberApplied } };
|
|
1889
|
+
})();
|
|
1890
|
+
}
|
|
1816
1891
|
precheckEditedRuleBeforeDecision(entry, parsed) {
|
|
1817
1892
|
const persistRule = parsed.persistRule;
|
|
1818
1893
|
if (persistRule === undefined || persistRule.kind !== "text" || !persistRule.edited)
|
|
@@ -2255,7 +2330,21 @@ export class ToolApprovalCoordinator {
|
|
|
2255
2330
|
}
|
|
2256
2331
|
finishRespond(id, entry, parsed, opts) {
|
|
2257
2332
|
let rememberApplied;
|
|
2258
|
-
const
|
|
2333
|
+
const home = standingAnswerHome(pendingEntryStandingAnswerFacts(entry));
|
|
2334
|
+
let grantable;
|
|
2335
|
+
switch (home) {
|
|
2336
|
+
case "seat":
|
|
2337
|
+
grantable = true;
|
|
2338
|
+
break;
|
|
2339
|
+
case "none":
|
|
2340
|
+
case "server_store":
|
|
2341
|
+
grantable = false;
|
|
2342
|
+
break;
|
|
2343
|
+
default: {
|
|
2344
|
+
const unhandled = home;
|
|
2345
|
+
throw new Error(`unhandled standing-answer home: ${String(unhandled)}`);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2259
2348
|
if (parsed.value === "allow_session")
|
|
2260
2349
|
rememberApplied = Boolean(entry.sessionId) && grantable;
|
|
2261
2350
|
if (parsed.value === "allow_session" && entry.sessionId && grantable) {
|
|
@@ -2299,7 +2388,7 @@ export class ToolApprovalCoordinator {
|
|
|
2299
2388
|
...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
|
|
2300
2389
|
...(e.governanceForced ? { governanceForced: true } : {}),
|
|
2301
2390
|
...(e.mandated ? { mandated: true } : {}),
|
|
2302
|
-
...(
|
|
2391
|
+
...(standingAnswerHome(pendingEntryStandingAnswerFacts(e)) === "none" ? { rememberRefused: true } : {}),
|
|
2303
2392
|
...(e.mandate !== undefined ? { mandate: e.mandate } : {}),
|
|
2304
2393
|
...(e.origin !== undefined ? { origin: e.origin } : {}),
|
|
2305
2394
|
...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
|
|
17
17
|
* 刚立,立完就在下一次加码时自己咬住了)。
|
|
18
18
|
*/
|
|
19
|
-
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.records_skipped", "server.model_unreachable", "classifier.unavailable", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.allowed_tool_not_granted", "task.tool_turn_incomplete"];
|
|
19
|
+
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.records_skipped", "server.model_unreachable", "classifier.unavailable", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.rule_row_dropped", "permission.allowed_tool_not_granted", "task.tool_turn_incomplete"];
|
|
20
20
|
export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
|
|
21
21
|
/** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
|
|
22
22
|
export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
|
|
@@ -63,10 +63,12 @@ export interface EngineNoticeWireRow {
|
|
|
63
63
|
*/
|
|
64
64
|
export declare function sanitizeNoticeDetail(detail: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
65
65
|
/**
|
|
66
|
-
* 通告 `detail`
|
|
67
|
-
* 与运维日志行(`boot/runner-deps.ts`
|
|
66
|
+
* 通告 `detail` 里的**规则文本**换成条目的脱敏原值({@link redactListEntry}:凭据换记号,不加引号、不转义 —— 机读位不是显示面,显示形只属于人读句)。
|
|
67
|
+
* 会话流行(经 {@link engineNoticeEventData},之后照常过 {@link sanitizeNoticeDetail})与运维日志行(`boot/runner-deps.ts` 的转发席,其余字段逐字)
|
|
68
|
+
* 同调这一只 ⇒ 两条出路上同一条规则文本逐字节同(会话流行另有 detail 串的 {@link NOTICE_TEXT_MAX} 字上限,超长的那一截只在日志行上),两只码里同一条
|
|
69
|
+
* 规则文本也同(引擎交来的两形本身一致时 —— `rule_row_dropped` 交的是引擎中和过的形,空白 / 超长的条目两只码今天不同,上游缝)。其余字段、其余码原样。
|
|
68
70
|
*/
|
|
69
|
-
export declare function
|
|
71
|
+
export declare function redactNoticeRuleText(code: string, detail: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
|
70
72
|
export declare function engineNoticeEventData(notice: {
|
|
71
73
|
code: string;
|
|
72
74
|
message: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { redactSecrets } from "./redact.js";
|
|
2
|
-
import {
|
|
2
|
+
import { redactListEntry } from "../list-entry-display.js";
|
|
3
3
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
4
4
|
export const ENGINE_NOTICE_WIRE_CODES = [
|
|
5
5
|
"memory.session_polluted",
|
|
@@ -34,6 +34,7 @@ export const ENGINE_NOTICE_WIRE_CODES = [
|
|
|
34
34
|
"permission.ask_released_by_mode",
|
|
35
35
|
"permission.read_deny_unapplied",
|
|
36
36
|
"permission.rule_store_unavailable",
|
|
37
|
+
"permission.rule_row_dropped",
|
|
37
38
|
"permission.allowed_tool_not_granted",
|
|
38
39
|
"task.tool_turn_incomplete",
|
|
39
40
|
];
|
|
@@ -92,22 +93,28 @@ function sanitizeValue(v, depth) {
|
|
|
92
93
|
}
|
|
93
94
|
return undefined;
|
|
94
95
|
}
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
const RULE_TEXT_DETAIL_FIELDS = {
|
|
97
|
+
"permission.allowed_tool_not_granted": { array: "entries", field: "entry" },
|
|
98
|
+
"permission.rule_row_dropped": { array: "rows", field: "rule" },
|
|
99
|
+
};
|
|
100
|
+
export function redactNoticeRuleText(code, detail) {
|
|
101
|
+
const at = Object.hasOwn(RULE_TEXT_DETAIL_FIELDS, code) ? RULE_TEXT_DETAIL_FIELDS[code] : undefined;
|
|
102
|
+
if (at === undefined || detail === undefined)
|
|
99
103
|
return detail;
|
|
100
|
-
const list = detail[
|
|
104
|
+
const list = detail[at.array];
|
|
101
105
|
if (!Array.isArray(list))
|
|
102
106
|
return detail;
|
|
103
|
-
const
|
|
104
|
-
|
|
107
|
+
const redacted = list.map((item) => {
|
|
108
|
+
const text = item !== null && typeof item === "object" ? item[at.field] : undefined;
|
|
109
|
+
return typeof text === "string" ? { ...item, [at.field]: redactListEntry(text) } : item;
|
|
110
|
+
});
|
|
111
|
+
return { ...detail, [at.array]: redacted };
|
|
105
112
|
}
|
|
106
113
|
export function engineNoticeEventData(notice, sessionId, nowMs) {
|
|
107
114
|
return {
|
|
108
115
|
code: notice.code,
|
|
109
116
|
message: redactSecrets(String(notice.message)).slice(0, NOTICE_TEXT_MAX),
|
|
110
|
-
detail: sanitizeNoticeDetail(
|
|
117
|
+
detail: sanitizeNoticeDetail(redactNoticeRuleText(notice.code, notice.detail)),
|
|
111
118
|
sessionId,
|
|
112
119
|
ts: nowMs,
|
|
113
120
|
};
|
package/dist/trace/redact.d.ts
CHANGED
|
@@ -50,6 +50,7 @@ import { stripFormatCharacters } from "@sema-agent/core";
|
|
|
50
50
|
* 现役消费方:`src/tool-approval.ts` 的 `durableRuleMaterial`(规则车道素材闸)与语料的 FP=0 格。
|
|
51
51
|
*/
|
|
52
52
|
export declare const normalizeForRedaction: typeof stripFormatCharacters;
|
|
53
|
+
export declare const REDACTION_TOKEN_RE_SRC: string;
|
|
53
54
|
export declare function armPoolBudgetForTest(): {
|
|
54
55
|
spanCount: number;
|
|
55
56
|
coveredPositions: number;
|
|
@@ -163,6 +164,11 @@ export declare function redactionSpansForTest(text: string, opts?: {
|
|
|
163
164
|
* 让下游看到"这里有东西被省略了",而不是丢一个空对象或炸掉整条流。
|
|
164
165
|
*/
|
|
165
166
|
export declare function redactDeep(value: unknown): unknown;
|
|
167
|
+
/** {@link redactDeep} 的**串叶处置可注入**形:同一只环 / 深度守卫的遍历,只有「一个串叶怎么脱」由调用方给。
|
|
168
|
+
* `redactDeep` 本身就是 `leaf = redactSecrets` 的那一档(行为逐字不变)。读面要的是另一档 —— 串叶走
|
|
169
|
+
* `redactModelText`(认出凭据形才改写字节,不含凭据形的串逐字节不变),遍历不该因此再写第二份。
|
|
170
|
+
* `leaf` 必须是纯的串→串函数且**自己不抛**(抛了就穿出本遍历)。 */
|
|
171
|
+
export declare function redactDeepWith(value: unknown, leaf: (s: string) => string): unknown;
|
|
166
172
|
/**
|
|
167
173
|
* A REDACTED, length-BOUNDED string preview of a structured value — for surfacing a pending tool call's `args`
|
|
168
174
|
* in the assistant inbox ("what is this ask about") WITHOUT leaking secrets or dumping an unbounded payload (a
|
package/dist/trace/redact.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { displayUrlOf, stripFormatCharacters } from "@sema-agent/core";
|
|
2
2
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
3
3
|
export const normalizeForRedaction = stripFormatCharacters;
|
|
4
|
-
const REDACTION_TOKEN_RE_SRC = String.raw `«redacted(?::[a-z-]{1,16}){0,2}»`;
|
|
4
|
+
export const REDACTION_TOKEN_RE_SRC = String.raw `«redacted(?::[a-z-]{1,16}){0,2}»`;
|
|
5
5
|
const ARM_VALUE_CLASSES = [];
|
|
6
6
|
const atLeast = (cls, min) => {
|
|
7
7
|
ARM_VALUE_CLASSES.push(cls);
|
|
@@ -1797,11 +1797,14 @@ export function redactionSpansForTest(text, opts) {
|
|
|
1797
1797
|
}
|
|
1798
1798
|
const REDACT_MAX_DEPTH = 64;
|
|
1799
1799
|
export function redactDeep(value) {
|
|
1800
|
-
return
|
|
1800
|
+
return redactDeepWith(value, redactSecrets);
|
|
1801
1801
|
}
|
|
1802
|
-
function
|
|
1802
|
+
export function redactDeepWith(value, leaf) {
|
|
1803
|
+
return redactDeepInner(value, 0, new WeakSet(), leaf);
|
|
1804
|
+
}
|
|
1805
|
+
function redactDeepInner(value, depth, seen, leaf) {
|
|
1803
1806
|
if (typeof value === "string")
|
|
1804
|
-
return
|
|
1807
|
+
return leaf(value);
|
|
1805
1808
|
if (!value || typeof value !== "object")
|
|
1806
1809
|
return value;
|
|
1807
1810
|
if (seen.has(value))
|
|
@@ -1811,11 +1814,8 @@ function redactDeepInner(value, depth, seen) {
|
|
|
1811
1814
|
seen.add(value);
|
|
1812
1815
|
try {
|
|
1813
1816
|
if (Array.isArray(value))
|
|
1814
|
-
return value.map((v) => redactDeepInner(v, depth + 1, seen));
|
|
1815
|
-
|
|
1816
|
-
for (const [k, v] of Object.entries(value))
|
|
1817
|
-
out[k] = redactDeepInner(v, depth + 1, seen);
|
|
1818
|
-
return out;
|
|
1817
|
+
return value.map((v) => redactDeepInner(v, depth + 1, seen, leaf));
|
|
1818
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactDeepInner(v, depth + 1, seen, leaf)]));
|
|
1819
1819
|
}
|
|
1820
1820
|
finally {
|
|
1821
1821
|
seen.delete(value);
|
|
@@ -21,7 +21,7 @@ export declare const SELF_MINTED_WIRE: {
|
|
|
21
21
|
/**
|
|
22
22
|
* S-928(7.110.0):`engine_notice` `task.user_steer_undrained` 上**本仓铸**的那一臂。词表属主是 core(`USER_INPUT_UNDRAINED_CAUSES`,两族共用;
|
|
23
23
|
* `leg_end` 由引擎铸、逐字过境,不在本表);本表登记的是「这个值自哪一版起由本服务出现在 wire 上」。`satisfies Record<CrashedCause, …>`:
|
|
24
|
-
* core 删掉 `crashed` ⇒ `CrashedCause` 成 never ⇒ 本行编译红。码按受理时的车道选(`user-input-ledger.ts` `
|
|
24
|
+
* core 删掉 `crashed` ⇒ `CrashedCause` 成 never ⇒ 本行编译红。码按受理时的车道选(`user-input-ledger.ts` `acceptedInputKindOf`,车道 = core `injectionLaneOf`,码 = core `USER_INPUT_UNDRAINED_FAMILIES`):本行 = steer 车道。
|
|
25
25
|
*/
|
|
26
26
|
readonly userSteerUndrainedServerCause: {
|
|
27
27
|
readonly at: "engine_notice task.user_steer_undrained detail.cause(本服务铸的臂;下一条同会话 run 开通告口那一拍)";
|
|
@@ -183,6 +183,20 @@ export declare const SELF_MINTED_WIRE: {
|
|
|
183
183
|
readonly attr: "redactErrorMessage";
|
|
184
184
|
readonly since: "7.108.0";
|
|
185
185
|
};
|
|
186
|
+
/**
|
|
187
|
+
* 7.120.0:通告 `detail` 里的规则文本 = 条目的脱敏原值(`list-entry-display.ts` `redactListEntry`,经 `engine-notice-wire.ts` 的
|
|
188
|
+
* `redactNoticeRuleText`;会话流行与运维日志行同一只;不加引号、不转义 —— 显示形只在人读句里)。修前(7.119.0):本仓手抄的呈现形
|
|
189
|
+
* (剥 C0 / 双向控制符、超过 160 字截断并标原长),运维日志行不脱敏。钉 test/notice-entry-presentation.test.ts。
|
|
190
|
+
*/
|
|
191
|
+
readonly "engine_notice permission.allowed_tool_not_granted#detail.entries[].entry": {
|
|
192
|
+
readonly attr: "redactListEntry";
|
|
193
|
+
readonly since: "7.120.0";
|
|
194
|
+
};
|
|
195
|
+
/** 7.120.0:同上一行同一只写法(码本版入册;引擎交来的是它中和过的规则文本,本仓再过脱敏读口)。钉同上「两只码」组。 */
|
|
196
|
+
readonly "engine_notice permission.rule_row_dropped#detail.rows[].rule": {
|
|
197
|
+
readonly attr: "redactListEntry";
|
|
198
|
+
readonly since: "7.120.0";
|
|
199
|
+
};
|
|
186
200
|
};
|
|
187
201
|
};
|
|
188
202
|
};
|
|
@@ -65,6 +65,8 @@ export const SELF_MINTED_WIRE = {
|
|
|
65
65
|
members: {
|
|
66
66
|
"GET /v1/runs/:id#result.errorMessage": { attr: "redactErrorMessage", since: "7.57.0" },
|
|
67
67
|
"POST /v1/side-query#errorMessage": { attr: "redactErrorMessage", since: "7.108.0" },
|
|
68
|
+
"engine_notice permission.allowed_tool_not_granted#detail.entries[].entry": { attr: "redactListEntry", since: "7.120.0" },
|
|
69
|
+
"engine_notice permission.rule_row_dropped#detail.rows[].rule": { attr: "redactListEntry", since: "7.120.0" },
|
|
68
70
|
},
|
|
69
71
|
},
|
|
70
72
|
};
|
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
* 子代 resume(`…/subagents/:target/resume`)不往在飞的队列里投,起的是一条新的子代腿;follow-up 在本仓**没有**独立的受理口(`src/http/routes` 零
|
|
24
24
|
* follow-up 入口)。steer 的 `later` 档在引擎里走 follow-up 车道(引擎在腿收场时按车道分账:没投出去的 `later` 记进 `task.user_followup_undrained`),
|
|
25
25
|
* 但它仍是一次 `TaskStream.steer` 受理、消费帧同为 `human_input{delivery:"applied"}`(两车道同一只消费口)⇒ 同进本账,消费即结清。
|
|
26
|
-
* **族 = 受理时它排进的引擎车道**({@link
|
|
27
|
-
* (core 7.44.0 #1362:两族同一只成因词表 `USER_INPUT_UNDRAINED_CAUSES
|
|
26
|
+
* **族 = 受理时它排进的引擎车道**(core `UserInputLane`,经引擎的 `injectionLaneOf(steerPriorityOf(…))` 读出 —— {@link acceptedInputKindOf};落盘 / wire 词 {@link UserInputKind}:
|
|
27
|
+
* `steer` / `follow_up`),崩溃告知的码与条数键按车道取 core `USER_INPUT_UNDRAINED_FAMILIES`(core 7.44.0 #1362:两族同一只成因词表 `USER_INPUT_UNDRAINED_CAUSES`,
|
|
28
|
+
* 服务层的 `crashed` 臂按车道选族,与引擎自己的 `leg_end` 臂同一种分账;core 7.45.0 #1408 起族表与车道读法在包根,本仓不再抄)。
|
|
28
29
|
*
|
|
29
30
|
* ## 五形行(append-only;行序 = 语义序;File 孪生的盘上形 = 下面的 {@link UserInputLedgerRecord},SQL 孪生 = 一行一受理 + CAS 列 + 删行,同义)
|
|
30
31
|
* · `accepted` —— 受理点(`routes/runs.ts` 的 live 腿,**调 `TaskStream.steer` 之前**、回 200 之前)写;写不进 ⇒ 503
|
|
@@ -85,28 +86,30 @@
|
|
|
85
86
|
* 不开旋钮(没有「关掉告知」的正当需求)· 不改 steer 的 200 体(回执仍是「已交付」,账本是它的影子)· 不重放丢失的输入(core 明写
|
|
86
87
|
* NOT redelivered;本账只告知,不替人决定重发)· 不存输入正文(行里只有身份与时刻)。
|
|
87
88
|
*/
|
|
88
|
-
import { type EngineNoticeCode, type SystemInjectionPriority, type TaskEvent, type UserInputUndrainedCause } from "@sema-agent/core";
|
|
89
|
+
import { type EngineNoticeCode, type SystemInjectionPriority, type TaskEvent, type UserInputLane, type UserInputUndrainedCause } from "@sema-agent/core";
|
|
89
90
|
import { type RunRecord } from "./plugins/store-contracts.js";
|
|
90
91
|
import { type EngineNoticeRouter } from "./trace/engine-notice-wire.js";
|
|
91
92
|
/** local 数据根下的子目录(File 孪生;与 `approval-ask-audit/` 同层同形)。 */
|
|
92
93
|
export declare const USER_INPUT_LEDGER_DIR = "user-input-ledger";
|
|
93
94
|
/**
|
|
94
|
-
* 账本收的输入族
|
|
95
|
-
*
|
|
95
|
+
* 账本收的输入族 = 受理时它排进的**引擎车道**(core `UserInputLane`:`steer` / `followUp`;车道怎么读、每条车道的告知码与条数键全归 core,见下)。
|
|
96
|
+
* 本表只做一件事:把车道写成本仓 7.118.0 起**已落盘、已上 wire**(`GET /v1/sessions/:id` 的 `userInputUndrained[].kind`)的那个词 —— `steer` / `follow_up`。
|
|
97
|
+
* `satisfies Record<UserInputLane, …>`:core 加一条车道 ⇒ 编译红,由人裁它落盘写什么。`follow_up` 自 7.118.0 起写(之前的版本受理口一律写 `steer`,`later` 档的旧行照它当时的族读)。
|
|
96
98
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
readonly steer: true;
|
|
101
|
-
readonly follow_up: true;
|
|
99
|
+
declare const LEDGER_KIND_OF_LANE: {
|
|
100
|
+
readonly steer: "steer";
|
|
101
|
+
readonly followUp: "follow_up";
|
|
102
102
|
};
|
|
103
|
+
/** 落盘 / wire 的族词(闭集,派生自 {@link LEDGER_KIND_OF_LANE})。 */
|
|
104
|
+
export type UserInputKind = (typeof LEDGER_KIND_OF_LANE)[UserInputLane];
|
|
105
|
+
/** 读盘词表(判定走 `Object.hasOwn`;派生自 {@link LEDGER_KIND_OF_LANE})。≤7.117.x 的读腿不认 `follow_up`(回滚须知在 MIGRATION)。 */
|
|
106
|
+
export declare const USER_INPUT_KIND_WORDS: Readonly<Record<UserInputKind, true>>;
|
|
103
107
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* 穷举 `never` 臂编译红,由人裁新档排进哪条车道。
|
|
108
|
+
* 一次受理的族词 —— 三个受理口(run steer 的 live 腿 / interrupt 带 text 形 / 子代 steer)同调这一只。车道是引擎的那一只读法
|
|
109
|
+
* `injectionLaneOf(steerPriorityOf(…))`(core 7.45.0 #1408:缺席档 = `next`;`later` 走 follow-up 车道,`now` / `next` 走 steer 车道;
|
|
110
|
+
* 档外值抛 `steering.invalid_content` —— 受理口在这之前已按 `isSystemInjectionPriority` 400 掉档外值,这一抛在本仓不可达)。
|
|
108
111
|
*/
|
|
109
|
-
export declare function
|
|
112
|
+
export declare function acceptedInputKindOf(priority: SystemInjectionPriority | undefined): UserInputKind;
|
|
110
113
|
/** `settled` 的两种收场(闭集,见顶注)。 */
|
|
111
114
|
export type SettleHow = "leg_end" | "not_applied" | "consumed";
|
|
112
115
|
/** 读盘词表(File 孪生的折叠守卫 / SQL 孪生的 `settle_how` 列读腿同读这一张;`consumed` 自 7.113.0 起,≤7.112.x 的读腿不认它 —— 回滚须知在 MIGRATION)。 */
|
|
@@ -380,4 +383,5 @@ export declare function createUserInputLedgerAtBoot(opts: Parameters<typeof crea
|
|
|
380
383
|
* · `mysql` / `pg` —— 多副本共享一张表,「我启动之前写的」证明不了写者已死 ⇒ `run_store_verdict`(等 reaper 落款)。
|
|
381
384
|
*/
|
|
382
385
|
export declare function writerGoneEvidenceFor(backendKind: "mysql" | "pg" | "local", runs: RunStoreReader): WriterGoneEvidence;
|
|
386
|
+
export {};
|
|
383
387
|
//# sourceMappingURL=user-input-ledger.d.ts.map
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
import { USER_INPUT_UNDRAINED_CAUSES } from "@sema-agent/core";
|
|
1
|
+
import { USER_INPUT_UNDRAINED_CAUSES, USER_INPUT_UNDRAINED_FAMILIES, injectionLaneOf, steerPriorityOf } from "@sema-agent/core";
|
|
2
2
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
3
3
|
import { isHolderDeathVerdict } from "./plugins/store-contracts.js";
|
|
4
4
|
import { defaultEngineNoticeRouter } from "./trace/engine-notice-wire.js";
|
|
5
5
|
export const USER_INPUT_LEDGER_DIR = "user-input-ledger";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
case "now":
|
|
12
|
-
case "next":
|
|
13
|
-
case undefined:
|
|
14
|
-
return "steer";
|
|
15
|
-
default: {
|
|
16
|
-
const unhandled = tier;
|
|
17
|
-
throw new Error(`unhandled steer tier: ${String(unhandled)}`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
6
|
+
const LEDGER_KIND_OF_LANE = { steer: "steer", followUp: "follow_up" };
|
|
7
|
+
const LANE_OF_LEDGER_KIND = Object.fromEntries(Object.entries(LEDGER_KIND_OF_LANE).map(([lane, kind]) => [kind, lane]));
|
|
8
|
+
export const USER_INPUT_KIND_WORDS = Object.freeze(Object.fromEntries(Object.values(LEDGER_KIND_OF_LANE).map((kind) => [kind, true])));
|
|
9
|
+
export function acceptedInputKindOf(priority) {
|
|
10
|
+
return LEDGER_KIND_OF_LANE[injectionLaneOf(steerPriorityOf(priority === undefined ? {} : { priority }))];
|
|
20
11
|
}
|
|
21
12
|
export const SETTLE_HOW_WORDS = { leg_end: true, not_applied: true, consumed: true };
|
|
22
13
|
const UNDRAINED_CAUSE_MINTER = { leg_end: "engine", crashed: "server" };
|
|
@@ -138,15 +129,16 @@ export function sortAcceptances(list) {
|
|
|
138
129
|
export const USER_INPUT_OPEN_SCAN_MAX = 1000;
|
|
139
130
|
export const USER_INPUT_OPEN_SCAN_PAGES = 100;
|
|
140
131
|
export const USER_INPUT_SESSION_READ_MAX = 500;
|
|
141
|
-
const
|
|
142
|
-
steer:
|
|
143
|
-
|
|
132
|
+
const UNDRAINED_WHAT_OF_LANE = {
|
|
133
|
+
steer: "user steer(s)",
|
|
134
|
+
followUp: "user steer(s) queued for the run's end (priority later)",
|
|
144
135
|
};
|
|
145
136
|
export function buildCrashedUndrainedNotice(input) {
|
|
146
|
-
const
|
|
137
|
+
const lane = LANE_OF_LEDGER_KIND[input.family];
|
|
138
|
+
const face = USER_INPUT_UNDRAINED_FAMILIES[lane];
|
|
147
139
|
return {
|
|
148
140
|
code: face.code,
|
|
149
|
-
message: `Check the earlier run's transcript and results before re-sending any of these steers to a live run. The server accepted ${input.count} ${
|
|
141
|
+
message: `Check the earlier run's transcript and results before re-sending any of these steers to a live run. The server accepted ${input.count} ${UNDRAINED_WHAT_OF_LANE[lane]} for run ${input.taskId} (each answered 200 applied or 202 queued-immediate), but the process running that run ended before the engine confirmed consuming them. They are NOT redelivered.`,
|
|
150
142
|
detail: { [face.countKey]: input.count, cause: CRASHED_CAUSE, taskId: input.taskId, sessionId: input.sessionId },
|
|
151
143
|
sessionId: input.sessionId,
|
|
152
144
|
};
|