@sema-agent/core 5.21.1 → 5.23.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 +103 -0
- package/dist/agents/send-message-tool.js +6 -3
- package/dist/agents/subagent.d.ts +6 -0
- package/dist/agents/subagent.js +48 -6
- package/dist/brain/errors.d.ts +20 -0
- package/dist/brain/errors.js +40 -0
- package/dist/brain/retry.d.ts +16 -2
- package/dist/brain/retry.js +3 -2
- package/dist/brain/status-sink.d.ts +9 -2
- package/dist/brain/stream-engine.d.ts +22 -0
- package/dist/brain/stream-engine.js +41 -10
- package/dist/core/ask-class.d.ts +48 -0
- package/dist/core/ask-class.js +33 -0
- package/dist/core/checkpoint-store.d.ts +103 -10
- package/dist/core/checkpoint-store.js +3 -1
- package/dist/core/governance-codes.d.ts +38 -0
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +101 -1
- package/dist/core/hooks.js +116 -14
- package/dist/core/locked-config.d.ts +7 -1
- package/dist/core/locked-config.js +2 -1
- package/dist/core/memory-engine/delegation-provenance.d.ts +62 -0
- package/dist/core/memory-engine/delegation-provenance.js +26 -0
- package/dist/core/memory-engine/engine.d.ts +94 -1
- package/dist/core/memory-engine/engine.js +329 -12
- package/dist/core/memory-engine/header-hints.d.ts +30 -0
- package/dist/core/memory-engine/header-hints.js +41 -0
- package/dist/core/memory-engine/index.d.ts +3 -2
- package/dist/core/memory-engine/index.js +3 -2
- package/dist/core/memory-engine/layout.d.ts +232 -0
- package/dist/core/memory-engine/layout.js +469 -1
- package/dist/core/memory-engine/tools.d.ts +30 -0
- package/dist/core/memory-engine/tools.js +108 -17
- package/dist/core/permission-rule-consent.d.ts +25 -9
- package/dist/core/permission-rule-consent.js +93 -21
- package/dist/core/permission-rule-model.d.ts +9 -1
- package/dist/core/permission-rule-model.js +2 -2
- package/dist/core/permission-rule-org.d.ts +195 -0
- package/dist/core/permission-rule-org.js +234 -0
- package/dist/core/permission-rule-store.d.ts +249 -6
- package/dist/core/permission-rule-store.js +314 -3
- package/dist/core/permission-rule-sync.d.ts +139 -0
- package/dist/core/permission-rule-sync.js +343 -0
- package/dist/core/runner/prepare-memory.js +35 -8
- package/dist/core/runner/prepare-task.d.ts +64 -3
- package/dist/core/runner/prepare-task.js +357 -32
- package/dist/core/runner/runtask.js +166 -6
- package/dist/core/shared-memory/contract.js +19 -4
- package/dist/core/shared-memory/normalize.d.ts +3 -1
- package/dist/core/shared-memory/tools.js +73 -17
- package/dist/core/shared-memory/types.d.ts +27 -1
- package/dist/core/store-contracts/permission-rule-sync-contract.d.ts +33 -0
- package/dist/core/store-contracts/permission-rule-sync-contract.js +186 -0
- package/dist/core/task-notification.d.ts +5 -2
- package/dist/core/task-registry-agent.d.ts +1 -1
- package/dist/core/task-registry-agent.js +6 -2
- package/dist/core/task-registry-shared.d.ts +9 -2
- package/dist/core/task-registry.d.ts +9 -3
- package/dist/core/task-registry.js +2 -0
- package/dist/core/tool-policy.d.ts +155 -4
- package/dist/core/tool-policy.js +148 -10
- package/dist/core/tool-result-store.d.ts +9 -1
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/trace.d.ts +79 -1
- package/dist/core/types.d.ts +94 -3
- package/dist/core/wiring-manifest.d.ts +16 -1
- package/dist/core/wiring-manifest.js +7 -1
- package/dist/index.d.ts +15 -8
- package/dist/index.js +12 -5
- package/dist/orchestration/goal.d.ts +10 -0
- package/dist/orchestration/goal.js +6 -5
- package/dist/stores/file/adoption/adopt.d.ts +146 -0
- package/dist/stores/file/adoption/adopt.js +616 -0
- package/dist/stores/file/adoption/marker.d.ts +194 -0
- package/dist/stores/file/adoption/marker.js +198 -0
- package/dist/stores/file/background-agent-store.js +2 -0
- package/dist/stores/file/checkpoint-store.d.ts +4 -0
- package/dist/stores/file/checkpoint-store.js +3 -0
- package/dist/stores/file/file-snapshot-store.js +2 -0
- package/dist/stores/file/index.d.ts +2 -0
- package/dist/stores/file/index.js +4 -0
- package/dist/stores/file/mailbox-store.js +2 -0
- package/dist/stores/file/memory-store.js +2 -0
- package/dist/stores/file/permission-rule-adopt.d.ts +62 -0
- package/dist/stores/file/permission-rule-adopt.js +95 -0
- package/dist/stores/file/permission-rule-store.d.ts +80 -2
- package/dist/stores/file/permission-rule-store.js +189 -46
- package/dist/stores/file/session-policy-store.js +2 -0
- package/dist/stores/file/session-store.js +2 -0
- package/dist/stores/file/task-list-store.js +2 -0
- package/dist/stores/file/tool-result-store.js +2 -0
- package/dist/stores/file/usage-window-store.js +2 -0
- package/dist/stores/file/workflow-journal-store.js +2 -0
- package/dist/stores/file/workflow-run-store.js +2 -0
- package/dist/tools/fs/bash-readonly-classifier.js +59 -10
- package/package.json +3 -2
package/dist/core/hooks.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
2
|
+
import { brandPolicyAskClass } from "./ask-class.js";
|
|
2
3
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
3
4
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
4
5
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
6
|
+
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
5
7
|
export function cloneObserverInput(input) {
|
|
6
8
|
try {
|
|
7
9
|
return structuredClone(input);
|
|
@@ -99,7 +101,7 @@ function screenPreToolUseResult(r) {
|
|
|
99
101
|
return refuseOutOfContractDecision(r);
|
|
100
102
|
}
|
|
101
103
|
export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash) {
|
|
102
|
-
return {
|
|
104
|
+
return brandPolicyAskClass({
|
|
103
105
|
check: async (req) => {
|
|
104
106
|
let r;
|
|
105
107
|
try {
|
|
@@ -128,7 +130,7 @@ export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash) {
|
|
|
128
130
|
}
|
|
129
131
|
return r.updatedInput !== undefined ? { action: "allow", updatedInput: r.updatedInput } : { action: "allow" };
|
|
130
132
|
},
|
|
131
|
-
};
|
|
133
|
+
}, "hook");
|
|
132
134
|
}
|
|
133
135
|
function traceHookCrash(input, err, notifier) {
|
|
134
136
|
notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
|
|
@@ -245,12 +247,73 @@ export async function runToolGate(input) {
|
|
|
245
247
|
denySource = input.shellGated === true ? "shellGate" : "safety";
|
|
246
248
|
}
|
|
247
249
|
}
|
|
250
|
+
let orgRealApprovalRequired = false;
|
|
251
|
+
let orgTightenCount = 0;
|
|
252
|
+
const applyOrgLayer = async (current, args) => {
|
|
253
|
+
if (input.orgRules === undefined)
|
|
254
|
+
return current;
|
|
255
|
+
if (toolName === ASK_USER_QUESTION_TOOL_NAME && input.orgRules.contentAskToolMounted === true)
|
|
256
|
+
return current;
|
|
257
|
+
let decided = current;
|
|
258
|
+
const answer = await settleOrgVerdictWithin(input.orgRules
|
|
259
|
+
.adjudicate({ toolName, args, toolCallId })
|
|
260
|
+
.catch((err) => ({
|
|
261
|
+
status: "unavailable",
|
|
262
|
+
disclosures: [`the org adjudication face threw: ${err instanceof Error ? err.message : String(err)}`],
|
|
263
|
+
})), {
|
|
264
|
+
status: "unavailable",
|
|
265
|
+
disclosures: [`the org adjudication face did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms (or the task ended first)`],
|
|
266
|
+
}, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
|
|
267
|
+
if (answer.status === "unavailable") {
|
|
268
|
+
orgRealApprovalRequired = true;
|
|
269
|
+
await notifier.notifyAsync(() => input.orgRules?.onUnavailable?.({ toolName, toolCallId, message: answer.disclosures.join("; ") }), "toolGate.orgSnapshotUnavailable");
|
|
270
|
+
if (decided.action === "allow") {
|
|
271
|
+
decided = {
|
|
272
|
+
action: "ask",
|
|
273
|
+
message: `tool "${toolName}" needs approval: this deployment is org-governed and cannot currently adjudicate ` +
|
|
274
|
+
`against an organization policy snapshot — every allow tightens to a real approval until it can`,
|
|
275
|
+
decisionReason: ORG_UNAVAILABLE_DECISION_REASON,
|
|
276
|
+
requiresRealApproval: true,
|
|
277
|
+
};
|
|
278
|
+
orgTightenCount += 1;
|
|
279
|
+
denySource = "org";
|
|
280
|
+
}
|
|
281
|
+
else if (decided.action === "ask" && decided.requiresRealApproval !== true) {
|
|
282
|
+
decided = { ...decided, requiresRealApproval: true };
|
|
283
|
+
orgTightenCount += 1;
|
|
284
|
+
}
|
|
285
|
+
return decided;
|
|
286
|
+
}
|
|
287
|
+
if (answer.verdict === undefined)
|
|
288
|
+
return decided;
|
|
289
|
+
const { behavior, rule } = answer.verdict;
|
|
290
|
+
if (behavior === "deny") {
|
|
291
|
+
orgTightenCount += 1;
|
|
292
|
+
denySource = "org";
|
|
293
|
+
return { action: "deny", message: `an organization policy rule (${rule}) denies this call`, decisionReason: ORG_RULE_DECISION_REASON };
|
|
294
|
+
}
|
|
295
|
+
if (decided.action === "deny")
|
|
296
|
+
return decided;
|
|
297
|
+
orgTightenCount += 1;
|
|
298
|
+
denySource = "org";
|
|
299
|
+
orgRealApprovalRequired = true;
|
|
300
|
+
return decided.action === "ask"
|
|
301
|
+
? { ...decided, requiresRealApproval: true }
|
|
302
|
+
: {
|
|
303
|
+
action: "ask",
|
|
304
|
+
message: `an organization policy rule (${rule}) requires approval for this call`,
|
|
305
|
+
decisionReason: ORG_RULE_DECISION_REASON,
|
|
306
|
+
requiresRealApproval: true,
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
decision = await applyOrgLayer(decision, policyRewrite !== undefined ? policyRewrite : currentInput);
|
|
248
310
|
if (decision.action === "ask") {
|
|
249
311
|
if (policyRewrite !== undefined) {
|
|
250
312
|
currentInput = policyRewrite;
|
|
251
313
|
req.args = policyRewrite;
|
|
252
314
|
}
|
|
253
315
|
if (input.persistedRules &&
|
|
316
|
+
!orgRealApprovalRequired &&
|
|
254
317
|
decision.action === "ask" &&
|
|
255
318
|
decision.requiresRealApproval !== true &&
|
|
256
319
|
decision.decisionReason !== "hook" &&
|
|
@@ -268,6 +331,7 @@ export async function runToolGate(input) {
|
|
|
268
331
|
}
|
|
269
332
|
}
|
|
270
333
|
if (input.autoMode &&
|
|
334
|
+
!orgRealApprovalRequired &&
|
|
271
335
|
decision.action === "ask" &&
|
|
272
336
|
req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
273
337
|
input.isMarkedUnresolvable?.(input.event.toolCallId) !== true) {
|
|
@@ -293,6 +357,29 @@ export async function runToolGate(input) {
|
|
|
293
357
|
denySource = "classifier";
|
|
294
358
|
}
|
|
295
359
|
}
|
|
360
|
+
if (input.sandboxAdmission !== undefined &&
|
|
361
|
+
decision.action === "ask" &&
|
|
362
|
+
req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
363
|
+
input.isMarkedUnresolvable?.(input.event.toolCallId) !== true &&
|
|
364
|
+
decision.requiresRealApproval !== true &&
|
|
365
|
+
decision.decisionReason !== "hook" &&
|
|
366
|
+
hookAsk === undefined &&
|
|
367
|
+
input.egress !== true &&
|
|
368
|
+
input.sandboxAdmission.boundaryCapable !== true) {
|
|
369
|
+
const recorded = input.sandboxAdmission.askClassesOf(toolCallId);
|
|
370
|
+
const admissible = recorded !== undefined && recorded.length > 0 && recorded.every((r) => r.cls === "sandbox_local");
|
|
371
|
+
if (admissible) {
|
|
372
|
+
const askClasses = recorded;
|
|
373
|
+
input.sandboxAdmission.onAdmitted({ toolCallId, toolName, askClasses, boundary: "sandbox_internal" });
|
|
374
|
+
preToolContext.push(`permission.sandbox_admitted: this call's pending approval was auto-admitted because the deployment declares an isolated sandbox execution env and the request stays inside it. Recorded for audit.`);
|
|
375
|
+
decision = {
|
|
376
|
+
action: "allow",
|
|
377
|
+
message: "admitted inside the deployment-declared sandbox (permission.sandbox_admitted)",
|
|
378
|
+
decisionReason: "sandbox",
|
|
379
|
+
...(policyRewrite !== undefined ? { updatedInput: policyRewrite } : {}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
296
383
|
const egressTool = input.egress === true;
|
|
297
384
|
const irreversibleTool = input.irreversibility === "always" || input.irreversibility === "maybe";
|
|
298
385
|
const safety = egressTool || irreversibleTool ? { egress: egressTool, irreversible: irreversibleTool } : undefined;
|
|
@@ -394,23 +481,34 @@ export async function runToolGate(input) {
|
|
|
394
481
|
editArgs = hr.updatedInput;
|
|
395
482
|
}
|
|
396
483
|
}
|
|
397
|
-
if (!adjudicate)
|
|
484
|
+
if (!adjudicate && input.orgRules === undefined)
|
|
398
485
|
break;
|
|
399
|
-
let recheck;
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
486
|
+
let recheck = { action: "allow" };
|
|
487
|
+
if (adjudicate) {
|
|
488
|
+
try {
|
|
489
|
+
recheck = await adjudicate({ toolName, args: editArgs, toolCallId });
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
recheck = {
|
|
493
|
+
action: "deny",
|
|
494
|
+
message: `policy re-check of the approved edit for "${toolName}" errored: ${err instanceof Error ? err.message : String(err)}`,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
408
497
|
}
|
|
409
498
|
if (recheck.action === "deny") {
|
|
410
499
|
editDenied = recheck;
|
|
411
500
|
denySource = "policy";
|
|
412
501
|
break;
|
|
413
502
|
}
|
|
503
|
+
if (recheck.updatedInput !== undefined)
|
|
504
|
+
editArgs = recheck.updatedInput;
|
|
505
|
+
const orgTightensBefore = orgTightenCount;
|
|
506
|
+
recheck = await applyOrgLayer(recheck, editArgs);
|
|
507
|
+
const orgRaisedThisRound = orgTightenCount > orgTightensBefore;
|
|
508
|
+
if (recheck.action === "deny") {
|
|
509
|
+
editDenied = recheck;
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
414
512
|
if (recheck.action === "allow") {
|
|
415
513
|
if (recheck.updatedInput !== undefined)
|
|
416
514
|
editArgs = recheck.updatedInput;
|
|
@@ -421,11 +519,15 @@ export async function runToolGate(input) {
|
|
|
421
519
|
const rr = await resolveAsk(recheck, { toolName, args: editArgs, toolCallId });
|
|
422
520
|
if (rr.action !== "allow") {
|
|
423
521
|
editDenied = rr;
|
|
424
|
-
|
|
522
|
+
if (!orgRaisedThisRound)
|
|
523
|
+
denySource = "policy";
|
|
425
524
|
break;
|
|
426
525
|
}
|
|
427
|
-
if (rr.updatedInput === undefined)
|
|
526
|
+
if (rr.updatedInput === undefined) {
|
|
527
|
+
if (rr.presentedInput !== undefined)
|
|
528
|
+
editArgs = rr.presentedInput;
|
|
428
529
|
break;
|
|
530
|
+
}
|
|
429
531
|
editArgs = rr.updatedInput;
|
|
430
532
|
}
|
|
431
533
|
decision = editDenied ?? { ...decision, updatedInput: editArgs };
|
|
@@ -18,7 +18,7 @@ export declare const LOCKED_KEY_REGISTRY: {
|
|
|
18
18
|
readonly specFields: readonly ["mcp"];
|
|
19
19
|
};
|
|
20
20
|
readonly toolPolicy: {
|
|
21
|
-
readonly specFields: readonly ["toolPolicy"];
|
|
21
|
+
readonly specFields: readonly ["toolPolicy", "basePolicyForResumeEdit"];
|
|
22
22
|
};
|
|
23
23
|
readonly compliancePosture: {
|
|
24
24
|
readonly specFields: readonly [];
|
|
@@ -46,6 +46,12 @@ export interface LockedPreflight {
|
|
|
46
46
|
* always enforced, resolved ONCE here. With `toolPolicy` locked, the deployment's own
|
|
47
47
|
* `deps.toolPolicy` is the whole answer (a spec-supplied policy has already been refused). */
|
|
48
48
|
toolPolicy: ToolPolicy | undefined;
|
|
49
|
+
/** F-012 (codex round 8): the effective resume-edit re-adjudication OVERRIDE, resolved lock-aware in
|
|
50
|
+
* the SAME slot as {@link toolPolicy}. With `toolPolicy` locked, a spec-supplied
|
|
51
|
+
* `basePolicyForResumeEdit` has already been refused wholesale, so this is the deployment's own
|
|
52
|
+
* `deps.basePolicyForResumeEdit` (or undefined — the caller-policy fallback then applies at the use
|
|
53
|
+
* site). Unlocked: `spec.basePolicyForResumeEdit ?? deps.basePolicyForResumeEdit`. */
|
|
54
|
+
basePolicyForResumeEdit: ToolPolicy | undefined;
|
|
49
55
|
}
|
|
50
56
|
/**
|
|
51
57
|
* Validate a deployment's lock declaration against the closed registry. Fail-loud on any unknown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const LOCKED_KEY_REGISTRY = {
|
|
2
2
|
mcp: { specFields: ["mcp"] },
|
|
3
|
-
toolPolicy: { specFields: ["toolPolicy"] },
|
|
3
|
+
toolPolicy: { specFields: ["toolPolicy", "basePolicyForResumeEdit"] },
|
|
4
4
|
compliancePosture: { specFields: [] },
|
|
5
5
|
retentionPolicy: { specFields: [] },
|
|
6
6
|
};
|
|
@@ -38,5 +38,6 @@ export function preflightLockedConfig(spec, deps) {
|
|
|
38
38
|
lockedKeys,
|
|
39
39
|
mcp: lockedKeys.has("mcp") ? undefined : spec.mcp,
|
|
40
40
|
toolPolicy: lockedKeys.has("toolPolicy") ? deps.toolPolicy : (spec.toolPolicy ?? deps.toolPolicy),
|
|
41
|
+
basePolicyForResumeEdit: lockedKeys.has("toolPolicy") ? deps.basePolicyForResumeEdit : (spec.basePolicyForResumeEdit ?? deps.basePolicyForResumeEdit),
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/180 half A — delegation RUNTIME provenance: the three-value attestation and its monotonic
|
|
3
|
+
* aggregate. The static tool-face judgment ({@link import("./content-origin.js").delegationCallIsExternal})
|
|
4
|
+
* stays the PERMANENT FLOOR: runtime evidence may only retire the static verdict when a COMPLETE
|
|
5
|
+
* clean proof is in hand; any evidence gap falls back to the floor. "We could not prove it clean"
|
|
6
|
+
* and "it is clean" are different answers — replacing the floor outright would fail OPEN on every
|
|
7
|
+
* evidence gap (old child vintages, crashes, un-armed sessions).
|
|
8
|
+
*
|
|
9
|
+
* The aggregate is two MONOTONIC bits plus versioning:
|
|
10
|
+
* - `sawExternal` — an actually-observed polluting-class invocation (own recorder, or a delivered
|
|
11
|
+
* child attestation of `external`). Once set, never cleared.
|
|
12
|
+
* - `incomplete` — some stretch of the session's conduct is unprovable (a delegation delivered no /
|
|
13
|
+
* an unknown attestation, a resume lost the aggregate, the recorder was not armed from turn 0).
|
|
14
|
+
* Once set, never cleared.
|
|
15
|
+
* Reduction order is FIXED (design/180 r3): `sawExternal ⇒ external` > `incomplete ⇒ unknown` >
|
|
16
|
+
* `clean` — an observed external event always outranks an evidence gap (missing evidence can make
|
|
17
|
+
* "clean" unprovable; it can never make an observed event disappear).
|
|
18
|
+
*/
|
|
19
|
+
/** The three-value delegation attestation. Only `clean` changes behavior (the parent skips the
|
|
20
|
+
* static mark); `unknown` is byte-equivalent to absence (static floor governs); `external` marks
|
|
21
|
+
* unconditionally, with the observed-event reason. */
|
|
22
|
+
export type DelegationAttestation = "clean" | "external" | "unknown";
|
|
23
|
+
/** The monotonic per-run provenance aggregate (in-process this slice; the durable-checkpoint
|
|
24
|
+
* persistence leg rides the background-face slice — absence after a resume reads `incomplete`,
|
|
25
|
+
* which is the fail-closed default this shape is built around). */
|
|
26
|
+
export interface DelegationProvenanceAggregate {
|
|
27
|
+
version: 1;
|
|
28
|
+
/** Turn index the recorder was armed from. 0 ⇔ armed at prepare (full coverage). */
|
|
29
|
+
coverageStartTurn: number;
|
|
30
|
+
sawExternal: boolean;
|
|
31
|
+
incomplete: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** The frozen content-safety snapshot a parent hands its delegated children (design/180 A-2): the
|
|
34
|
+
* classification config the child's recorder must judge under. Inherit-narrow-only — see
|
|
35
|
+
* {@link narrowContentSafety}. */
|
|
36
|
+
export interface DelegationContentSafety {
|
|
37
|
+
trustedTools: readonly string[];
|
|
38
|
+
execIsExternalContent: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare function newDelegationProvenanceAggregate(): DelegationProvenanceAggregate;
|
|
41
|
+
/**
|
|
42
|
+
* Effective content-safety for a run: the parent chain's frozen snapshot ∩ the run's own config.
|
|
43
|
+
* A child may NARROW what the chain trusts (fewer trusted tools, stricter exec), never widen it —
|
|
44
|
+
* a wider child-side allowlist must not launder a chain-external tool into `local` on the way up.
|
|
45
|
+
* At least one input must be present (the caller only computes this when armed).
|
|
46
|
+
*/
|
|
47
|
+
export declare function narrowContentSafety(parent: DelegationContentSafety | undefined, own: {
|
|
48
|
+
trustedTools: ReadonlySet<string>;
|
|
49
|
+
execIsExternalContent: boolean;
|
|
50
|
+
} | undefined): {
|
|
51
|
+
trustedTools: Set<string>;
|
|
52
|
+
execIsExternalContent: boolean;
|
|
53
|
+
};
|
|
54
|
+
/** Reduce an aggregate to the delivered attestation. `completed` = the child reached its terminal
|
|
55
|
+
* normally with its full result delivered — a salvage/partial/errored child can never prove clean
|
|
56
|
+
* (its conduct after the cut is unobserved), though an OBSERVED external event still reports. */
|
|
57
|
+
export declare function reduceDelegationAttestation(agg: DelegationProvenanceAggregate | undefined, opts: {
|
|
58
|
+
completed: boolean;
|
|
59
|
+
}): DelegationAttestation;
|
|
60
|
+
/** Read a delivered card's attestation field (the explicit-whitelist handback seat). Anything but
|
|
61
|
+
* the three spellings — including absence — reads as undefined, i.e. the static floor governs. */
|
|
62
|
+
export declare function readCardAttestation(details: unknown): DelegationAttestation | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function newDelegationProvenanceAggregate() {
|
|
2
|
+
return { version: 1, coverageStartTurn: 0, sawExternal: false, incomplete: false };
|
|
3
|
+
}
|
|
4
|
+
export function narrowContentSafety(parent, own) {
|
|
5
|
+
if (parent === undefined) {
|
|
6
|
+
return { trustedTools: new Set(own?.trustedTools ?? []), execIsExternalContent: own?.execIsExternalContent ?? false };
|
|
7
|
+
}
|
|
8
|
+
const fromParent = new Set(parent.trustedTools);
|
|
9
|
+
const trustedTools = own === undefined ? fromParent : new Set([...fromParent].filter((t) => own.trustedTools.has(t)));
|
|
10
|
+
return { trustedTools, execIsExternalContent: parent.execIsExternalContent || (own?.execIsExternalContent ?? false) };
|
|
11
|
+
}
|
|
12
|
+
export function reduceDelegationAttestation(agg, opts) {
|
|
13
|
+
if (agg === undefined)
|
|
14
|
+
return "unknown";
|
|
15
|
+
if (agg.sawExternal)
|
|
16
|
+
return "external";
|
|
17
|
+
if (agg.incomplete || !opts.completed || agg.version !== 1 || agg.coverageStartTurn !== 0)
|
|
18
|
+
return "unknown";
|
|
19
|
+
return "clean";
|
|
20
|
+
}
|
|
21
|
+
export function readCardAttestation(details) {
|
|
22
|
+
if (details === null || typeof details !== "object")
|
|
23
|
+
return undefined;
|
|
24
|
+
const v = details.attestation;
|
|
25
|
+
return v === "clean" || v === "external" || v === "unknown" ? v : undefined;
|
|
26
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MemoryPartitionIncidentSink, type RetrievedAccountRow, type SessionPollutionRecord } from "./layout.js";
|
|
1
|
+
import { type ChallengeAssignment, type ChallengeEvent, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, type MemoryPartitionIncidentSink, type RetrievedAccountRow, type SessionPollutionRecord } from "./layout.js";
|
|
2
2
|
import type { HarvestReport, MemoryAnnouncement, MemoryBackend, MemorySessionHandle, ScanFinding } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* The CC `# Memory` instruction section — VERBATIM from the live capture
|
|
@@ -31,6 +31,15 @@ export declare function buildMemoryInstruction(memoryDir: string, instructionFil
|
|
|
31
31
|
* dual-root instruction merge ("the write plane's instruction wins") never sees a second candidate.
|
|
32
32
|
*/
|
|
33
33
|
export declare const MEMORY_RECALL_DISCIPLINE = "Before answering questions about earlier work, decisions, dates, people, or the user's preferences, look them up: `memory_search` finds entries by keyword and `memory_get` reads a full entry \u2014 the injected memory index only lists what exists. When a lookup comes up empty, say that you checked memory and found nothing instead of guessing.";
|
|
34
|
+
/**
|
|
35
|
+
* design/180 B-3 — the preference-header discipline paragraph. Same carriage as
|
|
36
|
+
* {@link MEMORY_RECALL_DISCIPLINE}: a runner-appended segment OUTSIDE `inject()`, so the
|
|
37
|
+
* CC-verbatim `# Memory` instruction stays byte-identical to its sha256-pinned capture. Appended
|
|
38
|
+
* only when a WRITE channel exists (prepare-memory gates it) — the lines it teaches are frontmatter
|
|
39
|
+
* the MODEL writes; the engine has no write arm for them (writing usage evidence for the model
|
|
40
|
+
* would forge the very account the three-tier discipline forbids forging).
|
|
41
|
+
*/
|
|
42
|
+
export declare const MEMORY_PREFERENCE_DISCIPLINE = "When the user confirms a stored preference or fact still holds, refresh that entry's `last-confirmed: <YYYY-MM-DD>` frontmatter line (add it when absent). When you save a preference, add an `applies-when: <context>` frontmatter line naming when it applies. Both are plain frontmatter lines \u2014 write them yourself; nothing fills them in for you.";
|
|
34
43
|
/** CC index-injection parameters: MEMORY.md's first 200 lines / 25KB enter the prompt. */
|
|
35
44
|
export declare const MEMORY_INDEX_MAX_LINES = 200;
|
|
36
45
|
export declare const MEMORY_INDEX_MAX_BYTES: number;
|
|
@@ -188,6 +197,89 @@ export declare class MemoryEngine {
|
|
|
188
197
|
/** The session's pollution record (in-process mark first, then the durable marker) — undefined =
|
|
189
198
|
* clean. Reads are side-effect-free. */
|
|
190
199
|
sessionPollution(sessionId: string): SessionPollutionRecord | undefined;
|
|
200
|
+
/** Entry ids the session's memory tools RETURNED this process — the retrieved half of the
|
|
201
|
+
* challenged-history mechanical event (B-3). In-process on purpose: "same session" is one runner
|
|
202
|
+
* process by architecture, and the durable retrieved account is not session-keyed. */
|
|
203
|
+
private readonly retrievedThisSession;
|
|
204
|
+
/**
|
|
205
|
+
* The model-visible read faces' EXCLUSION view: entry id → why it is withheld.
|
|
206
|
+
* - `challenged` — an unresolved S-grade challenge generation exists (B-2');
|
|
207
|
+
* - `lineage_pending` — the dirty latch: a staged lineage row for this id has not been settled
|
|
208
|
+
* (credentialed-but-unpromoted, or the undecidable crash window) — "the account is not
|
|
209
|
+
* settled, the content does not go on the table" (r4).
|
|
210
|
+
* THROWS {@link import("./layout.js").ControlPlaneCorruptError} when either ledger's integrity is
|
|
211
|
+
* unknowable — consumers must refuse service loudly (corrupt ≠ clean), the same law the pollution
|
|
212
|
+
* marker already follows.
|
|
213
|
+
*/
|
|
214
|
+
readChallengeExclusions(): Map<string, {
|
|
215
|
+
code: "challenged" | "lineage_pending";
|
|
216
|
+
generation?: number;
|
|
217
|
+
at?: number;
|
|
218
|
+
}>;
|
|
219
|
+
/** The shared challenge writer: append events idempotently, then announce OPAQUELY (id + code +
|
|
220
|
+
* generation only — the announcement is a model-visible face, so no name/description/free reason
|
|
221
|
+
* rides it; content stays on the host audit face). Failures are disclosed, never thrown past. */
|
|
222
|
+
private challengeAndAnnounce;
|
|
223
|
+
/** design/180 §10 — one disclosure per PROCESS per engine, not one per append: the size is a
|
|
224
|
+
* standing property of the account, and repeating it on every challenge is the spam that gets
|
|
225
|
+
* advisories muted (the same argument the durable-park disclosures are built on). */
|
|
226
|
+
private challengeLedgerSizeDisclosed;
|
|
227
|
+
/**
|
|
228
|
+
* design/180 §10 — the challenge ledger's size bound is a DISCLOSURE threshold, never a cap: the
|
|
229
|
+
* ledger cannot be rotated (the exclusion predicate is derived from the events, so dropping the
|
|
230
|
+
* oldest rows would un-withhold their entries), so crossing {@link CHALLENGE_LEDGER_MAX_EVENTS}
|
|
231
|
+
* changes nothing about what is withheld — it tells the deployment that an account on the
|
|
232
|
+
* per-tool-call read path has grown to where it costs the session latency.
|
|
233
|
+
*/
|
|
234
|
+
private discloseChallengeLedgerSize;
|
|
235
|
+
/**
|
|
236
|
+
* design/180 §10 — the RECOVERY channel for a corrupt fail-closed ledger (lineage / challenges).
|
|
237
|
+
*
|
|
238
|
+
* The fail-closed family refuses every read on corruption so a silent "start fresh" cannot launder
|
|
239
|
+
* taint; permanent refusal is the point, and it left a deployment with genuinely corrupt bytes no way
|
|
240
|
+
* back except deleting engine files by hand. This is that way back, and it is explicit in every
|
|
241
|
+
* dimension: only a host call reaches it (nothing in the engine self-heals), a HEALTHY ledger is
|
|
242
|
+
* refused (`memory.control_plane_not_corrupt`), the corrupt bytes are quarantined beside the file
|
|
243
|
+
* rather than deleted, and the rebuild ANNOUNCES itself — resetting the challenge ledger un-withholds
|
|
244
|
+
* whatever it was withholding, which the model's next session must not learn about silently.
|
|
245
|
+
*
|
|
246
|
+
* `reason` is required and recorded in the announcement's shape (not its text — the announcement is a
|
|
247
|
+
* model-visible face and stays opaque); it is the operator's own note on the host error face.
|
|
248
|
+
*/
|
|
249
|
+
rebuildCorruptControlPlaneLedger(ledger: StrictControlPlaneLedger, reason: string): ControlPlaneRebuildReceipt;
|
|
250
|
+
/**
|
|
251
|
+
* Host API (advisory, control-plane trust level — the same plane that can mark pollution):
|
|
252
|
+
* challenge entries by id. `requestId` is REQUIRED (the generation ledger's idempotency identity;
|
|
253
|
+
* this engine never mints one for the caller — a retried request must carry the SAME id so a lost
|
|
254
|
+
* ack cannot open a second generation). Each entry's eventId derives as `<requestId>:<entryId>`,
|
|
255
|
+
* so one batched request stays per-entry attributable.
|
|
256
|
+
*/
|
|
257
|
+
challengeEntries(ids: readonly string[], reason: string, requestId: string): ChallengeAssignment[];
|
|
258
|
+
/** Host API: challenge every entry the lineage ledger attributes to `sessionId` (post-hoc source
|
|
259
|
+
* falsification — trustedTools misconfigured, a tool re-classified, late delegation evidence).
|
|
260
|
+
* Same requestId contract as {@link challengeEntries}. */
|
|
261
|
+
challengeSession(sessionId: string, reason: string, requestId: string): ChallengeAssignment[];
|
|
262
|
+
/** Host API: the TRUSTED resolve — closes exactly the named generation (a later challenge stays
|
|
263
|
+
* open; a resolve never blanket-clears, and there is no model-face path to this call). Returns
|
|
264
|
+
* false ⇔ no such generation was ever opened. */
|
|
265
|
+
resolveChallenge(entryId: string, generation: number, reason: string, requestId?: string): boolean;
|
|
266
|
+
/** Host API: adjudicate one UNDECIDABLE pending lineage transaction (the applyPatches-success →
|
|
267
|
+
* credential-write crash window; design/180 r5 — never auto-discarded). The host, holding its own
|
|
268
|
+
* deployment facts, states whether the commit landed. */
|
|
269
|
+
adjudicatePendingLineage(txnId: string, action: "promote" | "discard"): LineagePromotion[];
|
|
270
|
+
/** Host observability: the staged lineage transactions still awaiting settlement (their entry ids
|
|
271
|
+
* are latched — see {@link readChallengeExclusions}). Throws on a corrupt ledger. */
|
|
272
|
+
listPendingLineage(): Record<string, LineagePendingTxn>;
|
|
273
|
+
/** Host audit reads (throw on corruption — audit must not read a laundered ledger as clean). */
|
|
274
|
+
readChallengeLedger(): ChallengeEvent[];
|
|
275
|
+
/** Observability/tests — the collect-only challenged-history account (B-3; zero engine consumers). */
|
|
276
|
+
readChallengedHistoryAccount(): Record<string, ChallengedHistoryRow>;
|
|
277
|
+
/** Promotion settlement (shared by harvest step ⑤, reconciliation and host adjudication): a
|
|
278
|
+
* promoted contribution whose session ALREADY carries a durable pollution marker is challenged
|
|
279
|
+
* on the spot — "mark first, promote later" must converge exactly like the reverse order. The
|
|
280
|
+
* eventId anchors on the pollution record (sessionId + marked-at), so however many times this
|
|
281
|
+
* settlement replays the same pollution event, the generation is allocated once. */
|
|
282
|
+
private settlePromotions;
|
|
191
283
|
materialize(scopes: readonly string[], writeScope: string | null): Promise<MemorySessionHandle>;
|
|
192
284
|
inject(handle: MemorySessionHandle, opts?: {
|
|
193
285
|
writeToolMounted?: boolean;
|
|
@@ -222,6 +314,7 @@ export declare class MemoryEngine {
|
|
|
222
314
|
polluted?: {
|
|
223
315
|
reason: string;
|
|
224
316
|
};
|
|
317
|
+
sessionId?: string;
|
|
225
318
|
}): Promise<HarvestReport>;
|
|
226
319
|
private harvestCore;
|
|
227
320
|
/**
|