@sema-agent/core 5.12.0 → 5.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/agents/subagent.js +4 -2
- package/dist/core/checkpoint-store.d.ts +8 -1
- package/dist/core/checkpoint-store.js +3 -1
- package/dist/core/compliance.d.ts +11 -0
- package/dist/core/compliance.js +34 -0
- package/dist/core/governance-codes.d.ts +12 -0
- package/dist/core/governance-codes.js +24 -0
- package/dist/core/locked-config.d.ts +27 -0
- package/dist/core/locked-config.js +42 -0
- package/dist/core/memory-admission.d.ts +47 -0
- package/dist/core/memory-admission.js +156 -0
- package/dist/core/memory.d.ts +2 -0
- package/dist/core/memory.js +3 -2
- package/dist/core/retention.d.ts +36 -0
- package/dist/core/retention.js +31 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +9 -0
- package/dist/core/runner/prepare-memory.js +28 -2
- package/dist/core/runner/prepare-task.d.ts +2 -0
- package/dist/core/runner/prepare-task.js +130 -17
- package/dist/core/runner/runtask.js +72 -41
- package/dist/core/session-store.d.ts +1 -0
- package/dist/core/session-store.js +1 -0
- package/dist/core/tool-result-store.d.ts +2 -0
- package/dist/core/tool-result-store.js +1 -0
- package/dist/core/types.d.ts +6 -1
- package/dist/engine/harness/agent-harness.js +11 -1
- package/dist/engine/llm/validation.js +11 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.13.0 — 2026-08-05
|
|
4
|
+
|
|
5
|
+
### BREAKING
|
|
6
|
+
|
|
7
|
+
- **Request-origin `org:` memory scopes now require fail-closed admission** (`RunnerDeps.memoryScopeAdmission`, new module `memory-admission.ts`). Under the v2 scope contract, an org scope in `TaskSpec.memory` (or a subagent's `AgentDefinition.memory`) that is not deployment-declared refuses the WHOLE prepare: resolver absent ⇒ `memory.admission_required` (transient); resolver throw ⇒ same; principal absent / explicit refusal / partial admit / unrequested grant ⇒ `memory.admission_denied` (terminal). Verdicts are two-state — no silent narrowing of the request side (mount visibility must not become an org-membership probe); the one silent-but-disclosed narrowing is the org WRITE face, which collapses to read-only (`null`) unless the verdict grants it explicitly. **Zero migration for operator-pinned deployments**: scopes listed in the new `RunnerDeps.deploymentMemoryScopes` are deployment-origin (self-certified — no principal, no resolver needed); a present resolver may narrow them (disclosed via `onError`, phase `"memory"`). Delegation freeze: the admitted org set rides `InheritedGate.admittedOrgScopes` (always emitted; absent = older chain, read fail-closed as empty) and persists on the checkpoint data half (seed ∩ live fold — a resume can never widen it); a child's request-origin org scopes must sit inside the parent verdict. On admission-governed deployments, org-SHAPED scopes outside the v2 contract are refused outright; without a governance surface, opaque behavior is byte-identical. **Consumer flips**: probes pinning "request-side org scope mounts with no resolver" red.
|
|
8
|
+
|
|
9
|
+
- **`classifySubagentError` governance retryability is table-driven** (new module `governance-codes.ts`, closed `GOVERNANCE_CODES` registry — the machine reads the table, never the `_required`/`_denied` name suffix). `errorKind`'s closed domain is unchanged (no new members), but governance is no longer uniformly non-retryable: TRANSIENT codes (`memory.admission_required`, `config.compliance_required` — control-plane absence/outage) now classify `retryable: true`; TERMINAL codes (`*_denied`, `config.locked_key`, the whole legacy `usage.*` family — whose deferred retryability stays on the paired `retryAfterMs` field) stay `retryable: false`. **Consumer flips**: probes pinning "governance ⇒ retryable:false unconditionally" red.
|
|
10
|
+
|
|
11
|
+
- **Org-admission state is version-fenced on checkpoints** (`ORG_ADMISSION_CHECKPOINT_VERSION = 6`; `MAX_SUPPORTED_CHECKPOINT_VERSION` raised to 6). A suspend whose state carries org-admission freeze fields — the delegation-chain verdict, the session's OWN verdict (`ownAdmittedOrgScopes`, persisted even when adjudicated-EMPTY), and the frozen org WRITE grant (`ownAdmittedOrgWriteScope`; `null` = read-only, a resume can never gain write authority the suspend leg did not hold) — stamps v6, so a pre-admission worker rejects it pre-CAS (`unsupported_version`, stays pending) instead of resuming raw org scopes with no admission door. The session freeze applies to the FINAL admitted projection regardless of origin: a deployment-declared tenant layer the session already lost is dropped-and-disclosed on resume, never silently remounted. Checkpoints without org-admission state keep their historic stamps (zero version movement for deployments not using org memory).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Administrator lock layer** (`locked-config.ts`): `RunnerDeps.lockedConfig` declares locked keys from the closed registry (`mcp` / `toolPolicy` / `compliancePosture` / `retentionPolicy`). A task spec occupying a locked slot refuses the whole prepare (`config.locked_key`, two-state); an unknown key in the declaration refuses too (`config.locked_registry` — a security control that doesn't parse must not half-arm). One preflight resolver runs at the prepare door before any side effect and mints the snapshot all downstream assembly reads (fresh/resume/child legs covered structurally). Locks are config-time only — no request-time unlock; "who can unlock" ≡ "who can change the deployment config". Unlocked deployments byte-identical.
|
|
16
|
+
|
|
17
|
+
- **Compliance capability veto** (`compliance.ts`): `RunnerDeps.compliancePostureResolver` resolves a per-principal posture (closed profiles `hipaa`/`zdr` × closed capabilities `mcp_servers`/`workflows`/`web_fetch`/`org_memory_mount`; effective denies = builtin floor ∪ `additionalDenies`, supply can only tighten; unknown values refuse loudly). Fail-closed: resolver fault denies every managed capability + `onError`. A denied capability the spec explicitly requests refuses the prepare (`config.compliance_denied`; `config.compliance_required` when the denial came from the resolver fault). `web_fetch` is enforced in CC double-point form (roster refusal at prepare + a call-time lock in the tool gate). No resolver ⇒ byte-identical.
|
|
18
|
+
|
|
19
|
+
- **Managed retention declaration + contract** (`retention.ts`): stores declare `retention: "managed" | "none"` (declaration, never duck-typing; absence reads fail-closed as `"none"`; an unparseable declaration is a loud refusal). All bundled stores honestly declare `"none"`. `RunnerDeps.retentionPolicy` + a LOCKED `retentionPolicy` key over any `"none"` store refuses the deployment (`config.retention_capability` — via the exported `assertRetentionCapability` at startup, re-run every prepare as backstop). `ManagedRetentionCapability` is the contract TYPE a deployment scheduler implements (tenant-domain enumeration, CAS-fenced checkpoint expiry that releases pins, lineage-aware session deletion, orphan tool-result cleanup; idempotent, tombstoned, audited) — core ships no scheduler and does no cleanup on the task path.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Every task notification now delivers at the NEXT turn boundary (mid-work included).** `later`-priority frames (background agent/workflow completions, Monitor watch events) rode `harness.followUp()`, whose queue drains only when the agent would otherwise stop — a model busy-polling a background task (the exact model the receipt told "you will be notified, do not poll") never received the completion, and the end-of-run drain then extended finished runs with stale frames. Re-anchored against CC 2.1.221: queued task-notification inputs are unconditionally deliverable at the boundary. Delivery order is ARRIVAL order; consecutive buffered notifications drain as ONE boundary batch (the retired `followUpMode:"all"` batching, now on the steer lane), and an engine-note backlog past 50 frames parks per session instead of crowding out user steering. `priority` keeps its park/uplink semantics; the harness followUp/stop-gate seam is untouched. Consumer note: a probe pinning "a busy run never sees a `later` completion frame" reds.
|
|
24
|
+
|
|
25
|
+
- **`finalVerification` mechanism hardening (four of the seven field failure modes).** ① The injection headroom guard read only the turn axis — it now also reads the tightest caller-armed budget axis (tokens/cost/walltime, same axes as the limit-approach frames); ≥90% full skips the injection, so the gate can no longer convert an externally-passing run into `limits.*_exceeded`. ② Both injections emit the standard `steering_injected` echo (**new closed-set member: `source: "final_verification"`** — switches over the source union add an arm), closing the stats-vs-stream observability gap. ③ The nudge licenses cleaning up residue the model's own testing created (the state-harmless clause no longer reads as "leave your test residue in place"). ④ The nudge forbids laundering pre-existing uncertainty into "confirmed". The remaining modes are design-bounded: the engine never judges check semantics, and self-grading is the deployment's verifier-role wiring.
|
|
26
|
+
|
|
3
27
|
## 5.12.0 — 2026-08-05
|
|
4
28
|
|
|
5
29
|
### BREAKING
|
package/dist/agents/subagent.js
CHANGED
|
@@ -18,6 +18,7 @@ import { addWorktree } from "../core/git-worktree-env.js";
|
|
|
18
18
|
import { shellQuote } from "../tools/fs/search.js";
|
|
19
19
|
import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
|
|
20
20
|
import { extractErrorCode } from "../brain/errors.js";
|
|
21
|
+
import { governanceRetryClass } from "../core/governance-codes.js";
|
|
21
22
|
import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
|
|
22
23
|
function rollupDelegatedCost(stats, nested) {
|
|
23
24
|
if (stats.costMicroUsd === undefined)
|
|
@@ -221,6 +222,7 @@ export function classifySubagentError(child) {
|
|
|
221
222
|
return undefined;
|
|
222
223
|
const code = child.errorCode ?? extractErrorCode(child.errorMessage);
|
|
223
224
|
const msg = (child.errorMessage ?? "").toLowerCase();
|
|
225
|
+
const governanceClass = code !== undefined ? governanceRetryClass(code) : undefined;
|
|
224
226
|
const errorKind = code === "rate_limit"
|
|
225
227
|
? "rate_limit"
|
|
226
228
|
: code === "server" && (msg.includes("overloaded") || msg.includes("529"))
|
|
@@ -229,10 +231,10 @@ export function classifySubagentError(child) {
|
|
|
229
231
|
? "timeout"
|
|
230
232
|
: code === "network" || code === "server"
|
|
231
233
|
? "network"
|
|
232
|
-
:
|
|
234
|
+
: governanceClass !== undefined
|
|
233
235
|
? "governance"
|
|
234
236
|
: "logic";
|
|
235
|
-
return { errorKind, retryable: errorKind
|
|
237
|
+
return { errorKind, retryable: errorKind === "governance" ? governanceClass === "transient" : errorKind !== "logic" };
|
|
236
238
|
}
|
|
237
239
|
export function completedAgentCard(child, extras) {
|
|
238
240
|
return {
|
|
@@ -142,6 +142,10 @@ export interface CheckpointState {
|
|
|
142
142
|
rules: SessionPermissionRules;
|
|
143
143
|
}>;
|
|
144
144
|
shellGate?: "off" | "always" | "classify";
|
|
145
|
+
admittedOrgScopes?: readonly string[];
|
|
146
|
+
ownAdmittedOrgScopes?: readonly string[];
|
|
147
|
+
ownAdmittedOrgWriteScope?: string | null;
|
|
148
|
+
orgAdmissionGoverned?: true;
|
|
145
149
|
requiresParentConstraint: boolean;
|
|
146
150
|
parentConstraintCount?: number;
|
|
147
151
|
};
|
|
@@ -158,7 +162,8 @@ export declare const CURRENT_CHECKPOINT_VERSION = 1;
|
|
|
158
162
|
export declare const RESOURCE_CHECKPOINT_VERSION = 2;
|
|
159
163
|
export declare const BINDING_CHECKPOINT_VERSION = 3;
|
|
160
164
|
export declare const TOKEN_CHECKPOINT_VERSION = 5;
|
|
161
|
-
export declare const
|
|
165
|
+
export declare const ORG_ADMISSION_CHECKPOINT_VERSION = 6;
|
|
166
|
+
export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 6;
|
|
162
167
|
export declare function checkpointVersionOf(cp: Pick<Checkpoint, "version">): number;
|
|
163
168
|
export interface ResourceLedger {
|
|
164
169
|
totalBudgetMicroUsd?: number;
|
|
@@ -255,6 +260,7 @@ export declare class CheckpointError extends Error {
|
|
|
255
260
|
} | undefined);
|
|
256
261
|
}
|
|
257
262
|
export interface CheckpointStore {
|
|
263
|
+
readonly retention?: import("./retention.js").RetentionDeclaration;
|
|
258
264
|
put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
|
|
259
265
|
get(token: CheckpointToken): Promise<Checkpoint | null>;
|
|
260
266
|
resolve(token: CheckpointToken, scope: string, outcome: ResumeOutcome, expect?: ResolveExpectation): Promise<boolean>;
|
|
@@ -280,6 +286,7 @@ export declare function checkpointRowMatches(cp: Checkpoint | undefined, scope:
|
|
|
280
286
|
export declare function checkpointOccMatches(cp: Checkpoint, expect?: ResolveExpectation): boolean;
|
|
281
287
|
export type CheckpointFaultMode = "resolve-after-commit" | "resolve-before-commit";
|
|
282
288
|
export declare class InMemoryCheckpointStore implements CheckpointStore {
|
|
289
|
+
readonly retention: "none";
|
|
283
290
|
private cps;
|
|
284
291
|
private fault;
|
|
285
292
|
put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
|
|
@@ -128,7 +128,8 @@ export const CURRENT_CHECKPOINT_VERSION = 1;
|
|
|
128
128
|
export const RESOURCE_CHECKPOINT_VERSION = 2;
|
|
129
129
|
export const BINDING_CHECKPOINT_VERSION = 3;
|
|
130
130
|
export const TOKEN_CHECKPOINT_VERSION = 5;
|
|
131
|
-
export const
|
|
131
|
+
export const ORG_ADMISSION_CHECKPOINT_VERSION = 6;
|
|
132
|
+
export const MAX_SUPPORTED_CHECKPOINT_VERSION = 6;
|
|
132
133
|
export function checkpointVersionOf(cp) {
|
|
133
134
|
return cp.version ?? 0;
|
|
134
135
|
}
|
|
@@ -250,6 +251,7 @@ export function checkpointOccMatches(cp, expect) {
|
|
|
250
251
|
return expect === undefined || (cp.rev ?? 0) === expect.rev;
|
|
251
252
|
}
|
|
252
253
|
export class InMemoryCheckpointStore {
|
|
254
|
+
retention = "none";
|
|
253
255
|
cps = new Map();
|
|
254
256
|
fault = null;
|
|
255
257
|
async put(token, cp) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ComplianceProfile = "hipaa" | "zdr";
|
|
2
|
+
export type ComplianceCapability = "mcp_servers" | "workflows" | "web_fetch" | "org_memory_mount";
|
|
3
|
+
export declare const COMPLIANCE_CAPABILITIES: readonly ["mcp_servers", "workflows", "web_fetch", "org_memory_mount"];
|
|
4
|
+
export interface CompliancePosture {
|
|
5
|
+
profile: ComplianceProfile;
|
|
6
|
+
additionalDenies?: readonly ComplianceCapability[];
|
|
7
|
+
}
|
|
8
|
+
export declare const BUILTIN_COMPLIANCE_DENIES: Record<ComplianceProfile, readonly ComplianceCapability[]>;
|
|
9
|
+
export declare function resolveComplianceDenies(posture: CompliancePosture): ReadonlySet<ComplianceCapability>;
|
|
10
|
+
export declare const WEB_FETCH_TOOL_NAME = "WebFetch";
|
|
11
|
+
export declare function complianceCallDenial(denies: ReadonlySet<ComplianceCapability>, toolName: string): string | undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const COMPLIANCE_CAPABILITIES = ["mcp_servers", "workflows", "web_fetch", "org_memory_mount"];
|
|
2
|
+
export const BUILTIN_COMPLIANCE_DENIES = {
|
|
3
|
+
hipaa: ["web_fetch", "org_memory_mount"],
|
|
4
|
+
zdr: ["org_memory_mount"],
|
|
5
|
+
};
|
|
6
|
+
const PROFILES = ["hipaa", "zdr"];
|
|
7
|
+
function codedError(code, message) {
|
|
8
|
+
const e = new Error(message);
|
|
9
|
+
e.code = code;
|
|
10
|
+
return e;
|
|
11
|
+
}
|
|
12
|
+
export function resolveComplianceDenies(posture) {
|
|
13
|
+
if (!PROFILES.includes(posture.profile)) {
|
|
14
|
+
throw codedError("config.compliance_posture", `compliance posture profile "${String(posture.profile)}" is not a member of the closed profile set ` +
|
|
15
|
+
`(${PROFILES.join(", ")}) — refusing to half-apply an unparseable posture.`);
|
|
16
|
+
}
|
|
17
|
+
const denies = new Set(BUILTIN_COMPLIANCE_DENIES[posture.profile]);
|
|
18
|
+
for (const cap of posture.additionalDenies ?? []) {
|
|
19
|
+
if (!COMPLIANCE_CAPABILITIES.includes(cap)) {
|
|
20
|
+
throw codedError("config.compliance_posture", `compliance posture additionalDenies contains "${String(cap)}", not a member of the closed capability set ` +
|
|
21
|
+
`(${COMPLIANCE_CAPABILITIES.join(", ")}) — a misspelled deny must never silently not-enforce.`);
|
|
22
|
+
}
|
|
23
|
+
denies.add(cap);
|
|
24
|
+
}
|
|
25
|
+
return denies;
|
|
26
|
+
}
|
|
27
|
+
export const WEB_FETCH_TOOL_NAME = "WebFetch";
|
|
28
|
+
export function complianceCallDenial(denies, toolName) {
|
|
29
|
+
if (denies.has("web_fetch") && toolName === WEB_FETCH_TOOL_NAME) {
|
|
30
|
+
return (`"${toolName}" is denied by this principal's compliance posture (capability "web_fetch") — ` +
|
|
31
|
+
`the call is refused by policy; do not retry it.`);
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type GovernanceRetryClass = "transient" | "terminal";
|
|
2
|
+
export declare const GOVERNANCE_CODES: {
|
|
3
|
+
readonly "usage.window_exhausted": "terminal";
|
|
4
|
+
readonly "memory.admission_required": "transient";
|
|
5
|
+
readonly "memory.admission_denied": "terminal";
|
|
6
|
+
readonly "config.compliance_required": "transient";
|
|
7
|
+
readonly "config.compliance_denied": "terminal";
|
|
8
|
+
readonly "config.locked_key": "terminal";
|
|
9
|
+
};
|
|
10
|
+
export type GovernanceCode = keyof typeof GOVERNANCE_CODES;
|
|
11
|
+
export declare const NON_GOVERNANCE_MEMORY_CODES: ReadonlySet<string>;
|
|
12
|
+
export declare function governanceRetryClass(code: string): GovernanceRetryClass | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const GOVERNANCE_CODES = {
|
|
2
|
+
"usage.window_exhausted": "terminal",
|
|
3
|
+
"memory.admission_required": "transient",
|
|
4
|
+
"memory.admission_denied": "terminal",
|
|
5
|
+
"config.compliance_required": "transient",
|
|
6
|
+
"config.compliance_denied": "terminal",
|
|
7
|
+
"config.locked_key": "terminal",
|
|
8
|
+
};
|
|
9
|
+
export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
10
|
+
"memory.secret_detected",
|
|
11
|
+
"memory.promotable_ungated",
|
|
12
|
+
"memory.announce_failed",
|
|
13
|
+
"memory.partition_adopt_failed",
|
|
14
|
+
"memory.partition_split",
|
|
15
|
+
"memory.tail",
|
|
16
|
+
]);
|
|
17
|
+
export function governanceRetryClass(code) {
|
|
18
|
+
if (Object.prototype.hasOwnProperty.call(GOVERNANCE_CODES, code)) {
|
|
19
|
+
return GOVERNANCE_CODES[code];
|
|
20
|
+
}
|
|
21
|
+
if (code.startsWith("usage."))
|
|
22
|
+
return "terminal";
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RunnerDeps, TaskSpec } from "./types.js";
|
|
2
|
+
import type { ToolPolicy } from "./tool-policy.js";
|
|
3
|
+
export type LockedKey = "mcp" | "toolPolicy" | "compliancePosture" | "retentionPolicy";
|
|
4
|
+
export declare const LOCKED_KEY_REGISTRY: {
|
|
5
|
+
readonly mcp: {
|
|
6
|
+
readonly specFields: readonly ["mcp"];
|
|
7
|
+
};
|
|
8
|
+
readonly toolPolicy: {
|
|
9
|
+
readonly specFields: readonly ["toolPolicy"];
|
|
10
|
+
};
|
|
11
|
+
readonly compliancePosture: {
|
|
12
|
+
readonly specFields: readonly [];
|
|
13
|
+
};
|
|
14
|
+
readonly retentionPolicy: {
|
|
15
|
+
readonly specFields: readonly [];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export interface LockedConfig {
|
|
19
|
+
keys: readonly LockedKey[];
|
|
20
|
+
}
|
|
21
|
+
export interface LockedPreflight {
|
|
22
|
+
lockedKeys: ReadonlySet<LockedKey>;
|
|
23
|
+
mcp: TaskSpec["mcp"];
|
|
24
|
+
toolPolicy: ToolPolicy | undefined;
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveLockedKeys(config: LockedConfig | undefined): ReadonlySet<LockedKey>;
|
|
27
|
+
export declare function preflightLockedConfig(spec: TaskSpec, deps: RunnerDeps): LockedPreflight;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const LOCKED_KEY_REGISTRY = {
|
|
2
|
+
mcp: { specFields: ["mcp"] },
|
|
3
|
+
toolPolicy: { specFields: ["toolPolicy"] },
|
|
4
|
+
compliancePosture: { specFields: [] },
|
|
5
|
+
retentionPolicy: { specFields: [] },
|
|
6
|
+
};
|
|
7
|
+
function codedError(code, message) {
|
|
8
|
+
const e = new Error(message);
|
|
9
|
+
e.code = code;
|
|
10
|
+
return e;
|
|
11
|
+
}
|
|
12
|
+
export function resolveLockedKeys(config) {
|
|
13
|
+
if (config === undefined)
|
|
14
|
+
return new Set();
|
|
15
|
+
const keys = new Set();
|
|
16
|
+
for (const key of config.keys) {
|
|
17
|
+
if (!Object.prototype.hasOwnProperty.call(LOCKED_KEY_REGISTRY, key)) {
|
|
18
|
+
throw codedError("config.locked_registry", `lockedConfig.keys contains "${String(key)}", which is not a member of the locked-key registry ` +
|
|
19
|
+
`(${Object.keys(LOCKED_KEY_REGISTRY).join(", ")}). A lock that does not parse must not half-arm — ` +
|
|
20
|
+
`fix the deployment's lock declaration.`);
|
|
21
|
+
}
|
|
22
|
+
keys.add(key);
|
|
23
|
+
}
|
|
24
|
+
return keys;
|
|
25
|
+
}
|
|
26
|
+
export function preflightLockedConfig(spec, deps) {
|
|
27
|
+
const lockedKeys = resolveLockedKeys(deps.lockedConfig);
|
|
28
|
+
for (const key of lockedKeys) {
|
|
29
|
+
for (const field of LOCKED_KEY_REGISTRY[key].specFields) {
|
|
30
|
+
if (spec[field] !== undefined) {
|
|
31
|
+
throw codedError("config.locked_key", `TaskSpec.${field} is administratively locked by this deployment (locked key "${key}") — ` +
|
|
32
|
+
`the task-supplied value is refused wholesale (locks are config-time; there is no request-time ` +
|
|
33
|
+
`unlock). Remove the field from the task, or change the deployment's lock configuration.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
lockedKeys,
|
|
39
|
+
mcp: lockedKeys.has("mcp") ? undefined : spec.mcp,
|
|
40
|
+
toolPolicy: lockedKeys.has("toolPolicy") ? deps.toolPolicy : (spec.toolPolicy ?? deps.toolPolicy),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { NormalizedMemorySpec } from "./memory.js";
|
|
2
|
+
export type MemoryScopeOrigin = "deployment" | "request";
|
|
3
|
+
export interface MemoryScopeRequest {
|
|
4
|
+
scope: string;
|
|
5
|
+
origin: MemoryScopeOrigin;
|
|
6
|
+
}
|
|
7
|
+
export type MemoryAdmissionVerdict = {
|
|
8
|
+
ok: true;
|
|
9
|
+
scopes: readonly string[];
|
|
10
|
+
writeScope: string | null;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
reason: string;
|
|
14
|
+
};
|
|
15
|
+
export type MemoryScopeAdmission = (input: {
|
|
16
|
+
principal: string | undefined;
|
|
17
|
+
requested: ReadonlyArray<MemoryScopeRequest>;
|
|
18
|
+
requestedWriteScope: MemoryScopeRequest | null;
|
|
19
|
+
}) => MemoryAdmissionVerdict | Promise<MemoryAdmissionVerdict>;
|
|
20
|
+
export interface MemoryAdmissionInput {
|
|
21
|
+
memorySpec: NormalizedMemorySpec;
|
|
22
|
+
principal: string | undefined;
|
|
23
|
+
admission: MemoryScopeAdmission | undefined;
|
|
24
|
+
deploymentScopes: ReadonlySet<string>;
|
|
25
|
+
orgMemoryDenied: boolean;
|
|
26
|
+
complianceDegraded: boolean;
|
|
27
|
+
parentAdmittedOrgScopes: readonly string[] | undefined;
|
|
28
|
+
priorOwnVerdict: OwnOrgAdmissionVerdict | undefined;
|
|
29
|
+
governedProvenance: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface MemoryAdmissionOutcome {
|
|
32
|
+
scopes: string[];
|
|
33
|
+
writeScope: string | null;
|
|
34
|
+
admittedOrgScopes: string[];
|
|
35
|
+
droppedDeploymentScopes: string[];
|
|
36
|
+
writeScopeNarrowed: boolean;
|
|
37
|
+
ownVerdict: OwnOrgAdmissionVerdict | undefined;
|
|
38
|
+
}
|
|
39
|
+
export declare function foldAdmissionFreeze(input: {
|
|
40
|
+
delegated: boolean;
|
|
41
|
+
inherited: readonly string[] | undefined;
|
|
42
|
+
}): readonly string[] | undefined;
|
|
43
|
+
export interface OwnOrgAdmissionVerdict {
|
|
44
|
+
scopes: readonly string[];
|
|
45
|
+
writeScope: string | null;
|
|
46
|
+
}
|
|
47
|
+
export declare function admitMemoryScopes(input: MemoryAdmissionInput): Promise<MemoryAdmissionOutcome>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { parseScopeKey } from "./memory-engine/scope-contract.js";
|
|
2
|
+
function codedError(code, message) {
|
|
3
|
+
const e = new Error(message);
|
|
4
|
+
e.code = code;
|
|
5
|
+
return e;
|
|
6
|
+
}
|
|
7
|
+
export function foldAdmissionFreeze(input) {
|
|
8
|
+
return input.delegated ? (input.inherited ?? []) : input.inherited;
|
|
9
|
+
}
|
|
10
|
+
const ORG_SHAPE = /^org:/;
|
|
11
|
+
function orgScopesOf(spec) {
|
|
12
|
+
if (spec.scopeContract !== "v2")
|
|
13
|
+
return { readOrg: [], writeOrg: null };
|
|
14
|
+
const readOrg = spec.scopes.filter((s) => parseScopeKey(s).kind === "org");
|
|
15
|
+
const writeOrg = spec.writeScope !== null && parseScopeKey(spec.writeScope).kind === "org" ? spec.writeScope : null;
|
|
16
|
+
return { readOrg, writeOrg };
|
|
17
|
+
}
|
|
18
|
+
export async function admitMemoryScopes(input) {
|
|
19
|
+
const { memorySpec, principal, admission, deploymentScopes, parentAdmittedOrgScopes } = input;
|
|
20
|
+
const governed = admission !== undefined ||
|
|
21
|
+
deploymentScopes.size > 0 ||
|
|
22
|
+
input.priorOwnVerdict !== undefined ||
|
|
23
|
+
input.governedProvenance ||
|
|
24
|
+
input.orgMemoryDenied ||
|
|
25
|
+
(parentAdmittedOrgScopes !== undefined && parentAdmittedOrgScopes.length > 0);
|
|
26
|
+
if (governed && memorySpec.scopeContract !== "v2") {
|
|
27
|
+
const shaped = [...memorySpec.scopes, ...(memorySpec.writeScope !== null ? [memorySpec.writeScope] : [])].find((s) => ORG_SHAPE.test(s));
|
|
28
|
+
if (shaped !== undefined) {
|
|
29
|
+
throw codedError("memory.admission_denied", `memory scope "${shaped}" is org-shaped but the task did not opt into the v2 scope contract ` +
|
|
30
|
+
`(memory.scopeContract: "v2") — on a deployment with org-memory admission configured, org scopes ` +
|
|
31
|
+
`outside the contract are refused (fail-closed), not mounted as opaque keys.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const { readOrg, writeOrg } = orgScopesOf(memorySpec);
|
|
35
|
+
const allOrg = [...readOrg, ...(writeOrg !== null && !readOrg.includes(writeOrg) ? [writeOrg] : [])];
|
|
36
|
+
if (allOrg.length === 0) {
|
|
37
|
+
return {
|
|
38
|
+
scopes: [...memorySpec.scopes],
|
|
39
|
+
writeScope: memorySpec.writeScope,
|
|
40
|
+
admittedOrgScopes: [],
|
|
41
|
+
droppedDeploymentScopes: [],
|
|
42
|
+
writeScopeNarrowed: false,
|
|
43
|
+
ownVerdict: undefined,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const priorOwn = input.priorOwnVerdict;
|
|
47
|
+
const applySessionFreeze = (proj) => {
|
|
48
|
+
let { admitted, scopes, writeScope, dropped, writeScopeNarrowed } = proj;
|
|
49
|
+
if (priorOwn !== undefined) {
|
|
50
|
+
const outsideRequest = admitted.filter((s) => !deploymentScopes.has(s) && !priorOwn.scopes.includes(s));
|
|
51
|
+
if (outsideRequest.length > 0) {
|
|
52
|
+
throw codedError("memory.admission_denied", `org memory scope(s) ${outsideRequest.map((s) => `"${s}"`).join(", ")} are outside this session's frozen ` +
|
|
53
|
+
`admission verdict (recorded at suspend) — a session's org grant can only narrow across suspend/resume.`);
|
|
54
|
+
}
|
|
55
|
+
const frozenOut = admitted.filter((s) => !priorOwn.scopes.includes(s));
|
|
56
|
+
if (frozenOut.length > 0) {
|
|
57
|
+
dropped = [...dropped, ...frozenOut];
|
|
58
|
+
admitted = admitted.filter((s) => priorOwn.scopes.includes(s));
|
|
59
|
+
scopes = scopes.filter((s) => !frozenOut.includes(s));
|
|
60
|
+
}
|
|
61
|
+
if (writeScope !== null && writeOrg !== null && writeScope === writeOrg && priorOwn.writeScope !== writeScope) {
|
|
62
|
+
writeScope = null;
|
|
63
|
+
writeScopeNarrowed = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
scopes,
|
|
68
|
+
writeScope,
|
|
69
|
+
admittedOrgScopes: admitted,
|
|
70
|
+
droppedDeploymentScopes: dropped,
|
|
71
|
+
writeScopeNarrowed,
|
|
72
|
+
ownVerdict: { scopes: admitted, writeScope: writeScope !== null && writeOrg !== null && writeScope === writeOrg ? writeScope : null },
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const stampedOrigins = memorySpec.scopeOrigins;
|
|
76
|
+
const originOf = (scope) => stampedOrigins !== undefined
|
|
77
|
+
? (stampedOrigins[scope] === "deployment" ? "deployment" : "request")
|
|
78
|
+
: deploymentScopes.has(scope)
|
|
79
|
+
? "deployment"
|
|
80
|
+
: "request";
|
|
81
|
+
const requests = allOrg.map((scope) => ({ scope, origin: originOf(scope) }));
|
|
82
|
+
const requestOrigin = requests.filter((r) => r.origin === "request");
|
|
83
|
+
if (input.orgMemoryDenied && requestOrigin.length > 0) {
|
|
84
|
+
throw codedError(input.complianceDegraded ? "config.compliance_required" : "config.compliance_denied", `org memory scope(s) ${requestOrigin.map((r) => `"${r.scope}"`).join(", ")} are denied for this principal by the ` +
|
|
85
|
+
`compliance posture (capability "org_memory_mount"${input.complianceDegraded ? "; posture resolver failing, fail-closed" : ""}).`);
|
|
86
|
+
}
|
|
87
|
+
if (parentAdmittedOrgScopes !== undefined) {
|
|
88
|
+
const exceeding = requestOrigin.filter((r) => !parentAdmittedOrgScopes.includes(r.scope));
|
|
89
|
+
if (exceeding.length > 0) {
|
|
90
|
+
throw codedError("memory.admission_denied", `delegated child requests org memory scope(s) ${exceeding.map((r) => `"${r.scope}"`).join(", ")} outside the ` +
|
|
91
|
+
`parent's frozen admission verdict — a child plane can only narrow the parent's org grant, never exceed it.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (admission === undefined) {
|
|
95
|
+
if (requestOrigin.length > 0) {
|
|
96
|
+
throw codedError("memory.admission_required", `org memory scope(s) ${requestOrigin.map((r) => `"${r.scope}"`).join(", ")} come from the request side but this ` +
|
|
97
|
+
`deployment has no memory-scope admission resolver (RunnerDeps.memoryScopeAdmission) — refused fail-closed. ` +
|
|
98
|
+
`Deployment-pinned org scopes belong in RunnerDeps.deploymentMemoryScopes.`);
|
|
99
|
+
}
|
|
100
|
+
return applySessionFreeze({
|
|
101
|
+
admitted: allOrg,
|
|
102
|
+
scopes: [...memorySpec.scopes],
|
|
103
|
+
writeScope: memorySpec.writeScope,
|
|
104
|
+
dropped: [],
|
|
105
|
+
writeScopeNarrowed: false,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (requestOrigin.length > 0 && principal === undefined) {
|
|
109
|
+
throw codedError("memory.admission_denied", `org memory scope(s) ${requestOrigin.map((r) => `"${r.scope}"`).join(", ")} require an authenticated ` +
|
|
110
|
+
`TaskSpec.principal for admission — a principal-less request cannot be admitted to a tenant plane.`);
|
|
111
|
+
}
|
|
112
|
+
let verdict;
|
|
113
|
+
try {
|
|
114
|
+
verdict = await admission({
|
|
115
|
+
principal,
|
|
116
|
+
requested: requests,
|
|
117
|
+
requestedWriteScope: writeOrg !== null ? { scope: writeOrg, origin: originOf(writeOrg) } : null,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
const hinted = err.retryAfterMs;
|
|
122
|
+
const e = codedError("memory.admission_required", `memory-scope admission resolver failed (${err instanceof Error ? err.message : String(err)}) — refused fail-closed.`);
|
|
123
|
+
if (typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0)
|
|
124
|
+
e.retryAfterMs = hinted;
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
127
|
+
if (!verdict.ok) {
|
|
128
|
+
throw codedError("memory.admission_denied", `org memory admission denied: ${verdict.reason}`);
|
|
129
|
+
}
|
|
130
|
+
const unrequested = verdict.scopes.filter((s) => !allOrg.includes(s));
|
|
131
|
+
if (unrequested.length > 0 || (verdict.writeScope !== null && verdict.writeScope !== writeOrg)) {
|
|
132
|
+
throw codedError("memory.admission_denied", `memory-scope admission verdict granted unrequested scope(s) ` +
|
|
133
|
+
`${[...unrequested, ...(verdict.writeScope !== null && verdict.writeScope !== writeOrg ? [String(verdict.writeScope)] : [])].map((s) => `"${s}"`).join(", ")} — ` +
|
|
134
|
+
`admission can only narrow a request, never extend it; treating the verdict as a resolver fault.`);
|
|
135
|
+
}
|
|
136
|
+
const refusedRequest = requestOrigin.filter((r) => !verdict.scopes.includes(r.scope));
|
|
137
|
+
if (refusedRequest.length > 0) {
|
|
138
|
+
throw codedError("memory.admission_denied", `org memory scope(s) ${refusedRequest.map((r) => `"${r.scope}"`).join(", ")} were not admitted — the whole ` +
|
|
139
|
+
`prepare is refused (two-state verdict; partial admission is never applied silently).`);
|
|
140
|
+
}
|
|
141
|
+
const dropped = requests.filter((r) => r.origin === "deployment" && !verdict.scopes.includes(r.scope)).map((r) => r.scope);
|
|
142
|
+
const admitted = allOrg.filter((s) => !dropped.includes(s));
|
|
143
|
+
const scopes = memorySpec.scopes.filter((s) => !dropped.includes(s));
|
|
144
|
+
let writeScope = memorySpec.writeScope;
|
|
145
|
+
let writeScopeNarrowed = false;
|
|
146
|
+
if (writeOrg !== null) {
|
|
147
|
+
if (verdict.writeScope === writeOrg && !dropped.includes(writeOrg)) {
|
|
148
|
+
writeScope = writeOrg;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
writeScope = null;
|
|
152
|
+
writeScopeNarrowed = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return applySessionFreeze({ admitted, scopes, writeScope, dropped, writeScopeNarrowed });
|
|
156
|
+
}
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -94,12 +94,14 @@ export interface NormalizedMemorySpec {
|
|
|
94
94
|
writeScope: string | null;
|
|
95
95
|
enabled: boolean;
|
|
96
96
|
scopeContract?: "v2";
|
|
97
|
+
scopeOrigins?: Readonly<Record<string, "deployment" | "request">>;
|
|
97
98
|
}
|
|
98
99
|
export type MemorySpecInput = {
|
|
99
100
|
scopes?: ReadonlyArray<string>;
|
|
100
101
|
writeScope?: string | null;
|
|
101
102
|
enabled?: boolean;
|
|
102
103
|
scopeContract?: "v2";
|
|
104
|
+
scopeOrigins?: Readonly<Record<string, "deployment" | "request">>;
|
|
103
105
|
};
|
|
104
106
|
export declare function normalizeMemorySpec(input: MemorySpecInput | undefined): NormalizedMemorySpec | undefined;
|
|
105
107
|
export declare const MAX_MEMORY_BYTES: number;
|
package/dist/core/memory.js
CHANGED
|
@@ -384,12 +384,13 @@ export function normalizeMemorySpec(input) {
|
|
|
384
384
|
else {
|
|
385
385
|
writeScope = scopes[scopes.length - 1];
|
|
386
386
|
}
|
|
387
|
+
const origins = input.scopeOrigins !== undefined ? { scopeOrigins: input.scopeOrigins } : {};
|
|
387
388
|
if (input.scopeContract === "v2") {
|
|
388
389
|
for (const key of writeScope !== null ? [...scopes, writeScope] : scopes)
|
|
389
390
|
parseScopeKey(key);
|
|
390
|
-
return { scopes, writeScope, enabled: input.enabled !== false, scopeContract: "v2" };
|
|
391
|
+
return { scopes, writeScope, enabled: input.enabled !== false, scopeContract: "v2", ...origins };
|
|
391
392
|
}
|
|
392
|
-
return { scopes, writeScope, enabled: input.enabled !== false };
|
|
393
|
+
return { scopes, writeScope, enabled: input.enabled !== false, ...origins };
|
|
393
394
|
}
|
|
394
395
|
export const MAX_MEMORY_BYTES = 100 * 1024;
|
|
395
396
|
export const MEMORY_WRAPPER_TAGS = ["user_memory"];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type RetentionDeclaration = "managed" | "none";
|
|
2
|
+
export interface RetentionDeclaring {
|
|
3
|
+
readonly retention?: RetentionDeclaration;
|
|
4
|
+
}
|
|
5
|
+
export interface RetentionPolicy {
|
|
6
|
+
maxAgeDays: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ManagedRetentionCapability {
|
|
9
|
+
listRetentionDomains(): Promise<readonly string[]>;
|
|
10
|
+
expireCheckpoints(input: {
|
|
11
|
+
domain: string;
|
|
12
|
+
cutoffMs: number;
|
|
13
|
+
}): Promise<RetentionReceipt>;
|
|
14
|
+
deleteExpiredSessions(input: {
|
|
15
|
+
domain: string;
|
|
16
|
+
cutoffMs: number;
|
|
17
|
+
}): Promise<RetentionReceipt>;
|
|
18
|
+
deleteOrphanToolResults(input: {
|
|
19
|
+
domain: string;
|
|
20
|
+
cutoffMs: number;
|
|
21
|
+
}): Promise<RetentionReceipt>;
|
|
22
|
+
}
|
|
23
|
+
export interface RetentionReceipt {
|
|
24
|
+
domain: string;
|
|
25
|
+
deleted: number;
|
|
26
|
+
skipped: number;
|
|
27
|
+
tombstones: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function assertRetentionCapability(input: {
|
|
30
|
+
policy: RetentionPolicy | undefined;
|
|
31
|
+
locked: boolean;
|
|
32
|
+
stores: ReadonlyArray<{
|
|
33
|
+
name: string;
|
|
34
|
+
store: object | undefined;
|
|
35
|
+
}>;
|
|
36
|
+
}): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function codedError(code, message) {
|
|
2
|
+
const e = new Error(message);
|
|
3
|
+
e.code = code;
|
|
4
|
+
return e;
|
|
5
|
+
}
|
|
6
|
+
export function assertRetentionCapability(input) {
|
|
7
|
+
const { policy, locked, stores } = input;
|
|
8
|
+
if (policy !== undefined && (!Number.isFinite(policy.maxAgeDays) || policy.maxAgeDays < 0)) {
|
|
9
|
+
throw codedError("config.retention_policy", `retentionPolicy.maxAgeDays must be a finite non-negative number of days (got ${String(policy.maxAgeDays)}).`);
|
|
10
|
+
}
|
|
11
|
+
const declaredRetention = (name, store) => {
|
|
12
|
+
const r = store.retention;
|
|
13
|
+
if (r === undefined || r === "none")
|
|
14
|
+
return "none";
|
|
15
|
+
if (r === "managed")
|
|
16
|
+
return "managed";
|
|
17
|
+
throw codedError("config.retention_capability", `store "${name}" declares an unparseable retention value ${JSON.stringify(r)} — expected "managed" or "none".`);
|
|
18
|
+
};
|
|
19
|
+
const declarations = stores
|
|
20
|
+
.filter((s) => s.store !== undefined)
|
|
21
|
+
.map(({ name, store }) => ({ name, retention: declaredRetention(name, store) }));
|
|
22
|
+
if (!locked || policy === undefined)
|
|
23
|
+
return;
|
|
24
|
+
const incapable = declarations.filter(({ retention }) => retention !== "managed");
|
|
25
|
+
if (incapable.length > 0) {
|
|
26
|
+
throw codedError("config.retention_capability", `retention policy is LOCKED but ${incapable.map((s) => s.name).join(", ")} ` +
|
|
27
|
+
`declare${incapable.length === 1 ? "s" : ""} retention "none" (or no declaration — read fail-closed as "none"): ` +
|
|
28
|
+
`a locked retention policy over stores that cannot delete would be "policy locked, data immortal". ` +
|
|
29
|
+
`Wire managed-retention stores, or unlock/remove the retention policy.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -154,7 +154,7 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
154
154
|
if (errorCode !== undefined && SALVAGE_ELIGIBLE_TERMINALS.has(errorCode)) {
|
|
155
155
|
salvagedOutput = text.trim() || undefined;
|
|
156
156
|
}
|
|
157
|
-
const retryAfterMs = errorCode === "usage.window_exhausted" ? flags.retryAfterMs : undefined;
|
|
157
|
+
const retryAfterMs = errorCode === "usage.window_exhausted" || errorCode === "memory.admission_required" ? flags.retryAfterMs : undefined;
|
|
158
158
|
const { compactionMicroUsd: _internalCompaction, ...publicStats } = stats;
|
|
159
159
|
void _internalCompaction;
|
|
160
160
|
if (flags.unpricedSpend)
|
|
@@ -8,6 +8,13 @@ export interface PrepareMemoryInput {
|
|
|
8
8
|
memoryWriteGateRef: {
|
|
9
9
|
current?: BeforeWriteHook;
|
|
10
10
|
};
|
|
11
|
+
admissionCtx: {
|
|
12
|
+
orgMemoryDenied: boolean;
|
|
13
|
+
complianceDegraded: boolean;
|
|
14
|
+
parentAdmittedOrgScopes: readonly string[] | undefined;
|
|
15
|
+
priorOwnVerdict: import("../memory-admission.js").OwnOrgAdmissionVerdict | undefined;
|
|
16
|
+
governedProvenance: boolean;
|
|
17
|
+
};
|
|
11
18
|
}
|
|
12
19
|
export interface PrepareMemoryResult {
|
|
13
20
|
memoryEngineSession: Prepared["memoryEngineSession"];
|
|
@@ -16,5 +23,7 @@ export interface PrepareMemoryResult {
|
|
|
16
23
|
path: string;
|
|
17
24
|
content: string;
|
|
18
25
|
}>;
|
|
26
|
+
admittedOrgScopes: readonly string[];
|
|
27
|
+
ownOrgVerdict: import("../memory-admission.js").OwnOrgAdmissionVerdict | undefined;
|
|
19
28
|
}
|
|
20
29
|
export declare function prepareMemory(input: PrepareMemoryInput): Promise<PrepareMemoryResult>;
|