@sema-agent/core 5.60.0 → 5.60.1
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 +21 -0
- package/dist/core/checkpoint-store.d.ts +56 -6
- package/dist/core/mcp.d.ts +4 -5
- package/dist/core/memory-engine/engine.d.ts +5 -6
- package/dist/core/runner/prepare-task.js +1 -0
- package/dist/core/runner/runtask.js +56 -5
- package/dist/core/tool-policy.d.ts +8 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.60.1 — 2026-08-25
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Resume-leg usage-governance holes (#449, P0/P1)**: a resume no longer burns a full model turn
|
|
7
|
+
when the usage window is exhausted — an owed row (approval/review decision, wake, parked steer,
|
|
8
|
+
bg snapshot) refuses PRE-CAS with the new `resume.usage_window_exhausted` CheckpointError
|
|
9
|
+
(`detail.retryAfterMs`; the row stays pending, zero consumption, the same token redeems after the
|
|
10
|
+
window); a bare resource_limit continue re-suspends at turn 0 through the same platform-limit leg
|
|
11
|
+
the turn boundary uses (fresh checkpoint, new `resumeAfterMs`, zero model calls). The resume
|
|
12
|
+
ledger key now falls back to the checkpoint-pinned principal (`resource_limit` mints stamp
|
|
13
|
+
`cp.principal`) — a caller that omits principal no longer silently charges the GLOBAL bucket and
|
|
14
|
+
collects a fresh quota; supplying a DIFFERENT principal refuses loudly
|
|
15
|
+
(`resume.principal_mismatch`, pre-CAS, row pending). NARROWING, named: mismatched-principal
|
|
16
|
+
resumes previously proceeded under the wrong identity.
|
|
17
|
+
- **covering-ask audit arm (#450, core half)**: the prepare-time policy-name audit gains ask-slot
|
|
18
|
+
coverage pins (the exemption for `mcp__<server>__*` spellings now proves it speaks the truth
|
|
19
|
+
against a live in-engine ask lane); `isNamespacedCoveringRuleName`/`namespacedRuleNameCovers`
|
|
20
|
+
root-exported (additive) so out-of-engine name-set publishers can self-check the covering grammar
|
|
21
|
+
(the fifth-publisher fail-open shape stays pinned as-is until the server-side consumer lands its
|
|
22
|
+
fix). Export surface 1758 → 1760.
|
|
23
|
+
|
|
3
24
|
## 5.60.0 — 2026-08-25
|
|
4
25
|
|
|
5
26
|
### Added
|
|
@@ -1263,7 +1263,7 @@ export declare const FACE_CHECKPOINT_VERSION = 9;
|
|
|
1263
1263
|
* (binding human/irreversible_ask), v4 (a pre-164 row, refused only when it carries the retired
|
|
1264
1264
|
* wall-clock allocation), v5 (token-allocation-bearing resource/approval), v6 (org-admission freeze
|
|
1265
1265
|
* state), v7 (F-012 constraint-chain / delegation-provenance enforcement state), v8 (a
|
|
1266
|
-
* non-budgetable `realApproval` gate bit with its org-origin resume semantics)
|
|
1266
|
+
* non-budgetable `realApproval` gate bit with its org-origin resume semantics), v9 (the
|
|
1267
1267
|
* design/199 read-face section: resolved face + deny-set additions, folded stricter-wins on
|
|
1268
1268
|
* resume). */
|
|
1269
1269
|
export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 9;
|
|
@@ -1551,11 +1551,21 @@ export interface Checkpoint {
|
|
|
1551
1551
|
sourceTaskId?: string;
|
|
1552
1552
|
/**
|
|
1553
1553
|
* design S1d (source-tag persistence): the issuing task's authenticated end-user {@link
|
|
1554
|
-
* import("./types.js").TaskSpec.principal} (design/62), stamped at the APPROVAL suspend mint
|
|
1555
|
-
*
|
|
1556
|
-
* import("./tool-policy.js").AskRequest}.principal. Caller-set
|
|
1557
|
-
* {@link sourceTaskId} it is **
|
|
1558
|
-
* reads it
|
|
1554
|
+
* import("./types.js").TaskSpec.principal} (design/62), stamped at the APPROVAL suspend mint — and, since
|
|
1555
|
+
* #449 G2, at the `resource_limit` mint too — so an aggregating inbox can attribute a paused checkpoint
|
|
1556
|
+
* per-user — the durable analog of {@link import("./tool-policy.js").AskRequest}.principal. Caller-set
|
|
1557
|
+
* (never a tool/worker arg), and like {@link sourceTaskId} it is **NEVER a gate input**: no gate / CAS /
|
|
1558
|
+
* winner path reads it. The RESUME ENTRY however does read it as the row's identity record (#449 G2):
|
|
1559
|
+
* a resume config that omits `principal` inherits this value for the resumed leg (usage-ledger bucket,
|
|
1560
|
+
* later mints' scope derivation, ask attribution — a dropped field must not silently move the leg onto
|
|
1561
|
+
* the SHARED usage bucket), and a resume config carrying a DIFFERENT non-empty principal is refused
|
|
1562
|
+
* pre-CAS (`resume.principal_mismatch`). Absent ⇒ no principal (a legacy checkpoint, an unauthenticated
|
|
1563
|
+
* task, or a gate kind that predates the stamp). Rolling-deployment note (#449 G2 residual): the field
|
|
1564
|
+
* is NOT version-gated — an older worker accepts a principal-bearing row and keeps its historic
|
|
1565
|
+
* behavior (no fallback, no mismatch refusal); the identity-continuity guarantee holds once the fleet
|
|
1566
|
+
* is on enforcing workers. Version-gating it is a recorded design candidate: the lattice's newest rung
|
|
1567
|
+
* (design/199) encodes "v>=9 ⟺ face section present", so ANY new version above it must rewrite that
|
|
1568
|
+
* guard's window form and add its own presence invariant — a lattice-wide change, not a stamp.
|
|
1559
1569
|
*/
|
|
1560
1570
|
principal?: string;
|
|
1561
1571
|
/**
|
|
@@ -1812,6 +1822,23 @@ export declare class CheckpointError extends Error {
|
|
|
1812
1822
|
* thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
|
|
1813
1823
|
* stays `pending`); re-resume with the full original chain. */
|
|
1814
1824
|
| "resume.parent_constraint_mismatch"
|
|
1825
|
+
/** #449 G1 (codex r2, pre-CAS entrance): a deployment usage-governance window for this resume's
|
|
1826
|
+
* ledger key (the config's principal, else the row's recorded one, else the shared bucket) is
|
|
1827
|
+
* EXHAUSTED, and the row still OWES a delivery a re-mint cannot carry (an approval/review
|
|
1828
|
+
* decision, a wake, parked steers, a background-task obituary snapshot). Refused BEFORE the CAS,
|
|
1829
|
+
* so nothing is consumed and nothing is unpinned — the SAME token with the SAME decision is
|
|
1830
|
+
* redeemable once the window frees (`detail.retryAfterMs` carries the wait). A bare
|
|
1831
|
+
* `resource_limit` continue with nothing parked does NOT take this refusal: it proceeds and
|
|
1832
|
+
* re-suspends at turn 0 into a FRESH checkpoint carrying the new wait hint. */
|
|
1833
|
+
| "resume.usage_window_exhausted"
|
|
1834
|
+
/** #449 G2 (identity continuity): the resume config carries a NON-EMPTY `principal` that differs from
|
|
1835
|
+
* the {@link Checkpoint.principal} recorded at the suspend mint. `ResumeTaskConfig`'s contract is
|
|
1836
|
+
* "the same TaskSpec the run suspended with", and the principal keys real lanes on the resumed leg
|
|
1837
|
+
* (the usage-governance ledger bucket, later mints' scope derivation, session-rule reads, ask
|
|
1838
|
+
* attribution) — silently running the leg under a different identity is the bad-value direction this
|
|
1839
|
+
* refusal exists to close. Rejected PRE-CAS (the checkpoint stays `pending`); re-resume with the
|
|
1840
|
+
* original principal, or omit the field to inherit the recorded one. */
|
|
1841
|
+
| "resume.principal_mismatch"
|
|
1815
1842
|
/** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
|
|
1816
1843
|
* constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
|
|
1817
1844
|
* edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
|
|
@@ -1839,6 +1866,9 @@ export declare class CheckpointError extends Error {
|
|
|
1839
1866
|
* discriminant. */
|
|
1840
1867
|
readonly detail?: {
|
|
1841
1868
|
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
|
|
1869
|
+
/** #449 G1 — on `resume.usage_window_exhausted` ONLY: how long until the binding window frees
|
|
1870
|
+
* (the same wait a suspended row carries as `gate.resumeAfterMs`). Absent on every other code. */
|
|
1871
|
+
retryAfterMs?: number;
|
|
1842
1872
|
/** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
|
|
1843
1873
|
* deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
|
|
1844
1874
|
* damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
|
|
@@ -1941,6 +1971,23 @@ export declare class CheckpointError extends Error {
|
|
|
1941
1971
|
* thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
|
|
1942
1972
|
* stays `pending`); re-resume with the full original chain. */
|
|
1943
1973
|
| "resume.parent_constraint_mismatch"
|
|
1974
|
+
/** #449 G1 (codex r2, pre-CAS entrance): a deployment usage-governance window for this resume's
|
|
1975
|
+
* ledger key (the config's principal, else the row's recorded one, else the shared bucket) is
|
|
1976
|
+
* EXHAUSTED, and the row still OWES a delivery a re-mint cannot carry (an approval/review
|
|
1977
|
+
* decision, a wake, parked steers, a background-task obituary snapshot). Refused BEFORE the CAS,
|
|
1978
|
+
* so nothing is consumed and nothing is unpinned — the SAME token with the SAME decision is
|
|
1979
|
+
* redeemable once the window frees (`detail.retryAfterMs` carries the wait). A bare
|
|
1980
|
+
* `resource_limit` continue with nothing parked does NOT take this refusal: it proceeds and
|
|
1981
|
+
* re-suspends at turn 0 into a FRESH checkpoint carrying the new wait hint. */
|
|
1982
|
+
| "resume.usage_window_exhausted"
|
|
1983
|
+
/** #449 G2 (identity continuity): the resume config carries a NON-EMPTY `principal` that differs from
|
|
1984
|
+
* the {@link Checkpoint.principal} recorded at the suspend mint. `ResumeTaskConfig`'s contract is
|
|
1985
|
+
* "the same TaskSpec the run suspended with", and the principal keys real lanes on the resumed leg
|
|
1986
|
+
* (the usage-governance ledger bucket, later mints' scope derivation, session-rule reads, ask
|
|
1987
|
+
* attribution) — silently running the leg under a different identity is the bad-value direction this
|
|
1988
|
+
* refusal exists to close. Rejected PRE-CAS (the checkpoint stays `pending`); re-resume with the
|
|
1989
|
+
* original principal, or omit the field to inherit the recorded one. */
|
|
1990
|
+
| "resume.principal_mismatch"
|
|
1944
1991
|
/** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
|
|
1945
1992
|
* constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
|
|
1946
1993
|
* edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
|
|
@@ -1968,6 +2015,9 @@ export declare class CheckpointError extends Error {
|
|
|
1968
2015
|
* discriminant. */
|
|
1969
2016
|
detail?: {
|
|
1970
2017
|
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
|
|
2018
|
+
/** #449 G1 — on `resume.usage_window_exhausted` ONLY: how long until the binding window frees
|
|
2019
|
+
* (the same wait a suspended row carries as `gate.resumeAfterMs`). Absent on every other code. */
|
|
2020
|
+
retryAfterMs?: number;
|
|
1971
2021
|
/** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
|
|
1972
2022
|
* deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
|
|
1973
2023
|
* damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -177,11 +177,10 @@ export interface MaterializedMcp {
|
|
|
177
177
|
* fails the WHOLE model request (provider 400), bricking the task for every healthy tool
|
|
178
178
|
* (CC 2.1.216 ships the same intake prune, `tengu_mcp_drop_invalid_tool_schemas`).
|
|
179
179
|
*
|
|
180
|
-
* HONEST REACH (pinned in mcp-dropped-tools.test.ts):
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* transport/SDK revision delivers schemas the client does not fully validate.
|
|
180
|
+
* HONEST REACH (pinned in mcp-dropped-tools.test.ts): listings arrive through the lenient reader
|
|
181
|
+
* (`listToolsLenient` — raw request, no SDK-client zod pass), so a string-root or otherwise odd
|
|
182
|
+
* schema DOES reach this gate and is judged per tool; healthy siblings survive. This per-tool gate
|
|
183
|
+
* is therefore the PRIMARY adjudicator for schema shape, not a fallback behind SDK validation.
|
|
185
184
|
*
|
|
186
185
|
* The drop is NEVER silent, two faces (CC parity — its `mcp_dropped_tools_delta` system reminder):
|
|
187
186
|
* - operator: prepare-task forwards each entry to `onError(phase:"mcp")` alongside `warnings`;
|
|
@@ -406,12 +406,11 @@ export declare function memorySessionPollutedNotice(input: {
|
|
|
406
406
|
* arithmetic would report a clean containment while a known failure sat in the report. Every
|
|
407
407
|
* not-moved file does have an escalation row, so `escalated` is the superset and the honest clause.
|
|
408
408
|
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* The wording therefore says "entry file(s)" rather than implying the whole containment.
|
|
409
|
+
* The derived index is contained by a different path (`restorePollutedIndex` captures and restores
|
|
410
|
+
* `MEMORY.md`) that mints no rejection row; that gap was CLOSED by the structured containment signal
|
|
411
|
+
* on `HarvestReport.containment` (indexRolledBack — see the containment block below), so an
|
|
412
|
+
* index-only polluted session now signals and notices instead of hiding in warnings prose. The
|
|
413
|
+
* wording says "entry file(s)" because index containment reports through its own signal.
|
|
415
414
|
*
|
|
416
415
|
* `reason` is optional because the mark record is read separately from the report: a record that cannot
|
|
417
416
|
* be read must not turn a true "N files were withheld" into a lie about why.
|
|
@@ -3965,6 +3965,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3965
3965
|
: undefined,
|
|
3966
3966
|
resourceLedger: resourceLedgerOut,
|
|
3967
3967
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
3968
|
+
...(spec.principal ? { principal: spec.principal } : {}),
|
|
3968
3969
|
};
|
|
3969
3970
|
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).ok)
|
|
3970
3971
|
return false;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { persistedReadDenyEntryProblem } from "../../tools/fs/read-deny.js";
|
|
2
2
|
import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
|
|
3
3
|
import { mintSystemReminder, openSystemReminder } from "../reminder-mint.js";
|
|
4
|
+
import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
|
|
4
5
|
import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
|
|
5
6
|
import { planRejectionClears, resolveTriggerWindow } from "../context-edit.js";
|
|
6
7
|
import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, uuidv7 } from "../../internal/harness.js";
|
|
@@ -344,9 +345,11 @@ export async function raceUntilDeadline(p, deadline) {
|
|
|
344
345
|
function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") {
|
|
345
346
|
const message = moment === "entry"
|
|
346
347
|
? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the task was refused before its first model call — nothing ran and nothing was spent. There is no checkpoint to suspend into at this point, so the caller re-submits after the window frees, in ${String(retryAfterMs ?? 0)}ms.`
|
|
347
|
-
:
|
|
348
|
-
?
|
|
349
|
-
:
|
|
348
|
+
: moment === "resume"
|
|
349
|
+
? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the resumed leg was stopped BEFORE its first model call — nothing was delivered or executed on this leg. The window frees in ${String(retryAfterMs ?? 0)}ms.`
|
|
350
|
+
: reason === "env_lifetime"
|
|
351
|
+
? "the execution environment's declared lifetime is expiring and the run could not be suspended durably (not eligible, or the suspend was refused — see remoteEnvFailures / onError). It was stopped at a clean turn boundary instead of being killed mid-turn when the platform reclaims the environment."
|
|
352
|
+
: `a deployment usage window is exhausted (RunnerDeps.usageWindows) and the run could not be suspended durably, so it was stopped at a clean turn boundary. The window frees in ${String(retryAfterMs ?? 0)}ms.`;
|
|
350
353
|
const e = new Error(message);
|
|
351
354
|
e.code = reason === "env_lifetime" ? "env.lifetime_expired" : "usage.window_exhausted";
|
|
352
355
|
if (retryAfterMs !== undefined)
|
|
@@ -3186,7 +3189,29 @@ export class Runner {
|
|
|
3186
3189
|
}
|
|
3187
3190
|
if (resume) {
|
|
3188
3191
|
const walltimeExhaustedResume = effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0;
|
|
3189
|
-
|
|
3192
|
+
const resumeWindowRetryAfterMs = !walltimeExhaustedResume && prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
|
|
3193
|
+
if (resumeWindowRetryAfterMs !== undefined) {
|
|
3194
|
+
const owesNothing = resume.outcome.gate === "resource_limit" &&
|
|
3195
|
+
readPendingSteerQueue(resume.cp.state).length === 0 &&
|
|
3196
|
+
(resume.cp.state.runningBackgroundTasks?.length ?? 0) === 0;
|
|
3197
|
+
const committed = owesNothing &&
|
|
3198
|
+
prepared.suspendForPlatformLimit !== undefined &&
|
|
3199
|
+
(await prepared.suspendForPlatformLimit("usage_window", { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) }, { resumeAfterMs: resumeWindowRetryAfterMs }));
|
|
3200
|
+
if (committed) {
|
|
3201
|
+
rs.limits.turnsExceeded = false;
|
|
3202
|
+
rs.limits.budgetHit = undefined;
|
|
3203
|
+
}
|
|
3204
|
+
else {
|
|
3205
|
+
const terminal = platformLimitTerminal("usage_window", resumeWindowRetryAfterMs, "resume");
|
|
3206
|
+
rs.limits.platformTerminal = terminal;
|
|
3207
|
+
try {
|
|
3208
|
+
this.deps.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
|
|
3209
|
+
}
|
|
3210
|
+
catch {
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
if (resumeWindowRetryAfterMs === undefined && !walltimeExhaustedResume && resume.outcome.gate !== "wake") {
|
|
3190
3215
|
await this.applyResumeDecision(prepared, resume, (e) => pushContent({ ...e, ...ident() }), emitCommitted, (toolName, details) => {
|
|
3191
3216
|
if (rs.attach.attachState === undefined)
|
|
3192
3217
|
return;
|
|
@@ -3205,7 +3230,9 @@ export class Runner {
|
|
|
3205
3230
|
resume.decisionDelivered = true;
|
|
3206
3231
|
announceWorkspaceMove();
|
|
3207
3232
|
}
|
|
3208
|
-
if (
|
|
3233
|
+
if (resumeWindowRetryAfterMs !== undefined) {
|
|
3234
|
+
}
|
|
3235
|
+
else if (walltimeExhaustedResume) {
|
|
3209
3236
|
timeout.fired = true;
|
|
3210
3237
|
timeout.clear();
|
|
3211
3238
|
void prepared.harness.abort();
|
|
@@ -4455,6 +4482,29 @@ export class Runner {
|
|
|
4455
4482
|
throw new CheckpointError("checkpoint.invalid_outcome", "TaskSpec.basePolicyForResumeEdit is administratively locked by this deployment (locked key \"toolPolicy\") — a task-supplied " +
|
|
4456
4483
|
"resume-edit policy is refused pre-CAS (the checkpoint stays pending); remove the field or change the deployment's lock configuration");
|
|
4457
4484
|
}
|
|
4485
|
+
{
|
|
4486
|
+
const rowPrincipal = cp.principal || undefined;
|
|
4487
|
+
const suppliedPrincipal = taskConfig.principal || undefined;
|
|
4488
|
+
if (rowPrincipal !== undefined && suppliedPrincipal !== undefined && suppliedPrincipal !== rowPrincipal) {
|
|
4489
|
+
throw new CheckpointError("resume.principal_mismatch", `the resume config carries principal ${JSON.stringify(suppliedPrincipal)} but this checkpoint was suspended under principal ${JSON.stringify(rowPrincipal)} — ` +
|
|
4490
|
+
"running the resumed leg under a different identity would move its usage-ledger bucket, scope derivation and attribution; " +
|
|
4491
|
+
"refused pre-CAS (the checkpoint stays pending): re-resume with the original principal, or omit the field to inherit the recorded one");
|
|
4492
|
+
}
|
|
4493
|
+
const owesDelivery = outcomeGate !== "resource_limit" ||
|
|
4494
|
+
readPendingSteerQueue(cp.state).length > 0 ||
|
|
4495
|
+
(cp.state.runningBackgroundTasks?.length ?? 0) > 0;
|
|
4496
|
+
if (owesDelivery && this.deps.usageWindowStore !== undefined) {
|
|
4497
|
+
const preCasWindows = resolveUsageWindows(this.deps.usageWindows);
|
|
4498
|
+
if (preCasWindows !== undefined && preCasWindows.length > 0) {
|
|
4499
|
+
const ledgerKey = (suppliedPrincipal ?? rowPrincipal) || GLOBAL_USAGE_KEY;
|
|
4500
|
+
const wait = usageRetryAfterMs(await this.deps.usageWindowStore.read(ledgerKey, preCasWindows, Date.now()));
|
|
4501
|
+
if (wait !== undefined) {
|
|
4502
|
+
throw new CheckpointError("resume.usage_window_exhausted", `a deployment usage window for ledger key ${JSON.stringify(ledgerKey)} is exhausted, and this checkpoint still owes a delivery a re-mint cannot carry — ` +
|
|
4503
|
+
`refused pre-CAS (nothing consumed, nothing unpinned): the same token and the same decision are redeemable once the window frees, in ${String(wait)}ms`, { retryAfterMs: wait });
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4458
4508
|
if (plainPolicyOutcome !== undefined &&
|
|
4459
4509
|
plainPolicyOutcome.decision === "allow" &&
|
|
4460
4510
|
plainPolicyOutcome.updatedInput !== undefined &&
|
|
@@ -4571,6 +4621,7 @@ export class Runner {
|
|
|
4571
4621
|
: undefined;
|
|
4572
4622
|
const spec = {
|
|
4573
4623
|
...taskConfig,
|
|
4624
|
+
...((taskConfig.principal || undefined) === undefined && (cp.principal || undefined) !== undefined ? { principal: cp.principal } : {}),
|
|
4574
4625
|
objective: "",
|
|
4575
4626
|
sessionId: cp.sessionId,
|
|
4576
4627
|
requireExistingSession: true,
|
|
@@ -413,8 +413,14 @@ export interface ToolPolicyProjection {
|
|
|
413
413
|
*
|
|
414
414
|
* Exported for the OTHER name-keyed lane in this engine — the persisted session/ancestor rules in
|
|
415
415
|
* `session-rule-policy.ts`. Not part of the public API (`src/index.ts` re-exports by name and does not
|
|
416
|
-
* list these):
|
|
417
|
-
* way, because the prepare-time audit's exemption for these spellings speaks for
|
|
416
|
+
* list these two): every publisher of {@link ToolPolicyNameSets} must resolve a covering entry the same
|
|
417
|
+
* way, because the prepare-time audit's exemption for these spellings speaks for ALL published name sets
|
|
418
|
+
* at once — the four in-engine lanes do so by sharing this module's matcher. #450: that premise is NOT
|
|
419
|
+
* structural for an OUT-OF-ENGINE publisher (a host compiling its own settings into an exact-match ask
|
|
420
|
+
* closure is fail-open on these spellings while the audit stays silent) — which is why the underlying
|
|
421
|
+
* judgment predicates (`isNamespacedCoveringRuleName` / `namespacedRuleNameCovers`) ARE exported from
|
|
422
|
+
* the package index: an external lane either compiles through `createPermissionRulePolicy` or
|
|
423
|
+
* self-checks with those.
|
|
418
424
|
*/
|
|
419
425
|
export declare function namespacedCoveringEntries(entries: readonly string[] | undefined): readonly string[];
|
|
420
426
|
/** Does any covering entry reach `toolName`? See {@link namespacedCoveringEntries}. */
|
package/dist/index.d.ts
CHANGED
|
@@ -140,7 +140,7 @@ export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, create
|
|
|
140
140
|
export { parseAutoModeResponse, createAutoModeDecider, type AutoModeVerdict, type AutoModeDecider, type AutoModeDeciderOptions, type AutoModeClassifyFn, type AutoModeClassifyInput, } from "./core/auto-mode.js";
|
|
141
141
|
export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_MODE_DEFAULTS_SENTINEL, type AutoModeRules, type BuildAutoModePromptOptions, type AutoModeWindowOptions, } from "./core/auto-mode-prompt.js";
|
|
142
142
|
export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
|
|
143
|
-
export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, type PermissionRule, type ParsedPermissionRule, type PermissionRuleIssue, type PermissionRuleCaps, type PermissionRulePolicyOptions, } from "./core/permission-rules.js";
|
|
143
|
+
export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, isNamespacedCoveringRuleName, namespacedRuleNameCovers, type PermissionRule, type ParsedPermissionRule, type PermissionRuleIssue, type PermissionRuleCaps, type PermissionRulePolicyOptions, } from "./core/permission-rules.js";
|
|
144
144
|
/**
|
|
145
145
|
* design/179 — persisted ALLOW rules: the standing form of approvals a person already gave.
|
|
146
146
|
*
|
package/dist/index.js
CHANGED
|
@@ -117,7 +117,7 @@ export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, create
|
|
|
117
117
|
export { parseAutoModeResponse, createAutoModeDecider, } from "./core/auto-mode.js";
|
|
118
118
|
export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_MODE_DEFAULTS_SENTINEL, } from "./core/auto-mode-prompt.js";
|
|
119
119
|
export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
|
|
120
|
-
export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, } from "./core/permission-rules.js";
|
|
120
|
+
export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, isNamespacedCoveringRuleName, namespacedRuleNameCovers, } from "./core/permission-rules.js";
|
|
121
121
|
export { parseAllowRuleText, formatAllowRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, } from "./core/permission-rule-model.js";
|
|
122
122
|
export { removePersistedRule, applyTombstones, sameScope, InMemoryPermissionRuleStore, EMPTY_RULE_STORE, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, } from "./core/permission-rule-store.js";
|
|
123
123
|
export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, } from "./core/permission-rule-sync.js";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "design/87 L3 — frozen public export surface of src/index.ts (name -> kind). DO NOT edit by hand to silence a red test. A removed/changed entry = a SemVer-BREAKING change; bump MAJOR and update this fixture in the SAME commit (design/87 §4.2 / §5.2). Regenerate via REGEN in test/export-surface.test.ts.",
|
|
3
3
|
"_tierComment": "#435 v1 — machine-readable layering of the public surface: stable = demonstrated by README.md / src/examples; internal = an `Internal`-marked name or a runner/engine deep-subtree declaration (the model seam src/engine/llm is excluded — it is the BYOM contract, not an engine internal); advanced = a supported export the front door does not walk you through. THIS IS AN INITIAL HEURISTIC, derived mechanically and expected to be refined ticket by ticket: no human reviewed these 1700+ entries one by one, and nothing here claims otherwise. Known bias: a short or English-word export name (ok, err, Result, Usage) can match ordinary prose in README.md and land `stable` on a coincidence. Every export MUST carry a tier — a new export with no row fails the gate in export-surface.test.ts.",
|
|
4
|
-
"count":
|
|
4
|
+
"count": 1760,
|
|
5
5
|
"exports": {
|
|
6
6
|
"A2ATaskState": "type",
|
|
7
7
|
"A2ATaskStateReversal": "type",
|
|
@@ -1463,6 +1463,7 @@
|
|
|
1463
1463
|
"isInstructionEntry": "function",
|
|
1464
1464
|
"isIsolated": "function",
|
|
1465
1465
|
"isModelGatedForClass": "function",
|
|
1466
|
+
"isNamespacedCoveringRuleName": "function",
|
|
1466
1467
|
"isPersonalScope": "function",
|
|
1467
1468
|
"isQuestionUnavailable": "function",
|
|
1468
1469
|
"isRemoteExecutionEnv": "function",
|
|
@@ -1525,6 +1526,7 @@
|
|
|
1525
1526
|
"missingRestoreSurface": "function",
|
|
1526
1527
|
"modelCostToPricing": "function",
|
|
1527
1528
|
"mysqlQuery": "function",
|
|
1529
|
+
"namespacedRuleNameCovers": "function",
|
|
1528
1530
|
"nextSyncBaseline": "function",
|
|
1529
1531
|
"normalizeAgentName": "function",
|
|
1530
1532
|
"normalizeBaseUrl": "function",
|
|
@@ -3223,6 +3225,7 @@
|
|
|
3223
3225
|
"isInstructionEntry": "advanced",
|
|
3224
3226
|
"isIsolated": "advanced",
|
|
3225
3227
|
"isModelGatedForClass": "advanced",
|
|
3228
|
+
"isNamespacedCoveringRuleName": "advanced",
|
|
3226
3229
|
"isPersonalScope": "advanced",
|
|
3227
3230
|
"isQuestionUnavailable": "advanced",
|
|
3228
3231
|
"isRemoteExecutionEnv": "advanced",
|
|
@@ -3285,6 +3288,7 @@
|
|
|
3285
3288
|
"missingRestoreSurface": "advanced",
|
|
3286
3289
|
"modelCostToPricing": "advanced",
|
|
3287
3290
|
"mysqlQuery": "advanced",
|
|
3291
|
+
"namespacedRuleNameCovers": "advanced",
|
|
3288
3292
|
"nextSyncBaseline": "advanced",
|
|
3289
3293
|
"normalizeAgentName": "advanced",
|
|
3290
3294
|
"normalizeBaseUrl": "advanced",
|