@sema-agent/core 7.53.0 → 7.54.1
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 +291 -1
- package/LICENSE +3 -1
- package/dist/agents/peer-session-drain.d.ts +0 -1
- package/dist/agents/peer-session-drain.js +0 -2
- package/dist/agents/send-message-tool.js +3 -4
- package/dist/agents/subagent.d.ts +3 -5
- package/dist/agents/subagent.js +18 -11
- package/dist/agents/tool-filter.d.ts +7 -4
- package/dist/agents/tool-filter.js +7 -3
- package/dist/core/a2a.js +4 -2
- package/dist/core/allow-entry.d.ts +1 -0
- package/dist/core/allow-entry.js +1 -0
- package/dist/core/approval-object.d.ts +87 -0
- package/dist/core/approval-object.js +85 -0
- package/dist/core/arg-summary.d.ts +3 -2
- package/dist/core/arg-summary.js +17 -11
- package/dist/core/ask-origin.d.ts +43 -27
- package/dist/core/ask-origin.js +20 -11
- package/dist/core/ask-question.d.ts +0 -1
- package/dist/core/ask-question.js +4 -67
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/auto-compaction.d.ts +49 -50
- package/dist/core/auto-compaction.js +41 -51
- package/dist/core/auto-mode-prompt.d.ts +4 -2
- package/dist/core/auto-mode-prompt.js +4 -3
- package/dist/core/canonical-json.d.ts +69 -0
- package/dist/core/canonical-json.js +130 -8
- package/dist/core/checkpoint-requirements.d.ts +13 -7
- package/dist/core/checkpoint-requirements.js +4 -1
- package/dist/core/checkpoint-store.d.ts +33 -18
- package/dist/core/checkpoint-store.js +5 -4
- package/dist/core/child-clamp-seats.d.ts +45 -6
- package/dist/core/child-clamp-seats.js +56 -4
- package/dist/core/consolidate-scope.d.ts +1 -1
- package/dist/core/cross-boundary-taint.d.ts +213 -0
- package/dist/core/cross-boundary-taint.js +196 -0
- package/dist/core/decision-receipt.d.ts +44 -1
- package/dist/core/decision-receipt.js +16 -0
- package/dist/core/effective-path-target.d.ts +3 -0
- package/dist/core/effective-path-target.js +4 -0
- package/dist/core/engine-notice.d.ts +34 -2
- package/dist/core/engine-notice.js +17 -1
- package/dist/core/engine-storage-backend.d.ts +73 -0
- package/dist/core/engine-storage-backend.js +1 -0
- package/dist/core/file-history-store.d.ts +6 -2
- package/dist/core/file-history-store.js +1 -0
- package/dist/core/gate-fold.js +10 -2
- package/dist/core/gate-lanes.js +30 -21
- package/dist/core/gate-outcome.d.ts +7 -3
- package/dist/core/governance-codes.d.ts +7 -2
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/hooks.d.ts +14 -14
- package/dist/core/hooks.js +1 -2
- package/dist/core/locked-config.d.ts +28 -1
- package/dist/core/locked-config.js +30 -3
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mcp-server-spec.d.ts +66 -9
- package/dist/core/mcp-server-spec.js +52 -19
- package/dist/core/mcp.js +5 -4
- package/dist/core/memory-engine/engine.js +2 -1
- package/dist/core/memory-engine/export-bundle.d.ts +1 -1
- package/dist/core/memory-engine/export-bundle.js +1 -1
- package/dist/core/memory-engine/file-backend.d.ts +2 -6
- package/dist/core/memory-engine/file-backend.js +1 -12
- package/dist/core/memory.d.ts +3 -3
- package/dist/core/mode-policy.d.ts +13 -8
- package/dist/core/mode-policy.js +3 -2
- package/dist/core/mods/kernel/receive.d.ts +2 -1
- package/dist/core/mods/kernel/receive.js +7 -2
- package/dist/core/parent-spec-seat.d.ts +10 -13
- package/dist/core/parent-spec-seat.js +14 -6
- package/dist/core/pause-registry.d.ts +35 -1
- package/dist/core/pause-registry.js +10 -0
- package/dist/core/permission-rule-consent.d.ts +18 -5
- package/dist/core/permission-rule-consent.js +13 -8
- package/dist/core/permission-rule-model.d.ts +29 -6
- package/dist/core/permission-rule-model.js +6 -1
- package/dist/core/permission-rule-org.d.ts +37 -0
- package/dist/core/permission-rule-org.js +34 -42
- package/dist/core/permission-rule-session.js +2 -2
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +1 -0
- package/dist/core/permission-rule-sync.js +3 -2
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/read-only-shell.js +30 -9
- package/dist/core/remote-env.d.ts +41 -1
- package/dist/core/remote-env.js +18 -0
- package/dist/core/resume-startup-cause.d.ts +18 -0
- package/dist/core/resume-startup-cause.js +12 -0
- package/dist/core/runner/approval-feedback.d.ts +46 -0
- package/dist/core/runner/approval-feedback.js +41 -0
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.js +2 -2
- package/dist/core/runner/contracts.d.ts +33 -41
- package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
- package/dist/core/runner/cross-boundary-lane.js +190 -0
- package/dist/core/runner/decide-continuation.d.ts +4 -4
- package/dist/core/runner/decide-continuation.js +3 -1
- package/dist/core/runner/gate-exit.d.ts +32 -12
- package/dist/core/runner/gate-exit.js +75 -33
- package/dist/core/runner/inherited-ask-grants.js +2 -2
- package/dist/core/runner/person-path-rows.d.ts +2 -2
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +7 -9
- package/dist/core/runner/prepare-acquire-reconcile.js +8 -6
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
- package/dist/core/runner/prepare-config-doors.d.ts +12 -15
- package/dist/core/runner/prepare-config-doors.js +8 -3
- package/dist/core/runner/prepare-context-lane.js +6 -3
- package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
- package/dist/core/runner/prepare-delegation-surface.js +2 -3
- package/dist/core/runner/prepare-execution-env.d.ts +1 -2
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
- package/dist/core/runner/prepare-gate-stations.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
- package/dist/core/runner/prepare-hands-readface.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-park-ask.d.ts +1 -1
- package/dist/core/runner/prepare-park-ask.js +9 -3
- package/dist/core/runner/prepare-policy-chain.js +2 -7
- package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
- package/dist/core/runner/prepare-safety-scan.js +34 -27
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
- package/dist/core/runner/prepare-suspend-saga.js +6 -2
- package/dist/core/runner/prepare-task.js +13 -10
- package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
- package/dist/core/runner/prepare-turn-wiring.js +14 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/prepare-workspace-restore.js +2 -2
- package/dist/core/runner/resume-admission.js +9 -9
- package/dist/core/runner/resume-apply.js +4 -4
- package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
- package/dist/core/runner/resume-policy-outcome.js +18 -15
- package/dist/core/runner/resume-preflight.js +2 -2
- package/dist/core/runner/resume-review-outcome.js +3 -3
- package/dist/core/runner/run-leg.js +57 -33
- package/dist/core/runner/run-settle-and-teardown.js +2 -0
- package/dist/core/runner/run-terminal-adoption.js +15 -4
- package/dist/core/runner/session-file-state-replay.d.ts +5 -2
- package/dist/core/runner/session-file-state-replay.js +3 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/tool-face-overlay.d.ts +24 -6
- package/dist/core/runner/tool-face-overlay.js +24 -15
- package/dist/core/runner-deps.d.ts +9 -9
- package/dist/core/session-store.d.ts +35 -16
- package/dist/core/session-store.js +20 -12
- package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
- package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
- package/dist/core/shell-wrapper-table.d.ts +5 -3
- package/dist/core/shell-wrapper-table.js +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
- package/dist/core/store-contracts/session-store-contract.js +683 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
- package/dist/core/task-event.d.ts +6 -4
- package/dist/core/task-registry-agent.js +1 -0
- package/dist/core/task-registry-shared.d.ts +5 -0
- package/dist/core/task-registry.d.ts +7 -0
- package/dist/core/task-registry.js +17 -0
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +6 -6
- package/dist/core/tighten-task-spec.d.ts +6 -4
- package/dist/core/tighten-task-spec.js +2 -2
- package/dist/core/tool-catalog-entries.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +52 -52
- package/dist/core/tool-face-reader.d.ts +49 -0
- package/dist/core/tool-face-reader.js +94 -0
- package/dist/core/tool-face.d.ts +17 -8
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +64 -23
- package/dist/core/tool-policy.js +62 -56
- package/dist/core/tool-roster.d.ts +46 -28
- package/dist/core/tool-roster.js +91 -14
- package/dist/core/tool-spec-members.js +2 -2
- package/dist/core/tool-spec.d.ts +20 -4
- package/dist/core/tools.js +2 -0
- package/dist/core/trace.d.ts +24 -2
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +7 -0
- package/dist/core/untrusted-text.js +3 -8
- package/dist/core/user-attachment.d.ts +43 -0
- package/dist/core/user-attachment.js +92 -15
- package/dist/engine/compaction/compaction.d.ts +2 -2
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/harness/types.d.ts +7 -4
- package/dist/engine/llm/types.d.ts +7 -2
- package/dist/engine/llm/types.js +2 -1
- package/dist/engine/session/parent-chain-guard.d.ts +10 -0
- package/dist/engine/session/parent-chain-guard.js +10 -0
- package/dist/engine/session/repo-utils.d.ts +5 -0
- package/dist/engine/session/repo-utils.js +9 -0
- package/dist/engine/session/storage-base.js +5 -2
- package/dist/index.d.ts +23 -9
- package/dist/index.js +19 -8
- package/dist/internal/harness.d.ts +1 -0
- package/dist/internal/harness.js +1 -0
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +1 -0
- package/dist/orchestration/workflow-governance.d.ts +1 -1
- package/dist/orchestration/workflow.d.ts +3 -0
- package/dist/orchestration/workflow.js +2 -1
- package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
- package/dist/prompt-assembly/catalog/schema.js +3 -0
- package/dist/prompt-assembly/overlay.js +2 -2
- package/dist/prompt-assembly/select.js +2 -2
- package/dist/prompts/default.d.ts +1 -1
- package/dist/shell/lex.d.ts +87 -8
- package/dist/shell/lex.js +283 -49
- package/dist/stores/file/index.d.ts +8 -6
- package/dist/tools/artifact/artifact-tool.js +10 -0
- package/dist/tools/fs/bash-program-position.d.ts +3 -15
- package/dist/tools/fs/bash-program-position.js +3 -10
- package/dist/tools/fs/bash-readonly-classifier.js +2 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-bash.js +72 -101
- package/dist/tools/fs/fs-read.d.ts +12 -0
- package/dist/tools/fs/fs-read.js +10 -9
- package/dist/tools/fs/fs-shared.d.ts +27 -31
- package/dist/tools/fs/fs-shared.js +12 -15
- package/dist/tools/fs/fs-write.js +1 -1
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/read-deny-home.d.ts +1 -1
- package/dist/tools/fs/read-deny-home.js +5 -4
- package/dist/tools/fs/read-deny.d.ts +16 -3
- package/dist/tools/fs/read-deny.js +12 -9
- package/dist/tools/fs/safety.d.ts +7 -6
- package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
- package/dist/tools/fs/shell-read-bindings.js +15 -16
- package/dist/tools/fs/shell-read-device.js +1 -1
- package/dist/tools/fs/shell-read-plan.js +36 -47
- package/dist/tools/fs/shell-read-word.d.ts +1 -6
- package/dist/tools/fs/shell-read-word.js +2 -20
- package/dist/tools/fs/shell-reentry-text.js +1 -1
- package/dist/tools/web.d.ts +2 -2
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +5654 -5520
- package/wire-changes/core@7.53.0.json +30 -8
- package/wire-changes/core@7.54.0.json +2870 -0
- package/wire-changes/core@7.54.1.json +37 -0
- package/dist/core/shell-scan.d.ts +0 -60
- package/dist/core/shell-scan.js +0 -183
|
@@ -1,62 +1,74 @@
|
|
|
1
|
+
import { arithmeticTextIsPlain, compoundValueHidesRun, expansionHidesRun, heredocBodyHidesRun, lexShell, namesRedirectDescriptor, PARSE_FAILURE_SENTENCE, } from "../shell/lex.js";
|
|
1
2
|
import { shellWordsOf } from "../shell/shell-words.js";
|
|
3
|
+
import { isLauncherAssignmentOperand } from "../tools/fs/bash-program-position.js";
|
|
4
|
+
import { extglobOpenerIn } from "../tools/fs/bash-readonly-classifier.js";
|
|
2
5
|
import { evaluatesOperand, SHELL_WRAPPER_TABLE } from "./shell-wrapper-table.js";
|
|
3
|
-
import { arithmeticHidesRun, carriesSubstitution, freshWord, matchOp, readDoubleQuoted, readExpansion, skipBalanced, skipHeredocBodies } from "./shell-scan.js";
|
|
4
6
|
export { SHELL_WRAPPER_TABLE } from "./shell-wrapper-table.js";
|
|
5
7
|
const WRAPPERS = new Map(Object.entries(SHELL_WRAPPER_TABLE));
|
|
6
8
|
const PREFIX_KEYWORDS = shellWordsOf("stripped_prefix");
|
|
7
9
|
const CLOSING_KEYWORDS = shellWordsOf("stripped_standalone");
|
|
8
10
|
const OPAQUE_KEYWORDS = shellWordsOf("opaque");
|
|
9
|
-
const
|
|
11
|
+
const CONTROL_PREFIXES = shellWordsOf("control_prefix");
|
|
10
12
|
const DURATION_RE = /^\d+(?:\.\d+)?[smhd]?$/;
|
|
11
|
-
const REDIRECT_OPS = ["<<<", "<<-", "<<", "<>", "<&", "<", ">>", ">|", ">&", "&>>", "&>", ">"];
|
|
12
|
-
const CONNECTOR_OPS = ["&&", "||", "|&", "|", ";;&", ";&", ";;", ";", "&"];
|
|
13
|
-
const ALL_OPS = [...CONNECTOR_OPS, ...REDIRECT_OPS];
|
|
14
13
|
export const MAX_SHELL_READ_CHARS = 10_000;
|
|
14
|
+
const LEXED_WORDS = new WeakMap();
|
|
15
|
+
export function lexedWordOf(w) {
|
|
16
|
+
return LEXED_WORDS.get(w);
|
|
17
|
+
}
|
|
18
|
+
function wordOf(w) {
|
|
19
|
+
let expands = false;
|
|
20
|
+
const widen = (to) => { if (expands !== "many")
|
|
21
|
+
expands = to; };
|
|
22
|
+
let text = "";
|
|
23
|
+
for (const p of w.pieces) {
|
|
24
|
+
const e = p.expansion;
|
|
25
|
+
if (p.quotedBy === "$'" || (e === undefined && p.literal === undefined)) {
|
|
26
|
+
widen("many");
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (e === undefined) {
|
|
30
|
+
text += p.literal ?? "";
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (e.kind === "tilde") {
|
|
34
|
+
if (p === w.pieces[0])
|
|
35
|
+
widen(e.yields);
|
|
36
|
+
text += e.body ?? "";
|
|
37
|
+
}
|
|
38
|
+
else if (e.kind === "brace") {
|
|
39
|
+
widen("many");
|
|
40
|
+
text += `{${e.body ?? ""}}`;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
widen(e.yields);
|
|
44
|
+
text += e.pattern?.folded ?? "";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const out = { text, raw: w.raw, expands };
|
|
48
|
+
LEXED_WORDS.set(out, w);
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
const literalIn = (w, set) => w !== undefined && w.expands === false && set.has(w.text);
|
|
52
|
+
const bareAssignment = (w) => lexedWordOf(w)?.assignment?.bare === true;
|
|
53
|
+
const hidesRun = (w) => w.expansions.some((p) => p.expansion !== undefined && expansionHidesRun(p.expansion)) || (w.assignment?.subscript !== undefined && !arithmeticTextIsPlain(w.assignment.subscript.raw)) || compoundValueHidesRun(w);
|
|
54
|
+
const CASE_ARM_UNREAD = "a `case` arm terminator outside a `case` this lane reads";
|
|
55
|
+
const EXTGLOB_UNREAD = "an extended pattern (`?(…)` `*(…)` `+(…)` `@(…)` `!(…)`) is one this reader does not expand";
|
|
15
56
|
export function readShellCommand(source) {
|
|
16
57
|
if (source.length > MAX_SHELL_READ_CHARS)
|
|
17
58
|
return { segments: [{ argv: [], redirections: [], peeled: [], unreadable: `the command is longer than ${MAX_SHELL_READ_CHARS} characters — not read` }], connectors: [], grouped: false, backgrounded: false, strayRedirection: false };
|
|
59
|
+
const lexed = lexShell(source);
|
|
60
|
+
const failed = "parseFailure" in lexed ? lexed : undefined;
|
|
61
|
+
const read = "parseFailure" in lexed ? lexed.readBefore : lexed.segments;
|
|
18
62
|
const segments = [];
|
|
19
63
|
const connectors = [];
|
|
20
64
|
let grouped = false;
|
|
21
65
|
let backgrounded = false;
|
|
22
66
|
let strayRedirection = false;
|
|
23
67
|
let seg = { words: [], redirections: [] };
|
|
24
|
-
let word = freshWord();
|
|
25
|
-
let pendingRedirect;
|
|
26
|
-
let heredocs = [];
|
|
27
|
-
let badDelimiter = false;
|
|
28
68
|
let afterBoundary = true;
|
|
29
69
|
let lastConnector;
|
|
30
|
-
let
|
|
31
|
-
const n = source.length;
|
|
32
|
-
let i = 0;
|
|
33
|
-
const endWord = () => {
|
|
34
|
-
if (!word.started)
|
|
35
|
-
return;
|
|
36
|
-
if (word.braceExpands)
|
|
37
|
-
word.expands = "many";
|
|
38
|
-
if (word.substitution)
|
|
39
|
-
seg.substitution = true;
|
|
40
|
-
if (pendingRedirect !== undefined) {
|
|
41
|
-
if (pendingRedirect === "<<" || pendingRedirect === "<<-") {
|
|
42
|
-
if (word.expands !== false)
|
|
43
|
-
badDelimiter = true;
|
|
44
|
-
heredocs.push({ delimiter: word.text, quoted: word.raw !== word.text, dash: pendingRedirect === "<<-" });
|
|
45
|
-
}
|
|
46
|
-
seg.redirections.push({ op: pendingRedirect, target: publicWord(word) });
|
|
47
|
-
pendingRedirect = undefined;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
seg.words.push(word);
|
|
51
|
-
}
|
|
52
|
-
word = freshWord();
|
|
53
|
-
};
|
|
70
|
+
let arithmetic;
|
|
54
71
|
const finishSegment = (connector) => {
|
|
55
|
-
endWord();
|
|
56
|
-
if (pendingRedirect !== undefined) {
|
|
57
|
-
seg.unreadable ??= `the redirection \`${pendingRedirect}\` has no operand — a syntax error`;
|
|
58
|
-
pendingRedirect = undefined;
|
|
59
|
-
}
|
|
60
72
|
let built = buildSegment(seg);
|
|
61
73
|
if (built === "keyworded")
|
|
62
74
|
grouped = true;
|
|
@@ -86,214 +98,121 @@ export function readShellCommand(source) {
|
|
|
86
98
|
afterBoundary = false;
|
|
87
99
|
};
|
|
88
100
|
const boundary = () => { finishSegment(undefined); grouped = true; afterBoundary = true; lastConnector = undefined; };
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
break;
|
|
101
|
+
const word = (w) => { if (hidesRun(w))
|
|
102
|
+
seg.substitution = true; return wordOf(w); };
|
|
103
|
+
const readTokens = (s) => {
|
|
104
|
+
const tokens = s.tokens;
|
|
105
|
+
const shown = tokens.flatMap((t) => (t.kind === "word" ? t.expansions : t.kind === "redirect" ? t.target?.expansions ?? [] : [])).filter((p) => p.expansion?.kind === "command" || p.expansion?.kind === "process" || p.expansion?.kind === "arithmetic").length;
|
|
106
|
+
if (s.substitutions.length > shown)
|
|
107
|
+
seg.substitution = true;
|
|
108
|
+
let definitionClose = false;
|
|
109
|
+
tokens.forEach((t, i) => {
|
|
110
|
+
if (arithmetic !== undefined) {
|
|
111
|
+
if (t.kind === "group") {
|
|
112
|
+
arithmetic.depth += t.op === "(" ? 1 : -1;
|
|
113
|
+
if (arithmetic.depth === 0)
|
|
114
|
+
arithmetic = undefined;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const w = t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined;
|
|
118
|
+
if (w === undefined || hidesRun(w) || !arithmeticTextIsPlain(w.raw) || t.kind === "heredoc")
|
|
119
|
+
seg.substitution = true;
|
|
120
|
+
if (t.kind === "redirect" && t.dup !== undefined && !arithmeticTextIsPlain(t.dup))
|
|
121
|
+
seg.substitution = true;
|
|
122
|
+
return;
|
|
112
123
|
}
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (ch === "#" && !word.started) {
|
|
120
|
-
while (i < n && source[i] !== "\n")
|
|
121
|
-
i++;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (ch === "\\") {
|
|
125
|
-
if (i + 1 >= n) {
|
|
126
|
-
tail = "a trailing backslash — the line never ends";
|
|
127
|
-
break;
|
|
124
|
+
if (t.kind === "word") {
|
|
125
|
+
if (!namesRedirectDescriptor(t, tokens[i + 1]))
|
|
126
|
+
seg.words.push(word(t));
|
|
127
|
+
return;
|
|
128
128
|
}
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
if (t.kind === "redirect") {
|
|
130
|
+
const target = t.target !== undefined ? word(t.target) : { text: t.dup ?? "", raw: t.dup ?? "", expands: false };
|
|
131
|
+
seg.redirections.push({ op: t.op, target });
|
|
132
|
+
return;
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
if (ch === "'") {
|
|
140
|
-
const close = source.indexOf("'", i + 1);
|
|
141
|
-
if (close < 0) {
|
|
142
|
-
tail = "an unterminated single quote";
|
|
143
|
-
break;
|
|
134
|
+
if (t.kind === "heredoc") {
|
|
135
|
+
seg.redirections.push({ op: "<<", target: { text: t.delimiter, raw: t.delimiter, expands: false } });
|
|
136
|
+
if (!t.quoted && heredocBodyHidesRun(t.body))
|
|
137
|
+
seg.substitution = true;
|
|
138
|
+
return;
|
|
144
139
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
let dq = i + 1;
|
|
152
|
-
if (ch === "$")
|
|
153
|
-
while (source[dq] === "\\" && source[dq + 1] === "\n")
|
|
154
|
-
dq += 2;
|
|
155
|
-
if (ch === '"' || (ch === "$" && source[dq] === '"')) {
|
|
156
|
-
const open = ch === "$" ? dq + 1 : i + 1;
|
|
157
|
-
const end = readDoubleQuoted(source, open, word);
|
|
158
|
-
if (end === undefined) {
|
|
159
|
-
tail = "an unterminated double quote";
|
|
160
|
-
break;
|
|
140
|
+
const next = tokens[i + 1];
|
|
141
|
+
if (definitionClose) {
|
|
142
|
+
definitionClose = false;
|
|
143
|
+
return;
|
|
161
144
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
if (ch === "$" || ch === "`" || ((ch === "<" || ch === ">") && source[i + 1] === "(")) {
|
|
167
|
-
const end = readExpansion(source, i, word);
|
|
168
|
-
if (end === undefined) {
|
|
169
|
-
tail = "an unterminated substitution";
|
|
170
|
-
break;
|
|
145
|
+
if (t.op === "(" && next?.kind === "group" && next.op === "(" && seg.redirections.length === 0 && seg.words.every((w) => literalIn(w, CONTROL_PREFIXES) || literalIn(w, CLOSING_KEYWORDS) || (w.expands === false && w.text === "-p"))) {
|
|
146
|
+
arithmetic = { depth: 1 };
|
|
147
|
+
return;
|
|
171
148
|
}
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
word.raw += ch;
|
|
176
|
-
i = end;
|
|
177
|
-
continue;
|
|
149
|
+
if (t.op === "(" && next?.kind === "group" && next.op === ")" && seg.words.length === 1 && seg.words[0].expands === false && seg.redirections.length === 0) {
|
|
150
|
+
seg.words.pop();
|
|
151
|
+
definitionClose = true;
|
|
178
152
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (op === ";;" || op === ";&" || op === ";;&") {
|
|
191
|
-
endWord();
|
|
192
|
-
seg.unreadable ??= "a `case` arm terminator outside a `case` this lane reads";
|
|
193
|
-
i += op.length;
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (op === "&")
|
|
197
|
-
backgrounded = true;
|
|
198
|
-
finishSegment(op);
|
|
199
|
-
i += op.length;
|
|
153
|
+
boundary();
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
for (let k = 0; k < read.length; k++) {
|
|
157
|
+
const s = read[k];
|
|
158
|
+
if (k > 0) {
|
|
159
|
+
const armEnd = caseArmEnd(read, k);
|
|
160
|
+
if (armEnd !== undefined) {
|
|
161
|
+
seg.unreadable ??= CASE_ARM_UNREAD;
|
|
162
|
+
k = armEnd;
|
|
163
|
+
readTokens(read[k]);
|
|
200
164
|
continue;
|
|
201
165
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (pendingRedirect !== undefined)
|
|
207
|
-
seg.unreadable ??= `the redirection \`${pendingRedirect}\` has no operand — a syntax error`;
|
|
208
|
-
pendingRedirect = op;
|
|
209
|
-
i += op.length;
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (ch === "(" && word.started && "@?*+!".includes(word.raw.slice(-1)) && word.raw !== "!") {
|
|
213
|
-
const end = skipBalanced(source, i, "(", ")");
|
|
214
|
-
if (end === undefined) {
|
|
215
|
-
tail = "an unterminated extended pattern (`@(…)`)";
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
if (carriesSubstitution(source.slice(i + 1, end - 1)))
|
|
219
|
-
word.substitution = true;
|
|
220
|
-
word.expands = "many";
|
|
221
|
-
word.text += source.slice(i, end);
|
|
222
|
-
word.raw += source.slice(i, end);
|
|
223
|
-
i = end;
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
if (ch === "(" && source[i + 1] === "(" && (!word.started || word.raw === "!") && pendingRedirect === undefined && seg.words.every((w) => w.expands === false && (shellWordsOf("control_prefix").has(w.text) || CLOSING_KEYWORDS.has(w.text) || w.text === "-p"))) {
|
|
227
|
-
endWord();
|
|
228
|
-
const end = skipBalanced(source, i, "(", ")");
|
|
229
|
-
if (end === undefined) {
|
|
230
|
-
tail = "an unterminated arithmetic command";
|
|
231
|
-
break;
|
|
166
|
+
const connector = s.pipesStderr === true ? "|&" : s.connector;
|
|
167
|
+
if (arithmetic !== undefined) {
|
|
168
|
+
if (connector === ";")
|
|
169
|
+
seg.substitution = true;
|
|
232
170
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
if (ch === "(" || ch === ")") {
|
|
239
|
-
if (ch === "(" && word.started && !word.expands && seg.words.length === 0 && pendingRedirect === undefined) {
|
|
240
|
-
let j = i + 1;
|
|
241
|
-
while (j < n && (source[j] === " " || source[j] === "\t"))
|
|
242
|
-
j++;
|
|
243
|
-
if (source[j] === ")") {
|
|
244
|
-
word = freshWord();
|
|
245
|
-
boundary();
|
|
246
|
-
i = j + 1;
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
171
|
+
else {
|
|
172
|
+
if (connector === "&")
|
|
173
|
+
backgrounded = true;
|
|
174
|
+
finishSegment(connector);
|
|
249
175
|
}
|
|
250
|
-
boundary();
|
|
251
|
-
i++;
|
|
252
|
-
continue;
|
|
253
176
|
}
|
|
254
|
-
|
|
255
|
-
word.expands = "many";
|
|
256
|
-
if (ch === "[")
|
|
257
|
-
word.bracketOpen = true;
|
|
258
|
-
if (ch === "{")
|
|
259
|
-
word.braceOpen = true;
|
|
260
|
-
if (word.braceOpen && (ch === "," || (ch === "." && source[i + 1] === ".")))
|
|
261
|
-
word.braceExpands = true;
|
|
262
|
-
if (ch === "}" && !word.braceExpands)
|
|
263
|
-
word.braceOpen = false;
|
|
264
|
-
word.started = true;
|
|
265
|
-
word.text += ch;
|
|
266
|
-
word.raw += ch;
|
|
267
|
-
i++;
|
|
177
|
+
readTokens(s);
|
|
268
178
|
}
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
179
|
+
if (failed !== undefined) {
|
|
180
|
+
seg.unreadable ??= PARSE_FAILURE_SENTENCE[failed.parseFailure.reason];
|
|
181
|
+
}
|
|
182
|
+
else if (arithmetic !== undefined) {
|
|
183
|
+
seg.unreadable ??= "an unterminated arithmetic command";
|
|
273
184
|
}
|
|
274
185
|
else {
|
|
275
|
-
|
|
276
|
-
seg.unreadable ??= "a here-document with no body";
|
|
277
|
-
const dangling = seg.words.length === 0 && !word.started && lastConnector !== undefined && lastConnector !== ";" && lastConnector !== "&" && lastConnector !== "\n";
|
|
186
|
+
const dangling = seg.words.length === 0 && seg.redirections.length === 0 && lastConnector !== undefined && lastConnector !== ";" && lastConnector !== "&" && lastConnector !== "\n";
|
|
278
187
|
if (dangling)
|
|
279
188
|
seg.unreadable ??= `the connector \`${lastConnector}\` has no command after it — a syntax error`;
|
|
280
|
-
finishSegment(undefined);
|
|
281
189
|
}
|
|
190
|
+
finishSegment(undefined);
|
|
191
|
+
if (failed === undefined && "segments" in lexed && extglobOpenerIn(lexed, source))
|
|
192
|
+
return { segments: segments.map((s) => (s.unreadable !== undefined ? s : { ...s, peelUnreadable: s.peelUnreadable ?? EXTGLOB_UNREAD })), connectors, grouped, backgrounded, strayRedirection };
|
|
282
193
|
return { segments, connectors, grouped, backgrounded, strayRedirection };
|
|
283
194
|
}
|
|
284
|
-
|
|
195
|
+
function caseArmEnd(read, k) {
|
|
196
|
+
const arm = (j) => read[j].connector === ";" && read[j].text === "" && read[j].tokens.length === 0;
|
|
197
|
+
const next = read[k + 1];
|
|
198
|
+
if (!arm(k) || next === undefined || (next.connector !== ";" && next.connector !== "&"))
|
|
199
|
+
return undefined;
|
|
200
|
+
const third = read[k + 2];
|
|
201
|
+
return next.connector === ";" && arm(k + 1) && third?.connector === "&" ? k + 2 : k + 1;
|
|
202
|
+
}
|
|
285
203
|
const SUBSTITUTION_UNREAD = "a command substitution runs a program this reader does not read";
|
|
286
204
|
const MAX_WRAPPER_LAYERS = 8;
|
|
287
205
|
export function isFullyReadable(shape) {
|
|
288
206
|
return shape.segments.every((s) => s.unreadable === undefined && s.peelUnreadable === undefined && [s.argv, ...s.peeled].every((argv) => argv[0]?.expands === false));
|
|
289
207
|
}
|
|
208
|
+
const operandsOf = (run) => run.map((w) => ({ text: w.text, raw: w.raw, assignment: lexedWordOf(w)?.assignment }));
|
|
290
209
|
function buildSegment(seg) {
|
|
291
210
|
if (seg.unreadable !== undefined)
|
|
292
|
-
return { argv: seg.words
|
|
211
|
+
return { argv: seg.words, redirections: seg.redirections, peeled: [], unreadable: seg.unreadable };
|
|
293
212
|
const carried = () => (seg.substitution === true ? { argv: [], redirections: seg.redirections, peeled: [], peelUnreadable: SUBSTITUTION_UNREAD } : keyworded ? "keyworded" : "empty");
|
|
294
213
|
let keyworded = false;
|
|
295
214
|
let k0 = 0;
|
|
296
|
-
while (k0 < seg.words.length && seg.words[k0]
|
|
215
|
+
while (k0 < seg.words.length && (literalIn(seg.words[k0], PREFIX_KEYWORDS) || literalIn(seg.words[k0], CLOSING_KEYWORDS))) {
|
|
297
216
|
k0++;
|
|
298
217
|
keyworded = true;
|
|
299
218
|
}
|
|
@@ -301,24 +220,24 @@ function buildSegment(seg) {
|
|
|
301
220
|
const head = words[0];
|
|
302
221
|
if (head === undefined)
|
|
303
222
|
return carried();
|
|
304
|
-
if (evaluatesOperand(words))
|
|
223
|
+
if (evaluatesOperand(operandsOf(words)))
|
|
305
224
|
seg.substitution = true;
|
|
306
225
|
if (head.expands === false && (head.text === "[[" || head.text === "["))
|
|
307
226
|
return carried();
|
|
308
|
-
if (head
|
|
309
|
-
return { argv: words
|
|
310
|
-
const argv = words
|
|
227
|
+
if (literalIn(head, OPAQUE_KEYWORDS))
|
|
228
|
+
return { argv: words, redirections: seg.redirections, peeled: [], unreadable: `a \`${head.text}\` control structure — its commands need a syntax tree` };
|
|
229
|
+
const argv = words;
|
|
311
230
|
const peeled = [];
|
|
312
231
|
let peelUnreadable;
|
|
313
232
|
let k = 0;
|
|
314
|
-
while (k < argv.length &&
|
|
233
|
+
while (k < argv.length && bareAssignment(argv[k]))
|
|
315
234
|
k++;
|
|
316
235
|
let run = argv.slice(k);
|
|
317
236
|
if (k > 0 && run.length > 0)
|
|
318
237
|
peeled.push(run);
|
|
319
238
|
for (let layer = 0; run.length > 0; layer++) {
|
|
320
239
|
let j = 0;
|
|
321
|
-
while (j < run.length && ((run[j].text === "!" && run[j].expands === false) ||
|
|
240
|
+
while (j < run.length && ((run[j].text === "!" && run[j].expands === false) || bareAssignment(run[j])))
|
|
322
241
|
j++;
|
|
323
242
|
if (j > 0) {
|
|
324
243
|
run = run.slice(j);
|
|
@@ -344,7 +263,7 @@ function buildSegment(seg) {
|
|
|
344
263
|
run = next;
|
|
345
264
|
peeled.push(run);
|
|
346
265
|
}
|
|
347
|
-
if (seg.substitution === true || [argv, ...peeled].some((c) => c[0]?.expands === false && evaluatesOperand(c)))
|
|
266
|
+
if (seg.substitution === true || [argv, ...peeled].some((c) => c[0]?.expands === false && evaluatesOperand(operandsOf(c))))
|
|
348
267
|
peelUnreadable ??= SUBSTITUTION_UNREAD;
|
|
349
268
|
return { argv, redirections: seg.redirections, peeled, ...(peelUnreadable !== undefined ? { peelUnreadable } : {}) };
|
|
350
269
|
}
|
|
@@ -364,6 +283,7 @@ function unwrap(words) {
|
|
|
364
283
|
const flags = new Set(row.flags);
|
|
365
284
|
const attached = new Set(row.attached ?? []);
|
|
366
285
|
const commandString = new Set(row.commandString);
|
|
286
|
+
const assignmentOperand = (w) => row.assignments === true && w.expands === false && isLauncherAssignmentOperand(w.text);
|
|
367
287
|
let positionalTaken = row.positional === undefined;
|
|
368
288
|
let durationTaken = row.duration !== true;
|
|
369
289
|
let replaceToken;
|
|
@@ -405,7 +325,7 @@ function unwrap(words) {
|
|
|
405
325
|
return r;
|
|
406
326
|
i++;
|
|
407
327
|
}
|
|
408
|
-
while (
|
|
328
|
+
while (i < words.length && assignmentOperand(words[i]))
|
|
409
329
|
i++;
|
|
410
330
|
break;
|
|
411
331
|
}
|
|
@@ -464,7 +384,7 @@ function unwrap(words) {
|
|
|
464
384
|
i++;
|
|
465
385
|
continue;
|
|
466
386
|
}
|
|
467
|
-
if (
|
|
387
|
+
if (assignmentOperand(w)) {
|
|
468
388
|
i++;
|
|
469
389
|
continue;
|
|
470
390
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { AssignmentFact } from "../shell/lex.js";
|
|
1
2
|
/**
|
|
2
|
-
* The wrapper programs the tightening reader ({@link import("./shell-
|
|
3
|
+
* The wrapper programs the tightening reader ({@link import("./shell-run-candidates.js").readShellCommand}) peels to
|
|
3
4
|
* reach the program they run — upstream's three tables (its light strip on every arm, its full strip on
|
|
4
5
|
* the deny/ask arms, and its `xargs` clause inlined in the matcher) as ONE closed table. Peeling is
|
|
5
6
|
* ADDITIVE on the reading side: the segment keeps its spelled argv as one run candidate and adds one
|
|
@@ -395,13 +396,14 @@ export interface ShellWrapperRow {
|
|
|
395
396
|
* subscript (`arr[a]`, quoted, bare, attached `-varr[a]`, or `arr[a]=1`) is evaluated as arithmetic —
|
|
396
397
|
* recursively, like `$((a))` — when the builtin reads or writes it (`printf -v 'arr[a]' …`, `unset arr[a]`,
|
|
397
398
|
* `declare arr[a]=1`, `read arr[a]`, `[[ -v arr[a] ]]`, `test -v arr[a]`), and so is the value of an
|
|
398
|
-
* INTEGER-attribute assignment (`declare -i x=a`). The
|
|
399
|
+
* INTEGER-attribute assignment (`declare -i x=a`). The tightening reader marks such a run's hidden run at every peel
|
|
399
400
|
* candidate (`command printf -v …`). An operand that is WHOLLY an expansion is not judged here (`unset "$X"`
|
|
400
401
|
* — one more indirection; the price of reading it would be a prompt on every `export FOO="$BAR"`), nor is
|
|
401
402
|
* an arithmetic comparison's operand (`[[ $n -gt 0 ]]` evaluates `$n` — the same price). */
|
|
402
403
|
export declare const VARIABLE_OPERAND_BUILTINS: ReadonlySet<string>;
|
|
403
|
-
/** Does this run of a variable-operand builtin evaluate an operand arithmetically?
|
|
404
|
+
/** Does this run of a variable-operand builtin evaluate an operand arithmetically? `assignment`: the lexer's fact on the word. */
|
|
404
405
|
export declare function evaluatesOperand(run: readonly {
|
|
405
406
|
readonly text: string;
|
|
406
407
|
readonly raw: string;
|
|
408
|
+
readonly assignment?: AssignmentFact | undefined;
|
|
407
409
|
}[]): boolean;
|
|
@@ -67,7 +67,7 @@ export function evaluatesOperand(run) {
|
|
|
67
67
|
if (!VARIABLE_OPERAND_BUILTINS.has(run[0]?.text ?? ""))
|
|
68
68
|
return false;
|
|
69
69
|
const integer = run.some((w) => /^-[A-Za-z]*i/.test(w.text));
|
|
70
|
-
return run.some((w) => namesArrayElement(w.text) || namesArrayElement(w.raw) || (integer &&
|
|
70
|
+
return run.some((w) => namesArrayElement(w.text) || namesArrayElement(w.raw) || (integer && w.assignment !== undefined && /[A-Za-z_$]/.test(w.assignment.value.folded)));
|
|
71
71
|
}
|
|
72
72
|
function namesArrayElement(word) {
|
|
73
73
|
for (let open = word.indexOf("["); open > 0; open = word.indexOf("[", open + 1)) {
|
|
@@ -308,7 +308,7 @@ export async function checkpointStoreContract(make, runAssertion) {
|
|
|
308
308
|
const stored = (await store.get(bitless.token)).pendingAction;
|
|
309
309
|
assert.equal("hasBidiControls" in stored, false, "projection backfill must not write back to the row");
|
|
310
310
|
});
|
|
311
|
-
run("
|
|
311
|
+
run("the approval object's members: a row's identity + face group and its targets (capabilityId/contractId/shapeDigest/implementationRevision + family/pathTarget/names + targets) survive the round-trip verbatim; an absent group stays absent", async () => {
|
|
312
312
|
const store = make();
|
|
313
313
|
const carried = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "id-carried" });
|
|
314
314
|
const group = {
|
|
@@ -316,10 +316,10 @@ export async function checkpointStoreContract(make, runAssertion) {
|
|
|
316
316
|
contractId: "acme.write@2",
|
|
317
317
|
shapeDigest: "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
318
318
|
implementationRevision: "r7",
|
|
319
|
-
rosterDigest: "sha256:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
|
|
320
319
|
family: "file-write",
|
|
321
320
|
pathTarget: { param: "destination", aliases: ["path"], access: "create", skillScopeEligible: true },
|
|
322
321
|
names: ["AcmeWrite", "acme_write"],
|
|
322
|
+
targets: { protective: "/srv/work/out.txt", declared: "/srv/work/out.txt" },
|
|
323
323
|
};
|
|
324
324
|
Object.assign(carried.pendingAction, group);
|
|
325
325
|
const bare = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "id-bare" });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type SessionStore } from "../session.js";
|
|
2
|
+
import { type ContractAssertionRunner } from "./contract-harness.js";
|
|
3
|
+
import { type SessionStoreIncarnationContractHooks } from "./session-store-incarnation-contract.js";
|
|
4
|
+
/**
|
|
5
|
+
* The cross-backend contract for {@link SessionStore} as the interface states it TODAY — what a store must
|
|
6
|
+
* do so that the engine, a deployment's authorizer and a shell read the same answers off it whether the
|
|
7
|
+
* store is the bundled `TtlSessionStore` (over an in-memory or file repo) or a backend that implements the
|
|
8
|
+
* interface directly (a SQL store). vitest-free (see `contract-harness.ts`); every case builds a FRESH store
|
|
9
|
+
* through `make()` and disposes it.
|
|
10
|
+
*
|
|
11
|
+
* Each case pins one sentence of the interface (`core/session.ts`) or of the session-tree storage it hands
|
|
12
|
+
* out (`SessionStorage`, whose path / leaf / write-CAS rules every store's sessions carry). Optional faces
|
|
13
|
+
* are judged only when present; a group whose face is absent passes WITHOUT asserting and says so through
|
|
14
|
+
* `onOptionalMember` — a case name cannot carry "absent" (the default runner drops names, and a green
|
|
15
|
+
* absent run is byte-identical to a green verified one), so a consumer that relies on a face asserts the
|
|
16
|
+
* report, never the green.
|
|
17
|
+
*
|
|
18
|
+
* What the kit cannot see from outside, it does not claim: idle eviction and `pin` (the interface has no
|
|
19
|
+
* clock seam), what `release` does to an ordinary session's live set (`size` is best-effort and a durable
|
|
20
|
+
* `list` may still show the history), a durable backend's crash recovery, and a delete that fails half-way.
|
|
21
|
+
* One stated obligation is not a case yet because the bundled store does not keep it: an ownership row
|
|
22
|
+
* outliving its session across a restart (`TtlSessionStore` holds owners in process memory).
|
|
23
|
+
*
|
|
24
|
+
* Semantics since @sema-agent/core 7.55.0.
|
|
25
|
+
*/
|
|
26
|
+
export interface SessionStoreContractHooks {
|
|
27
|
+
/** The same hook as the incarnation kit's ({@link SessionStoreIncarnationContractHooks.deleteSession}): one
|
|
28
|
+
* function serves both kits. */
|
|
29
|
+
deleteSession: SessionStoreIncarnationContractHooks["deleteSession"];
|
|
30
|
+
/** A SECOND, independent store instance over the same rows as `store` — what a restarted process
|
|
31
|
+
* constructs. After calling it the kit no longer uses `store`, so a backend that holds an exclusive lock
|
|
32
|
+
* may release `store`'s first (the kit still disposes `store` at the end of the case: that dispose must
|
|
33
|
+
* then be harmless). The kit disposes the returned instance. */
|
|
34
|
+
reopen: (store: SessionStore) => SessionStore | Promise<SessionStore>;
|
|
35
|
+
/** A second LIVE instance over the same rows, used CONCURRENTLY with `store` — what another replica
|
|
36
|
+
* constructs. Absent ⇒ the replica case is reported absent (a backend that is single-process by
|
|
37
|
+
* contract has no replica). The kit disposes it. */
|
|
38
|
+
replica?: (store: SessionStore) => SessionStore;
|
|
39
|
+
runAssertion?: ContractAssertionRunner;
|
|
40
|
+
/** Optional faces (plus the replica hook, and `requireExisting` — absent = the store rejects the option, its other
|
|
41
|
+
* legal mode) a run verified or found absent; reported per judging case, after its assertions held. */
|
|
42
|
+
onOptionalMember?: (report: {
|
|
43
|
+
member: "requireExisting" | "forget" | "fork" | "list" | "noteTaskRun" | "listPlaced" | "placements" | "ownership" | "incarnationOf" | "replica";
|
|
44
|
+
status: "verified" | "absent";
|
|
45
|
+
}) => void;
|
|
46
|
+
}
|
|
47
|
+
/** A hooks record the kit cannot run with is refused SYNCHRONOUSLY, before any case is registered, so a
|
|
48
|
+
* `void sessionStoreContract(…)` registration fails at collection instead of as an unhandled rejection. */
|
|
49
|
+
export declare function sessionStoreContract(make: () => SessionStore, hooks: SessionStoreContractHooks): Promise<void>;
|