@sema-agent/core 5.65.0 → 6.0.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 +46 -0
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +11 -0
- package/dist/agents/verify.d.ts +1 -1
- package/dist/brain/anthropic.js +1 -1
- package/dist/brain/errors.d.ts +29 -0
- package/dist/brain/errors.js +20 -0
- package/dist/brain/open-responses.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +1 -0
- package/dist/brain/status-sink.js +12 -1
- package/dist/brain/stream-engine.js +17 -6
- package/dist/core/auto-compaction.d.ts +26 -0
- package/dist/core/auto-compaction.js +7 -2
- package/dist/core/auto-mode-arming.d.ts +138 -0
- package/dist/core/auto-mode-arming.js +181 -0
- package/dist/core/auto-mode-defaults.d.ts +13 -0
- package/dist/core/auto-mode-defaults.js +5 -0
- package/dist/core/auto-mode-prompt.d.ts +14 -3
- package/dist/core/auto-mode-prompt.js +10 -7
- package/dist/core/auto-mode-rebuild.d.ts +75 -0
- package/dist/core/auto-mode-rebuild.js +41 -0
- package/dist/core/auto-mode.d.ts +15 -0
- package/dist/core/auto-mode.js +4 -2
- package/dist/core/checkpoint-store.d.ts +18 -0
- package/dist/core/context-edit.d.ts +47 -5
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +106 -0
- package/dist/core/file-history-retention.js +36 -0
- package/dist/core/file-history-store.d.ts +768 -0
- package/dist/core/file-history-store.js +880 -0
- package/dist/core/governance-codes.d.ts +2 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +39 -7
- package/dist/core/hooks.js +38 -21
- package/dist/core/lsp.d.ts +2 -2
- package/dist/core/mcp.d.ts +29 -7
- package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
- package/dist/core/memory-engine/consolidation-driver.js +71 -4
- package/dist/core/memory-engine/consolidation.d.ts +25 -2
- package/dist/core/memory-engine/consolidation.js +4 -1
- package/dist/core/memory-engine/distiller.d.ts +84 -1
- package/dist/core/memory-engine/distiller.js +68 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +328 -15
- package/dist/core/memory-engine/engine.js +355 -29
- package/dist/core/memory-engine/file-backend.d.ts +30 -0
- package/dist/core/memory-engine/file-backend.js +14 -13
- package/dist/core/memory-engine/frontmatter.d.ts +22 -1
- package/dist/core/memory-engine/frontmatter.js +3 -0
- package/dist/core/memory-engine/header-hints.d.ts +5 -0
- package/dist/core/memory-engine/index.d.ts +5 -4
- package/dist/core/memory-engine/index.js +5 -4
- package/dist/core/memory-engine/layout.d.ts +88 -2
- package/dist/core/memory-engine/layout.js +112 -3
- package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
- package/dist/core/memory-engine/provenance-wording.js +3 -0
- package/dist/core/memory-engine/tools.d.ts +89 -8
- package/dist/core/memory-engine/tools.js +263 -22
- package/dist/core/memory-engine/types.d.ts +64 -1
- package/dist/core/memory-recall.d.ts +6 -0
- package/dist/core/memory.d.ts +27 -1
- package/dist/core/memory.js +16 -2
- package/dist/core/permission-rule-consent.d.ts +20 -0
- package/dist/core/permission-rule-consent.js +12 -3
- package/dist/core/permission-rule-model.d.ts +67 -7
- package/dist/core/permission-rule-model.js +53 -7
- package/dist/core/permission-rule-store.js +15 -10
- package/dist/core/permission-rule-sync.js +15 -11
- package/dist/core/retention-policy.d.ts +9 -0
- package/dist/core/retention-policy.js +5 -2
- package/dist/core/retention.d.ts +13 -2
- package/dist/core/runner/assemble-result.d.ts +19 -1
- package/dist/core/runner/assemble-result.js +17 -2
- package/dist/core/runner/compaction-call-options.d.ts +93 -0
- package/dist/core/runner/compaction-call-options.js +3 -0
- package/dist/core/runner/memory-capture-optout.d.ts +80 -0
- package/dist/core/runner/memory-capture-optout.js +53 -0
- package/dist/core/runner/prepare-config-doors.d.ts +5 -0
- package/dist/core/runner/prepare-config-doors.js +16 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +110 -5
- package/dist/core/runner/prepare-hands-readface.js +99 -7
- package/dist/core/runner/prepare-memory.d.ts +88 -0
- package/dist/core/runner/prepare-memory.js +305 -24
- package/dist/core/runner/prepare-task.d.ts +141 -1
- package/dist/core/runner/prepare-task.js +443 -79
- package/dist/core/runner/runtask.d.ts +9 -20
- package/dist/core/runner/runtask.js +133 -96
- package/dist/core/runner/session-file-state-replay.d.ts +18 -10
- package/dist/core/runner/session-file-state-replay.js +52 -1
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/turn-attachments.d.ts +22 -12
- package/dist/core/session-store.d.ts +1 -1
- package/dist/core/session-store.js +6 -1
- package/dist/core/session.d.ts +34 -1
- package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-history-store-contract.js +720 -0
- package/dist/core/task-registry-shared.js +11 -1
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +172 -1
- package/dist/core/tool-policy.js +32 -1
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/trace.d.ts +24 -0
- package/dist/core/types.d.ts +784 -89
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/workflow-run-store-contract.js +6 -2
- package/dist/core/workflow-run-store.d.ts +4 -1
- package/dist/engine/compaction/compaction.d.ts +88 -10
- package/dist/engine/compaction/compaction.js +109 -30
- package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
- package/dist/engine/execution-env/node-execution-env.js +28 -0
- package/dist/engine/harness/agent-harness.d.ts +52 -1
- package/dist/engine/harness/agent-harness.js +36 -1
- package/dist/engine/harness/types.d.ts +26 -1
- package/dist/engine/llm/types.d.ts +50 -4
- package/dist/engine/loop/agent-loop.d.ts +5 -1
- package/dist/engine/loop/agent-loop.js +25 -0
- package/dist/engine/loop/types.d.ts +19 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/session/session.js +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +14 -6
- package/dist/orchestration/run-workflow-tool.d.ts +20 -2
- package/dist/orchestration/run-workflow-tool.js +22 -3
- package/dist/orchestration/workflow-governance.d.ts +59 -1
- package/dist/orchestration/workflow-governance.js +61 -8
- package/dist/orchestration/workflow-meta.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +56 -13
- package/dist/orchestration/workflow-types.d.ts +78 -2
- package/dist/orchestration/workflow.d.ts +20 -0
- package/dist/orchestration/workflow.js +163 -14
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompts/default.d.ts +7 -7
- package/dist/stores/file/file-history-store.d.ts +368 -0
- package/dist/stores/file/file-history-store.js +1248 -0
- package/dist/stores/file/index.d.ts +22 -13
- package/dist/stores/file/index.js +4 -4
- package/dist/stores/file/permission-rule-store.js +1 -0
- package/dist/stores/file/strategy-store.d.ts +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
- package/dist/tools/fs/bash-readonly-classifier.js +106 -4
- package/dist/tools/fs/fs-bash.js +9 -5
- package/dist/tools/fs/fs-shared.d.ts +52 -1
- package/dist/tools/fs/fs-shared.js +14 -0
- package/dist/tools/fs/fs-write.d.ts +5 -5
- package/dist/tools/fs/fs-write.js +71 -14
- package/dist/tools/fs/index.d.ts +6 -1
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/web.js +2 -1
- package/package.json +5 -1
- package/test/export-surface.snapshot.json +155 -23
- package/dist/core/file-snapshot-store.d.ts +0 -165
- package/dist/core/file-snapshot-store.js +0 -259
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
- package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
- package/dist/stores/file/file-snapshot-store.d.ts +0 -58
- package/dist/stores/file/file-snapshot-store.js +0 -353
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BeforeWriteHook, EffectiveMemoryScopes, RunnerDeps, TaskSpec, ToolSpec } from "../types.js";
|
|
2
|
+
import { type CaptureEntitlementInput, type MemoryCapturePosture } from "./memory-capture-optout.js";
|
|
2
3
|
import type { Prepared } from "./prepare-task.js";
|
|
3
4
|
export interface PrepareMemoryInput {
|
|
4
5
|
spec: TaskSpec;
|
|
@@ -74,6 +75,85 @@ export interface PrepareMemoryInput {
|
|
|
74
75
|
* top-level task; for a delegated child the caller passes the fail-closed empty set when the
|
|
75
76
|
* chain carries no verdict (an older-shape chain must not read as "unconstrained").
|
|
76
77
|
*/
|
|
78
|
+
/**
|
|
79
|
+
* design/383 §3.1 — the SCREENED capture posture (prepare-config-doors; absent deps seat folded
|
|
80
|
+
* to `"open"`) plus whether the deployment DECLARED one at all (the once-per-process "switch in
|
|
81
|
+
* use, no policy configured" operator line fires only on the undeclared arm).
|
|
82
|
+
*/
|
|
83
|
+
memoryCapturePolicy?: MemoryCapturePosture;
|
|
84
|
+
memoryCapturePolicyDeclared?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* design/383 §3.1 — the per-principal entitlement as resolved by prepare-task's ONE caps resolve
|
|
87
|
+
* (`value` = `runtimeCaps.allowMemoryOptOut` when boolean; `faulted` = the resolve threw or the
|
|
88
|
+
* member was non-boolean — per the member's F2 fault-transit contract a fault must never arrive
|
|
89
|
+
* as a coined explicit value). Consumed by the adjudicator on every ingress of this prepare.
|
|
90
|
+
*/
|
|
91
|
+
captureEntitlement?: CaptureEntitlementInput;
|
|
92
|
+
/**
|
|
93
|
+
* design/383 §2.1b — the carrier-absence verdict, judged by prepare-task from the DECLARED
|
|
94
|
+
* deployment shape: a remote-execution deployment whose control plane does not follow the run
|
|
95
|
+
* (the per-run sandbox / stateless-replica form) with no
|
|
96
|
+
* {@link RunnerDeps.memoryCaptureRecordStore} supplied. TRUE + a `capture:"off"` declaration ⇒
|
|
97
|
+
* the declaration is refused (`config.memory_capture_unsupported`) — a promise the next resume
|
|
98
|
+
* replica cannot see is a false promise, and a loud refusal beats one.
|
|
99
|
+
*/
|
|
100
|
+
captureCarrierUnsupported?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* design/383 §2.5 — the IMMEDIATE spawner's session id (trusted internals seat, filled by the
|
|
103
|
+
* delegation lanes; absent on top-level runs). The record-query leg of the delegation floor: a
|
|
104
|
+
* child's harvest legs also read the PARENT's opt-out record off the shared write-plane control
|
|
105
|
+
* store, so a parent whose opt-out landed AFTER this child spawned (the mid-run flip) still gets
|
|
106
|
+
* zero admission from in-flight children — the spec-carried floor covers only spawn-time state.
|
|
107
|
+
* Honest bounds (registered): the query sees the CHILD's write-plane store — a parent whose
|
|
108
|
+
* record lives on a different plane (a dual-root split where the two write different planes) is
|
|
109
|
+
* outside its reach, and a lane that fills no parent seat (TIER-3 durable revival) is covered
|
|
110
|
+
* only insofar as the child's OWN record (same sessionId across revival) or spec carriage
|
|
111
|
+
* survives that lane.
|
|
112
|
+
*/
|
|
113
|
+
parentSessionId?: string;
|
|
114
|
+
/**
|
|
115
|
+
* design/383 §2.5 — the delegation FLOOR bit off the trusted internals chain
|
|
116
|
+
* (`internals.memoryCaptureFloor === true`): the spawning session was opted out at spawn time.
|
|
117
|
+
* A floored leg runs opted-out WITHOUT re-adjudication (the parent's granted opt-out tightens
|
|
118
|
+
* the tree; re-asking could only loosen) and mints its own one-way record (reason names the
|
|
119
|
+
* floor), so its grandchildren and its own resumes inherit mechanically.
|
|
120
|
+
*/
|
|
121
|
+
captureFloor?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* design/383 §2.5 (rescan post-6.0.0-RC) — the floor seat's THIRD state off the same trusted
|
|
124
|
+
* chain: the spawning session's capture state was INDETERMINATE at spawn time (its record store
|
|
125
|
+
* faulted at the getter read). This leg resolves it against the live record query (see
|
|
126
|
+
* `armCaptureOptOut`): ancestor record found ⇒ floor; still faulting ⇒ this session runs
|
|
127
|
+
* indeterminate itself (mechanical restrictions, no record mint); readable-and-clean ⇒ clean.
|
|
128
|
+
* Mutually exclusive with `captureFloor` at the mint site (the spread keys on optedOut !== true).
|
|
129
|
+
*/
|
|
130
|
+
captureFloorIndeterminate?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* design/383 §2.5 (rescan post-6.0.0-RC) — the HOST-SIDE FORK lane: the session this one was
|
|
133
|
+
* forked FROM (`SessionMetadata.forkedFrom`, read by prepare-task off the acquired session). A
|
|
134
|
+
* fork byte-copies the source's committed transcript, so the source's standing opt-out governs
|
|
135
|
+
* the copy: record found at prepare ⇒ the floor arm (mint, reason naming the fork, no
|
|
136
|
+
* re-adjudication — the source's granted opt-out tightens the lineage); source store faulting ⇒
|
|
137
|
+
* indeterminate (suppress, no mint); clean ⇒ ordinary run. Also seeded into the harvest
|
|
138
|
+
* record-query walk, so a source that flips WHILE the fork runs suppresses it live.
|
|
139
|
+
*/
|
|
140
|
+
captureForkOrigin?: string;
|
|
141
|
+
/** design/383 §2.5 (rescan, codex adversarial round) — the fork-lineage read itself FAULTED
|
|
142
|
+
* (session metadata unreadable): whether this session forks an opted-out source is unknowable,
|
|
143
|
+
* so the capture arm runs INDETERMINATE (suppress fail-closed, disclose, mint nothing) — a
|
|
144
|
+
* metadata outage must not read as "no lineage" (读错不许伪装 clean). */
|
|
145
|
+
captureLineageFault?: boolean;
|
|
146
|
+
/** design/383 §2.5 (codex review adopted) — the SPAWNING session's write-plane control dir
|
|
147
|
+
* (trusted internals chain), so the record-query leg reaches a parent on a DIFFERENT memory
|
|
148
|
+
* plane (project-writing parent, personal-writing child). Absent ⇒ same-plane query only. */
|
|
149
|
+
parentCaptureQueryDir?: string;
|
|
150
|
+
/** design/383 §2.5 (codex round 3) — the full ancestor chain's capture coordinates off the
|
|
151
|
+
* trusted internals seat; the harvest closure walks every row (deduped with the parent seat),
|
|
152
|
+
* so a mid-run flip anywhere up the tree suppresses this in-flight descendant. */
|
|
153
|
+
captureAncestors?: ReadonlyArray<{
|
|
154
|
+
sessionId: string;
|
|
155
|
+
controlDir?: string;
|
|
156
|
+
}>;
|
|
77
157
|
admissionCtx: {
|
|
78
158
|
orgMemoryDenied: boolean;
|
|
79
159
|
complianceDegraded: boolean;
|
|
@@ -96,6 +176,14 @@ export interface PrepareMemoryResult {
|
|
|
96
176
|
* them (atomic pair) and runs the remaining conjuncts.
|
|
97
177
|
*/
|
|
98
178
|
memoryTools?: ToolSpec[];
|
|
179
|
+
/**
|
|
180
|
+
* #493 单铸 — the recall-discipline segment EXACTLY as this phase appended it to
|
|
181
|
+
* {@link memoryBlock} (present iff {@link memoryTools} is). prepare-task's support-name
|
|
182
|
+
* retraction strips THIS string: the segment's composition now depends on the marked-presence
|
|
183
|
+
* signal only this phase holds, so a strip-site re-composition could mint a string that was
|
|
184
|
+
* never appended and leave the real one standing.
|
|
185
|
+
*/
|
|
186
|
+
recallDisciplineSegment?: string;
|
|
99
187
|
/**
|
|
100
188
|
* RB-276 — engine-materialized files whose FULL disk text rides in {@link memoryBlock}, for the
|
|
101
189
|
* caller to pre-mark READ through the parity-204 seeding lane (`prepare-task`'s `seedContextFiles`,
|
|
@@ -4,10 +4,13 @@ import { admitMemoryScopes } from "../memory-admission.js";
|
|
|
4
4
|
import { adoptLegacyRepoDirs, canonicalize, deriveRepoControlPlaneDir, deriveProjectControlDir, deriveProjectMemoryDir, deriveRepoMemoryDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, isContainedIn, lookupProjectIdHint, recordProjectIdHint, resolveMemoryEngineRoot } from "../memory-engine/layout.js";
|
|
5
5
|
import { classifyScopePlanes, derivePersonalControlDir, derivePersonalMemoryDir, mergeHarvestReports, mergeInjections, needsDualRoots, parsedProjectPlane } from "../memory-engine/dual-root.js";
|
|
6
6
|
import { normalizeMemorySpec } from "../memory.js";
|
|
7
|
-
import { MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MemoryEngine, entryFileHeadCarriesOrigin, memoryConsolidationRecommendedNotice, memoryHarvestQuarantinedNotice, memoryHoldNotices, memoryRecallDisciplineSegment, memorySessionPollutedNotice, pollutionContainmentCounts, } from "../memory-engine/engine.js";
|
|
7
|
+
import { MEMORY_ANNOUNCEMENT_CAPTURE_OPTOUT_CODA, MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_CAPTURE_INDETERMINATE_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MemoryEngine, entryFileHeadCarriesOrigin, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, memoryConsolidationRecommendedNotice, memoryHarvestQuarantinedNotice, memoryHoldNotices, memoryRecallDisciplineSegment, memorySessionPollutedNotice, pollutionContainmentCounts, } from "../memory-engine/engine.js";
|
|
8
|
+
import { adjudicateCaptureOptOut, captureOptOutDeniedError, captureOptOutUnpersistedError } from "./memory-capture-optout.js";
|
|
9
|
+
import { readSessionCaptureOptOut } from "../memory-engine/layout.js";
|
|
8
10
|
import { createMemoryEngineTools } from "../memory-engine/tools.js";
|
|
9
11
|
import { assertScopeContractPlacement, parseScopeKey, resolveProjectId } from "../memory-engine/scope-contract.js";
|
|
10
12
|
import { FileMemoryEngineBackend } from "../memory-engine/file-backend.js";
|
|
13
|
+
let undeclaredCapturePostureAnnounced = false;
|
|
11
14
|
export async function prepareMemory(input) {
|
|
12
15
|
const { spec, deps, sessionId, taskRootPath, memoryWriteGateRef, admissionCtx } = input;
|
|
13
16
|
const memorySpec = normalizeMemorySpec(spec.memory);
|
|
@@ -41,8 +44,37 @@ export async function prepareMemory(input) {
|
|
|
41
44
|
memorySpec.writeScope = outcome.writeScope;
|
|
42
45
|
}
|
|
43
46
|
const useMemoryEngine = Boolean(memorySpec && memorySpec.enabled && deps.memoryBackend);
|
|
47
|
+
const capturePosture = input.memoryCapturePolicy ?? "open";
|
|
48
|
+
const captureEntitlement = input.captureEntitlement ?? { value: undefined, faulted: false };
|
|
49
|
+
const captureDeclaredRaw = memorySpec?.capture === "off";
|
|
50
|
+
if (captureDeclaredRaw && memorySpec !== undefined && !memorySpec.enabled) {
|
|
51
|
+
deps.onError?.(new Error(`memory.capture:"off" appears beside memory.enabled:false — not a contradiction (memory is off entirely, so nothing is captured either way); the opt-out declaration is covered by enabled:false this run.`), { phase: "config", sessionId });
|
|
52
|
+
}
|
|
53
|
+
const captureDeclared = captureDeclaredRaw && useMemoryEngine;
|
|
54
|
+
const captureFloored = input.captureFloor === true && useMemoryEngine;
|
|
55
|
+
if (captureDeclared && !captureFloored) {
|
|
56
|
+
const verdict = adjudicateCaptureOptOut({ posture: capturePosture, entitlement: captureEntitlement, subject: "declaration" });
|
|
57
|
+
if (!verdict.allowed)
|
|
58
|
+
throw captureOptOutDeniedError(verdict.detail);
|
|
59
|
+
if (verdict.faultDisclosure !== undefined) {
|
|
60
|
+
deps.onError?.(new Error(verdict.faultDisclosure), { phase: "config", sessionId });
|
|
61
|
+
}
|
|
62
|
+
if (input.captureCarrierUnsupported === true) {
|
|
63
|
+
const e = new Error(`memory.capture:"off" cannot be honored on this deployment shape: the execution environment is remote/per-run ` +
|
|
64
|
+
`and no RunnerDeps.memoryCaptureRecordStore is supplied, so core cannot know the control plane survives to a ` +
|
|
65
|
+
`resume replica — a privacy promise that silently expires at the first resume is refused rather than made. A ` +
|
|
66
|
+
`deployment whose control plane IS durable (a single persistent host using a remote sandbox) opts back in by ` +
|
|
67
|
+
`supplying the store explicitly — fileSessionCaptureRecordStore(controlDir) is that one-line declaration.`);
|
|
68
|
+
e.code = "config.memory_capture_unsupported";
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let captureOptedOut = false;
|
|
73
|
+
let captureIndeterminate = false;
|
|
74
|
+
let captureLineageIndeterminate = false;
|
|
44
75
|
let memoryEngineSession;
|
|
45
76
|
let memoryTools;
|
|
77
|
+
let memoryMarkedEntriesServed = false;
|
|
46
78
|
let effectiveMemoryScopes;
|
|
47
79
|
const originOf = (scope) => scopeOriginsSnapshot !== undefined
|
|
48
80
|
? scopeOriginsSnapshot[scope] === "deployment"
|
|
@@ -132,15 +164,106 @@ export async function prepareMemory(input) {
|
|
|
132
164
|
const pinnedCtl = b.controlPlaneRoot;
|
|
133
165
|
return typeof pinnedCtl === "string" && pinnedCtl ? pinnedCtl : fallback;
|
|
134
166
|
};
|
|
167
|
+
const captureStoreOpt = (b, fallback) => deps.memoryCaptureRecordStore !== undefined ? { captureRecordStore: deps.memoryCaptureRecordStore({ controlDir: effectiveControlDir(b, fallback) }) } : {};
|
|
135
168
|
const choosePersonalBackend = () => typeof pinned === "string" && pinned ? new FileMemoryEngineBackend(personalMemoryDir, { controlDir: personalControlDir }) : backend;
|
|
136
169
|
const createPersonalEngine = (personalBackend) => {
|
|
137
170
|
return {
|
|
138
|
-
engine: new MemoryEngine({ backend: personalBackend, memoryDir: personalMemoryDir, controlDir: personalControlDir, onIncident: onEngineIncident, provenance: input.memoryProvenance, ...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}) }),
|
|
171
|
+
engine: new MemoryEngine({ backend: personalBackend, memoryDir: personalMemoryDir, controlDir: personalControlDir, onIncident: onEngineIncident, provenance: input.memoryProvenance, ...captureStoreOpt(personalBackend, personalControlDir), ...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}) }),
|
|
139
172
|
backend: personalBackend,
|
|
140
173
|
};
|
|
141
174
|
};
|
|
142
175
|
const onEngineIncident = (err) => deps.onError?.(err, { phase: "memory", sessionId });
|
|
143
176
|
const adoptionRestricted = input.memoryPersistenceDeclared === false;
|
|
177
|
+
const armCaptureOptOut = (eng) => {
|
|
178
|
+
const standingRead = eng.sessionCaptureOptOutOrFault(sessionId);
|
|
179
|
+
if (standingRead.fault) {
|
|
180
|
+
captureIndeterminate = true;
|
|
181
|
+
deps.onError?.(new Error(`the capture opt-out record store is unreadable at prepare — this session's capture state is indeterminate, and every memory commit boundary (read-side adoption included) suppresses fail-closed until the store answers.`), { phase: "memory", sessionId });
|
|
182
|
+
}
|
|
183
|
+
if (input.captureLineageFault === true) {
|
|
184
|
+
const firstIndeterminate = !captureIndeterminate;
|
|
185
|
+
captureIndeterminate = true;
|
|
186
|
+
captureLineageIndeterminate = true;
|
|
187
|
+
if (firstIndeterminate) {
|
|
188
|
+
deps.onError?.(new Error(`this session's fork-lineage metadata is unreadable — whether it continues an opted-out source session cannot be established, so its capture state is indeterminate: every memory commit boundary (read-side adoption included) suppresses fail-closed; no opt-out record is minted from an unreadable state.`), { phase: "memory", sessionId });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const standing = standingRead.record;
|
|
192
|
+
let floored = captureFloored;
|
|
193
|
+
if (!floored && !captureDeclared && standing === undefined && input.captureFloorIndeterminate === true) {
|
|
194
|
+
const rows = new Map();
|
|
195
|
+
for (const row of input.captureAncestors ?? [])
|
|
196
|
+
rows.set(row.sessionId, row.controlDir);
|
|
197
|
+
if (input.parentSessionId !== undefined && !rows.has(input.parentSessionId))
|
|
198
|
+
rows.set(input.parentSessionId, input.parentCaptureQueryDir);
|
|
199
|
+
let anyFault = standingRead.fault;
|
|
200
|
+
let found = false;
|
|
201
|
+
for (const [ancestorId, ancestorDir] of rows) {
|
|
202
|
+
const samePlane = eng.sessionCaptureOptOutOrFault(ancestorId);
|
|
203
|
+
if (samePlane.record !== undefined) {
|
|
204
|
+
found = true;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
if (samePlane.fault)
|
|
208
|
+
anyFault = true;
|
|
209
|
+
if (ancestorDir !== undefined && ancestorDir !== eng.controlPlaneDir) {
|
|
210
|
+
try {
|
|
211
|
+
const crossPlane = deps.memoryCaptureRecordStore !== undefined ? deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
|
|
212
|
+
if (crossPlane !== undefined) {
|
|
213
|
+
found = true;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
anyFault = true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (found)
|
|
223
|
+
floored = true;
|
|
224
|
+
else if (anyFault) {
|
|
225
|
+
if (!captureIndeterminate) {
|
|
226
|
+
captureIndeterminate = true;
|
|
227
|
+
deps.onError?.(new Error(`the spawning session's capture opt-out state was INDETERMINATE at spawn (its record store faulted) and the live re-query still cannot answer — this delegated session inherits the suppression: every memory commit boundary (read-side adoption included) suppresses fail-closed until the store answers; no opt-out record is minted from an unreadable state.`), { phase: "memory", sessionId });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
let forkInherited = false;
|
|
232
|
+
if (!floored && !captureDeclared && standing === undefined && input.captureForkOrigin !== undefined) {
|
|
233
|
+
const src = eng.sessionCaptureOptOutOrFault(input.captureForkOrigin);
|
|
234
|
+
if (src.record !== undefined)
|
|
235
|
+
forkInherited = true;
|
|
236
|
+
else if (src.fault && !captureIndeterminate) {
|
|
237
|
+
captureIndeterminate = true;
|
|
238
|
+
deps.onError?.(new Error(`this session is a FORK of session "${input.captureForkOrigin}" whose capture opt-out record store is unreadable — the fork's capture state is indeterminate (the copied transcript may be governed by a standing opt-out), so every memory commit boundary suppresses fail-closed until the store answers; no opt-out record is minted from an unreadable state.`), { phase: "memory", sessionId });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (standing === undefined && !captureDeclared && !floored && !forkInherited)
|
|
242
|
+
return;
|
|
243
|
+
if (standing !== undefined && !captureDeclared && !floored && capturePosture !== "open") {
|
|
244
|
+
const verdict = adjudicateCaptureOptOut({ posture: capturePosture, entitlement: captureEntitlement, subject: "standing-record" });
|
|
245
|
+
if (!verdict.allowed)
|
|
246
|
+
throw captureOptOutDeniedError(verdict.detail);
|
|
247
|
+
}
|
|
248
|
+
if (standing === undefined) {
|
|
249
|
+
const outcome = eng.markSessionCaptureOptOut(sessionId, captureDeclared ? `declared on TaskSpec.memory.capture` : forkInherited ? `inherited capture opt-out from the forked source session` : `inherited capture opt-out floor from the spawning session`);
|
|
250
|
+
if (outcome === "unpersisted") {
|
|
251
|
+
deliverEngineNotice(deps.onNotice, memoryCaptureOptOutUnpersistedNotice({ sessionId, ingress: "declaration" }));
|
|
252
|
+
throw captureOptOutUnpersistedError(sessionId, "declaration");
|
|
253
|
+
}
|
|
254
|
+
if (outcome === "created")
|
|
255
|
+
deliverEngineNotice(deps.onNotice, memoryCaptureOptedOutNotice({ sessionId, origin: captureDeclared ? "declaration" : forkInherited ? "fork-origin" : "delegation-floor" }));
|
|
256
|
+
}
|
|
257
|
+
else if (!captureDeclared) {
|
|
258
|
+
deliverEngineNotice(deps.onNotice, memoryCaptureOptedOutNotice({ sessionId, origin: "standing-record" }));
|
|
259
|
+
}
|
|
260
|
+
captureOptedOut = true;
|
|
261
|
+
if (input.memoryCapturePolicyDeclared !== true && !undeclaredCapturePostureAnnounced) {
|
|
262
|
+
undeclaredCapturePostureAnnounced = true;
|
|
263
|
+
deps.onError?.(new Error(`a session memory-capture opt-out was honored under the DEFAULT capture posture (no RunnerDeps.memoryCapturePolicy configured) — ` +
|
|
264
|
+
`the switch is now in use; declare "open", "governed" or "capture-required" to make the deployment's stance explicit.`), { phase: "config", sessionId });
|
|
265
|
+
}
|
|
266
|
+
};
|
|
144
267
|
const retrievalBackend = (b, planeRestricted) => planeRestricted
|
|
145
268
|
? (b.restrictedAdoptionView?.({ audit: false }) ??
|
|
146
269
|
b.retrievalView?.() ??
|
|
@@ -164,9 +287,47 @@ export async function prepareMemory(input) {
|
|
|
164
287
|
let injectFn;
|
|
165
288
|
let harvestBoth;
|
|
166
289
|
let toolPlanes;
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
290
|
+
const admitNothingOptsNow = () => {
|
|
291
|
+
if (input.memoryPersistenceDeclared === false) {
|
|
292
|
+
return { admitNothing: { reason: "harvest admitted nothing: memory persistence is declared unavailable for this session (memoryPersistenceCapable: false)" } };
|
|
293
|
+
}
|
|
294
|
+
if (captureLineageIndeterminate) {
|
|
295
|
+
return { admitNothing: { reason: `harvest admitted nothing: this session's fork-lineage metadata is unreadable — whether it continues an opted-out source cannot be established, so commits are suppressed fail-closed` } };
|
|
296
|
+
}
|
|
297
|
+
const own = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : writeEngine.sessionCaptureOptOutOrFault(sessionId);
|
|
298
|
+
if (own.record !== undefined) {
|
|
299
|
+
return { admitNothing: { reason: `harvest admitted nothing: this session declared memory capture opt-out (memory.capture: "off")` } };
|
|
300
|
+
}
|
|
301
|
+
if (own.fault) {
|
|
302
|
+
return { admitNothing: { reason: `harvest admitted nothing: the capture opt-out record store is unreadable — the opt-out state is indeterminate and commits are suppressed fail-closed` } };
|
|
303
|
+
}
|
|
304
|
+
const ancestors = new Map();
|
|
305
|
+
for (const row of input.captureAncestors ?? [])
|
|
306
|
+
ancestors.set(row.sessionId, row.controlDir);
|
|
307
|
+
if (input.parentSessionId !== undefined && !ancestors.has(input.parentSessionId))
|
|
308
|
+
ancestors.set(input.parentSessionId, input.parentCaptureQueryDir);
|
|
309
|
+
if (input.captureForkOrigin !== undefined && !ancestors.has(input.captureForkOrigin))
|
|
310
|
+
ancestors.set(input.captureForkOrigin, undefined);
|
|
311
|
+
for (const [ancestorId, ancestorDir] of ancestors) {
|
|
312
|
+
const samePlane = writeEngine.sessionCaptureOptOutOrFault(ancestorId);
|
|
313
|
+
let crossPlane;
|
|
314
|
+
if (ancestorDir !== undefined && ancestorDir !== writeEngine.controlPlaneDir) {
|
|
315
|
+
try {
|
|
316
|
+
crossPlane = deps.memoryCaptureRecordStore !== undefined ? deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return { admitNothing: { reason: `harvest admitted nothing: a spawning session's capture record store is unreadable — the floor state is indeterminate and commits are suppressed fail-closed` } };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (samePlane.record !== undefined || crossPlane !== undefined) {
|
|
323
|
+
return { admitNothing: { reason: `harvest admitted nothing: a spawning session declared memory capture opt-out — the opt-out is a delegation floor no descendant loosens`, cause: "capture_optout_inherited" } };
|
|
324
|
+
}
|
|
325
|
+
if (samePlane.fault) {
|
|
326
|
+
return { admitNothing: { reason: `harvest admitted nothing: the capture opt-out record store is unreadable while a spawning session's floor is in question — suppressed fail-closed` } };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return {};
|
|
330
|
+
};
|
|
170
331
|
if (dual) {
|
|
171
332
|
const personalBackendChosen = choosePersonalBackend();
|
|
172
333
|
{
|
|
@@ -195,16 +356,19 @@ export async function prepareMemory(input) {
|
|
|
195
356
|
controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot),
|
|
196
357
|
onIncident: onEngineIncident,
|
|
197
358
|
provenance: input.memoryProvenance,
|
|
359
|
+
...captureStoreOpt(backend, identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot)),
|
|
198
360
|
...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}),
|
|
199
361
|
});
|
|
200
362
|
const personal = createPersonalEngine(personalBackendChosen);
|
|
201
363
|
const personalEngine = personal.engine;
|
|
202
364
|
const p = planes;
|
|
203
|
-
|
|
365
|
+
armCaptureOptOut(p.writePlane === "personal" ? personalEngine : projectEngine);
|
|
366
|
+
const projectHandle = await projectEngine.materialize(p.project, p.writePlane === "project" ? memorySpec.writeScope : null, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
|
|
204
367
|
materializedResidue.push(...planeScopes(p.project, p.writePlane === "project" ? memorySpec.writeScope : null));
|
|
205
|
-
const personalHandle = await personalEngine.materialize(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null, { adoptionRestricted, sessionId });
|
|
368
|
+
const personalHandle = await personalEngine.materialize(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
|
|
206
369
|
materializedResidue.push(...planeScopes(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null));
|
|
207
370
|
const writeIsPersonal = p.writePlane === "personal";
|
|
371
|
+
memoryMarkedEntriesServed = projectHandle.markedEntriesPresent === true || personalHandle.markedEntriesPresent === true;
|
|
208
372
|
writeEngine = writeIsPersonal ? personalEngine : projectEngine;
|
|
209
373
|
writeHandle = writeIsPersonal ? personalHandle : projectHandle;
|
|
210
374
|
readOnlyEngine = writeIsPersonal ? projectEngine : personalEngine;
|
|
@@ -212,22 +376,24 @@ export async function prepareMemory(input) {
|
|
|
212
376
|
injectFn = () => mergeInjections(projectEngine.inject(projectHandle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark }), personalEngine.inject(personalHandle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark }));
|
|
213
377
|
toolPlanes = [
|
|
214
378
|
{
|
|
215
|
-
backend: retrievalBackend(backend, adoptionRestricted || p.writePlane !== "project"),
|
|
379
|
+
backend: retrievalBackend(backend, adoptionRestricted || captureOptedOut || captureIndeterminate || p.writePlane !== "project"),
|
|
216
380
|
scopes: planeScopes(p.project, p.writePlane === "project" ? memorySpec.writeScope : null),
|
|
217
381
|
recordRetrieved: (ids) => projectEngine.recordRetrieved(ids),
|
|
218
382
|
challengeExclusions: () => projectEngine.readChallengeExclusions(),
|
|
383
|
+
enginePlaneProvenance: projectEngine.provenanceMode,
|
|
219
384
|
},
|
|
220
385
|
{
|
|
221
|
-
backend: retrievalBackend(personal.backend, adoptionRestricted || p.writePlane !== "personal"),
|
|
386
|
+
backend: retrievalBackend(personal.backend, adoptionRestricted || captureOptedOut || captureIndeterminate || p.writePlane !== "personal"),
|
|
222
387
|
scopes: planeScopes(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null),
|
|
223
388
|
recordRetrieved: (ids) => personalEngine.recordRetrieved(ids),
|
|
224
389
|
challengeExclusions: () => personalEngine.readChallengeExclusions(),
|
|
390
|
+
enginePlaneProvenance: personalEngine.provenanceMode,
|
|
225
391
|
},
|
|
226
392
|
];
|
|
227
393
|
harvestBoth = async () => {
|
|
228
394
|
const writeFirst = writeIsPersonal ? [personalEngine, personalHandle] : [projectEngine, projectHandle];
|
|
229
395
|
const readOther = writeIsPersonal ? [projectEngine, projectHandle] : [personalEngine, personalHandle];
|
|
230
|
-
const writeReport = await writeFirst[0].harvest(writeFirst[1], { ...pollutedOpts(writeFirst[0]), sessionId, ...
|
|
396
|
+
const writeReport = await writeFirst[0].harvest(writeFirst[1], { ...pollutedOpts(writeFirst[0]), sessionId, ...admitNothingOptsNow() });
|
|
231
397
|
let readReport;
|
|
232
398
|
let readFailure;
|
|
233
399
|
try {
|
|
@@ -245,18 +411,21 @@ export async function prepareMemory(input) {
|
|
|
245
411
|
else if (personalOnly) {
|
|
246
412
|
const personal = createPersonalEngine(choosePersonalBackend());
|
|
247
413
|
const personalEngine = personal.engine;
|
|
248
|
-
|
|
414
|
+
armCaptureOptOut(personalEngine);
|
|
415
|
+
const handle = await personalEngine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
|
|
249
416
|
materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
|
|
417
|
+
memoryMarkedEntriesServed = handle.markedEntriesPresent === true;
|
|
250
418
|
writeEngine = personalEngine;
|
|
251
419
|
writeHandle = handle;
|
|
252
420
|
injectFn = () => personalEngine.inject(handle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark });
|
|
253
|
-
harvestBoth = () => personalEngine.harvest(handle, { ...pollutedOpts(personalEngine), sessionId, ...
|
|
421
|
+
harvestBoth = () => personalEngine.harvest(handle, { ...pollutedOpts(personalEngine), sessionId, ...admitNothingOptsNow() });
|
|
254
422
|
toolPlanes = [
|
|
255
423
|
{
|
|
256
|
-
backend: retrievalBackend(personal.backend, adoptionRestricted || memorySpec.writeScope === null),
|
|
424
|
+
backend: retrievalBackend(personal.backend, adoptionRestricted || captureOptedOut || captureIndeterminate || memorySpec.writeScope === null),
|
|
257
425
|
scopes: planeScopes(memorySpec.scopes, memorySpec.writeScope),
|
|
258
426
|
recordRetrieved: (ids) => personalEngine.recordRetrieved(ids),
|
|
259
427
|
challengeExclusions: () => personalEngine.readChallengeExclusions(),
|
|
428
|
+
enginePlaneProvenance: personalEngine.provenanceMode,
|
|
260
429
|
},
|
|
261
430
|
];
|
|
262
431
|
}
|
|
@@ -267,20 +436,24 @@ export async function prepareMemory(input) {
|
|
|
267
436
|
controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot),
|
|
268
437
|
onIncident: onEngineIncident,
|
|
269
438
|
provenance: input.memoryProvenance,
|
|
439
|
+
...captureStoreOpt(backend, identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot)),
|
|
270
440
|
...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}),
|
|
271
441
|
});
|
|
272
|
-
|
|
442
|
+
armCaptureOptOut(engine);
|
|
443
|
+
const handle = await engine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
|
|
273
444
|
materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
|
|
445
|
+
memoryMarkedEntriesServed = handle.markedEntriesPresent === true;
|
|
274
446
|
writeEngine = engine;
|
|
275
447
|
writeHandle = handle;
|
|
276
448
|
injectFn = () => engine.inject(handle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark });
|
|
277
|
-
harvestBoth = () => engine.harvest(handle, { ...pollutedOpts(engine), sessionId, ...
|
|
449
|
+
harvestBoth = () => engine.harvest(handle, { ...pollutedOpts(engine), sessionId, ...admitNothingOptsNow() });
|
|
278
450
|
toolPlanes = [
|
|
279
451
|
{
|
|
280
|
-
backend: retrievalBackend(backend, adoptionRestricted || memorySpec.writeScope === null),
|
|
452
|
+
backend: retrievalBackend(backend, adoptionRestricted || captureOptedOut || captureIndeterminate || memorySpec.writeScope === null),
|
|
281
453
|
scopes: planeScopes(memorySpec.scopes, memorySpec.writeScope),
|
|
282
454
|
recordRetrieved: (ids) => engine.recordRetrieved(ids),
|
|
283
455
|
challengeExclusions: () => engine.readChallengeExclusions(),
|
|
456
|
+
enginePlaneProvenance: engine.provenanceMode,
|
|
284
457
|
},
|
|
285
458
|
];
|
|
286
459
|
}
|
|
@@ -301,6 +474,22 @@ export async function prepareMemory(input) {
|
|
|
301
474
|
};
|
|
302
475
|
}
|
|
303
476
|
}
|
|
477
|
+
{
|
|
478
|
+
const root = writeHandle.writableRoot;
|
|
479
|
+
if (w.key === root || w.key.startsWith(`${root}${sep}`)) {
|
|
480
|
+
const state = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : writeEngine.sessionCaptureOptOutOrFault(sessionId);
|
|
481
|
+
if (state.record !== undefined || state.fault) {
|
|
482
|
+
return {
|
|
483
|
+
ok: false,
|
|
484
|
+
code: "read_only_layering",
|
|
485
|
+
reason: state.record !== undefined
|
|
486
|
+
? `memory capture is off for this session at the user's request (memory.capture: "off") — writes into the memory store are refused. Nothing was written.`
|
|
487
|
+
: `the capture opt-out record store is unreadable, so this session's capture state is indeterminate — writes into the memory store are refused fail-closed. Nothing was written.`,
|
|
488
|
+
muted: false,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
304
493
|
return writeEngine.gateWrite(writeHandle, w.key, w.content);
|
|
305
494
|
};
|
|
306
495
|
let pollutionAnnounced = false;
|
|
@@ -326,7 +515,8 @@ export async function prepareMemory(input) {
|
|
|
326
515
|
try {
|
|
327
516
|
const report = await harvestBoth();
|
|
328
517
|
if (!report.ok && report.incident) {
|
|
329
|
-
|
|
518
|
+
const named = report.reportId !== undefined ? `${report.incident.kind}, report ${report.reportId}` : report.incident.kind;
|
|
519
|
+
deps.onError?.(new Error(`memory harvest refused (${named}): ${report.incident.detail}`), { phase: "memory", sessionId });
|
|
330
520
|
}
|
|
331
521
|
announceHarvestContainment(report);
|
|
332
522
|
try {
|
|
@@ -345,11 +535,76 @@ export async function prepareMemory(input) {
|
|
|
345
535
|
return undefined;
|
|
346
536
|
}
|
|
347
537
|
};
|
|
538
|
+
const resolveCaptureEntitlementFresh = async () => {
|
|
539
|
+
if (deps.runtimeCapsResolver === undefined)
|
|
540
|
+
return { value: undefined, faulted: false };
|
|
541
|
+
try {
|
|
542
|
+
const caps = (await deps.runtimeCapsResolver(spec.principal)) ?? undefined;
|
|
543
|
+
const v = caps?.allowMemoryOptOut;
|
|
544
|
+
if (v !== undefined && typeof v !== "boolean")
|
|
545
|
+
return { value: undefined, faulted: true };
|
|
546
|
+
return { value: v, faulted: false };
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
return { value: undefined, faulted: true };
|
|
550
|
+
}
|
|
551
|
+
};
|
|
348
552
|
memoryEngineSession = {
|
|
349
553
|
engine: writeEngine,
|
|
350
554
|
handle: writeHandle,
|
|
351
555
|
inject: injectFn,
|
|
352
556
|
harvest: harvestSafe,
|
|
557
|
+
captureOptOut: {
|
|
558
|
+
optedOut: () => captureOptedOut || writeEngine.sessionCaptureOptOutOrFault(sessionId).record !== undefined,
|
|
559
|
+
indeterminate: () => {
|
|
560
|
+
if (captureOptedOut)
|
|
561
|
+
return false;
|
|
562
|
+
const s = writeEngine.sessionCaptureOptOutOrFault(sessionId);
|
|
563
|
+
return s.record === undefined && (captureIndeterminate || s.fault);
|
|
564
|
+
},
|
|
565
|
+
flip: async (reason) => {
|
|
566
|
+
const entitlement = await resolveCaptureEntitlementFresh();
|
|
567
|
+
const verdict = adjudicateCaptureOptOut({ posture: capturePosture, entitlement, subject: "declaration" });
|
|
568
|
+
if (!verdict.allowed)
|
|
569
|
+
throw captureOptOutDeniedError(verdict.detail);
|
|
570
|
+
if (verdict.faultDisclosure !== undefined)
|
|
571
|
+
deps.onError?.(new Error(verdict.faultDisclosure), { phase: "config", sessionId });
|
|
572
|
+
if (input.captureCarrierUnsupported === true) {
|
|
573
|
+
const e = new Error(`memory capture opt-out cannot be honored on this deployment shape: the execution environment is remote/per-run ` +
|
|
574
|
+
`and no RunnerDeps.memoryCaptureRecordStore is supplied — the record would not survive to a resume replica. Nothing was recorded.`);
|
|
575
|
+
e.code = "config.memory_capture_unsupported";
|
|
576
|
+
throw e;
|
|
577
|
+
}
|
|
578
|
+
const trimmed = reason?.trim();
|
|
579
|
+
const outcome = writeEngine.markSessionCaptureOptOut(sessionId, trimmed !== undefined && trimmed !== "" ? `host flip verb: ${trimmed.slice(0, 200)}` : "host flip verb");
|
|
580
|
+
if (outcome === "unpersisted") {
|
|
581
|
+
deliverEngineNotice(deps.onNotice, memoryCaptureOptOutUnpersistedNotice({ sessionId, ingress: "flip-verb" }));
|
|
582
|
+
throw captureOptOutUnpersistedError(sessionId, "flip-verb");
|
|
583
|
+
}
|
|
584
|
+
captureOptedOut = true;
|
|
585
|
+
if (outcome === "created") {
|
|
586
|
+
deliverEngineNotice(deps.onNotice, memoryCaptureOptedOutNotice({ sessionId, origin: "flip-verb" }));
|
|
587
|
+
if (input.memoryCapturePolicyDeclared !== true && !undeclaredCapturePostureAnnounced) {
|
|
588
|
+
undeclaredCapturePostureAnnounced = true;
|
|
589
|
+
deps.onError?.(new Error(`a session memory-capture opt-out was honored under the DEFAULT capture posture (no RunnerDeps.memoryCapturePolicy configured) — ` +
|
|
590
|
+
`the switch is now in use; declare "open", "governed" or "capture-required" to make the deployment's stance explicit.`), { phase: "config", sessionId });
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const sweep = await writeEngine.sweepSessionCaptureResidue(writeHandle);
|
|
594
|
+
if (sweep.failures.length > 0) {
|
|
595
|
+
const named = sweep.failures
|
|
596
|
+
.slice(0, 5)
|
|
597
|
+
.map((f) => `${f.path} (${f.detail})`)
|
|
598
|
+
.join("; ");
|
|
599
|
+
const e = new Error(`memory capture opt-out is ON (the one-way record stands) but the boundary sweep could not contain ` +
|
|
600
|
+
`${sweep.failures.length} path(s) on the writable memory root — the named residue stays on the authoritative ` +
|
|
601
|
+
`plane until a retry or host action clears it: ${named}${sweep.failures.length > 5 ? "; …" : ""}`);
|
|
602
|
+
e.code = "memory.capture_optout_sweep_failed";
|
|
603
|
+
throw e;
|
|
604
|
+
}
|
|
605
|
+
return { outcome };
|
|
606
|
+
},
|
|
607
|
+
},
|
|
353
608
|
pollution: {
|
|
354
609
|
polluted: () => writeEngine.sessionPollution(sessionId),
|
|
355
610
|
markPolluted: (reason, cause) => {
|
|
@@ -409,7 +664,10 @@ export async function prepareMemory(input) {
|
|
|
409
664
|
if (typeof err.code === "string" &&
|
|
410
665
|
(err.code.startsWith("config.memory_scope") ||
|
|
411
666
|
err.code.startsWith("config.memory_project") ||
|
|
412
|
-
err.code === "config.memory_dual_root_overlap"
|
|
667
|
+
err.code === "config.memory_dual_root_overlap" ||
|
|
668
|
+
err.code === "memory.capture_optout_denied" ||
|
|
669
|
+
err.code === "memory.capture_optout_unpersisted" ||
|
|
670
|
+
err.code.startsWith("config.memory_capture"))) {
|
|
413
671
|
throw err;
|
|
414
672
|
}
|
|
415
673
|
deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "memory", sessionId });
|
|
@@ -427,6 +685,7 @@ export async function prepareMemory(input) {
|
|
|
427
685
|
}
|
|
428
686
|
let memoryBlock;
|
|
429
687
|
let seedFiles;
|
|
688
|
+
let recallDisciplineSegment;
|
|
430
689
|
if (memorySpec && memorySpec.enabled && !useMemoryEngine) {
|
|
431
690
|
const detail = "TaskSpec.memory is enabled but RunnerDeps.memoryBackend is not configured; memory is inactive for this task (the legacy memoryStore fallback was retired in design/138 S4 and the dep was removed in design/157 B19).";
|
|
432
691
|
deps.onError?.(new Error(detail), { phase: "config", sessionId });
|
|
@@ -434,7 +693,13 @@ export async function prepareMemory(input) {
|
|
|
434
693
|
if (memoryEngineSession) {
|
|
435
694
|
const injection = memoryEngineSession.inject();
|
|
436
695
|
let blockBody = injection.block;
|
|
437
|
-
if (
|
|
696
|
+
if (captureOptedOut) {
|
|
697
|
+
blockBody = [MEMORY_CAPTURE_OPTOUT_NOTICE, injection.index, injection.announceBlock].filter((s) => Boolean(s && s.trim())).join("\n\n");
|
|
698
|
+
}
|
|
699
|
+
else if (captureIndeterminate && input.rosterCanPersist) {
|
|
700
|
+
blockBody = [MEMORY_CAPTURE_INDETERMINATE_NOTICE, injection.index, injection.announceBlock].filter((s) => Boolean(s && s.trim())).join("\n\n");
|
|
701
|
+
}
|
|
702
|
+
else if (!input.rosterCanPersist && injection.instruction === "" && injection.readOnlyNotice === undefined) {
|
|
438
703
|
blockBody = blockBody.trim() ? `${MEMORY_READONLY_NOTICE}\n\n${blockBody}` : MEMORY_READONLY_NOTICE;
|
|
439
704
|
}
|
|
440
705
|
else if (!input.rosterCanPersist && injection.instruction !== "") {
|
|
@@ -449,16 +714,23 @@ export async function prepareMemory(input) {
|
|
|
449
714
|
!blockBody.trimEnd().endsWith(MEMORY_ANNOUNCEMENT_READONLY_CODA)) {
|
|
450
715
|
blockBody = `${blockBody}\n\n${MEMORY_ANNOUNCEMENT_READONLY_CODA}`;
|
|
451
716
|
}
|
|
717
|
+
if (captureOptedOut &&
|
|
718
|
+
input.rosterCanPersist &&
|
|
719
|
+
(injection.announceBlock?.trim() ?? "") !== "" &&
|
|
720
|
+
blockBody.includes(injection.announceBlock) &&
|
|
721
|
+
!blockBody.trimEnd().endsWith(MEMORY_ANNOUNCEMENT_CAPTURE_OPTOUT_CODA)) {
|
|
722
|
+
blockBody = `${blockBody}\n\n${MEMORY_ANNOUNCEMENT_CAPTURE_OPTOUT_CODA}`;
|
|
723
|
+
}
|
|
452
724
|
if (blockBody.trim())
|
|
453
725
|
memoryBlock = blockBody;
|
|
454
726
|
if (memoryTools !== undefined) {
|
|
455
|
-
|
|
456
|
-
memoryBlock = memoryBlock !== undefined ? `${memoryBlock}\n\n${
|
|
727
|
+
recallDisciplineSegment = memoryRecallDisciplineSegment(input.memoryProvenance, { markedEntriesServed: memoryMarkedEntriesServed });
|
|
728
|
+
memoryBlock = memoryBlock !== undefined ? `${memoryBlock}\n\n${recallDisciplineSegment}` : recallDisciplineSegment;
|
|
457
729
|
}
|
|
458
|
-
if (memoryEngineSession.handle.writeScope !== null && input.writeToolsMounted && input.rosterCanPersist) {
|
|
730
|
+
if (memoryEngineSession.handle.writeScope !== null && input.writeToolsMounted && input.rosterCanPersist && !captureOptedOut && !captureIndeterminate) {
|
|
459
731
|
memoryBlock = memoryBlock !== undefined ? `${memoryBlock}\n\n${MEMORY_PREFERENCE_DISCIPLINE}` : MEMORY_PREFERENCE_DISCIPLINE;
|
|
460
732
|
}
|
|
461
|
-
if (memoryBlock !== undefined && injection.indexSeed !== undefined && input.rosterCanPersist)
|
|
733
|
+
if (memoryBlock !== undefined && injection.indexSeed !== undefined && input.rosterCanPersist && !captureOptedOut && !captureIndeterminate)
|
|
462
734
|
seedFiles = [injection.indexSeed];
|
|
463
735
|
}
|
|
464
736
|
if (effectiveMemoryScopes === undefined) {
|
|
@@ -468,5 +740,14 @@ export async function prepareMemory(input) {
|
|
|
468
740
|
: { state: "none", reason: "disabled", scopes: [], writeScope: null }
|
|
469
741
|
: { state: "none", reason: "no-spec", scopes: [], writeScope: null };
|
|
470
742
|
}
|
|
471
|
-
return {
|
|
743
|
+
return {
|
|
744
|
+
memoryEngineSession,
|
|
745
|
+
memoryBlock,
|
|
746
|
+
admittedOrgScopes,
|
|
747
|
+
ownOrgVerdict,
|
|
748
|
+
effectiveMemoryScopes,
|
|
749
|
+
...(memoryTools !== undefined ? { memoryTools } : {}),
|
|
750
|
+
...(recallDisciplineSegment !== undefined ? { recallDisciplineSegment } : {}),
|
|
751
|
+
...(seedFiles !== undefined ? { seedFiles } : {}),
|
|
752
|
+
};
|
|
472
753
|
}
|