@sema-agent/core 7.24.4 → 7.25.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 +78 -0
- package/dist/agents/peer-session-drain.d.ts +16 -1
- package/dist/agents/peer-session-drain.js +16 -1
- package/dist/agents/send-message-tool.d.ts +10 -2
- package/dist/agents/send-message-tool.js +18 -7
- package/dist/agents/subagent.js +13 -10
- package/dist/core/background-agent-store.d.ts +3 -3
- package/dist/core/checkpoint-store.d.ts +1 -0
- package/dist/core/delegation-frames.d.ts +5 -0
- package/dist/core/gate-fold.js +85 -0
- package/dist/core/gate-lanes.js +23 -1
- package/dist/core/gate-outcome.d.ts +8 -2
- package/dist/core/gate-outcome.js +3 -1
- package/dist/core/hooks.d.ts +29 -0
- package/dist/core/hooks.js +2 -0
- package/dist/core/mailbox-store.d.ts +5 -0
- package/dist/core/mailbox-store.js +1 -1
- package/dist/core/parent-spec-seat.d.ts +7 -1
- package/dist/core/parent-spec-seat.js +2 -0
- package/dist/core/permission-rule-consent.js +29 -47
- package/dist/core/permission-rule-provider.js +4 -3
- package/dist/core/permission-rule-store.d.ts +143 -7
- package/dist/core/permission-rule-store.js +165 -15
- package/dist/core/runner/contracts.d.ts +36 -2
- package/dist/core/runner/gate-exit.d.ts +14 -0
- package/dist/core/runner/gate-exit.js +4 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +4 -4
- package/dist/core/runner/prepare-delegation-surface.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
- package/dist/core/runner/prepare-gate-stations.js +7 -4
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +25 -10
- package/dist/core/runner/prepare-inherited-gate.d.ts +23 -0
- package/dist/core/runner/prepare-inherited-gate.js +11 -6
- package/dist/core/runner/prepare-policy-chain.d.ts +15 -1
- package/dist/core/runner/prepare-policy-chain.js +14 -4
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -2
- package/dist/core/runner/prepare-suspend-saga.js +4 -3
- package/dist/core/runner/prepare-task.js +10 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +11 -4
- package/dist/core/runner/prepare-wiring-manifest.js +8 -4
- package/dist/core/runner/resume-apply.js +2 -0
- package/dist/core/runner/run-harness-handlers.js +1 -0
- package/dist/core/runner/run-identity-wiring.d.ts +2 -2
- package/dist/core/runner/run-leg.js +26 -0
- package/dist/core/runner/run-notification-lane.d.ts +2 -2
- package/dist/core/runner/run-notification-lane.js +2 -1
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/session-rule-policy.d.ts +3 -4
- package/dist/core/runner/session-rule-policy.js +11 -3
- package/dist/core/session-policy-store.d.ts +27 -3
- package/dist/core/session-policy-store.js +12 -4
- package/dist/core/store-contracts/background-agent-store-contract.js +10 -0
- package/dist/core/store-contracts/peer-directory-contract.d.ts +23 -0
- package/dist/core/store-contracts/peer-directory-contract.js +82 -0
- package/dist/core/task-event.d.ts +6 -0
- package/dist/core/task-notification.d.ts +79 -4
- package/dist/core/task-notification.js +90 -1
- package/dist/core/task-registry-agent.js +1 -2
- package/dist/core/task-registry-monitor.js +2 -1
- package/dist/core/task-registry-shared.d.ts +53 -5
- package/dist/core/task-registry-shared.js +13 -2
- package/dist/core/task-registry.js +8 -5
- package/dist/core/task-spec.d.ts +12 -5
- package/dist/core/tool-catalog-entries.js +1 -0
- package/dist/core/tool-spec.d.ts +1 -1
- package/dist/core/wiring-manifest.d.ts +2 -2
- package/dist/engine/loop/agent-loop.js +1 -1
- package/dist/engine/loop/types.d.ts +4 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +4 -3
- package/dist/prompts/coordinator.d.ts +2 -1
- package/dist/stores/file/adoption/adopt.js +23 -1
- package/dist/stores/file/permission-rule-store.js +3 -1
- package/dist/stores/file/session-policy-store.d.ts +31 -0
- package/dist/stores/file/session-policy-store.js +87 -7
- package/dist/tools/fs/bash-program-position.d.ts +4 -26
- package/dist/tools/fs/bash-program-position.js +0 -11
- package/dist/tools/fs/bash-readonly-classifier.d.ts +20 -309
- package/dist/tools/fs/bash-readonly-classifier.js +28 -766
- package/dist/tools/fs/fs-bash.d.ts +43 -207
- package/dist/tools/fs/fs-bash.js +20 -341
- package/dist/tools/fs/index.d.ts +12 -13
- package/dist/tools/fs/index.js +3 -4
- package/dist/tools/fs/read-deny.d.ts +25 -0
- package/dist/tools/fs/read-deny.js +19 -0
- package/dist/tools/fs/safety.js +2 -6
- package/dist/tools/fs/shell-read-check.d.ts +99 -0
- package/dist/tools/fs/shell-read-check.js +229 -0
- package/dist/tools/fs/shell-read-establish.d.ts +70 -115
- package/dist/tools/fs/shell-read-establish.js +216 -102
- package/dist/tools/fs/shell-read-judge.d.ts +231 -0
- package/dist/tools/fs/shell-read-judge.js +170 -0
- package/dist/tools/fs/shell-read-plan.d.ts +3 -0
- package/dist/tools/fs/shell-read-plan.js +82 -19
- package/dist/tools/monitor.d.ts +5 -5
- package/dist/tools/monitor.js +3 -5
- package/dist/tools/task-list.d.ts +4 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +19 -3
- package/wire-changes/core@7.25.0.json +671 -0
|
@@ -101,14 +101,25 @@ export function spoolDropNote(acct) {
|
|
|
101
101
|
return "[!] an unknown amount of earlier output was permanently dropped before this window (buffer evicted)\n";
|
|
102
102
|
return "";
|
|
103
103
|
}
|
|
104
|
+
export const TASK_OUTPUT_REPLAY_KINDS = ["full", "cursor"];
|
|
105
|
+
export const REPLAY_FACE = {
|
|
106
|
+
full: { stdout: "stdout (full, re-readable)", stdoutWhenGapped: "stdout (re-readable spool — INCOMPLETE, see note above)", stderr: "stderr" },
|
|
107
|
+
cursor: { stdout: "new stdout", stdoutWhenGapped: undefined, stderr: "new stderr" },
|
|
108
|
+
};
|
|
109
|
+
export const SPOOL_REPLAY = "full";
|
|
110
|
+
export const INCREMENT_REPLAY = "cursor";
|
|
111
|
+
export function stdoutHeadingOf(replay, gapDisclosed) {
|
|
112
|
+
const face = REPLAY_FACE[replay];
|
|
113
|
+
return gapDisclosed ? (face.stdoutWhenGapped ?? face.stdout) : face.stdout;
|
|
114
|
+
}
|
|
104
115
|
export function renderSpoolBody(spool, opts) {
|
|
105
116
|
const timedOutClause = spool.timedOut === true ? ` (timed out after ${spool.timeoutSec !== undefined ? `${spool.timeoutSec}s` : "its background time budget"})` : "";
|
|
106
117
|
const displayStatus = (spool.exitCode !== undefined ? `exited(code ${spool.exitCode})` : opts.running ? "running" : opts.handleStatus) + timedOutClause;
|
|
107
118
|
const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)${opts.rolledExtraClause}\n` : "";
|
|
108
119
|
const droppedNote = spoolDropNote(spool);
|
|
109
|
-
const stdoutHeading = droppedNote !== "" || opts.drainNote !== ""
|
|
120
|
+
const stdoutHeading = stdoutHeadingOf(SPOOL_REPLAY, droppedNote !== "" || opts.drainNote !== "");
|
|
110
121
|
return (`status: ${displayStatus}\n${rolledNote}${droppedNote}${opts.drainNote}--- ${stdoutHeading} ---\n${opts.shape(spool.stdout) || "(none)"}\n` +
|
|
111
|
-
`--- stderr ---\n${opts.shape(spool.stderr) || "(none)"}`);
|
|
122
|
+
`--- ${REPLAY_FACE[SPOOL_REPLAY].stderr} ---\n${opts.shape(spool.stderr) || "(none)"}`);
|
|
112
123
|
}
|
|
113
124
|
export function droppedGapNote(spool) {
|
|
114
125
|
if ((spool.droppedBytes ?? 0) > 0)
|
|
@@ -8,7 +8,7 @@ import { createSafeNotifier } from "./safe-notify.js";
|
|
|
8
8
|
import { stripCwdSentinel } from "../tools/fs/fs-shared.js";
|
|
9
9
|
import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-registry-monitor.js";
|
|
10
10
|
import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
|
|
11
|
-
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
11
|
+
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, stdoutHeadingOf, REPLAY_FACE, SPOOL_REPLAY, INCREMENT_REPLAY, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
12
12
|
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
13
13
|
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
|
|
14
14
|
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, recordBackgroundAgentOrgAdmissionLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, backgroundAgentCycleSeqLane, activeDelegationHandlesLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
@@ -1075,6 +1075,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1075
1075
|
details: {
|
|
1076
1076
|
status: running ? "running" : handle.status,
|
|
1077
1077
|
exitCode: spool.exitCode,
|
|
1078
|
+
replay: SPOOL_REPLAY,
|
|
1078
1079
|
...(spool.droppedBytes !== undefined ? { bytesDroppedBeforeCursor: spool.droppedBytes } : {}),
|
|
1079
1080
|
...(spool.timedOut === true ? { timedOut: true, ...(spool.timeoutSec !== undefined ? { timeoutSec: spool.timeoutSec } : {}) } : {}),
|
|
1080
1081
|
},
|
|
@@ -1099,12 +1100,13 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1099
1100
|
if (r.error.code === "not_found")
|
|
1100
1101
|
return noTask(requestedId, runningAgentFooterLane(this.core, access));
|
|
1101
1102
|
const retrieval_status = r.error.code === "timeout" ? "timeout" : "not_ready";
|
|
1102
|
-
const
|
|
1103
|
+
const carriedOutput = stdout.length > 0 || stderr.length > 0;
|
|
1104
|
+
const salvaged = carriedOutput
|
|
1103
1105
|
? `\n--- output accumulated before the error ---\n${clipTaskOutput(stdout, handle.outputFile)}${stderr ? `\n--- stderr ---\n${clipTaskOutput(stderr, handle.outputFile)}` : ""}`
|
|
1104
1106
|
: "";
|
|
1105
1107
|
return {
|
|
1106
1108
|
content: `Error (TaskOutput): ${r.error.message}${salvaged}`,
|
|
1107
|
-
details: { task_id: handle.id, type: "background_bash", status: handle.status, retrieval_status, error: r.error.code },
|
|
1109
|
+
details: { task_id: handle.id, type: "background_bash", status: handle.status, retrieval_status, ...(carriedOutput ? { details: { replay: INCREMENT_REPLAY } } : {}), error: r.error.code },
|
|
1108
1110
|
isError: true,
|
|
1109
1111
|
};
|
|
1110
1112
|
}
|
|
@@ -1125,8 +1127,8 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1125
1127
|
const displayStatus = (r.value.status === "exited" ? `exited(code ${r.value.exitCode})` : r.value.status) + timedOutClause;
|
|
1126
1128
|
const droppedNote = spoolDropNote(dropAcct) +
|
|
1127
1129
|
(rolledChars > 0 ? `[!] ${rolledChars} accumulated char(s) dropped from the middle during the blocked wait (memory bound)\n` : "");
|
|
1128
|
-
const body = `status: ${displayStatus}\n${droppedNote}---
|
|
1129
|
-
`---
|
|
1130
|
+
const body = `status: ${displayStatus}\n${droppedNote}--- ${stdoutHeadingOf(INCREMENT_REPLAY, droppedNote !== "")} ---\n${shape(stdout) || "(none)"}\n` +
|
|
1131
|
+
`--- ${REPLAY_FACE[INCREMENT_REPLAY].stderr} ---\n${shape(stderr) || "(none)"}`;
|
|
1130
1132
|
return {
|
|
1131
1133
|
content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
|
|
1132
1134
|
details: {
|
|
@@ -1139,6 +1141,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1139
1141
|
details: {
|
|
1140
1142
|
status: r.value.status,
|
|
1141
1143
|
exitCode: r.value.exitCode,
|
|
1144
|
+
replay: INCREMENT_REPLAY,
|
|
1142
1145
|
bytesDroppedBeforeCursor: dropAcct.droppedBytes ?? 0,
|
|
1143
1146
|
...(r.value.timedOut === true ? { timedOut: true, ...(r.value.timeoutSec !== undefined ? { timeoutSec: r.value.timeoutSec } : {}) } : {}),
|
|
1144
1147
|
},
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -783,9 +783,12 @@ export interface TaskSpec {
|
|
|
783
783
|
/**
|
|
784
784
|
* design/199 件B — TASK-layer ADDITIONS to the built-in sensitive-path READ deny set
|
|
785
785
|
* ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}). Judged by the
|
|
786
|
-
* structured read faces (Read/Grep/Glob/RepoMap, their traversals, the
|
|
787
|
-
*
|
|
788
|
-
* {@link
|
|
786
|
+
* structured read faces (Read/Grep/Glob/RepoMap, their traversals, the compaction attachment reader) and
|
|
787
|
+
* by the shell READ STATION under every doctrine — the full shell (`Bash`, `Monitor`, whatever
|
|
788
|
+
* {@link shellGate} says) and the `bash_readonly` shell mounted by {@link handsReadOnly} alike: a
|
|
789
|
+
* deny-listed shell read is REFUSED at the gate (`deniedBy: "read_boundary"`), never asked, with the same
|
|
790
|
+
* sentence the structured faces refuse with — one deny judge for every read face — in BOTH containment
|
|
791
|
+
* modes. Add-only at
|
|
789
792
|
* the task layer: entries here UNION with the active built-ins and the deployment's
|
|
790
793
|
* {@link RunnerDeps.readDenyPatterns}; `[]` ≡ absent (union identity); NOTHING at the task layer
|
|
791
794
|
* can remove a built-in — the one removal channel is the DEPLOYMENT's built-in configuration
|
|
@@ -873,8 +876,12 @@ export interface TaskSpec {
|
|
|
873
876
|
* - `"always"`: every `bash` command tightens to an `irreversible_ask` durable suspend (fail-closed; the
|
|
874
877
|
* correct default for an unattended/supervisor deployment with no classifier).
|
|
875
878
|
* - `"classify"`: mark `bash` `irreversibility:"maybe"` with {@link import("../tools/fs/index.js").bashReversibilityProbe}
|
|
876
|
-
* — a provably-benign single allowlisted read-only command (no operators)
|
|
877
|
-
* (writes, egress, pipes, unknown commands) tightens to ask.
|
|
879
|
+
* — the SHAPE classifier alone: a provably-benign single allowlisted read-only command (no operators)
|
|
880
|
+
* auto-allows; everything else (writes, egress, pipes, unknown commands) tightens to ask. WHERE a command
|
|
881
|
+
* reads is not this doctrine's question under any value: the gate's shell read station judges every
|
|
882
|
+
* shell call's read candidates against the deny set and the containment roots (a deny-listed read is
|
|
883
|
+
* refused, an out-of-root read is a mandated ask) whether `shellGate` is `"off"`, `"classify"` or
|
|
884
|
+
* `"always"`. An egress command (e.g. `curl`) suspends under
|
|
878
885
|
* the IRREVERSIBLE axis (the shell mark is irreversibility, not egress) — the kind is non-budgetable either
|
|
879
886
|
* way, so the budget resolver still never auto-approves it; the axis label is informational.
|
|
880
887
|
* NOT unconditional: a POSITIVE per-tool mark on the shell tool keeps its seat — an explicit
|
|
@@ -154,6 +154,7 @@ export const ENGINE_CARDS = Object.freeze({
|
|
|
154
154
|
a2a: "src/core/a2a.ts",
|
|
155
155
|
path_not_in_root: "src/tools/fs/safety.ts",
|
|
156
156
|
read_path_denied: "src/tools/fs/safety.ts",
|
|
157
|
+
readonly_out_of_root: "src/tools/fs/shell-read-check.ts",
|
|
157
158
|
});
|
|
158
159
|
const byId = new Map();
|
|
159
160
|
for (const e of TOOL_CATALOG_ENTRIES) {
|
package/dist/core/tool-spec.d.ts
CHANGED
|
@@ -1180,7 +1180,7 @@ export interface ToolExecuteContext {
|
|
|
1180
1180
|
*/
|
|
1181
1181
|
onTaskNotification?: (n: import("./task-notification.js").TaskNotificationPayload, opts?: {
|
|
1182
1182
|
priority?: import("./task-notification.js").SystemInjectionPriority;
|
|
1183
|
-
}) =>
|
|
1183
|
+
}) => unknown;
|
|
1184
1184
|
/**
|
|
1185
1185
|
* design/99 (nested-subagent live observability): the host run's OPT-IN display sink — a deployment sets it (via
|
|
1186
1186
|
* `RunInternals.onForwardEvent`) to receive a SUBAGENT's live `task_progress` ticks that otherwise stay in the
|
|
@@ -432,8 +432,8 @@ export interface WiringFacts {
|
|
|
432
432
|
parkDurableApprovalOptIn: boolean;
|
|
433
433
|
/** `undefined` = unresolved at this half (per-principal runtime caps are a prepare-time fact). */
|
|
434
434
|
parkForceDurableGate?: boolean;
|
|
435
|
-
/** `undefined` = unresolved at this half (the irreversible/egress tool vocabulary
|
|
436
|
-
* prepare). */
|
|
435
|
+
/** `undefined` = unresolved at this half (the irreversible/egress tool vocabulary and the shell read station —
|
|
436
|
+
* the three sources of a mandated ask — are assembled in prepare). */
|
|
437
437
|
parkSafetyVocabularyArmed?: boolean;
|
|
438
438
|
/** Present iff `parkCapable` (the resolved checkpoint store's declaration). */
|
|
439
439
|
checkpointDurability?: StoreDurability;
|
|
@@ -1325,7 +1325,7 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
1325
1325
|
};
|
|
1326
1326
|
}
|
|
1327
1327
|
if (beforeResult?.block) {
|
|
1328
|
-
const blocked = createErrorToolResult(beforeResult.reason || "Tool execution was blocked");
|
|
1328
|
+
const blocked = createErrorToolResult(beforeResult.reason || "Tool execution was blocked", beforeResult.details !== undefined ? { details: beforeResult.details } : undefined);
|
|
1329
1329
|
if (beforeResult.haltRemaining !== undefined) {
|
|
1330
1330
|
return { kind: "immediate", result: blocked, isError: true, halt: beforeResult.haltRemaining };
|
|
1331
1331
|
}
|
|
@@ -83,6 +83,10 @@ export type AgentToolCall = Extract<AssistantMessage["content"][number], {
|
|
|
83
83
|
export interface BeforeToolCallResult {
|
|
84
84
|
block?: boolean;
|
|
85
85
|
reason?: string;
|
|
86
|
+
/** With `block`: the structured details the refusing layer minted, attached to the error result the loop
|
|
87
|
+
* builds from `reason` (a `{ type, code, … }` card reaches `tool_end.structured` / `errorCode` exactly as a
|
|
88
|
+
* tool's own refusal would). Ignored without `block`. */
|
|
89
|
+
details?: unknown;
|
|
86
90
|
updatedInput?: unknown;
|
|
87
91
|
haltRemaining?: HaltRemainingDirective;
|
|
88
92
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -100,7 +100,7 @@ export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core
|
|
|
100
100
|
export { mintReminderMark, isValidReminderMark, openSystemReminder, mintSystemReminder, reminderMarkDeclaration } from "./core/reminder-mint.js";
|
|
101
101
|
export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
|
|
102
102
|
export type { InvariantKind, FunctionContract, Invariant, InvariantViolation, CheckResult, } from "./core/property-harness.js";
|
|
103
|
-
export { bashReversibilityProbe,
|
|
103
|
+
export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
|
|
104
104
|
export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, type BashReadonlyRootBoundary, type CompoundReadonlyVerdict, } from "./tools/fs/index.js";
|
|
105
105
|
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
106
106
|
export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, type ReadDenyBuiltinTier, type ReadDenyBuiltinRow, type ReadDenyBuiltinConfig, } from "./tools/fs/index.js";
|
|
@@ -132,7 +132,9 @@ export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./cor
|
|
|
132
132
|
export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE, type FsWriteGatePolicyOptions } from "./core/fs-write-gate-policy.js";
|
|
133
133
|
export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
|
|
134
134
|
export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
|
|
135
|
-
export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority,
|
|
135
|
+
export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority, type NotificationInjectionOutcome, // the injector's outcome words (`parked_durably` = the session's durable box took the frame)
|
|
136
|
+
NOTIFICATION_INJECTION_OUTCOMES, // …and their value half + guard, so a host wiring its own uplink validates a reported outcome without duplicating the vocabulary
|
|
137
|
+
isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, type SemaProvenance, } from "./core/task-notification.js";
|
|
136
138
|
export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, type ToolFamily, type ToolMountTag, type ToolApprovalCard, type ToolPathAccess, type ToolPathBase, type ToolPathAbsence, type ToolPathTarget, type ToolCallPathTarget, type ToolRosterPathTarget, pathTargetSlotOnly, type ToolRuleFace, type ToolRenderHints, type ToolFaceDeclaration, type ToolCatalogEntry, } from "./core/tool-face.js";
|
|
137
139
|
export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, type ToolCallLike, pathConfinableWriteToolNames, skillScopeWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
|
|
138
140
|
export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, type RosterSource, type ToolIdentity, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, type RosterBoundMembers, type RosterBoundCode, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
|
|
@@ -201,7 +203,7 @@ export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRul
|
|
|
201
203
|
* allowed and widening is not.
|
|
202
204
|
*/
|
|
203
205
|
export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, type ProgramRunReachOutcome, pathRuleReachOf, ruleBasesNeeded, PATH_RULE_BASES, PATH_RULE_BASE_LABEL, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, type PathRuleBase, type PathRuleBases, type RuleBehavior, type PersistedRuleVerdict, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, type UncoveredSegmentDetail, type EditedRuleBreadthWarning, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, type PersistedRule, type RuleTombstone, type RuleScope, type RuleDot, type RuleAdd, type RuleAddOrigin, type RuleOffer, type RuleOfferBatchMember, type SegmentRuleSuggestion, type SegmentCoverage, type RuleReject, type RuleRejectCode, type ParsedRule, type PersistedRuleTool, type PersistedRuleMatch, } from "./core/permission-rule-model.js";
|
|
204
|
-
export { removePersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, type DurableRulePartition, type DurableRulePartitionProvider, type RuleWriteOutcome, type StoredRules, type RemoveResult, type PutResult, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, type RuleSyncState, type RuleSyncFrontier, type RuleSyncDrop, type RuleSyncLandingReport, type RuleOwner, type QuarantinedRuleAdd, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, type PermissionRuleWriter, type WritableDurableRulePartition, type RuleWriteDelta, type RuleAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
|
|
206
|
+
export { removePersistedRule, addPersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, type DurableRulePartition, type DurableRulePartitionProvider, type RuleWriteOutcome, type StoredRules, type RemoveResult, type AddResult, type PutResult, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, type RuleSyncState, type RuleSyncFrontier, type RuleSyncDrop, type RuleSyncLandingReport, type RuleOwner, type QuarantinedRuleAdd, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, type PermissionRuleWriter, type WritableDurableRulePartition, type RuleWriteDelta, type RuleAddDelta, type RuleTightenAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
|
|
205
207
|
export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, type PermissionRuleSyncTransport, type PermissionRuleSyncResult, type RuleSyncRequestBody, type RuleSyncResponseBody, } from "./core/permission-rule-sync.js";
|
|
206
208
|
export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, type PermissionRuleStore, type PermissionRuleStoreProvider, type PermissionRuleStoreConfig, type EffectivePermissionRules, type EffectivePermissionRule, type RemovedPermissionRule, type RuleSource, } from "./core/permission-rule-provider.js";
|
|
207
209
|
export { InMemorySessionRulePartition, type SessionRulePartition, type SessionRuleAdd, type SessionRuleApplyResult, } from "./core/permission-rule-session.js";
|
|
@@ -277,6 +279,7 @@ export { workflowJournalStoreContract } from "./core/store-contracts/workflow-jo
|
|
|
277
279
|
export { permissionRuleSyncContract, type PermissionRuleSyncContractHooks } from "./core/store-contracts/permission-rule-sync-contract.js";
|
|
278
280
|
export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, mailboxTombstonedRecipientContract, type MailboxTombstonedRecipientContractHooks, mailboxCrossProcessContract, type MailboxCrossProcessContractHooks, } from "./core/store-contracts/mailbox-store-contract.js";
|
|
279
281
|
export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
|
|
282
|
+
export { PEER_DIRECTORY_CONTRACT_SCOPE, peerDirectoryContract, type PeerDirectoryContractHooks } from "./core/store-contracts/peer-directory-contract.js";
|
|
280
283
|
export { type BackgroundAgentStore, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentUsage, canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, type BackgroundAgentQuery, } from "./core/background-agent-store.js";
|
|
281
284
|
export { serveDurableAgentRowLane, buildAgentPollDetails, type AgentPollDetailsInput, } from "./core/task-registry-agent.js";
|
|
282
285
|
export type { UnifiedTaskResult } from "./core/task-registry-shared.js";
|
package/dist/index.js
CHANGED
|
@@ -78,7 +78,7 @@ export { runExecGate } from "./core/exec-gate.js";
|
|
|
78
78
|
export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core/untrusted-text.js";
|
|
79
79
|
export { mintReminderMark, isValidReminderMark, openSystemReminder, mintSystemReminder, reminderMarkDeclaration } from "./core/reminder-mint.js";
|
|
80
80
|
export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
|
|
81
|
-
export { bashReversibilityProbe,
|
|
81
|
+
export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
|
|
82
82
|
export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, } from "./tools/fs/index.js";
|
|
83
83
|
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
84
84
|
export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, } from "./tools/fs/index.js";
|
|
@@ -107,7 +107,7 @@ export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./cor
|
|
|
107
107
|
export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE } from "./core/fs-write-gate-policy.js";
|
|
108
108
|
export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
|
|
109
109
|
export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
|
|
110
|
-
export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, } from "./core/task-notification.js";
|
|
110
|
+
export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, NOTIFICATION_INJECTION_OUTCOMES, isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, } from "./core/task-notification.js";
|
|
111
111
|
export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, pathTargetSlotOnly, } from "./core/tool-face.js";
|
|
112
112
|
export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, pathConfinableWriteToolNames, skillScopeWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
|
|
113
113
|
export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
|
|
@@ -159,7 +159,7 @@ export { AUTO_MODE_ARMING_RECIPE_VERSION, autoModeArmingRecipeOf, sanitizeAutoMo
|
|
|
159
159
|
export { rebuildAutoModeDecider, } from "./core/auto-mode-rebuild.js";
|
|
160
160
|
export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, isNamespacedCoveringRuleName, namespacedRuleNameCovers, } from "./core/permission-rules.js";
|
|
161
161
|
export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, pathRuleReachOf, ruleBasesNeeded, PATH_RULE_BASES, PATH_RULE_BASE_LABEL, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, } from "./core/permission-rule-model.js";
|
|
162
|
-
export { removePersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, } from "./core/permission-rule-store.js";
|
|
162
|
+
export { removePersistedRule, addPersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, } from "./core/permission-rule-store.js";
|
|
163
163
|
export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, } from "./core/permission-rule-sync.js";
|
|
164
164
|
export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, } from "./core/permission-rule-provider.js";
|
|
165
165
|
export { InMemorySessionRulePartition, } from "./core/permission-rule-session.js";
|
|
@@ -233,6 +233,7 @@ export { workflowJournalStoreContract } from "./core/store-contracts/workflow-jo
|
|
|
233
233
|
export { permissionRuleSyncContract } from "./core/store-contracts/permission-rule-sync-contract.js";
|
|
234
234
|
export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, mailboxTombstonedRecipientContract, mailboxCrossProcessContract, } from "./core/store-contracts/mailbox-store-contract.js";
|
|
235
235
|
export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
|
|
236
|
+
export { PEER_DIRECTORY_CONTRACT_SCOPE, peerDirectoryContract } from "./core/store-contracts/peer-directory-contract.js";
|
|
236
237
|
export { canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, } from "./core/background-agent-store.js";
|
|
237
238
|
export { serveDurableAgentRowLane, buildAgentPollDetails, } from "./core/task-registry-agent.js";
|
|
238
239
|
export { FileBackgroundAgentStore } from "./stores/file/background-agent-store.js";
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* design/147 S3b — the teammate communication addendum (CC 2.1.212
|
|
3
3
|
* `TEAMMATE_SYSTEM_PROMPT_ADDENDUM`, pretty.js:322948, sema-ized): composed for a NAMED delegated
|
|
4
4
|
* child (`core/mode.teammate` section, admit = facts.teammateEnabled — the CC injection condition
|
|
5
|
-
* `agentId && agentName && teamName` maps to sema's named-spawn internals chain
|
|
5
|
+
* `agentId && agentName && teamName` maps to sema's named-spawn internals chain; sema keeps no team
|
|
6
|
+
* name — one implicit team per session, keyed by the root session). Divergences:
|
|
6
7
|
* CC's "task system"/"team lead" wording maps to sema's `"main"` reserved lane (the spawning
|
|
7
8
|
* conversation); and the capability sentences track the shipped contract (S2a
|
|
8
9
|
* delivered mid-run delivery): a message to a RUNNING teammate is queued and reaches it at its next
|
|
@@ -3,6 +3,7 @@ import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { adoptFilePermissionRuleStore } from "../permission-rule-adopt.js";
|
|
5
5
|
import { BootLock, atomicWriteFile, ensureDir, sanitizeScope } from "../fs-atomic.js";
|
|
6
|
+
import { SESSION_POLICY_MARK_SUFFIX, readSessionPolicyMark } from "../session-policy-store.js";
|
|
6
7
|
import { thrownText } from "../../../core/thrown-value.js";
|
|
7
8
|
import { AdoptionError, NOT_MIGRATED_BY_DESIGN, readRootAdoptionFile, writeRootAdoptionFile, } from "./marker.js";
|
|
8
9
|
const CONFIG_ACCOUNT_FILE = "adoption-configs.json";
|
|
@@ -194,6 +195,10 @@ function rewriteSessionPolicyRows(root, fromPrincipal, toPrincipal) {
|
|
|
194
195
|
for (const file of files) {
|
|
195
196
|
if (!file.endsWith(".json"))
|
|
196
197
|
continue;
|
|
198
|
+
if (file.endsWith(SESSION_POLICY_MARK_SUFFIX)) {
|
|
199
|
+
carrySessionPolicyMark(dir, tmpDir, file, fromPrincipal, toPrincipal);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
197
202
|
const source = join(dir, file);
|
|
198
203
|
let raw;
|
|
199
204
|
try {
|
|
@@ -218,8 +223,9 @@ function rewriteSessionPolicyRows(root, fromPrincipal, toPrincipal) {
|
|
|
218
223
|
continue;
|
|
219
224
|
if (typeof row.__sid !== "string")
|
|
220
225
|
continue;
|
|
226
|
+
const stem = sanitizeScope(JSON.stringify([row.__sid, toPrincipal]));
|
|
221
227
|
const rewritten = JSON.stringify({ ...row, __principal: toPrincipal });
|
|
222
|
-
const target = join(dir, `${
|
|
228
|
+
const target = join(dir, `${stem}.json`);
|
|
223
229
|
if (existsSync(target)) {
|
|
224
230
|
if (readFileSync(target, "utf8") === rewritten) {
|
|
225
231
|
removeIfPresent(source);
|
|
@@ -235,6 +241,22 @@ function rewriteSessionPolicyRows(root, fromPrincipal, toPrincipal) {
|
|
|
235
241
|
}
|
|
236
242
|
return rows;
|
|
237
243
|
}
|
|
244
|
+
function carrySessionPolicyMark(dir, tmpDir, file, fromPrincipal, toPrincipal) {
|
|
245
|
+
const source = join(dir, file);
|
|
246
|
+
const mark = readSessionPolicyMark(source);
|
|
247
|
+
if (mark === null)
|
|
248
|
+
return;
|
|
249
|
+
if (mark.__principal !== fromPrincipal)
|
|
250
|
+
return;
|
|
251
|
+
if (typeof mark.__sid !== "string")
|
|
252
|
+
return;
|
|
253
|
+
const target = join(dir, `${sanitizeScope(JSON.stringify([mark.__sid, toPrincipal]))}${SESSION_POLICY_MARK_SUFFIX}`);
|
|
254
|
+
const there = readSessionPolicyMark(target);
|
|
255
|
+
const merged = there === null ? mark.__hwm : Math.max(mark.__hwm, there.__hwm);
|
|
256
|
+
const mergedGen = Math.max(mark.__gen ?? 0, there?.__gen ?? 0);
|
|
257
|
+
atomicWriteFile(tmpDir, target, JSON.stringify({ ...mark, __principal: toPrincipal, __hwm: merged, __gen: mergedGen }));
|
|
258
|
+
removeIfPresent(source);
|
|
259
|
+
}
|
|
238
260
|
function removeIfPresent(path) {
|
|
239
261
|
try {
|
|
240
262
|
unlinkSync(path);
|
|
@@ -311,7 +311,9 @@ class FileDurableRulePartition {
|
|
|
311
311
|
}
|
|
312
312
|
const next = delta.kind === "redemption-add"
|
|
313
313
|
? (assertWriteDeltaScopeDurable(delta), assertRedemptionNotQuarantined(cur.quarantined ?? [], delta), { ...cur, rev: cur.rev + 1, rules: foldDelta(cur.rules, delta) })
|
|
314
|
-
:
|
|
314
|
+
: delta.kind === "tighten-add"
|
|
315
|
+
? (assertWriteDeltaScopeDurable(delta), { ...cur, rev: cur.rev + 1, rules: foldDelta(cur.rules, delta) })
|
|
316
|
+
: (assertWriteDeltaScopeDurable(delta), assertDeleteDeltaCarriesNoAdd(delta), { ...cur, rev: cur.rev + 1, tombstones: [...cur.tombstones, delta.tombstone] });
|
|
315
317
|
if (!this.writeAndVerify(next)) {
|
|
316
318
|
throw new Error("the permission-rule store did not survive its own write; the change was not committed");
|
|
317
319
|
}
|
|
@@ -15,6 +15,32 @@ export interface FileSessionPolicyStoreOptions {
|
|
|
15
15
|
* Never fires on plain ENOENT. Swallow-guarded: a throwing sink cannot re-introduce the fault. */
|
|
16
16
|
onCorruptRead?: (info: SessionPolicyCorruptReadInfo) => void;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* The suffix that tells a MARK file apart from a rules ROW in the shared directory — injective by
|
|
20
|
+
* construction, not by convention: `sanitizeScope` ends every row's basename with a 64-hex digest, so the
|
|
21
|
+
* character before a row's `.json` is always a hex digit and no row can be spelled `*.hwm.json`. Every sweep
|
|
22
|
+
* of this directory tests it FIRST: a mark must never be listed as a row, nor judged as crash litter.
|
|
23
|
+
*/
|
|
24
|
+
export declare const SESSION_POLICY_MARK_SUFFIX = ".hwm.json";
|
|
25
|
+
/** What one mark file carries: the number, plus the row's attribution fields (the filename is a one-way hash,
|
|
26
|
+
* so those are the only way a sweep can tell whose mark it is). Attribution is OPTIONAL on the read side —
|
|
27
|
+
* this store always writes it, a hand-written file need not, and a mark with no owner is simply nobody's. */
|
|
28
|
+
export interface StoredHighWaterMark {
|
|
29
|
+
__hwm: number;
|
|
30
|
+
/** The lineage the key's NEXT row starts (bumped by every erase); absent = 0 (a mark written before lineages). */
|
|
31
|
+
__gen?: number;
|
|
32
|
+
__sid?: string;
|
|
33
|
+
__principal?: string | null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The ONE reader of a mark file — the number it holds, or `null` when there is no file (the ordinary state of
|
|
37
|
+
* a key nobody erased). Every other outcome is a refusal, and that is the point: for a ROW "unreadable"
|
|
38
|
+
* degrades to "absent" and costs nothing, while for a mark absence reads as ZERO, which is precisely the
|
|
39
|
+
* rollback the erase contract forbids. Bytes that are not a mark raise `SessionPolicyError("corrupt")` naming
|
|
40
|
+
* the file; an IO failure propagates as it already does for a row. Exported because ownership adoption moves
|
|
41
|
+
* these files too, and a second, more tolerant reader over there is how the guarantee goes quietly missing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readSessionPolicyMark(path: string): StoredHighWaterMark | null;
|
|
18
44
|
export declare class FileSessionPolicyStore implements SessionPolicyStore {
|
|
19
45
|
private readonly dir;
|
|
20
46
|
private readonly onCorruptRead;
|
|
@@ -40,6 +66,11 @@ export declare class FileSessionPolicyStore implements SessionPolicyStore {
|
|
|
40
66
|
* composite key, so distinct keys never collide on disk). On a local-owner-adopted root the
|
|
41
67
|
* anonymous lane resolves to the adopted principal's key (#132 — see {@link anonymousAlias}). */
|
|
42
68
|
private pathFor;
|
|
69
|
+
/** The shared, injective stem of a key's two files — the row (`<stem>.json`) and its mark
|
|
70
|
+
* (`<stem>${MARK_SUFFIX}`), so the pair can never drift onto different keys. */
|
|
71
|
+
private basenameFor;
|
|
72
|
+
private markPathFor;
|
|
73
|
+
private writeMarkFile;
|
|
43
74
|
private read;
|
|
44
75
|
getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
|
|
45
76
|
putRules(sessionId: string, principal: string | undefined, rules: SessionPermissionRules, opts?: PutRulesOptions): Promise<StoredSessionRules>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { loosenReasons, normalizeRules, stripRev, SessionPolicyError, } from "../../core/session-policy-store.js";
|
|
4
4
|
import { atomicWriteFile, ensureDir, sanitizeScope } from "./fs-atomic.js";
|
|
@@ -9,6 +9,37 @@ function containSinkThenable(r) {
|
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
export const SESSION_POLICY_MARK_SUFFIX = ".hwm.json";
|
|
13
|
+
const MARK_SUFFIX = SESSION_POLICY_MARK_SUFFIX;
|
|
14
|
+
export function readSessionPolicyMark(path) {
|
|
15
|
+
let raw;
|
|
16
|
+
try {
|
|
17
|
+
raw = readFileSync(path, "utf8");
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err.code === "ENOENT")
|
|
21
|
+
return null;
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
const refuse = (fault) => {
|
|
25
|
+
throw new SessionPolicyError("corrupt", `session rules high-water mark ${fault}: ${path} — refusing rather than restart this key's rev from zero. Deleting that file is the repair, and it gives up the number it was holding.`);
|
|
26
|
+
};
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return refuse("is unparseable");
|
|
33
|
+
}
|
|
34
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
35
|
+
return refuse("is not a JSON object");
|
|
36
|
+
const mark = parsed;
|
|
37
|
+
if (typeof mark.__hwm !== "number" || !Number.isFinite(mark.__hwm))
|
|
38
|
+
return refuse("carries no numeric value");
|
|
39
|
+
if (mark.__gen !== undefined && (typeof mark.__gen !== "number" || !Number.isInteger(mark.__gen) || mark.__gen < 0))
|
|
40
|
+
return refuse("carries a lineage that is not a whole number");
|
|
41
|
+
return mark;
|
|
42
|
+
}
|
|
12
43
|
export class FileSessionPolicyStore {
|
|
13
44
|
dir;
|
|
14
45
|
onCorruptRead;
|
|
@@ -33,9 +64,19 @@ export class FileSessionPolicyStore {
|
|
|
33
64
|
this.disclose({ sessionId, ...(principal !== undefined ? { principal } : {}), path: this.pathFor(sessionId, principal), reason });
|
|
34
65
|
}
|
|
35
66
|
pathFor(sessionId, principal) {
|
|
67
|
+
return join(this.dir, `${this.basenameFor(sessionId, principal)}.json`);
|
|
68
|
+
}
|
|
69
|
+
basenameFor(sessionId, principal) {
|
|
36
70
|
const effective = principal ?? this.anonymousAlias;
|
|
37
71
|
const composite = JSON.stringify([sessionId, effective ?? null]);
|
|
38
|
-
return
|
|
72
|
+
return sanitizeScope(composite);
|
|
73
|
+
}
|
|
74
|
+
markPathFor(sessionId, principal) {
|
|
75
|
+
return join(this.dir, `${this.basenameFor(sessionId, principal)}${MARK_SUFFIX}`);
|
|
76
|
+
}
|
|
77
|
+
writeMarkFile(path, rev, sessionId, principal, gen) {
|
|
78
|
+
const mark = { __hwm: rev, __gen: gen, __sid: sessionId, __principal: principal };
|
|
79
|
+
atomicWriteFile(join(this.dir, "tmp"), path, JSON.stringify(mark));
|
|
39
80
|
}
|
|
40
81
|
read(sessionId, principal) {
|
|
41
82
|
let raw;
|
|
@@ -54,8 +95,12 @@ export class FileSessionPolicyStore {
|
|
|
54
95
|
return null;
|
|
55
96
|
}
|
|
56
97
|
const r = parsed;
|
|
57
|
-
if (
|
|
58
|
-
this.discloseCorrupt(sessionId, principal, "
|
|
98
|
+
if (typeof r.rev !== "number" || !Number.isFinite(r.rev) || r.rev < 1) {
|
|
99
|
+
this.discloseCorrupt(sessionId, principal, "rev is not a positive number");
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (r.gen !== undefined && (typeof r.gen !== "number" || !Number.isInteger(r.gen) || r.gen < 0)) {
|
|
103
|
+
this.discloseCorrupt(sessionId, principal, "gen is not a whole number");
|
|
59
104
|
return null;
|
|
60
105
|
}
|
|
61
106
|
const strArr = (x) => x === undefined || (Array.isArray(x) && x.every((e) => typeof e === "string"));
|
|
@@ -88,8 +133,13 @@ export class FileSessionPolicyStore {
|
|
|
88
133
|
throw new SessionPolicyError("loosen_forbidden", `a non-operator session-rule write may only tighten; refused: ${loosens.join("; ")}`);
|
|
89
134
|
}
|
|
90
135
|
}
|
|
91
|
-
const
|
|
92
|
-
|
|
136
|
+
const mark = readSessionPolicyMark(this.markPathFor(sessionId, principal));
|
|
137
|
+
const floor = Math.max(prior?.rev ?? 0, mark?.__hwm ?? 0);
|
|
138
|
+
const gen = prior?.gen ?? (existsSync(this.pathFor(sessionId, principal)) ? (mark?.__gen ?? 0) + 1 : (mark?.__gen ?? 0));
|
|
139
|
+
const next = { ...clean, rev: floor + 1, gen };
|
|
140
|
+
const attributedPrincipal = (principal ?? this.anonymousAlias) ?? null;
|
|
141
|
+
this.writeMarkFile(this.markPathFor(sessionId, principal), next.rev, sessionId, attributedPrincipal, gen);
|
|
142
|
+
atomicWriteFile(join(this.dir, "tmp"), this.pathFor(sessionId, principal), JSON.stringify({ ...next, __sid: sessionId, __principal: attributedPrincipal }));
|
|
93
143
|
return next;
|
|
94
144
|
}
|
|
95
145
|
async listBySession(sessionId) {
|
|
@@ -104,6 +154,8 @@ export class FileSessionPolicyStore {
|
|
|
104
154
|
}
|
|
105
155
|
const out = [];
|
|
106
156
|
for (const file of files) {
|
|
157
|
+
if (file.endsWith(MARK_SUFFIX))
|
|
158
|
+
continue;
|
|
107
159
|
if (!file.endsWith(".json"))
|
|
108
160
|
continue;
|
|
109
161
|
const full = join(this.dir, file);
|
|
@@ -149,6 +201,8 @@ export class FileSessionPolicyStore {
|
|
|
149
201
|
throw err;
|
|
150
202
|
}
|
|
151
203
|
for (const file of files) {
|
|
204
|
+
if (file.endsWith(MARK_SUFFIX))
|
|
205
|
+
continue;
|
|
152
206
|
if (!file.endsWith(".json"))
|
|
153
207
|
continue;
|
|
154
208
|
let raw;
|
|
@@ -163,6 +217,17 @@ export class FileSessionPolicyStore {
|
|
|
163
217
|
parsed = JSON.parse(raw);
|
|
164
218
|
}
|
|
165
219
|
catch {
|
|
220
|
+
const tornMarkPath = join(this.dir, `${file.slice(0, -".json".length)}${MARK_SUFFIX}`);
|
|
221
|
+
try {
|
|
222
|
+
const tornMark = readSessionPolicyMark(tornMarkPath);
|
|
223
|
+
if (tornMark !== null && tornMark.__sid === sessionId) {
|
|
224
|
+
this.writeMarkFile(tornMarkPath, tornMark.__hwm, sessionId, tornMark.__principal ?? null, (tornMark.__gen ?? 0) + 1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
if (!(err instanceof SessionPolicyError))
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
166
231
|
try {
|
|
167
232
|
unlinkSync(join(this.dir, file));
|
|
168
233
|
}
|
|
@@ -174,8 +239,23 @@ export class FileSessionPolicyStore {
|
|
|
174
239
|
}
|
|
175
240
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
176
241
|
continue;
|
|
177
|
-
|
|
242
|
+
const row = parsed;
|
|
243
|
+
if (row.__sid !== sessionId)
|
|
178
244
|
continue;
|
|
245
|
+
const markPath = join(this.dir, `${file.slice(0, -".json".length)}${MARK_SUFFIX}`);
|
|
246
|
+
let mark;
|
|
247
|
+
try {
|
|
248
|
+
mark = readSessionPolicyMark(markPath);
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
if (!(err instanceof SessionPolicyError))
|
|
252
|
+
throw err;
|
|
253
|
+
mark = undefined;
|
|
254
|
+
}
|
|
255
|
+
if (mark !== undefined) {
|
|
256
|
+
const rowRev = typeof row.rev === "number" && Number.isFinite(row.rev) ? row.rev : 0;
|
|
257
|
+
this.writeMarkFile(markPath, Math.max(mark?.__hwm ?? 0, rowRev), sessionId, row.__principal ?? null, (mark?.__gen ?? 0) + 1);
|
|
258
|
+
}
|
|
179
259
|
try {
|
|
180
260
|
unlinkSync(join(this.dir, file));
|
|
181
261
|
}
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* does (an option skipped, a chain member missed) is how one face used to vouch for a program another refused.
|
|
10
10
|
*/
|
|
11
11
|
import type { SegmentTokens } from "./bash-lexer.js";
|
|
12
|
-
import type { AssertAllKeysHandled } from "../../core/ask-origin.js";
|
|
13
12
|
/** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what
|
|
14
13
|
* they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */
|
|
15
14
|
export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
|
|
@@ -169,35 +168,14 @@ export declare function readProgramPosition(toks: SegmentTokens): ProgramPositio
|
|
|
169
168
|
* consumer; WHICH targets are accepted is the caller's declaration, not this reader's. */
|
|
170
169
|
export declare function argvReentryUnjudged(pos: ProgramPosition, acceptedTargets: ReadonlySet<string>): boolean;
|
|
171
170
|
/**
|
|
172
|
-
* The two ways this reader DECLINES rather than judges — a CLOSED set, because
|
|
173
|
-
*
|
|
174
|
-
*
|
|
171
|
+
* The two ways this reader DECLINES rather than judges — a CLOSED set (`docs/CLOSED-SETS.md`), because what a
|
|
172
|
+
* seat does with a declined segment is a ROW of the shell read decision table (`shell-read-judge.ts`, the
|
|
173
|
+
* `declined` row: the one row the seats disagree on), not a boolean buried in a caller.
|
|
174
|
+
* `unreadable_program_position`: the walk cannot name the program bash will run (`env -- ls`).
|
|
175
175
|
* `stream_fed_operands`: the operands arrive on a STREAM it never saw (`… | xargs cat`). Neither names a WORD.
|
|
176
176
|
*/
|
|
177
177
|
export declare const SHELL_SCAN_DECLINES: readonly ["unreadable_program_position", "stream_fed_operands"];
|
|
178
178
|
export type ShellScanDecline = (typeof SHELL_SCAN_DECLINES)[number];
|
|
179
|
-
/** The two SEAT TABLES over that set — ROW PER MEMBER behind a compile-time fence, registered in
|
|
180
|
-
* `docs/CLOSED-SETS.md` so `gate:closed-sets` is their runtime twin. The ARGUMENT for the rows is at the
|
|
181
|
-
* seats that read them (`fs-bash.ts`), which DERIVE the `Set` shape their readers take. Rows and not two
|
|
182
|
-
* sets because a derived set beside an empty literal made a third decline SILENTLY asymmetric, in the
|
|
183
|
-
* widening direction; a member with no row now reds `tsc` at both fences with the word spelled out. */
|
|
184
|
-
export declare const BOUNDARY_SEAT_READS_DECLINE: {
|
|
185
|
-
readonly unreadable_program_position: true;
|
|
186
|
-
readonly stream_fed_operands: true;
|
|
187
|
-
};
|
|
188
|
-
/** The fence over the read-boundary seat's table: `never` while every decline has a row. */
|
|
189
|
-
export type BoundarySeatTableCoversEveryDecline = AssertAllKeysHandled<Exclude<ShellScanDecline, keyof typeof BOUNDARY_SEAT_READS_DECLINE>>;
|
|
190
|
-
/** The classify doctrine's table — every row `false` BY THE ARGUMENT (stated at the seat), not by omission. */
|
|
191
|
-
export declare const CLASSIFY_SEAT_READS_DECLINE: {
|
|
192
|
-
readonly unreadable_program_position: false;
|
|
193
|
-
readonly stream_fed_operands: false;
|
|
194
|
-
};
|
|
195
|
-
/** The fence over the classify seat's table. */
|
|
196
|
-
export type ClassifySeatTableCoversEveryDecline = AssertAllKeysHandled<Exclude<ShellScanDecline, keyof typeof CLASSIFY_SEAT_READS_DECLINE>>;
|
|
197
|
-
/** A seat's table in the shape its readers take — the members it answers YES for. DERIVED, so the ROWS are
|
|
198
|
-
* the single source and a set can never say something its table does not. The parameter is TOTAL, not
|
|
199
|
-
* `Partial`: that stops a third seat being written `seatDeclineEvidence({})`, one level up from the fences. */
|
|
200
|
-
export declare function seatDeclineEvidence(table: Record<ShellScanDecline, boolean>): ReadonlySet<ShellScanDecline>;
|
|
201
179
|
/** Does a SHELL stand where this segment's program would be — as the program itself (`sh -c`), or as a launcher
|
|
202
180
|
* ROW handing its operands to one (`watch "…"`)? ONE predicate for the re-entry class, so the two spellings
|
|
203
181
|
* cannot be answered differently; which names are shells is the caller's declaration. Not a decline. */
|
|
@@ -159,17 +159,6 @@ export function argvReentryUnjudged(pos, acceptedTargets) {
|
|
|
159
159
|
return runs === undefined || !acceptedTargets.has(runs);
|
|
160
160
|
}
|
|
161
161
|
export const SHELL_SCAN_DECLINES = ["unreadable_program_position", "stream_fed_operands"];
|
|
162
|
-
export const BOUNDARY_SEAT_READS_DECLINE = {
|
|
163
|
-
unreadable_program_position: true,
|
|
164
|
-
stream_fed_operands: true,
|
|
165
|
-
};
|
|
166
|
-
export const CLASSIFY_SEAT_READS_DECLINE = {
|
|
167
|
-
unreadable_program_position: false,
|
|
168
|
-
stream_fed_operands: false,
|
|
169
|
-
};
|
|
170
|
-
export function seatDeclineEvidence(table) {
|
|
171
|
-
return new Set(SHELL_SCAN_DECLINES.filter((decline) => table[decline] === true));
|
|
172
|
-
}
|
|
173
162
|
export function programPositionRunsShell(pos, shellProgram) {
|
|
174
163
|
if (pos.refusal !== undefined)
|
|
175
164
|
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell === true;
|