@sema-agent/core 7.50.1 → 7.50.2
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
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 7.50.2 — 2026-10-14
|
|
2
|
+
|
|
3
|
+
**Hard-breaking changes:** none. **Closed sets:** none. **Exports:** +0 / −0 (`releasePolicyDefects` is module-level, not on the package root).
|
|
4
|
+
|
|
5
|
+
### The gate-outcome screen reads the shape of a mode release, not today's release policy (#1730; fix; truthfulness of persisted records; hard-breaking: none) (@server @test)
|
|
6
|
+
|
|
7
|
+
- **Before (v7.50.0 / v7.50.1).** `screenGateOutcome` I7 asked whether `released.askClass` is releasable under TODAY's table (`releasableAskClassesFor`). Release policy moves between versions (7.50.0 made `rule_unreadable` bypass-immune), so a record a 7.49.5 engine minted lawfully — a `rule_unreadable` ask released under `bypassPermissions` — screened as `I7: released.askClass "rule_unreadable" is not a releasable class` on 7.50.x, and a reader of persisted records (server's event-ledger replay, S-1546) recorded lawful history as a defective producer.
|
|
8
|
+
- **Now.** I7 is a shape invariant: the class word is a member of the closed set (`isAskClass`), the release carries no settlement, the mode is `bypassPermissions`, `classes` is a non-empty list of ask classes, `origin` / `reason` are members of their sets. Whether a class MAY be released is the producer's law at mint time: `releasePolicyDefects` (module-level) is read beside the shape screen at the gate's exit (`gate-exit.ts`) and at the station screen (`prepare-gate-stations.ts`), so this engine still refuses to mint a release of an immune class today, while a reader of persisted records asks only the shape.
|
|
9
|
+
- **Who is hurt.** A consumer that used `screenGateOutcome` as a producer check on records it mints itself (none known; the engine's two producer sites now read both halves) no longer gets the policy defect from the screen alone. Nobody else: a record this engine minted screens exactly as before; a lawful historical release stops reading as defective.
|
|
10
|
+
- **Compensation.** None needed for readers of persisted records. A host that mints gate records and wants today's policy judged calls both functions, or waits for 7.51.0's `screenGateOutcome(g, { reading: "producer" | "archive" })` with the engine's retired-vocabulary table (so 7.50.x records naming `governance_unavailable` read clean after its 7.51.0 retirement).
|
|
11
|
+
- **Pins.** `test/backlog1730-gate-screen-shape-not-policy.test.ts` P1–P5 (P1 red on v7.50.1); `test/backlog1423-wpu-by-face.test.ts` re-pinned: the policy defect is the producer's, the shape screen accepts the record.
|
|
12
|
+
|
|
1
13
|
## 7.50.1 — 2026-10-14
|
|
2
14
|
|
|
3
15
|
**Hard-breaking changes:** none. **Closed sets:** none — the read station's details code stays `read_unestablished` on both faces (whether a write finding gets its own code is a closed-set question, left to 7.51.0). **Package exports:** +0 / −0 names (`npm run handoff:diff v7.50.0 HEAD`); no type, signature or closed-set change (`wire-changes` preview: 0 / 0 / 0). **Behavior:** a tool policy that throws is denied in its own words with one redacted line and reported once on `onError`, never as a timeout; a write the read station could not place is carded as a write. Both defects were present in 7.49.5 and 7.50.0.
|
|
@@ -401,9 +401,11 @@ export interface ModeRefusal {
|
|
|
401
401
|
* {@link import("./auto-mode.js").AUTO_MODE_FALLBACK_CAUSES} — a record whose `to` and `modelUsed`
|
|
402
402
|
* disagree would let a reader answer "which model decided this call?" two ways;
|
|
403
403
|
* I7 `disposition.released` present ⇒ `kind === "allowed"`, no settlement, `mode` is `bypassPermissions`,
|
|
404
|
-
* `askClass` is
|
|
404
|
+
* `askClass` is an ask class (a member of the closed set — SHAPE; whether the class was releasable is the mint's
|
|
405
|
+
* law at mint time, read by the gate's mode-release arm, never re-judged here against today's table: a persisted
|
|
406
|
+
* record outlives the policy of its day), every member of `classes` is an ask class, `origin` / `reason`, when
|
|
405
407
|
* present, are members of their closed sets, and `reason` names a member of `classes` (the mint takes it from
|
|
406
|
-
* that set) — a mode releases nothing a person answered
|
|
408
|
+
* that set) — a mode releases nothing a person answered;
|
|
407
409
|
* I8 `deniedBy === "mode"` ⇔ `mode` present (`bypassPermissions` | `dontAsk`) with `askClass` and `classes`.
|
|
408
410
|
* Nothing here is read out of an ABSENCE: an ordinary allow with no ask is `{ disposition: { kind: "allowed" } }`,
|
|
409
411
|
* a direct policy deny is `{ disposition: { kind: "denied", deniedBy: "policy" } }`, and neither carries a
|
|
@@ -422,4 +424,12 @@ export interface GateOutcome {
|
|
|
422
424
|
* clean record); the caller decides the fail-loud form (report + withhold, or refuse), never silently
|
|
423
425
|
* repairs the record.
|
|
424
426
|
*/
|
|
427
|
+
/**
|
|
428
|
+
* The PRODUCER's half of I7, read beside {@link screenGateOutcome} wherever this engine composes or screens a record it is
|
|
429
|
+
* minting today: a mode release names a class this engine's table lets a mode release (`releasableAskClassesFor` on some
|
|
430
|
+
* face). Policy, not shape — it moves between versions (7.50.0 made `rule_unreadable` immune), so a reader of PERSISTED
|
|
431
|
+
* records (a replay, an archive) does not read it: a release lawful when minted is not a defect under a later table.
|
|
432
|
+
* Module-level only; not part of the package root.
|
|
433
|
+
*/
|
|
434
|
+
export declare function releasePolicyDefects(g: GateOutcome): string[];
|
|
425
435
|
export declare function screenGateOutcome(g: GateOutcome): string[];
|
|
@@ -108,6 +108,15 @@ export const DENIED_BY_MAY_CARRY_CAUSE = {
|
|
|
108
108
|
read_boundary: false,
|
|
109
109
|
mode: false,
|
|
110
110
|
};
|
|
111
|
+
export function releasePolicyDefects(g) {
|
|
112
|
+
const d = g.disposition;
|
|
113
|
+
if (d.kind !== "allowed" || d.released === undefined)
|
|
114
|
+
return [];
|
|
115
|
+
const askClass = d.released.askClass;
|
|
116
|
+
if (!isAskClass(askClass))
|
|
117
|
+
return [];
|
|
118
|
+
return ASK_FACES.some((face) => releasableAskClassesFor(face).includes(askClass)) ? [] : [`I7: released.askClass "${askClass}" is not a releasable class under this engine's table`];
|
|
119
|
+
}
|
|
111
120
|
export function screenGateOutcome(g) {
|
|
112
121
|
const defects = [];
|
|
113
122
|
const d = g.disposition;
|
|
@@ -144,8 +153,8 @@ export function screenGateOutcome(g) {
|
|
|
144
153
|
defects.push("I7: a mode release carries no settlement — nobody answered the ask it released");
|
|
145
154
|
if (r.mode !== "bypassPermissions")
|
|
146
155
|
defects.push(`I7: a mode release names mode "${String(r.mode)}" — only bypassPermissions releases`);
|
|
147
|
-
if (!isAskClass(r.askClass)
|
|
148
|
-
defects.push(`I7: released.askClass "${String(r.askClass)}" is
|
|
156
|
+
if (!isAskClass(r.askClass))
|
|
157
|
+
defects.push(`I7: released.askClass "${String(r.askClass)}" is outside the closed set`);
|
|
149
158
|
if (!Array.isArray(r.classes) || r.classes.length === 0 || !r.classes.every((c) => isAskClass(c)))
|
|
150
159
|
defects.push("I7: released.classes must be a non-empty list of ask classes");
|
|
151
160
|
if (r.origin !== undefined && !isAskOrigin(r.origin))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
|
|
1
|
+
import { screenGateOutcome, SETTLEMENT_IS_REFUSAL, releasePolicyDefects } from "../gate-outcome.js";
|
|
2
2
|
import { askFaceOf, classifyAsk, headlessApprovalRemedy } from "../ask-origin.js";
|
|
3
3
|
import { wireOf } from "../tool-name-readings.js";
|
|
4
4
|
import { isProtectedWrite } from "../tool-registry.js";
|
|
@@ -23,7 +23,7 @@ export function mintGateOutcome(facts) {
|
|
|
23
23
|
: { kind: "allowed", ...(attribution !== undefined ? { classifier: attribution } : {}), ...(facts.released !== undefined ? { released: Object.freeze({ ...facts.released, classes: Object.freeze([...facts.released.classes]) }) } : {}) }),
|
|
24
24
|
...(facts.settled !== undefined ? { settlement: settlement, origin: facts.settled.origin } : {}),
|
|
25
25
|
});
|
|
26
|
-
const defects = screenGateOutcome(outcome);
|
|
26
|
+
const defects = [...screenGateOutcome(outcome), ...releasePolicyDefects(outcome)];
|
|
27
27
|
if (defects.length > 0)
|
|
28
28
|
throw new Error(`the tool gate composed an outcome that violates its own record: ${defects.join("; ")}`);
|
|
29
29
|
return outcome;
|
|
@@ -2,7 +2,7 @@ import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
|
2
2
|
import { mountedToolRequiresUserInteraction } from "../mode-policy.js";
|
|
3
3
|
import { complianceCallDenial } from "../compliance.js";
|
|
4
4
|
import { cloneObserverInput, formatHookFeedback, runToolGate } from "../hooks.js";
|
|
5
|
-
import { screenGateOutcome } from "../gate-outcome.js";
|
|
5
|
+
import { screenGateOutcome, releasePolicyDefects } from "../gate-outcome.js";
|
|
6
6
|
import { directDeny, frozenInputSnapshot } from "./gate-exit.js";
|
|
7
7
|
import { emitTrace } from "../trace.js";
|
|
8
8
|
import { deliverEngineNotice } from "../engine-notice.js";
|
|
@@ -20,7 +20,7 @@ function screenGateResult(result) {
|
|
|
20
20
|
const gate = result.gate;
|
|
21
21
|
if (gate === undefined)
|
|
22
22
|
return { defects: ["a tool-gate result carried no outcome record on a terminal exit — every terminal exit mints one; the frame carries none"] };
|
|
23
|
-
const defects = screenGateOutcome(gate);
|
|
23
|
+
const defects = [...screenGateOutcome(gate), ...releasePolicyDefects(gate)];
|
|
24
24
|
const blocked = result.block === true;
|
|
25
25
|
if ((gate.disposition.kind === "denied") !== blocked) {
|
|
26
26
|
defects.push(`a tool-gate result's outcome reads "${gate.disposition.kind}" on ${blocked ? "a blocked" : "an executing"} call — the two faces of one verdict disagree; the frame carries no outcome`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"repo": "core",
|
|
3
|
+
"version": "7.50.2",
|
|
4
|
+
"from": "c9b91932c9c442efbedc38a9fd43629d6da3b64c",
|
|
5
|
+
"to": "6420369270dad7ad426f39d338defa4eeee9594b",
|
|
6
|
+
"added": [],
|
|
7
|
+
"removed": [],
|
|
8
|
+
"changed": [],
|
|
9
|
+
"narrowed": [],
|
|
10
|
+
"closedSetMembers": [],
|
|
11
|
+
"closedSetsUnreadable": [],
|
|
12
|
+
"typeKeys": [],
|
|
13
|
+
"typeKeysUnreadableArms": [],
|
|
14
|
+
"typeKeysFlattenedCount": 229,
|
|
15
|
+
"signatures": [],
|
|
16
|
+
"declarationMoves": [],
|
|
17
|
+
"mintMoves": [],
|
|
18
|
+
"criteriaObserves": [
|
|
19
|
+
{
|
|
20
|
+
"kind": "taskEventField",
|
|
21
|
+
"name": "tool_end.gate",
|
|
22
|
+
"assertions": [
|
|
23
|
+
"G1.b"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"kind": "typeMember",
|
|
28
|
+
"name": "GateOutcome.disposition",
|
|
29
|
+
"assertions": [
|
|
30
|
+
"G1.a"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"consumersHint": {
|
|
35
|
+
"cli": [],
|
|
36
|
+
"server": [],
|
|
37
|
+
"sdk": [],
|
|
38
|
+
"note": "scanned cli@37f37540 server@9ce3abaf sdk@fe943ec — a name listed is MENTIONED there today, which is evidence of consumption, not proof of it"
|
|
39
|
+
},
|
|
40
|
+
"breakingCount": 0,
|
|
41
|
+
"breakingItems": []
|
|
42
|
+
}
|