@sema-agent/server 7.104.0 → 7.105.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 +32 -0
- package/USAGE.md +59 -10
- package/dist/boot/runner-deps.js +2 -1
- package/dist/config.d.ts +15 -2
- package/dist/config.js +42 -7
- package/dist/file-history-disclosure.d.ts +7 -31
- package/dist/file-history-disclosure.js +12 -0
- package/dist/fleet/fleet-bus.d.ts +5 -1
- package/dist/http/admission.d.ts +1 -1
- package/dist/http/dispatch.d.ts +18 -0
- package/dist/http/dispatch.js +17 -4
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/route-table.d.ts +15 -4
- package/dist/http/route-table.js +6 -5
- package/dist/http/routes/fleet.d.ts +3 -2
- package/dist/http/routes/images.js +0 -1
- package/dist/http/routes/runs.js +2 -8
- package/dist/http/routes/session-sync.js +6 -1
- package/dist/http/routes/sessions.js +5 -2
- package/dist/http/routes/side-query.js +8 -1
- package/dist/http/routes/trace-usage.d.ts +2 -0
- package/dist/http/routes/trace-usage.js +4 -9
- package/dist/http/server.d.ts +9 -4
- package/dist/http/server.js +4 -1
- package/dist/http/sse-lifecycle.d.ts +1 -1
- package/dist/http/sse-log.d.ts +12 -9
- package/dist/http/sse-log.js +1 -8
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.d.ts +45 -9
- package/dist/observability/run-terminal.js +115 -9
- package/dist/plugins/image-bake-store-sql.d.ts +0 -2
- package/dist/plugins/image-bake-store-sql.js +0 -5
- package/dist/plugins/retention-store-sql.js +1 -0
- package/dist/plugins/run-store-sql.d.ts +3 -2
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/session-policy-store-sql.js +18 -5
- package/dist/plugins/store-backend.d.ts +6 -4
- package/dist/plugins/tidb-pool.js +4 -3
- package/dist/read-face-posture.d.ts +21 -0
- package/dist/read-face-posture.js +9 -0
- package/dist/rules-consent.js +0 -11
- package/dist/session-policy-wire.d.ts +98 -12
- package/dist/session-policy-wire.js +71 -8
- package/dist/trace/project.d.ts +10 -5
- package/dist/trace/project.js +3 -3
- package/dist/trace/redact.d.ts +7 -0
- package/dist/trace/redact.js +153 -61
- package/package.json +1 -1
- package/dist/plugins/memory-session-policy-store.d.ts +0 -46
- package/dist/plugins/memory-session-policy-store.js +0 -52
|
@@ -97,28 +97,72 @@ export function redactModelText(raw) {
|
|
|
97
97
|
return OVERSIZED_REDACTION_PLACEHOLDER;
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
+
const PROVIDER_ECHO_SHAPE = /^[\x21-\x7E]{1,128}$/;
|
|
101
|
+
function isProviderEchoShape(value) {
|
|
102
|
+
return PROVIDER_ECHO_SHAPE.test(value);
|
|
103
|
+
}
|
|
104
|
+
export function admitProviderEcho(value, seat) {
|
|
105
|
+
if (value === undefined)
|
|
106
|
+
return false;
|
|
107
|
+
if (typeof value === "string" && isProviderEchoShape(value))
|
|
108
|
+
return true;
|
|
109
|
+
const what = typeof value === "string" ? `len=${value.length}` : `type=${value === null ? "null" : typeof value}`;
|
|
110
|
+
recordFailOpen("server.run-terminal.provider-echo-shaped", `seat=${seat} ${what}`);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function shapeEchoMembers(node, table, seat) {
|
|
114
|
+
if (node === null || typeof node !== "object" || Array.isArray(node))
|
|
115
|
+
return node;
|
|
116
|
+
const n = node;
|
|
117
|
+
let out;
|
|
118
|
+
for (const [key, treatment] of Object.entries(table)) {
|
|
119
|
+
const v = n[key];
|
|
120
|
+
if (v === undefined)
|
|
121
|
+
continue;
|
|
122
|
+
switch (treatment) {
|
|
123
|
+
case "verbatim":
|
|
124
|
+
case "provider-join-key":
|
|
125
|
+
break;
|
|
126
|
+
case "provider-echo":
|
|
127
|
+
if (!admitProviderEcho(v, `${seat}.${key}`))
|
|
128
|
+
delete (out ??= { ...n })[key];
|
|
129
|
+
break;
|
|
130
|
+
default: {
|
|
131
|
+
const unhandled = treatment;
|
|
132
|
+
throw new Error(`unhandled echo-member treatment: ${String(unhandled)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return out ?? node;
|
|
137
|
+
}
|
|
138
|
+
function shapeEchoRows(rows, table, seat) {
|
|
139
|
+
if (!Array.isArray(rows))
|
|
140
|
+
return rows;
|
|
141
|
+
const next = rows.map((row) => shapeEchoMembers(row, table, seat));
|
|
142
|
+
return next.every((x, i) => x === rows[i]) ? rows : next;
|
|
143
|
+
}
|
|
100
144
|
const MAX_EDITED_FILES = 1000;
|
|
101
145
|
const RESULT_KEY_TREATMENT = {
|
|
102
146
|
terminal: "redacted",
|
|
103
147
|
remoteEnvFailures: "redacted",
|
|
104
148
|
editedFiles: "redacted",
|
|
149
|
+
responseModel: "provider-echo",
|
|
150
|
+
apiFailure: "provider-echo",
|
|
151
|
+
strandedHumanAnswers: "provider-echo",
|
|
105
152
|
taskId: "verbatim",
|
|
106
153
|
runId: "verbatim",
|
|
107
154
|
sessionId: "verbatim",
|
|
108
155
|
model: "verbatim",
|
|
109
|
-
responseModel: "verbatim",
|
|
110
156
|
compaction: "verbatim",
|
|
111
157
|
effectiveReasoning: "verbatim",
|
|
112
158
|
result: "redacted",
|
|
113
159
|
salvagedOutput: "redacted",
|
|
114
|
-
apiFailure: "verbatim",
|
|
115
160
|
retryAfterMs: "verbatim",
|
|
116
161
|
degraded: "verbatim",
|
|
117
162
|
structuredOutput: "verbatim",
|
|
118
163
|
rewindNotes: "redacted",
|
|
119
164
|
haltedOnUserRejection: "verbatim",
|
|
120
165
|
haltedByUser: "verbatim",
|
|
121
|
-
strandedHumanAnswers: "verbatim",
|
|
122
166
|
pendingApproval: "verbatim",
|
|
123
167
|
approvalWaitedMs: "verbatim",
|
|
124
168
|
effectiveReadFace: "verbatim",
|
|
@@ -127,9 +171,28 @@ const RESULT_KEY_TREATMENT = {
|
|
|
127
171
|
effectiveMemoryScopes: "verbatim",
|
|
128
172
|
stats: "redacted",
|
|
129
173
|
};
|
|
174
|
+
const API_FAILURE_TREATMENT = {
|
|
175
|
+
status: "verbatim",
|
|
176
|
+
requestId: "provider-echo",
|
|
177
|
+
};
|
|
178
|
+
const STRANDED_ANSWER_TREATMENT = {
|
|
179
|
+
deliveryId: "verbatim",
|
|
180
|
+
toolCallId: "provider-join-key",
|
|
181
|
+
};
|
|
182
|
+
const PROVIDER_ECHO_SHAPERS = {
|
|
183
|
+
responseModel: (v) => (admitProviderEcho(v, "responseModel") ? v : undefined),
|
|
184
|
+
apiFailure: (v) => shapeEchoMembers(v, API_FAILURE_TREATMENT, "apiFailure"),
|
|
185
|
+
strandedHumanAnswers: (v) => shapeEchoRows(v, STRANDED_ANSWER_TREATMENT, "strandedHumanAnswers[]"),
|
|
186
|
+
};
|
|
187
|
+
{
|
|
188
|
+
const judged = Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t === "provider-echo").map(([k]) => k).sort();
|
|
189
|
+
const wired = Object.keys(PROVIDER_ECHO_SHAPERS).sort();
|
|
190
|
+
if (judged.join(",") !== wired.join(","))
|
|
191
|
+
throw new Error(`run-terminal: provider-echo classification [${judged.join(",")}] ≠ shaper table [${wired.join(",")}]`);
|
|
192
|
+
}
|
|
130
193
|
const LEGACY_RESULT_LEAVES = ["errorMessage", "blockedReason", "checkpointGate"];
|
|
131
|
-
export const
|
|
132
|
-
...Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t
|
|
194
|
+
export const TREATED_RESULT_LEAVES = [
|
|
195
|
+
...Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t !== "verbatim").map(([k]) => k),
|
|
133
196
|
...LEGACY_RESULT_LEAVES,
|
|
134
197
|
];
|
|
135
198
|
export function redactTerminalResult(result) {
|
|
@@ -235,6 +298,20 @@ export function redactTerminalResult(result) {
|
|
|
235
298
|
changed = true;
|
|
236
299
|
}
|
|
237
300
|
}
|
|
301
|
+
const rr = result;
|
|
302
|
+
for (const [key, shape] of Object.entries(PROVIDER_ECHO_SHAPERS)) {
|
|
303
|
+
const v = rr[key];
|
|
304
|
+
if (v === undefined || shape === undefined)
|
|
305
|
+
continue;
|
|
306
|
+
const next = shape(v);
|
|
307
|
+
if (next === v)
|
|
308
|
+
continue;
|
|
309
|
+
if (next === undefined)
|
|
310
|
+
delete out[key];
|
|
311
|
+
else
|
|
312
|
+
out[key] = next;
|
|
313
|
+
changed = true;
|
|
314
|
+
}
|
|
238
315
|
return changed ? out : result;
|
|
239
316
|
}
|
|
240
317
|
export function sealRunRowResult(result) {
|
|
@@ -267,8 +344,18 @@ export function redactLedgerEventData(type, data) {
|
|
|
267
344
|
const next = redactCheckpointGate(gate);
|
|
268
345
|
return next === gate ? data : { ...data, gate: next };
|
|
269
346
|
}
|
|
347
|
+
if (type === "turn_end")
|
|
348
|
+
return shapeEchoMembers(data, TURN_END_ECHO_TREATMENT, "turn_end");
|
|
349
|
+
if (type === "task_notification") {
|
|
350
|
+
const af = data.apiFailure;
|
|
351
|
+
if (af === undefined)
|
|
352
|
+
return data;
|
|
353
|
+
const next = shapeEchoMembers(af, API_FAILURE_TREATMENT, "task_notification.apiFailure");
|
|
354
|
+
return next === af ? data : { ...data, apiFailure: next };
|
|
355
|
+
}
|
|
270
356
|
return data;
|
|
271
357
|
}
|
|
358
|
+
const TURN_END_ECHO_TREATMENT = { responseModel: "provider-echo" };
|
|
272
359
|
export function redactWorkerReportError(report) {
|
|
273
360
|
const gate = redactCheckpointGate(report.checkpointGate);
|
|
274
361
|
const error = report.error === undefined ? undefined : (redactErrorMessage(report.error) ?? "");
|
|
@@ -389,7 +476,7 @@ const STATS_GATE_ROW_TREATMENT = {
|
|
|
389
476
|
decision: "verbatim",
|
|
390
477
|
toolName: "verbatim",
|
|
391
478
|
toolArg: "model-text",
|
|
392
|
-
toolCallId: "
|
|
479
|
+
toolCallId: "provider-join-key",
|
|
393
480
|
};
|
|
394
481
|
const STATS_REPETITION_EVENT_TREATMENT = {
|
|
395
482
|
turn: "verbatim",
|
|
@@ -420,6 +507,8 @@ function redactStatsNode(node, table) {
|
|
|
420
507
|
switch (treatment) {
|
|
421
508
|
case "verbatim":
|
|
422
509
|
break;
|
|
510
|
+
case "provider-join-key":
|
|
511
|
+
break;
|
|
423
512
|
case "model-text":
|
|
424
513
|
if (typeof v === "string")
|
|
425
514
|
next = redactModelText(v);
|
|
@@ -674,15 +763,32 @@ export function memoRedactTerminalResult(seat, result) {
|
|
|
674
763
|
if (result === undefined || result === null)
|
|
675
764
|
return result;
|
|
676
765
|
const r = result;
|
|
677
|
-
if (
|
|
766
|
+
if (TREATED_RESULT_LEAVES.every((k) => r[k] === undefined))
|
|
678
767
|
return result;
|
|
679
768
|
const leaves = {};
|
|
680
|
-
for (const k of
|
|
769
|
+
for (const k of TREATED_RESULT_LEAVES)
|
|
681
770
|
leaves[k] = r[k];
|
|
682
771
|
const redacted = memoRedactedRead(seat, leaves, (l) => redactTerminalResult(l));
|
|
683
772
|
if (redacted === leaves)
|
|
684
773
|
return result;
|
|
685
|
-
|
|
774
|
+
const merged = { ...result, ...redacted };
|
|
775
|
+
for (const k of TREATED_RESULT_LEAVES)
|
|
776
|
+
if (!Object.hasOwn(redacted, k))
|
|
777
|
+
delete merged[k];
|
|
778
|
+
return merged;
|
|
779
|
+
}
|
|
780
|
+
export function redactLedgerEventForRead(type, data) {
|
|
781
|
+
if (type === "done" && data !== null && typeof data === "object") {
|
|
782
|
+
const result = data.result;
|
|
783
|
+
if (result && typeof result === "object") {
|
|
784
|
+
return { ...data, result: memoRedactTerminalResult("ev.done.result", result) };
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (type === "failed")
|
|
788
|
+
return memoRedactedRead("ev.failed", data, (d) => redactLedgerEventData(type, d));
|
|
789
|
+
if (isModelTextLedgerEventType(type))
|
|
790
|
+
return memoRedactedRead("ev.model-text", data, (d) => redactLedgerEventData(type, d));
|
|
791
|
+
return redactLedgerEventData(type, data);
|
|
686
792
|
}
|
|
687
793
|
let shadowScans = 0;
|
|
688
794
|
export function diffScanShadowCountForTest() { return shadowScans; }
|
|
@@ -122,8 +122,6 @@ export declare class SqlImageBake {
|
|
|
122
122
|
private lineOrdExists;
|
|
123
123
|
/** Highest event seq for a bake (0 if none) — the ++seq allocator + the resume cursor head. */
|
|
124
124
|
maxSeq(bakeId: string): Promise<number>;
|
|
125
|
-
/** Earliest retained event seq (0 if none) → the SSE 416 boundary (a resume below this was evicted, P2.9). */
|
|
126
|
-
retainedFrom(bakeId: string): Promise<number>;
|
|
127
125
|
/** Events with seq strictly greater than `afterSeq` (0 for the whole stream), ascending. */
|
|
128
126
|
getEvents(bakeId: string, afterSeq: number): Promise<BakeEvent[]>;
|
|
129
127
|
/**
|
|
@@ -379,11 +379,6 @@ export class SqlImageBake {
|
|
|
379
379
|
const m = rows[0]?.m;
|
|
380
380
|
return m == null ? 0 : Number(m);
|
|
381
381
|
}
|
|
382
|
-
async retainedFrom(bakeId) {
|
|
383
|
-
const { rows } = await this.db.query(this.q("SELECT MIN(seq) AS m FROM image_bake_event WHERE bake_id = ?", "SELECT MIN(seq) AS m FROM image_bake_event WHERE bake_id = $1"), [bakeId]);
|
|
384
|
-
const m = rows[0]?.m;
|
|
385
|
-
return m == null ? 0 : Number(m);
|
|
386
|
-
}
|
|
387
382
|
async getEvents(bakeId, afterSeq) {
|
|
388
383
|
const { rows } = await this.db.query(this.q(`SELECT seq, line_ord, kind, level, ${jsonColumnText("tidb", "data")}, ts FROM image_bake_event WHERE bake_id = ? AND seq > ? ORDER BY seq ASC`, `SELECT seq, line_ord, kind, level, ${jsonColumnText("pg", "data")}, ts FROM image_bake_event WHERE bake_id = $1 AND seq > $2 ORDER BY seq ASC`), [bakeId, afterSeq]);
|
|
389
384
|
return rows.map((r) => ({
|
|
@@ -380,6 +380,7 @@ export class SqlRetentionStore {
|
|
|
380
380
|
["resume_anchor", "session_id"],
|
|
381
381
|
["approval_exemption", "session_id"],
|
|
382
382
|
["session_policy", "session_id"],
|
|
383
|
+
["session_policy_rev_mark", "session_id"],
|
|
383
384
|
...(this.opts.taskAttachmentTable ? [["task_attachment", "session_id"]] : []),
|
|
384
385
|
["file_history_tracked", "scope"],
|
|
385
386
|
["file_history_version", "scope"],
|
|
@@ -248,8 +248,9 @@ export declare class SqlRunStore {
|
|
|
248
248
|
rows: UsageRow[];
|
|
249
249
|
truncated: boolean;
|
|
250
250
|
}>;
|
|
251
|
-
/** Earliest retained event seq for a task (0 if none) → `retainedFrom`
|
|
252
|
-
*
|
|
251
|
+
/** Earliest retained event seq for a task (0 if none) → the `retainedFrom` field of `GET /v1/tasks/:id/turns`.
|
|
252
|
+
* Not an eviction boundary: the ledger has no partial head trim (the only deletion takes the whole session tree),
|
|
253
|
+
* so a value > 1 means head rows whose write failed after consuming their seq (S-211 ①). */
|
|
253
254
|
retainedFrom(taskId: string): Promise<number>;
|
|
254
255
|
/** Events with seq strictly greater than `afterSeq` (use 0 for the whole stream). */
|
|
255
256
|
getEvents(taskId: string, afterSeq: number): Promise<RunEvent[]>;
|
|
@@ -10,6 +10,7 @@ export const CENTRAL_SCHEMA_INDEXES = [
|
|
|
10
10
|
{ table: "approval_nonce", name: "idx_approval_nonce_expires", columns: ["expires_at_ms"] },
|
|
11
11
|
{ table: "sendfile_link", name: "idx_sendfile_link_scope", columns: ["scope", "id"] },
|
|
12
12
|
{ table: "session_policy", name: "idx_session_policy_session", columns: ["session_id"] },
|
|
13
|
+
{ table: "session_policy_rev_mark", name: "idx_session_policy_rev_mark_session", columns: ["session_id"] },
|
|
13
14
|
{ table: "snapshot_blob", name: "idx_snapshot_blob_created", columns: ["created_at"] },
|
|
14
15
|
{ table: "file_history_version", name: "idx_file_history_version_blob", columns: ["blob_hash"] },
|
|
15
16
|
{ table: "file_history_boundary_file", name: "idx_file_history_bf_key", columns: ["scope", "key_hash"] },
|
|
@@ -22,18 +22,21 @@ function readRulesBody(text, where) {
|
|
|
22
22
|
throw new SessionPolicyError("corrupt", `${e.message} — an unreadable rules body served as an EMPTY rule set would silently drop every tightening this session carries`);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
function isStoredCounter(n, floor) {
|
|
26
|
+
return Number.isSafeInteger(n) && n >= floor;
|
|
27
|
+
}
|
|
25
28
|
function rowToStored(r, where) {
|
|
26
29
|
const rev = Number(r.rev);
|
|
27
|
-
if (!
|
|
28
|
-
throw new SessionPolicyError("corrupt", `session rules row ${where} carries rev ${String(r.rev)} — a stored row's rev is always ≥ 1 (the stamp is floor+1), and rev 0 is the live reader's synthetic empty seed; refusing rather than serve it as rules`);
|
|
30
|
+
if (!isStoredCounter(rev, 1)) {
|
|
31
|
+
throw new SessionPolicyError("corrupt", `session rules row ${where} carries rev ${String(r.rev)} — a stored row's rev is always a whole number ≥ 1 within the safe-integer range (the stamp is floor+1), and rev 0 is the live reader's synthetic empty seed; refusing rather than serve it as rules`);
|
|
29
32
|
}
|
|
30
33
|
if (r.gen === undefined || r.gen === null) {
|
|
31
34
|
throw new Error(`session_policy row ${where} came back without a \`gen\` column — a read leg did not project it (the column is BIGINT NOT NULL on both dialects). ` +
|
|
32
35
|
"Refusing rather than judge this row's lineage as generation 0, which is a value nothing minted.");
|
|
33
36
|
}
|
|
34
37
|
const gen = Number(r.gen);
|
|
35
|
-
if (!
|
|
36
|
-
throw new SessionPolicyError("corrupt", `session rules row ${where} carries lineage gen ${String(r.gen)} — a generation is a whole number ≥ 0; refusing rather than let a reader judge lineage by a value nothing minted`);
|
|
38
|
+
if (!isStoredCounter(gen, 0)) {
|
|
39
|
+
throw new SessionPolicyError("corrupt", `session rules row ${where} carries lineage gen ${String(r.gen)} — a generation is a whole number ≥ 0 within the safe-integer range; refusing rather than let a reader judge lineage by a value nothing minted`);
|
|
37
40
|
}
|
|
38
41
|
return { ...readRulesBody(r.rules, `session_policy.rules ${where}`), rev, gen };
|
|
39
42
|
}
|
|
@@ -73,7 +76,7 @@ export class SqlSessionPolicyStore {
|
|
|
73
76
|
const { rows: markRows } = await conn.query(this.q("SELECT rev, gen FROM session_policy_rev_mark WHERE policy_key = ? FOR UPDATE", "SELECT rev, gen FROM session_policy_rev_mark WHERE policy_key = $1 FOR UPDATE"), [key]);
|
|
74
77
|
const markRev = markRows[0] ? Number(markRows[0].rev) : 0;
|
|
75
78
|
const markGen = markRows[0] ? Number(markRows[0].gen) : 0;
|
|
76
|
-
if (markRows[0] !== undefined && (!
|
|
79
|
+
if (markRows[0] !== undefined && (!isStoredCounter(markGen, 0) || !isStoredCounter(markRev, 0))) {
|
|
77
80
|
throw new SessionPolicyError("corrupt", `the session rules rev mark for session ${JSON.stringify(sessionId)} is unreadable (rev ${String(markRows[0].rev)}, gen ${String(markRows[0].gen)}) — refusing rather than stamp a rev/lineage nothing proves`);
|
|
78
81
|
}
|
|
79
82
|
const clean = normalizeRules(stripRev(rules));
|
|
@@ -88,6 +91,9 @@ export class SqlSessionPolicyStore {
|
|
|
88
91
|
}
|
|
89
92
|
const nextRev = Math.max(priorRev, markRev) + 1;
|
|
90
93
|
const nextGen = prior?.gen ?? markGen;
|
|
94
|
+
if (!isStoredCounter(nextRev, 1) || !isStoredCounter(nextGen, 0)) {
|
|
95
|
+
throw new SessionPolicyError("corrupt", `session rules for session ${JSON.stringify(sessionId)}: the next stamp (rev ${nextRev}, gen ${nextGen}) would leave the safe-integer range this store reads back — refusing before writing a row nothing could read`);
|
|
96
|
+
}
|
|
91
97
|
const json = JSON.stringify(clean);
|
|
92
98
|
const now = new Date();
|
|
93
99
|
if (prior) {
|
|
@@ -121,6 +127,13 @@ export class SqlSessionPolicyStore {
|
|
|
121
127
|
const now = new Date();
|
|
122
128
|
const keys = rows.map((r) => String(r.policy_key));
|
|
123
129
|
const params = [];
|
|
130
|
+
for (const r of rows) {
|
|
131
|
+
const rev = Number(r.rev);
|
|
132
|
+
const nextGen = Number(r.gen ?? 0) + 1;
|
|
133
|
+
if (!isStoredCounter(rev, 1) || !isStoredCounter(nextGen, 0)) {
|
|
134
|
+
throw new SessionPolicyError("corrupt", `session rules row for session ${JSON.stringify(sessionId)} carries rev ${String(r.rev)} / gen ${String(r.gen)} — the erase would leave a mark outside the safe-integer range; refusing before writing it`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
124
137
|
const tuples = rows.map((r) => {
|
|
125
138
|
params.push(String(r.policy_key), sessionId, Number(r.rev), Number(r.gen ?? 0) + 1, now);
|
|
126
139
|
const base = params.length - 5;
|
|
@@ -44,9 +44,11 @@ export type { ApprovalNonceStore } from "./approval-nonce-store.js";
|
|
|
44
44
|
/** [ref]([ref] v3.1 §3.0-§3.2 流内审批协议持久层):`ApprovalAskStore` 接口 + 行/补丁类型。
|
|
45
45
|
* 车3 刀 3a 起 `StoreBackend.approvalAsk()` 已在(见接口体的那条注)。 */
|
|
46
46
|
export type { ApprovalAskStore, AskRow, NewAskRow, AskTransitionPatch, DecideAskInput, DecideResult, ExpireResult, BindGateInput, BatchRow, AskDecision } from "./approval-ask-store-sql.js";
|
|
47
|
-
/** E6 SessionPolicyStore — core's interface
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
/** E6 SessionPolicyStore — core's interface, incl. its optional `deleteBySession` / `listBySession` seams (E21 purge / 2c
|
|
48
|
+
* export; core's File and InMemory stores carry both since core 7.25.0 — S-546 A7 retired the service's own in-memory
|
|
49
|
+
* twin that existed only because they once did not). tidb/pg = the SQL twin; local = core's FileSessionPolicyStore
|
|
50
|
+
* (works local — rules need no cloud-only infra). The rev-line contract kit runs the SQL twin against core's InMemory
|
|
51
|
+
* as oracle (`test/helpers/session-policy-rev-line-contract.ts`). */
|
|
50
52
|
export type ServiceSessionPolicyStore = SessionPolicyStore;
|
|
51
53
|
/** [ref] per-edited-file rewind FileHistoryStore — core's interface PLUS the service extras only the SQL twins
|
|
52
54
|
* carry (optional so the local lane's core `FileFileHistoryStore` still fits). Replaces the retired E19
|
|
@@ -192,7 +194,7 @@ export interface StoreBackend {
|
|
|
192
194
|
* mapping from URLs). REQUIRED on all backends (works local — one JSONL, like approvalExemption). */
|
|
193
195
|
sendFileLedger(): SendFileLedger;
|
|
194
196
|
/** E6 durable SessionPolicyStore (operator-tightened per-session tool rules). REQUIRED on all backends — works local
|
|
195
|
-
* (local = core's
|
|
197
|
+
* (local = core's FileSessionPolicyStore), like resumeAnchor. Backs core's RunnerDeps.sessionPolicyStore. */
|
|
196
198
|
sessionPolicy(): ServiceSessionPolicyStore;
|
|
197
199
|
/** [ref] per-edited-file rewind FileHistoryStore (trackEdit/annulTrack/snapshot/restore over the tracked set;
|
|
198
200
|
* also the 2c history-transfer store). REQUIRED on all backends (local = core's file-backed
|
|
@@ -387,8 +387,8 @@ export const SCHEMA_STATEMENTS = [
|
|
|
387
387
|
) COLLATE utf8mb4_bin`,
|
|
388
388
|
`CREATE TABLE IF NOT EXISTS session_policy_rev_mark (
|
|
389
389
|
policy_key CHAR(64) NOT NULL,
|
|
390
|
-
--
|
|
391
|
-
--
|
|
390
|
+
-- 被删会话(**明文**会话 id —— 所以它必须随会话一起走,见上)。记号的读写按 policy_key 单键;托管留存的整树
|
|
391
|
+
-- 清扫按本列删(S-546 A9,索引 idx_session_policy_rev_mark_session)。
|
|
392
392
|
session_id VARCHAR(64) NOT NULL,
|
|
393
393
|
-- 抹除时刻该键带过的最高 rev(GREATEST 合并:记号只升不降)。
|
|
394
394
|
rev BIGINT NOT NULL,
|
|
@@ -397,7 +397,8 @@ export const SCHEMA_STATEMENTS = [
|
|
|
397
397
|
-- DEFAULT 0 = 本列到货前落下的记号(那些键的行也都是第 0 代 ⇒ 下一次抹除把它们抬到 1,前后自洽)。
|
|
398
398
|
gen BIGINT NOT NULL DEFAULT 0,
|
|
399
399
|
erased_at DATETIME(3) NOT NULL,
|
|
400
|
-
PRIMARY KEY (policy_key)
|
|
400
|
+
PRIMARY KEY (policy_key),
|
|
401
|
+
KEY idx_session_policy_rev_mark_session (session_id)
|
|
401
402
|
) COLLATE utf8mb4_bin`,
|
|
402
403
|
`CREATE TABLE IF NOT EXISTS snapshot_blob (
|
|
403
404
|
blob_hash CHAR(64) NOT NULL,
|
|
@@ -65,4 +65,25 @@ export declare function buildReadFaceWiring(facts: ReadFacePostureFacts): {
|
|
|
65
65
|
source: ReadFaceSource;
|
|
66
66
|
note: string;
|
|
67
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* S-176 —— posture 派生的档位在 **boot 期推送面**的那一行(`config.ts` 经既有 `CONFIG_NOTICES` 以 info 级排空)。
|
|
70
|
+
*
|
|
71
|
+
* 为什么要有:S-167 的补偿项是「升级即放开,得让运维看得见并知道怎么钉回」,可此前唯一看得见的是**拉取**面
|
|
72
|
+
* (`GET /v1/diagnostics/wiring`)—— 只对会去拉的人成立。单机 boot 日志里零字。
|
|
73
|
+
* 为什么只在 `posture` 一词上说:另外三词都不是「没人表态、服务替它选了一个更宽的档」——`env` 是运维自己写的,
|
|
74
|
+
* `engine-default` 是本服务没铸(引擎默认接管),`center` 在 boot 这一拍还没到货(它有自己的换席通告)。
|
|
75
|
+
* 为什么是 info 不是 warn:每一台照常配置的单机每次启动都成立,是常态事实不是异常(与执行车道落点同级)。
|
|
76
|
+
*
|
|
77
|
+
* 字段与极性表的 posture 行同名(`knob` / `value` / `source`),另带指路句;三格由 {@link buildReadFaceWiring} 投影出 ——
|
|
78
|
+
* 拉取面与推送面**同一只函数**,不各算一次。`value` 是字符串:只有 `face` 在场的那一形才会走到这里。
|
|
79
|
+
*/
|
|
80
|
+
export declare function buildReadFacePostureNotice(facts: ReadFacePostureFacts): {
|
|
81
|
+
event: "config_knob_posture_derived";
|
|
82
|
+
fields: {
|
|
83
|
+
knob: "READ_FACE";
|
|
84
|
+
value: string;
|
|
85
|
+
source: ReadFaceSource;
|
|
86
|
+
note: string;
|
|
87
|
+
};
|
|
88
|
+
} | undefined;
|
|
68
89
|
//# sourceMappingURL=read-face-posture.d.ts.map
|
|
@@ -14,4 +14,13 @@ export function readFaceSourceNote(source) {
|
|
|
14
14
|
export function buildReadFaceWiring(facts) {
|
|
15
15
|
return { face: facts.readFace ?? null, source: facts.readFaceSource, note: readFaceSourceNote(facts.readFaceSource) };
|
|
16
16
|
}
|
|
17
|
+
export function buildReadFacePostureNotice(facts) {
|
|
18
|
+
if (facts.readFaceSource !== "posture")
|
|
19
|
+
return undefined;
|
|
20
|
+
const wiring = buildReadFaceWiring(facts);
|
|
21
|
+
if (wiring.face === null) {
|
|
22
|
+
throw new Error("read-face posture notice: source is `posture` but no face was minted — readFaceForPosture's invariant broke");
|
|
23
|
+
}
|
|
24
|
+
return { event: "config_knob_posture_derived", fields: { knob: "READ_FACE", value: wiring.face, source: wiring.source, note: wiring.note } };
|
|
25
|
+
}
|
|
17
26
|
//# sourceMappingURL=read-face-posture.js.map
|
package/dist/rules-consent.js
CHANGED
|
@@ -736,17 +736,6 @@ export function createRuleConsentLane(stores, opts) {
|
|
|
736
736
|
adds: [],
|
|
737
737
|
};
|
|
738
738
|
const rowOf = (rows) => rows.find((r) => sameWireRuleIdentity(r, { behavior: input.behavior, rule: written.rule.rule, scope: row.scope }));
|
|
739
|
-
let before;
|
|
740
|
-
try {
|
|
741
|
-
before = await lane.listRules(input.principal);
|
|
742
|
-
}
|
|
743
|
-
catch (e) {
|
|
744
|
-
localImportLogger.warn("direct_rule_write_pre_read_failed", { behavior: input.behavior, err: e instanceof Error ? e.message : String(e) });
|
|
745
|
-
return { status: "failed", detail: `the rule store could not be read before writing: ${e instanceof Error ? e.message : String(e)}`, committed: "no" };
|
|
746
|
-
}
|
|
747
|
-
const standing = rowOf(before.rows);
|
|
748
|
-
if (standing !== undefined)
|
|
749
|
-
return { status: "no-op", rev: before.rev, stillLive: "yes", row: standing };
|
|
750
739
|
const screened = screenLocalImportRow(row);
|
|
751
740
|
if ("reason" in screened)
|
|
752
741
|
return { status: "rejected", reason: screened.reason, detail: screened.detail };
|
|
@@ -16,10 +16,16 @@
|
|
|
16
16
|
* 自陈「Malformed shape → 400(never a downstream TypeError)」,这一半没守);
|
|
17
17
|
* · `loosenReasons` 的每一臂都从 `prior` 出发,**prior 为空时恒回空集** ⇒ tighten-only 门对一条
|
|
18
18
|
* 全新的会话放行任何内容,坏形因此连「收紧门」这一层都碰不到。
|
|
19
|
-
* ⇒ 类修:一只读者,键集由 `keyof SessionPermissionRules` **派生**(下方 `
|
|
20
|
-
* `satisfies Record<keyof SessionPermissionRules,
|
|
19
|
+
* ⇒ 类修:一只读者,键集由 `keyof SessionPermissionRules` **派生**(下方 `RULE_FIELD_ROLE` 的
|
|
20
|
+
* `satisfies Record<keyof SessionPermissionRules, RuleFieldRole>`:core 加桶 ⇒ 缺键**编译红**;core 删桶 ⇒
|
|
21
21
|
* 多键**编译红**),两条腿同调。
|
|
22
22
|
*
|
|
23
|
+
* ── S-800(7.105.0):同一只读者多判一问 —— 写得进却跑不了的工具名 ─────────────────────────────────
|
|
24
|
+
* 两条写入腿是 server 往会话规则店写的**全部**写口,所以「写口 fail-closed」收成一只**写口读者**
|
|
25
|
+
* ({@link readWritableSessionPermissionRules} = 形读者 + {@link legacyToolNameOf}),两腿同调,将来第三条写入腿
|
|
26
|
+
* 调同一只也同得。判词多一种(`legacy-tool-name`),两腿各把它映射成 `400 rules.legacy_tool_name`(映射点仍在各自
|
|
27
|
+
* 站点:码位只放字面量)。形读者本身不变 —— SQL 店的读回也用它,读回不许判名字(理由见写口读者头注)。
|
|
28
|
+
*
|
|
23
29
|
* ── 文案也在这里(codex 一轮追加:两条腿各铸一份句子 = 第二份手抄)───────────────────────────────
|
|
24
30
|
* 初版把 wire 文案留在两个路由各自铸,理由是错误文案冻结门(`test/api-error-text-freeze.test.ts`)锚的是
|
|
25
31
|
* `src/http/` 桶里的 `sendError` 站点。codex 一轮指出那等于**把同一句话写了两遍**(`sessions.ts` 与
|
|
@@ -28,10 +34,52 @@
|
|
|
28
34
|
* 渲染器对每一种判词的输出字节,且对判词闭集穷尽(加一种判词 ⇒ 那一格编译红)。文案一个字节未改。
|
|
29
35
|
*/
|
|
30
36
|
import type { SessionPermissionRules, SessionPolicyError, SessionRulesRecord } from "@sema-agent/core";
|
|
37
|
+
declare const RULE_FIELD_ROLE: {
|
|
38
|
+
readonly toolAllow: "tool-name";
|
|
39
|
+
readonly toolDeny: "tool-name";
|
|
40
|
+
readonly allowDirs: "path";
|
|
41
|
+
readonly commandAllow: "command";
|
|
42
|
+
readonly commandDeny: "command";
|
|
43
|
+
};
|
|
31
44
|
/** core `SessionPermissionRules` 的桶名全集(派生,不是手抄)。 */
|
|
32
45
|
export declare const SESSION_PERMISSION_RULE_FIELDS: readonly (keyof SessionPermissionRules)[];
|
|
33
|
-
/**
|
|
34
|
-
export type
|
|
46
|
+
/** 装工具名的那几桶(由 {@link RULE_FIELD_ROLE} 派生的型)。 */
|
|
47
|
+
export type ToolNameRuleField = {
|
|
48
|
+
[K in keyof typeof RULE_FIELD_ROLE]: (typeof RULE_FIELD_ROLE)[K] extends "tool-name" ? K : never;
|
|
49
|
+
}[keyof typeof RULE_FIELD_ROLE];
|
|
50
|
+
/**
|
|
51
|
+
* S-800(client-core [ref] CC-226;server [ref];core [ref])—— 「写得进、跑不了、撤不掉」的工具名。
|
|
52
|
+
*
|
|
53
|
+
* 两臂,逐字对应 core 7.33.8 准备阶段名审计抛 `config.legacy_tool_name` 的那两臂(判据只用 core **导出**的
|
|
54
|
+
* 两件,零镜像表 —— 表随 core 钉版走):
|
|
55
|
+
* · `"retired"`:名字在 core 的 `RETIRED_TOOL_NAMES` 里;`guidance` = 那张表给它的一句现役指引(原样);
|
|
56
|
+
* · `"unprefixed-namespace"`:名字里有 `"__"` 分隔符,而 `protocolOf(name)` 认不出它属于哪个协议。
|
|
57
|
+
*
|
|
58
|
+
* 为什么在**写口**判:会话规则店只准非 operator 收紧,删掉一条 deny 算放宽 ⇒ 写进去的这种名字属主自己删不掉,
|
|
59
|
+
* 而准备阶段见它就整只拒启 ⇒ 这个会话之后的每一跑都起不来。写口是唯一还来得及说「不」的地方。
|
|
60
|
+
*
|
|
61
|
+
* ⚠️ **比准备阶段严一格**(方向安全,与 client-core 0.83.6 的 KL-157 同向):准备阶段对**本 run 名册里挂着**的
|
|
62
|
+
* 同名工具放行(部署自己挂了一只叫 `Task` 的工具就不审计它),写口看不到将来哪一跑的名册 ⇒ 一律拒。
|
|
63
|
+
* 受伤的是「部署自带与退役名同名的工具、又要在会话规则里点它」的形;出路 = 部署级工具策略(不经会话规则店)。
|
|
64
|
+
* ⚠️ **过渡形**:core 已表态(#1156,7.34.1)把这两臂抽成一只导出纯函数并在会话规则店写口即拒 —— 到货后本函数
|
|
65
|
+
* 换成调 core 那只(拒因原样透传),三份判定收成一份。
|
|
66
|
+
*
|
|
67
|
+
* 🔴 **为什么不复用 `config.ts` 的 `findUnmatchableToolNames`**(本仓另一只按名字判的单点;读过实现后裁):两只回答的
|
|
68
|
+
* 不是同一个问题。那一只问「部署名单里这条名字会不会**永远匹配不上**活工具」—— 它刻意豁免 `allow`(白名单匹配不上 =
|
|
69
|
+
* fail-closed 且当场可见),并且拒两段 MCP 名 `mcp__srv`(一名两义)与 core 文法编不出的串。本函数问的是「引擎见到这条
|
|
70
|
+
* 会话规则会不会**拒启**」:引擎准备阶段的名审计对 `allow` 桶照样抛(豁免它 = 放过一条砖化路径),对 `mcp__srv` 却
|
|
71
|
+
* **不**抛(会话层自 core 5.55 / 5.56 起把它当整对端覆盖形真命中)。判据只能逐字跟 core 那两臂走,不能借另一只的口径。
|
|
72
|
+
* 两只在「退役名」「无前缀 `__` 名」两臂上同源(都读 core 导出的同两件),7.34.1 提货时一并换调 core 的导出函数。
|
|
73
|
+
*/
|
|
74
|
+
export type LegacyToolName = {
|
|
75
|
+
why: "retired";
|
|
76
|
+
guidance: string;
|
|
77
|
+
} | {
|
|
78
|
+
why: "unprefixed-namespace";
|
|
79
|
+
};
|
|
80
|
+
export declare function legacyToolNameOf(name: string): LegacyToolName | undefined;
|
|
81
|
+
/** 一条规则体的**形**问题。判词而非文案(见文件头注的「文案属主」一段)。 */
|
|
82
|
+
export type SessionPermissionRulesShapeProblem =
|
|
35
83
|
/** 整体不是一只普通对象(`null` / 数组 / 标量 / 缺席)。 */
|
|
36
84
|
{
|
|
37
85
|
kind: "not-an-object";
|
|
@@ -41,22 +89,45 @@ export type SessionPermissionRulesProblem =
|
|
|
41
89
|
kind: "field-not-string-array";
|
|
42
90
|
field: keyof SessionPermissionRules;
|
|
43
91
|
};
|
|
44
|
-
/** {@link
|
|
45
|
-
export type
|
|
46
|
-
|
|
92
|
+
/** 一条**要写进店**的规则体的问题 = 形问题 ∪ S-800 的名字问题(判据 {@link legacyToolNameOf})。 */
|
|
93
|
+
export type SessionPermissionRulesProblem = SessionPermissionRulesShapeProblem
|
|
94
|
+
/** S-800:形对,但工具名桶里有一条写进去就跑不了的名字。 */
|
|
95
|
+
| {
|
|
96
|
+
kind: "legacy-tool-name";
|
|
97
|
+
field: ToolNameRuleField;
|
|
98
|
+
name: string;
|
|
99
|
+
legacy: LegacyToolName;
|
|
100
|
+
};
|
|
101
|
+
/** 两只读者的读数:有问题给判词,没问题给**已收窄**的规则体(调用方零 cast)。`P` = 这只读者会给的判词集。 */
|
|
102
|
+
export type SessionPermissionRulesRead<P extends SessionPermissionRulesProblem = SessionPermissionRulesProblem> = {
|
|
103
|
+
problem: P;
|
|
47
104
|
rules?: undefined;
|
|
48
105
|
} | {
|
|
49
106
|
problem?: undefined;
|
|
50
107
|
rules: SessionPermissionRules;
|
|
51
108
|
};
|
|
52
109
|
/**
|
|
53
|
-
*
|
|
110
|
+
* **形读者**:读一只 `SessionPermissionRules`,形不对给判词,形对给**只含五桶**的规则体。三个消费者:两条写入腿
|
|
111
|
+
* (经 {@link readWritableSessionPermissionRules})与 SQL 店的**读回**(`session-policy-store-sql.ts` 的
|
|
112
|
+
* `readSessionRulesBody`:读不出形的行判 `corrupt`)。
|
|
54
113
|
*
|
|
55
114
|
* 认识面窄得刻意:**只判形**,不判内容(桶内字符串的 pattern 文法、路径是否可达、tighten-only 是否成立
|
|
56
|
-
* 都不在这里 —— 前两者归 core 的 rule 文法与 `normalizeRules`,后者归 `putRules`
|
|
57
|
-
*
|
|
115
|
+
* 都不在这里 —— 前两者归 core 的 rule 文法与 `normalizeRules`,后者归 `putRules` 的收紧门;S-800 的名字判官
|
|
116
|
+
* 只在**写口**那一只读者里,见下)。未知键**照旧忽略**(不是新的拒因):core 的 `normalizeRules` 本来就只搬这五桶,
|
|
117
|
+
* 本读者与它同口径。
|
|
58
118
|
*/
|
|
59
|
-
export declare function readSessionPermissionRules(raw: unknown): SessionPermissionRulesRead
|
|
119
|
+
export declare function readSessionPermissionRules(raw: unknown): SessionPermissionRulesRead<SessionPermissionRulesShapeProblem>;
|
|
120
|
+
/**
|
|
121
|
+
* **写口读者**(S-800)= 形读者 + 名字判官:两条写入腿(`PUT /v1/sessions/:id/policy`、sync/import 的 `policy`
|
|
122
|
+
* 面经 {@link readSessionRulesRecords})往会话规则店写之前的**唯一**判据。两问,按序:
|
|
123
|
+
* ① 形(同 {@link readSessionPermissionRules};一份坏形的体答形的 400);
|
|
124
|
+
* ② 工具名桶里的每一条过 {@link legacyToolNameOf} —— 写进去之后属主撤不掉、会话之后每一跑都起不来的名字,
|
|
125
|
+
* 写口是唯一来得及拒的地方。
|
|
126
|
+
* 🔴 **只在写口,不在读回**:SQL 店读回同一行时只判形(形读者)。一条早先写进去的退役名行必须**读得出来** ——
|
|
127
|
+
* 那是运营方清掉它的唯一路径(operator 的 PUT 先读写前快照),也是 core 准备阶段报 `config.legacy_tool_name`
|
|
128
|
+
* 而不是一只 500 `corrupt` 的前提。把判官放进形读者会让读回把它判坏(7.105.0 车QQ 双库套件实抓,修在同批)。
|
|
129
|
+
*/
|
|
130
|
+
export declare function readWritableSessionPermissionRules(raw: unknown): SessionPermissionRulesRead;
|
|
60
131
|
/** 一条 `SessionRulesRecord` 的形问题(`policy[]` 的元素级;桶级问题原样转载在 `rules` 位)。 */
|
|
61
132
|
export type SessionRulesRecordProblem =
|
|
62
133
|
/** `policy` 本身不是数组。 */
|
|
@@ -90,6 +161,8 @@ export type SessionRulesRecordsRead = {
|
|
|
90
161
|
};
|
|
91
162
|
/**
|
|
92
163
|
* 读 sync/import 的 `policy` 面(`SessionRulesRecord[]`)。好形返回**已收窄**的记录数组,坏形给判词。
|
|
164
|
+
* 它只服务那一条**写入**腿(导入的记录随后 `putRules` 进店),所以每条记录的规则体走**写口读者**
|
|
165
|
+
* {@link readWritableSessionPermissionRules}(与 PUT 腿同一只判官)。
|
|
93
166
|
*
|
|
94
167
|
* `rev`:记录里带的 `rev` 是**导出端的**乐观锁读数,`putRules` 侧一进门就 `stripRev` 掉(`sameRulesContent`
|
|
95
168
|
* 也比的是去 rev 的字节形),所以它在本读者里只是过境位 —— 非数字即当缺席(不是拒因:一个坏 rev 改变不了
|
|
@@ -98,6 +171,15 @@ export type SessionRulesRecordsRead = {
|
|
|
98
171
|
export declare function readSessionRulesRecords(raw: unknown): SessionRulesRecordsRead;
|
|
99
172
|
/** 桶级判词 → 人话。闭集穷尽:{@link SessionPermissionRulesProblem} 加一种判词 ⇒ 本 switch 编译红。 */
|
|
100
173
|
export declare function describeSessionPermissionRulesProblem(p: SessionPermissionRulesProblem): string;
|
|
174
|
+
/**
|
|
175
|
+
* S-800:一只 `policy[]` 判词**是不是**「写得进却跑不了的名字」那一类 —— 是就交回那一格(桶 + 名字),
|
|
176
|
+
* 调用点据此发 `rules.legacy_tool_name`,否则发形的码。闭集穷尽两层:记录级判词或桶级判词加一种 ⇒ 编译红,
|
|
177
|
+
* 新判词不会静默落进某一个码。
|
|
178
|
+
*/
|
|
179
|
+
export declare function legacyToolNameProblemOf(p: SessionRulesRecordProblem): {
|
|
180
|
+
field: ToolNameRuleField;
|
|
181
|
+
name: string;
|
|
182
|
+
} | undefined;
|
|
101
183
|
/** `policy[]` 判词 → 人话(sync/import 腿在它前面拼 `policy: `)。闭集穷尽同上。 */
|
|
102
184
|
export declare function describeSessionRulesRecordProblem(p: SessionRulesRecordProblem): string;
|
|
103
185
|
/**
|
|
@@ -115,7 +197,10 @@ export declare function describeSessionRulesRecordProblem(p: SessionRulesRecordP
|
|
|
115
197
|
*
|
|
116
198
|
* 逐词:
|
|
117
199
|
* · `conflict`(409)—— CAS 输了,重读 `rev` 再来。**码位与状态逐字不变**。
|
|
118
|
-
* · `loosen_forbidden`(403)—— 非 operator
|
|
200
|
+
* · `loosen_forbidden`(403)—— 非 operator 想放松。**码位与状态逐字不变**。⚠️ 这个词还有**第二个铸点**(S-546 B5,
|
|
201
|
+
* 如实登记、未改):PG 后端的 SQL 双生对含 NUL / 孤代理字节的规则拒收时借用了它
|
|
202
|
+
* (`plugins/session-policy-store-sql.ts` 的 `pgHasUnstorable` 臂)—— 那一形**既非放宽也非越权**,operator 写同样撞上;
|
|
203
|
+
* core 的闭集里没有「输入不可存」这个词,改码位要先裁定新码与 PUT / 2c 导入两条入口同判(DEBTS S-546 B5)。
|
|
119
204
|
* · `corrupt`(**500**)—— 店读不出它自己该读的那份字节(记号/行被列外篡改或撕裂)。
|
|
120
205
|
* 不是 4xx:调用方没做错任何事,它也改不了这件事;不是 503:重试改不了判(core 成文的修复路径是
|
|
121
206
|
* **人**去改回或删掉那一行,而删记号要交出它握着的那个号)。
|
|
@@ -124,4 +209,5 @@ export declare function sessionPolicyErrorHttp(code: SessionPolicyError["code"])
|
|
|
124
209
|
status: number;
|
|
125
210
|
errorCode: string;
|
|
126
211
|
};
|
|
212
|
+
export {};
|
|
127
213
|
//# sourceMappingURL=session-policy-wire.d.ts.map
|