@sema-agent/core 7.22.0 → 7.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 +168 -1
- package/dist/core/auto-compaction.d.ts +2 -2
- package/dist/core/checkpoint-store.d.ts +1 -5
- package/dist/core/context-edit.js +1 -1
- package/dist/core/engine-notice.d.ts +13 -0
- package/dist/core/gate-bounded-await.d.ts +13 -3
- package/dist/core/gate-fold.js +3 -3
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/park-selfcheck.js +1 -1
- package/dist/core/path-identity.d.ts +11 -4
- package/dist/core/path-identity.js +5 -2
- package/dist/core/read-root-candidate.js +11 -1
- package/dist/core/runner/assemble-result.d.ts +2 -7
- package/dist/core/runner/assemble-result.js +25 -8
- package/dist/core/runner/contracts.d.ts +15 -15
- package/dist/core/runner/inherited-ask-grants.d.ts +2 -7
- package/dist/core/runner/inherited-ask-grants.js +1 -0
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-context-lane.d.ts +43 -1
- package/dist/core/runner/prepare-context-lane.js +25 -4
- package/dist/core/runner/prepare-task.js +2 -2
- package/dist/core/runner/prepare-turn-wiring.js +2 -2
- package/dist/core/runner/run-harness-handlers.js +4 -2
- package/dist/core/runner/run-recovery-lanes.js +6 -3
- package/dist/core/runner/run-telemetry-and-budget-seats.js +11 -1
- package/dist/core/runner/write-protection-station.d.ts +7 -25
- package/dist/core/runner/write-protection-station.js +18 -0
- package/dist/core/task-registry.d.ts +0 -6
- package/dist/core/task-result.d.ts +28 -10
- package/dist/core/types.d.ts +1 -1
- package/dist/core/write-protect.d.ts +64 -12
- package/dist/core/write-protect.js +88 -34
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -2
- package/dist/engine/execution-env/node-execution-env.js +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/tools/fs/fs-read.js +2 -1
- package/dist/tools/fs/fs-shared.d.ts +33 -7
- package/dist/tools/fs/fs-shared.js +12 -1
- package/dist/tools/fs/safety.d.ts +37 -0
- package/dist/tools/fs/safety.js +31 -3
- package/package.json +2 -2
- package/test/export-surface.snapshot.json +3 -1
- package/wire-changes/core@7.23.0.json +316 -0
|
@@ -23,6 +23,7 @@ export function consumeInheritedAskGrant(grants, onAsk, humanReviewRef, decision
|
|
|
23
23
|
waitMs: grant.waitMs,
|
|
24
24
|
decision: "allow",
|
|
25
25
|
toolName: req.toolName,
|
|
26
|
+
toolCallId: req.toolCallId,
|
|
26
27
|
...(toolArg !== undefined ? { toolArg } : {}),
|
|
27
28
|
});
|
|
28
29
|
const reused = { action: "allow", presentedInput: grant.presented };
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { type AgentHarness, type AgentMessage } from "../../internal/harness.js";
|
|
19
19
|
import type { Model } from "../../internal/llm.js";
|
|
20
|
-
import { type MicroCompactMachine, type OccurrenceIndex } from "../context-edit.js";
|
|
20
|
+
import { type ClearedProjectionLedger, type MicroCompactMachine, type OccurrenceFingerprint, type OccurrenceIndex } from "../context-edit.js";
|
|
21
21
|
import { type ToolResultStore } from "../tool-result-store.js";
|
|
22
22
|
import { type RunnerDeps } from "../types.js";
|
|
23
23
|
import type { Prepared, PreparedMicroCompact } from "./contracts.js";
|
|
24
24
|
import { type GitStatusLaneRef } from "./git-status-frame.js";
|
|
25
|
+
import type { ReadFileState } from "../../tools/fs/safety.js";
|
|
25
26
|
export interface PrepareContextLaneInput {
|
|
26
27
|
/** borrowed-readonly — the task model: the budgets, the trigger-window geometry and `charsPerToken` are read off it. */
|
|
27
28
|
model: Model;
|
|
@@ -56,6 +57,12 @@ export interface PrepareContextLaneInput {
|
|
|
56
57
|
requestLossyRef: {
|
|
57
58
|
current: boolean;
|
|
58
59
|
};
|
|
60
|
+
/** borrowed-mutable — the hands' read-state map, or undefined without hands. Writer here: a landed stale-result
|
|
61
|
+
* clear RETIRES the entries whose transmission it just replaced with a marker — the same retirement the compaction
|
|
62
|
+
* lanes run through the SAME function (`applyContextShrinkToReadFileState`), because a Read body taken off the wire
|
|
63
|
+
* is off the wire whichever mechanism took it. Without it a re-read of an unchanged file answers "content omitted"
|
|
64
|
+
* pointing at a cleared marker. */
|
|
65
|
+
readFileState?: ReadFileState;
|
|
59
66
|
/** borrowed-mutable — the trim-pressure flag (`Prepared.trimPressureRef`). Writer here: the guard chain's trim arm sets
|
|
60
67
|
* `droppedMessages`; the run loop's boundary reads and clears it. */
|
|
61
68
|
trimPressureRef: {
|
|
@@ -72,6 +79,38 @@ export interface PrepareContextLaneResult {
|
|
|
72
79
|
}
|
|
73
80
|
/** The M21 phase body — prepareTask's context-lane stretch, verbatim (see the module header). */
|
|
74
81
|
export declare function prepareContextLane(input: PrepareContextLaneInput): PrepareContextLaneResult;
|
|
82
|
+
/** Record a cc-machine pass's clears into the ledger + emit the G11
|
|
83
|
+
* frame (one frame per firing pass; the replay of these markers on later requests emits nothing —
|
|
84
|
+
* cardinality is per CLEAR, not per request). `trigger` names the arm: `"frontier"` = the
|
|
85
|
+
* proactive request-build pass, `"blocking"` = the slice-3 arm-A pre-guard pass (the machine's
|
|
86
|
+
* one shot when the frontier hook is off). Marker bytes are read back off the edited array
|
|
87
|
+
* (the machine composed them, ref/media notes included) so the ledger replays EXACTLY what went
|
|
88
|
+
* on the wire; an occurrence the index cannot key (pathological same-id-same-timestamp ambiguity)
|
|
89
|
+
* is simply not recorded — its clear stays request-local, the safe (full-text-returns) direction.
|
|
90
|
+
* `at.fp` is the occurrence's fingerprint in the PRE-CAP replayed view (the same bytes the next
|
|
91
|
+
* session rebuild presents) — the replay-side mis-paint guard's comparand. */
|
|
92
|
+
/**
|
|
93
|
+
* The ONE landing of a set of stale-result clears: the ledger entry that makes the marker survive every
|
|
94
|
+
* later rebuild, and the retirement of the read-state entries those transmissions carried. The pair is
|
|
95
|
+
* inseparable — a ledger entry WITHOUT the retirement leaves a dedup stub pointing at a marker — which
|
|
96
|
+
* is why it is a function rather than two statements repeated at each of the two clear sites (the
|
|
97
|
+
* frontier/blocking pass and the provider-rejection recovery arm, which used to write the ledger
|
|
98
|
+
* independently, so only one of them retired anything). Posture: a ledger entry means the marker is
|
|
99
|
+
* repainted on every later request build, so the occurrence is cleared for good even when the
|
|
100
|
+
* projection that produced it is discarded — which is why the retirement belongs here and not in the
|
|
101
|
+
* per-request observation buffer.
|
|
102
|
+
*/
|
|
103
|
+
export declare function landStaleResultClears(args: {
|
|
104
|
+
ledger: ClearedProjectionLedger;
|
|
105
|
+
cleared: ReadonlyArray<{
|
|
106
|
+
key: string;
|
|
107
|
+
marker: string;
|
|
108
|
+
groupCount: number;
|
|
109
|
+
fp: OccurrenceFingerprint;
|
|
110
|
+
toolCallId?: string;
|
|
111
|
+
}>;
|
|
112
|
+
readFileState?: ReadFileState;
|
|
113
|
+
}): void;
|
|
75
114
|
/** See {@link runGuardChain}. */
|
|
76
115
|
export interface GuardChainArgs {
|
|
77
116
|
/** The post-frontier view the chain starts from. */
|
|
@@ -94,6 +133,9 @@ export interface GuardChainArgs {
|
|
|
94
133
|
/** The r7 per-request observation buffer — the chain DROPS it on its early returns (a discarded
|
|
95
134
|
* provisional projection must not narrate itself). */
|
|
96
135
|
pendingProjectionObservations: Array<() => void>;
|
|
136
|
+
/** The hands' read-state map, or undefined without hands — arm A's clear retires the entries its
|
|
137
|
+
* cleared transmissions carried (see `PrepareContextLaneInput.readFileState`). */
|
|
138
|
+
readFileState?: ReadFileState;
|
|
97
139
|
}
|
|
98
140
|
export type GuardChainOutcome = {
|
|
99
141
|
earlyReturn: {
|
|
@@ -7,8 +7,9 @@ import { createOffloadPersist } from "../tool-result-store.js";
|
|
|
7
7
|
import { emitTrace } from "../trace.js";
|
|
8
8
|
import { deliverEngineNotice } from "../types.js";
|
|
9
9
|
import { applyGitFrameGuard } from "./git-status-frame.js";
|
|
10
|
+
import { applyContextShrinkToReadFileState } from "../../tools/fs/index.js";
|
|
10
11
|
export function prepareContextLane(input) {
|
|
11
|
-
const { model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef } = input;
|
|
12
|
+
const { model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef, readFileState } = input;
|
|
12
13
|
const editAt = editBudget(model);
|
|
13
14
|
const guardAt = guardBudget(model);
|
|
14
15
|
const microCompact = buildMicroCompactState(deps, model, sessionId, runId, offloadStore, microCompactKnob);
|
|
@@ -57,12 +58,13 @@ export function prepareContextLane(input) {
|
|
|
57
58
|
: {}),
|
|
58
59
|
});
|
|
59
60
|
if (ccPass !== undefined) {
|
|
60
|
-
recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: hostTaskId, trigger: "frontier" });
|
|
61
|
+
recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: hostTaskId, trigger: "frontier", ...(readFileState !== undefined ? { readFileState } : {}) });
|
|
61
62
|
}
|
|
62
63
|
const chain = await runGuardChain({
|
|
63
64
|
edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken, offloadStore,
|
|
64
65
|
sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
|
|
65
66
|
pendingProjectionObservations,
|
|
67
|
+
...(readFileState !== undefined ? { readFileState } : {}),
|
|
66
68
|
});
|
|
67
69
|
if ("earlyReturn" in chain) {
|
|
68
70
|
return chain.earlyReturn;
|
|
@@ -138,15 +140,34 @@ function buildMicroCompactState(deps, model, sessionId, runId, offloadStore, kno
|
|
|
138
140
|
...(offloadStore ? { offloadPersist: createOffloadPersist(offloadStore, sessionId, deps.onNotice) } : {}),
|
|
139
141
|
};
|
|
140
142
|
}
|
|
143
|
+
export function landStaleResultClears(args) {
|
|
144
|
+
const clearedToolCallIds = [];
|
|
145
|
+
for (const c of args.cleared) {
|
|
146
|
+
args.ledger.entries.set(c.key, { marker: c.marker, groupCount: c.groupCount, fp: c.fp });
|
|
147
|
+
if (c.toolCallId !== undefined)
|
|
148
|
+
clearedToolCallIds.push(c.toolCallId);
|
|
149
|
+
}
|
|
150
|
+
if (args.readFileState !== undefined) {
|
|
151
|
+
applyContextShrinkToReadFileState(args.readFileState, { kind: "tool-results-cleared", clearedToolCallIds });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
141
154
|
function recordFrontierClears(args) {
|
|
155
|
+
const landed = [];
|
|
142
156
|
for (const { index } of args.pass.clears) {
|
|
143
157
|
const at = args.index.keyAt(index);
|
|
144
158
|
const clearedMsg = args.edited[index];
|
|
145
159
|
const markerText = clearedMsg.content?.[0]?.text;
|
|
146
160
|
if (at !== undefined && typeof markerText === "string") {
|
|
147
|
-
|
|
161
|
+
landed.push({
|
|
162
|
+
key: at.key,
|
|
163
|
+
marker: markerText,
|
|
164
|
+
groupCount: at.groupCount,
|
|
165
|
+
fp: at.fp,
|
|
166
|
+
...(typeof clearedMsg.toolCallId === "string" ? { toolCallId: clearedMsg.toolCallId } : {}),
|
|
167
|
+
});
|
|
148
168
|
}
|
|
149
169
|
}
|
|
170
|
+
landStaleResultClears({ ledger: args.ledger, cleared: landed, ...(args.readFileState !== undefined ? { readFileState: args.readFileState } : {}) });
|
|
150
171
|
emitTrace(args.tracer, () => ({
|
|
151
172
|
kind: "context.mc_clear",
|
|
152
173
|
version: 1,
|
|
@@ -198,7 +219,7 @@ export async function runGuardChain(args) {
|
|
|
198
219
|
...(args.offloadStore ? { offload: { persist: createOffloadPersist(args.offloadStore, args.sessionId, deps.onNotice) } } : {}),
|
|
199
220
|
});
|
|
200
221
|
if (blockingPass !== undefined) {
|
|
201
|
-
recordFrontierClears({ pass: blockingPass, index: args.index, edited: blockingEdited, ledger: microCompact.ledger, tracer: deps.tracer, taskId, trigger: "blocking" });
|
|
222
|
+
recordFrontierClears({ pass: blockingPass, index: args.index, edited: blockingEdited, ledger: microCompact.ledger, tracer: deps.tracer, taskId, trigger: "blocking", ...(args.readFileState !== undefined ? { readFileState: args.readFileState } : {}) });
|
|
202
223
|
working = blockingEdited;
|
|
203
224
|
anchoredWorking = estimateContextTokens(working, charsPerToken).tokens;
|
|
204
225
|
}
|
|
@@ -702,11 +702,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
702
702
|
const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
|
|
703
703
|
const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef });
|
|
704
704
|
prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, writeProtectionCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash });
|
|
705
|
-
const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef });
|
|
705
|
+
const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}) });
|
|
706
706
|
const preparedHolder = {};
|
|
707
707
|
const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
|
|
708
708
|
await onceLedger.settle(session, announcedListingsRef);
|
|
709
|
-
const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
|
|
709
|
+
const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
|
|
710
710
|
rollback.commit();
|
|
711
711
|
const prepared = buildPrepared();
|
|
712
712
|
preparedHolder.current = prepared;
|
|
@@ -6,7 +6,7 @@ import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-det
|
|
|
6
6
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
7
7
|
import { refuseOutOfContractDecision } from "../tool-policy.js";
|
|
8
8
|
import { decodeTextBytes } from "../../tools/fs/encoding.js";
|
|
9
|
-
import {
|
|
9
|
+
import { applyContextShrinkToReadFileState, isReadDedupStubResult } from "../../tools/fs/index.js";
|
|
10
10
|
import { resolveKey } from "../../tools/fs/safety.js";
|
|
11
11
|
import { PROMPT_HASH_SALT } from "./prompt-hash-salt.js";
|
|
12
12
|
import { CHANGED_FILES_MTIME_EPS_MS } from "./turn-attachments.js";
|
|
@@ -98,7 +98,7 @@ export function prepareTurnWiring(input) {
|
|
|
98
98
|
.map(([path]) => path)
|
|
99
99
|
: undefined;
|
|
100
100
|
const onCompactionApplied = handsEnabled && readFileStateForCheckpoint
|
|
101
|
-
? (attachedComplete, preserveReadState) =>
|
|
101
|
+
? (attachedComplete, preserveReadState) => applyContextShrinkToReadFileState(readFileStateForCheckpoint, { kind: "summarized", attachedComplete, ...(preserveReadState !== undefined ? { preserveKeys: preserveReadState } : {}) })
|
|
102
102
|
: undefined;
|
|
103
103
|
const changedFilesReadDenied = denyNarrowingPolicy === undefined
|
|
104
104
|
? undefined
|
|
@@ -2,6 +2,7 @@ import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js"
|
|
|
2
2
|
import { forwardSafely } from "./forward-sink.js";
|
|
3
3
|
import { emitTrace } from "../trace.js";
|
|
4
4
|
import { primaryActivityArg } from "../arg-summary.js";
|
|
5
|
+
import { redactSecrets } from "../untrusted-egress.js";
|
|
5
6
|
import { readDegradation } from "../../brain/degrading.js";
|
|
6
7
|
import { OUTPUT_TOOL_NAME } from "./synthetic-tools.js";
|
|
7
8
|
import { usageCostMicroUsd } from "./usage-accounting.js";
|
|
@@ -157,8 +158,9 @@ export function createHarnessHandlers(input) {
|
|
|
157
158
|
rs.counters.repetitionCuts += 1;
|
|
158
159
|
else
|
|
159
160
|
rs.counters.repetitionSpared += 1;
|
|
161
|
+
const segment = redactSecrets(e.segment);
|
|
160
162
|
if (rs.counters.repetitionEvents.length < rs.counters.REPETITION_EVENTS_CAP) {
|
|
161
|
-
rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment
|
|
163
|
+
rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment });
|
|
162
164
|
}
|
|
163
165
|
emitTrace(rs.telemetry.tracer, () => ({
|
|
164
166
|
kind: "repetition.detected",
|
|
@@ -169,7 +171,7 @@ export function createHarnessHandlers(input) {
|
|
|
169
171
|
rule: e.rule,
|
|
170
172
|
period: e.period,
|
|
171
173
|
reps: e.reps,
|
|
172
|
-
segment
|
|
174
|
+
segment,
|
|
173
175
|
ts: Date.now(),
|
|
174
176
|
}));
|
|
175
177
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
|
|
2
2
|
import { planRejectionClears } from "../context-edit.js";
|
|
3
|
+
import { landStaleResultClears } from "./prepare-context-lane.js";
|
|
3
4
|
import { DEFAULT_COMPACTION_INSTRUCTIONS, maybeCompact } from "../auto-compaction.js";
|
|
4
5
|
import { emitTrace } from "../trace.js";
|
|
5
6
|
import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
|
|
@@ -145,9 +146,11 @@ export function runRecoveryLanes(input) {
|
|
|
145
146
|
}));
|
|
146
147
|
}
|
|
147
148
|
else {
|
|
148
|
-
|
|
149
|
-
prepared.microCompact.ledger
|
|
150
|
-
|
|
149
|
+
landStaleResultClears({
|
|
150
|
+
ledger: prepared.microCompact.ledger,
|
|
151
|
+
cleared: plan.cleared,
|
|
152
|
+
...(prepared.readFileState !== undefined ? { readFileState: prepared.readFileState } : {}),
|
|
153
|
+
});
|
|
151
154
|
emitTrace(rs.telemetry.tracer, () => ({
|
|
152
155
|
kind: "context.mc_clear",
|
|
153
156
|
version: 1,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { primaryActivityArg } from "../arg-summary.js";
|
|
1
2
|
import { remainingBudgetMicroUsd, remainingTokens } from "../checkpoint-store.js";
|
|
2
3
|
import { computeCostMicroUsd, isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
|
|
3
4
|
import { resolveModel } from "../roles.js";
|
|
@@ -19,7 +20,16 @@ export function runTelemetryAndBudgetSeats(input) {
|
|
|
19
20
|
prepared.humanReviewRef.count += 1;
|
|
20
21
|
prepared.humanReviewRef.totalWaitMs += waitMs;
|
|
21
22
|
const gateToolName = "toolName" in resume.cp.gate ? resume.cp.gate.toolName : undefined;
|
|
22
|
-
|
|
23
|
+
const gatedCall = resume.cp.pendingAction.kind === "tool_approval" ? resume.cp.pendingAction : undefined;
|
|
24
|
+
const gateToolArg = gatedCall === undefined ? undefined : primaryActivityArg(gatedCall.args);
|
|
25
|
+
prepared.humanReviewRef.gates.push({
|
|
26
|
+
kind: resume.cp.gate.kind,
|
|
27
|
+
waitMs,
|
|
28
|
+
...(decision !== undefined ? { decision } : {}),
|
|
29
|
+
...(gateToolName !== undefined ? { toolName: gateToolName } : {}),
|
|
30
|
+
...(gatedCall !== undefined ? { toolCallId: gatedCall.toolCallId } : {}),
|
|
31
|
+
...(gateToolArg !== undefined ? { toolArg: gateToolArg } : {}),
|
|
32
|
+
});
|
|
23
33
|
}
|
|
24
34
|
if (resume !== undefined && resume.outcome.gate === "plan_review" && resume.outcome.decision === "reject") {
|
|
25
35
|
prepared.planModeRef.active = true;
|
|
@@ -12,36 +12,18 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Two readers, one compile: the gate station mounts `check`, and the leg's wiring manifest reports
|
|
14
14
|
* `targetView`. Compiling twice would be two judges free to disagree about the seats they were handed.
|
|
15
|
+
*
|
|
16
|
+
* The SECOND absence this seat carries is the per-judgment one: a judge that HAS its target view but
|
|
17
|
+
* whose environment could not answer a particular target drops that view and judges by the spelling alone.
|
|
18
|
+
* That is a run-time fact, so it cannot be announced at compile like the one above — but it is a fact about
|
|
19
|
+
* the ENVIRONMENT rather than about any one write, so it is announced from here, once per leg per reason,
|
|
20
|
+
* and carries no call identity. This is the leg-scoped home the dedupe needs: the judge is compiled once
|
|
21
|
+
* per leg and every judgment of the leg goes through the check minted here.
|
|
15
22
|
*/
|
|
16
23
|
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
17
24
|
import type { RunnerDeps } from "../runner-deps.js";
|
|
18
25
|
import type { ToolRoster } from "../tool-roster.js";
|
|
19
26
|
import { type WriteProtectionMount } from "../write-protect.js";
|
|
20
|
-
/**
|
|
21
|
-
* Compile the leg's write-protection judge and announce a missing target view.
|
|
22
|
-
*
|
|
23
|
-
* Returns the mount, or `undefined` when the deployment emptied the table (`[]`): no judge, no tighten
|
|
24
|
-
* mounted at all, and nothing announced — an explicit opt-out is not a degrade. A garbage table refuses
|
|
25
|
-
* prepare loudly from here, as it did from the gate station before, so an unusable table cannot lie
|
|
26
|
-
* dormant on a write-tool-less task.
|
|
27
|
-
*
|
|
28
|
-
* The notice fires exactly once per prepared leg and only when a judge with NO target view sits beside at
|
|
29
|
-
* least one tool whose writes it covers. Coverage runs the judge's OWN per-call test ({@link
|
|
30
|
-
* protectivePathTargetOf}) rather than a copy of it, over every name a row DISPATCHES under — its own and
|
|
31
|
-
* its aliases, because the gate hands the judge the name the call was made with. A row that declares a
|
|
32
|
-
* path target is covered by that declaration under every name; a row that declares none is covered under
|
|
33
|
-
* whichever of its names the catalog gives a non-read slot to. Both halves are load-bearing: a tool named
|
|
34
|
-
* `Write` that declares nothing, and a tool aliased to `Write` that declares nothing, are each judged by
|
|
35
|
-
* spelling alone on a hands-less leg, and each would otherwise go unnamed by the very line that exists to
|
|
36
|
-
* name them. Reported by the ROW's own name — what the deployment mounted.
|
|
37
|
-
*
|
|
38
|
-
* SNAPSHOT BOUNDARY, deliberate and disclosed: the roster read here is the LEG-START one, the same one
|
|
39
|
-
* the wiring manifest reports and for the same reason — this is one announcement per prepared leg, not a
|
|
40
|
-
* subscription. A run that gains its FIRST covered write tool after prepare (a mid-run roster delta) is
|
|
41
|
-
* judged by the same spelling-only judge and is not announced; the leg's `writeProtection.targetView`
|
|
42
|
-
* still reports the judge's reading correctly, because the judge never gains an environment mid-run —
|
|
43
|
-
* only the covered SET can widen.
|
|
44
|
-
*/
|
|
45
27
|
export declare function mountWriteProtection(input: {
|
|
46
28
|
/** borrowed-readonly — the deployment seats: the table (`writeProtectedPaths`; absent ⇒ the default
|
|
47
29
|
* table, `[]` ⇒ opted out), the data root the memory library is materialized under
|
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import { deliverEngineNotice } from "../engine-notice.js";
|
|
2
2
|
import { protectivePathTargetOf } from "../tool-registry.js";
|
|
3
3
|
import { createWriteProtectionCheck } from "../write-protect.js";
|
|
4
|
+
const ABSENCE_REASON = { symlink: "unresolved-symlink", target: "unresolvable-target" };
|
|
4
5
|
export function mountWriteProtection(input) {
|
|
6
|
+
const announced = new Set();
|
|
5
7
|
const mount = createWriteProtectionCheck(input.deps.writeProtectedPaths, {
|
|
6
8
|
...(input.deps.memoryEngineDir !== undefined ? { dataRoot: input.deps.memoryEngineDir } : {}),
|
|
7
9
|
...(input.env !== undefined ? { env: input.env } : {}),
|
|
8
10
|
rootPath: input.rootPath,
|
|
11
|
+
onViewAbsent: (absence) => {
|
|
12
|
+
const reason = absence.unresolvedSymlink ? ABSENCE_REASON.symlink : ABSENCE_REASON.target;
|
|
13
|
+
if (announced.has(reason))
|
|
14
|
+
return;
|
|
15
|
+
announced.add(reason);
|
|
16
|
+
deliverEngineNotice(input.deps.onNotice, {
|
|
17
|
+
code: "config.write_protection_unresolved",
|
|
18
|
+
message: `This run's write-protection judge could not resolve a write target in its execution environment ` +
|
|
19
|
+
`(${reason === ABSENCE_REASON.symlink ? "a symlink whose real target is unknowable" : "the environment refused or errored on the spelling"}: ${absence.cause}). ` +
|
|
20
|
+
`A view that cannot answer does not vote, so that write was judged by its SPELLING against the table and nothing else — ` +
|
|
21
|
+
`a target reaching a protected path only through an alias, a bind mount or a ".." walk is cleared. Nothing about this run was ` +
|
|
22
|
+
`refused because of it, and nothing here is a defect in the write itself: an environment that cannot answer path questions is a ` +
|
|
23
|
+
`deployment fact. Announced once per leg per reason; later writes of the same shape are judged the same way, silently.`,
|
|
24
|
+
detail: { reason, cause: absence.cause, sessionId: input.sessionId, runId: input.runId },
|
|
25
|
+
});
|
|
26
|
+
},
|
|
9
27
|
});
|
|
10
28
|
if (mount === undefined || mount.targetView !== "spelling-only")
|
|
11
29
|
return mount;
|
|
@@ -185,10 +185,6 @@ export declare class TaskRegistry {
|
|
|
185
185
|
tupleIncomplete: true;
|
|
186
186
|
})>>;
|
|
187
187
|
};
|
|
188
|
-
/** design/151 §7.7 (F-13) — the row's mailbox dies with the row: a WINNING delete also drops
|
|
189
|
-
* the (scope, handle) mailbox (advisory — a mailbox fault never blocks the reap; an orphaned
|
|
190
|
-
* box is bounded by the mailbox's own `reap` policy). Structural face (drop only), so the
|
|
191
|
-
* deployment passes its `RunnerDeps.mailboxStore` directly. */
|
|
192
188
|
mailbox?: {
|
|
193
189
|
drop(scope: string, handle: string): Promise<void>;
|
|
194
190
|
};
|
|
@@ -634,8 +630,6 @@ export declare class TaskRegistry {
|
|
|
634
630
|
private pollBackgroundBash;
|
|
635
631
|
private pollWorkflow;
|
|
636
632
|
private stopBackgroundBash;
|
|
637
|
-
/** design/135 G2: TaskOutput over a monitor — a RE-READABLE spool snapshot (the watcher owns the env
|
|
638
|
-
* cursor, exactly the bash G2b spool posture): repeated polls return the same accumulated output. */
|
|
639
633
|
private pollMonitor;
|
|
640
634
|
private stopMonitor;
|
|
641
635
|
private stopWorkflow;
|
|
@@ -38,6 +38,30 @@ export type TaskStatus = "completed" | "blocked" | "failed" | "suspended" | "nee
|
|
|
38
38
|
* One remote-workspace lifecycle failure, reported as data on {@link TaskResult.remoteEnvFailures}.
|
|
39
39
|
* See that field for why the eleven-code taxonomy needed a structured channel to reach a caller at all.
|
|
40
40
|
*/
|
|
41
|
+
/**
|
|
42
|
+
* ONE `humanReview` ledger row — what `stats.humanReview.gates[]` carries, what the runner's live
|
|
43
|
+
* accumulator holds, and what a checkpoint persists. It lived as six structurally-identical inline
|
|
44
|
+
* copies, the persisted one NARROWER than the rest, so "does a row carry the call it gated?" had six
|
|
45
|
+
* answers and the persisted one said no while the bytes it stored said yes. One declaration now.
|
|
46
|
+
*/
|
|
47
|
+
export interface HumanReviewGateRow {
|
|
48
|
+
/** `kind` / `decision` / `toolName` / `toolArg` are documented once, on `stats.humanReview.gates`. */
|
|
49
|
+
kind: string;
|
|
50
|
+
waitMs: number;
|
|
51
|
+
decision?: string;
|
|
52
|
+
toolName?: string;
|
|
53
|
+
toolArg?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The id of the tool call this row gated — the row's IDENTITY, not a display field. Minted with the
|
|
56
|
+
* row at each of the four mint sites, never re-derived downstream: a run pushes rows from several
|
|
57
|
+
* mints while only one passes through the host's `onAsk`, so the two ledgers' counts are legitimately
|
|
58
|
+
* unequal and there was no key to reconcile on. Absent means one thing — the mint had no call id to
|
|
59
|
+
* give (a probe row; a durable gate whose checkpoint carries a non-`tool_approval` pending action,
|
|
60
|
+
* since a `resource_limit` / `needs_review` / `plan_review` pause gates no call; or an older engine).
|
|
61
|
+
* Never a placeholder.
|
|
62
|
+
*/
|
|
63
|
+
toolCallId?: string;
|
|
64
|
+
}
|
|
41
65
|
export interface RemoteEnvFailureNote {
|
|
42
66
|
/** Which VM-lifecycle call failed. */
|
|
43
67
|
op: "suspendVM" | "resumeVM" | "postResumeInit";
|
|
@@ -781,16 +805,10 @@ export interface TaskResult {
|
|
|
781
805
|
* `irreversible_ask` for durable; `human` for a synchronous ask). `decision` = the adjudication when
|
|
782
806
|
* known (`allow`/`deny`). design/99 MF-24: `toolName` = the gated tool; `toolArg` = a
|
|
783
807
|
* SHORT, SECRET-SCRUBBED one-line summary of its input (the same `primaryActivityArg` boundary as the MF-W
|
|
784
|
-
* activity arg) — the permission-denial ledger's "denied: Bash(rm …)" display. `toolArg`
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
gates:
|
|
788
|
-
kind: string;
|
|
789
|
-
waitMs: number;
|
|
790
|
-
decision?: string;
|
|
791
|
-
toolName?: string;
|
|
792
|
-
toolArg?: string;
|
|
793
|
-
}>;
|
|
808
|
+
* activity arg) — the permission-denial ledger's "denied: Bash(rm …)" display. `toolArg` rides every mint
|
|
809
|
+
* that has the input in scope: the synchronous ask from the request, the durable-resume gate from the
|
|
810
|
+
* checkpoint's `pendingAction`; `toolCallId` names the call the row gated (absent = not joinable). */
|
|
811
|
+
gates: HumanReviewGateRow[];
|
|
794
812
|
};
|
|
795
813
|
/**
|
|
796
814
|
* design/80 D-E-core: a thin FINANCE TAXONOMY of the LLM-derived costs the engine actually prices,
|
package/dist/core/types.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditR
|
|
|
18
18
|
export type { AgentDefinition, SkillManifest, SkillSpec } from "./agent-definition.js";
|
|
19
19
|
export type { McpServerSpec, A2aServerSpec, McpElicitRequest, McpElicitResponse, OnElicit } from "./mcp-server-spec.js";
|
|
20
20
|
export type { TaskLimits } from "./task-limits.js";
|
|
21
|
-
export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResult } from "./task-result.js";
|
|
21
|
+
export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResult, HumanReviewGateRow } from "./task-result.js";
|
|
22
22
|
export type { TerminalCause, PausedCause } from "./terminal-cause.js";
|
|
23
23
|
export type { TaskEventIdentity, BrainStatusPhase, BrainRetryErrClass, BrainStatus, ToolActivity, HumanInputSource, HumanInputDelivery, DelegationTaskType, TaskEvent, HumanInputEvent, CompactOutcome } from "./task-event.js";
|
|
24
24
|
export type { TaskStream } from "./task-stream.js";
|
|
@@ -30,13 +30,26 @@ export interface WriteProtectedHit {
|
|
|
30
30
|
}
|
|
31
31
|
export declare const WRITE_PROTECTED_DEFAULT_TABLE: readonly WriteProtectedRow[];
|
|
32
32
|
/**
|
|
33
|
-
* The ONE
|
|
34
|
-
* compile, path segments at match)
|
|
35
|
-
* (U+0131 dotless ı → i, U+017F long ſ → s)
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
* The ONE SEGMENT fold of this module, applied to BOTH sides of every comparison (table names at
|
|
34
|
+
* compile, path segments at match), in the reference implementation's own order:
|
|
35
|
+
* · `toLowerCase` plus the two confusable letters its fold maps (U+0131 dotless ı → i, U+017F long ſ → s);
|
|
36
|
+
* · the INVISIBLE FORMAT characters it strips — the zero-width joiners/non-joiners, the bidirectional
|
|
37
|
+
* overrides and the byte-order mark (U+200C–U+200F, U+202A–U+202E, U+206A–U+206F, U+FEFF), verbatim.
|
|
38
|
+
* A segment carrying one of these LOOKS like the row it is not, on both sides of every comparison, so
|
|
39
|
+
* the strip is a deception axis rather than a platform one: it applies to every reading below, not to
|
|
40
|
+
* the win32 one alone. It can only ADD matches, which on a TIGHTEN costs one extra question — and the
|
|
41
|
+
* data-root passthrough, where an added match would be a guard BYPASSED, deliberately does not use this
|
|
42
|
+
* fold at all (its comparison is byte-exact; see {@link relativeUnderDataRoot}).
|
|
43
|
+
* · …and a strip that would EMPTY the segment keeps the segment instead (the reference fold's own `|| n`):
|
|
44
|
+
* an all-invisible component names a real file, and folding it to "" would make it collide with nothing
|
|
45
|
+
* or with everything depending on the caller.
|
|
46
|
+
* Deliberately NOT full Unicode confusable folding: that would be a different, wider claim than the one made
|
|
47
|
+
* (the read deny set's ASCII-contract doc states the same boundary for its own fold). The NTFS data-stream
|
|
48
|
+
* suffix the reference fold also strips is a win32 NAMESPACE fact, not a comparison one, and lives with the
|
|
49
|
+
* other component normalization of those views ({@link ComponentNormalization}) so a POSIX file legally
|
|
50
|
+
* named `a:b` keeps its own identity on the POSIX reading.
|
|
38
51
|
*/
|
|
39
|
-
export declare function
|
|
52
|
+
export declare function foldWriteProtectSegment(s: string): string;
|
|
40
53
|
/**
|
|
41
54
|
* Resolve the ACTIVE table under the deployment seat (validated, loud — #123's bad-value states all
|
|
42
55
|
* throw and name the knob). `undefined` = the default table; `[]` = NO table (an explicit, legal
|
|
@@ -47,15 +60,32 @@ export declare function foldWriteProtectCase(s: string): string;
|
|
|
47
60
|
* candidate configuration with the engine's own rules.
|
|
48
61
|
*/
|
|
49
62
|
export declare function resolveWriteProtectedTable(entries?: readonly WriteProtectedEntry[]): readonly WriteProtectedRow[];
|
|
63
|
+
/**
|
|
64
|
+
* WHY the ENVIRONMENT half of one judgment could not contribute — the canonicalizer refused the spelling,
|
|
65
|
+
* a link the tree could not follow, a namespace spelling it declines, an environment call that threw.
|
|
66
|
+
* Reported through {@link WriteProtectionCompileOptions.onViewAbsent}; it is a DISCLOSURE, never a verdict
|
|
67
|
+
* (see the absence law on {@link WriteProtectionMatcher.judge}).
|
|
68
|
+
*/
|
|
69
|
+
export interface WriteProtectionViewAbsence {
|
|
70
|
+
/** The environment's own sentence, verbatim — what it said when asked to resolve this target. */
|
|
71
|
+
readonly cause: string;
|
|
72
|
+
/** The failure was specifically a symlink whose real target is unknowable, as opposed to a spelling the
|
|
73
|
+
* tree refuses or a probe that errored. */
|
|
74
|
+
readonly unresolvedSymlink: boolean;
|
|
75
|
+
}
|
|
50
76
|
/**
|
|
51
77
|
* The judge's answer for ONE covered write ({@link WriteProtectionMatcher.judge}):
|
|
52
|
-
* · `clear` — no row on
|
|
78
|
+
* · `clear` — no row on any view that could answer: the write is not this layer's business;
|
|
53
79
|
* · `hit` — a row matched; `via` says which view named it: `"spelling"` (the anchored spelling, no
|
|
54
80
|
* filesystem consulted), `"chain"` (a spelling the leaf symlink chain passes through, judged as a
|
|
55
81
|
* spelling) or `"target"` (the resolved real target, the spelling being innocent);
|
|
56
|
-
* · `unresolvable` — the
|
|
57
|
-
*
|
|
58
|
-
*
|
|
82
|
+
* · `unresolvable` — NO half of the judgment answered at all. The judge itself no longer mints this: an
|
|
83
|
+
* environment that cannot resolve the target has ANSWERED, and an answer of "I cannot resolve this"
|
|
84
|
+
* leaves the lexical half's judgment standing (the absence law below). What is left for this verdict is
|
|
85
|
+
* the judgment that never happened — the gate's bounded wait elapsing or the task ending before
|
|
86
|
+
* anything came back (`awaitWriteProtectionJudgment`, gate-bounded-await.ts) — where there is no
|
|
87
|
+
* lexical answer to stand on either, and the consumer REFUSES. `unresolvedSymlink` is that minter's
|
|
88
|
+
* `false`: a wait that produced nothing knows nothing about what it was waiting for.
|
|
59
89
|
*/
|
|
60
90
|
export type WriteProtectionVerdict = {
|
|
61
91
|
readonly kind: "clear";
|
|
@@ -115,8 +145,8 @@ export interface WriteProtectionMatcher {
|
|
|
115
145
|
* @contract write_protection.judges_the_real_target — the always-on write-protection table and the
|
|
116
146
|
* opt-in sensitive-path deny set differ in VOCABULARY (literal rows / segment globs) and SEVERITY
|
|
117
147
|
* (ask / deny), never in REACH: both judge the file the tool will write — the spelling anchored at the
|
|
118
|
-
* live cwd AND its resolved real target, through one canonicalizer
|
|
119
|
-
*
|
|
148
|
+
* live cwd AND its resolved real target, through one canonicalizer. Where they DO part: a target the write-protection
|
|
149
|
+
* judge cannot resolve leaves its resolved view ABSENT (the spelling still judges); the deny set still REFUSES whole. An environment-less judge has no target view and answers by the spelling alone —
|
|
120
150
|
* the same absence law the cwd follows (absent ⇒ the spelling is judged exactly as given). REACH is what
|
|
121
151
|
* this sentence promises, not a LOCK: the identity is resolved at judgment time and re-resolved by the
|
|
122
152
|
* tool at execute time (the module header states that window and the seat that would close it).
|
|
@@ -136,6 +166,17 @@ export interface WriteProtectionCompileOptions {
|
|
|
136
166
|
* path-family source when the call carries no cwd. Omitted ⇒ relative spellings resolve against the
|
|
137
167
|
* cwd, then the environment's own working directory. */
|
|
138
168
|
readonly rootPath?: string;
|
|
169
|
+
/**
|
|
170
|
+
* Called at most ONCE per judgment, at the moment the ENVIRONMENT half is found unable to contribute
|
|
171
|
+
* ({@link WriteProtectionViewAbsence}) — before the judgment returns, and on the hit arm as well as the
|
|
172
|
+
* clear one, because the fact is about the ENVIRONMENT rather than about the answer it did not change.
|
|
173
|
+
* Omitted ⇒ the absence is silent, which is the honest default for a judge compiled outside a leg.
|
|
174
|
+
* A throwing sink is the caller's bug and is not caught here; the leg that wires one wraps it.
|
|
175
|
+
*
|
|
176
|
+
* Deliberately carries no call identity: what it reports is a deployment-level fact (this environment
|
|
177
|
+
* cannot answer path questions), and its consumer announces once per leg rather than once per write.
|
|
178
|
+
*/
|
|
179
|
+
readonly onViewAbsent?: (absence: WriteProtectionViewAbsence) => void;
|
|
139
180
|
}
|
|
140
181
|
/**
|
|
141
182
|
* Compile the write-protection judge for a deployment configuration. Returns `undefined` when the
|
|
@@ -204,5 +245,16 @@ export interface WriteProtectionMount {
|
|
|
204
245
|
* environment cannot RESOLVE is the judge's own `unresolvable` verdict, not this null.
|
|
205
246
|
* Returns `undefined` when the resolved table is empty — nothing to judge, mount nothing.
|
|
206
247
|
* `opts` are the judge's own seats, passed straight through ({@link compileWriteProtection}).
|
|
248
|
+
*
|
|
249
|
+
* @contract write_protection.membership_is_the_call — WHICH calls the always-on table judges is a
|
|
250
|
+
* PREDICATE OVER THE CALL and nothing else: the call's own face declares a path target whose access is not
|
|
251
|
+
* a read, so a custom writer joins this judge by DECLARING what it writes and a tool that declares nothing
|
|
252
|
+
* is not this layer's business under any name. There is deliberately NO name-list seat beside it, and the
|
|
253
|
+
* asymmetry with the opt-in deny policy — which does take one (`createSensitivePathPolicy({ tools })`, its
|
|
254
|
+
* own pre-declaration hatch) — is the intended shape rather than a gap: a name list on a default-ON floor
|
|
255
|
+
* is a way to narrow that floor silently, one tool name at a time, while the seat this layer gives a
|
|
256
|
+
* deployment for narrowing is the whole-table replacement, which says what it removes. A deployment that
|
|
257
|
+
* wants name-list membership composes the deny policy alongside; the two then answer under the shared
|
|
258
|
+
* `deny > ask > allow` fold, which is what makes the missing seat cost nothing.
|
|
207
259
|
*/
|
|
208
260
|
export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?: WriteProtectionCompileOptions): WriteProtectionMount | undefined;
|