@sema-agent/core 7.10.0 → 7.11.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 +90 -0
- package/dist/agents/child-model-seat.d.ts +45 -18
- package/dist/agents/child-model-seat.js +12 -8
- package/dist/agents/subagent.js +6 -5
- package/dist/agents/teacher.js +2 -2
- package/dist/core/auto-mode-defaults.d.ts +19 -3
- package/dist/core/auto-mode-defaults.js +1 -0
- package/dist/core/auto-mode.d.ts +24 -20
- package/dist/core/auto-mode.js +12 -12
- package/dist/core/gate-fold.js +1 -0
- package/dist/core/gate-lanes.d.ts +6 -1
- package/dist/core/gate-lanes.js +45 -18
- package/dist/core/governance-codes.d.ts +1 -0
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +13 -0
- package/dist/core/permission-rule-model.d.ts +5 -3
- package/dist/core/permission-rule-model.js +7 -3
- package/dist/core/persisted-rule-arms.js +4 -3
- package/dist/core/read-only-shell-table.d.ts +87 -0
- package/dist/core/read-only-shell-table.js +485 -0
- package/dist/core/read-only-shell.d.ts +42 -0
- package/dist/core/read-only-shell.js +316 -0
- package/dist/core/roles.d.ts +3 -2
- package/dist/core/runner/contracts.d.ts +70 -0
- package/dist/core/runner/gate-exit.d.ts +5 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +38 -12
- package/dist/core/runner/prepare-gate-stations.js +9 -0
- package/dist/core/runner/prepare-task.js +1 -1
- package/dist/core/runner/prepare-turn-wiring.js +1 -1
- package/dist/core/runner/runtask.js +34 -510
- package/dist/core/runner/stream-halt-verbs.d.ts +38 -0
- package/dist/core/runner/stream-halt-verbs.js +82 -0
- package/dist/core/runner/stream-lifecycle-verbs.d.ts +34 -0
- package/dist/core/runner/stream-lifecycle-verbs.js +126 -0
- package/dist/core/runner/stream-reap.d.ts +30 -0
- package/dist/core/runner/stream-reap.js +40 -0
- package/dist/core/runner/stream-settle-backstop.d.ts +38 -0
- package/dist/core/runner/stream-settle-backstop.js +113 -0
- package/dist/core/runner/stream-steer-verb.d.ts +30 -0
- package/dist/core/runner/stream-steer-verb.js +185 -0
- package/dist/core/shell-lexer.d.ts +18 -0
- package/dist/core/shell-lexer.js +17 -10
- package/dist/core/shell-wrapper-table.js +8 -5
- package/dist/core/tool-policy.d.ts +4 -1
- package/dist/core/tool-policy.js +1 -1
- package/dist/core/tools.d.ts +28 -7
- package/dist/core/tools.js +44 -4
- package/dist/core/trace.d.ts +15 -0
- package/dist/engine/harness/agent-harness.d.ts +3 -1
- package/dist/engine/harness/agent-harness.js +1 -1
- package/dist/engine/harness/types.d.ts +4 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/orchestration/run-workflow-tool.d.ts +5 -2
- package/dist/orchestration/run-workflow-tool.js +2 -1
- package/dist/orchestration/workflow-governance.d.ts +3 -2
- package/dist/orchestration/workflow-primitives.d.ts +4 -1
- package/dist/orchestration/workflow-primitives.js +1 -6
- package/dist/orchestration/workflow.d.ts +12 -4
- package/dist/orchestration/workflow.js +24 -7
- package/dist/prompt-assembly/turn-snapshot.d.ts +4 -2
- package/dist/tools/fs/bash-readonly-classifier.d.ts +5 -0
- package/dist/tools/fs/bash-readonly-classifier.js +1 -0
- package/dist/tools/fs/fs-bash.js +3 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +35 -1
package/dist/core/gate-lanes.js
CHANGED
|
@@ -5,6 +5,9 @@ import { inlineUntrusted } from "./untrusted-text.js";
|
|
|
5
5
|
import { isRuleBehavior } from "./permission-rule-model.js";
|
|
6
6
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
7
7
|
import { applyPersistedTightening, disclosedRuleSet } from "./persisted-rule-arms.js";
|
|
8
|
+
import { readOnlyShellVerdict } from "./read-only-shell.js";
|
|
9
|
+
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
10
|
+
import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
8
11
|
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
9
12
|
import { exitGate, traceHookCrash } from "./runner/gate-exit.js";
|
|
10
13
|
function isPlainOwnRecord(x) {
|
|
@@ -144,15 +147,17 @@ export function normalizePersistedRuleHit(hit) {
|
|
|
144
147
|
export function persistedRuleMandateOf(marks) {
|
|
145
148
|
return marks.probeMandated === true
|
|
146
149
|
? "probe_mandate"
|
|
147
|
-
: marks.
|
|
148
|
-
? "
|
|
149
|
-
: marks.
|
|
150
|
-
?
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
:
|
|
150
|
+
: marks.probeUnanswered === true
|
|
151
|
+
? "probe_unanswered"
|
|
152
|
+
: marks.egress === true
|
|
153
|
+
? "tool_marks"
|
|
154
|
+
: marks.shellGated === true
|
|
155
|
+
? marks.irreversibility === "always"
|
|
156
|
+
? "operator_always"
|
|
157
|
+
: undefined
|
|
158
|
+
: marks.irreversibility === "always" || marks.irreversibility === "maybe"
|
|
159
|
+
? "tool_marks"
|
|
160
|
+
: undefined;
|
|
156
161
|
}
|
|
157
162
|
function parkWith(suspendAsk, parkArgs, carry) {
|
|
158
163
|
return suspendAsk(...parkArgs, carry);
|
|
@@ -160,9 +165,11 @@ function parkWith(suspendAsk, parkArgs, carry) {
|
|
|
160
165
|
export async function runGateLanes(pass) {
|
|
161
166
|
const { input, toolName, toolCallId, callSignal, ledger, notifier, screening, adjudicate, resolveAsk, suspendAsk } = pass;
|
|
162
167
|
let orgRealApprovalRequired = false;
|
|
168
|
+
let markedThisRound;
|
|
169
|
+
const markedUnresolvable = () => (markedThisRound ??= input.isMarkedUnresolvable?.(input.event.toolCallId) === true);
|
|
163
170
|
const askOriginFacts = (org, ruleStore) => ({
|
|
164
171
|
contentQuestion: toolName === ASK_USER_QUESTION_TOOL_NAME,
|
|
165
|
-
markedUnresolvable:
|
|
172
|
+
markedUnresolvable: markedUnresolvable(),
|
|
166
173
|
org,
|
|
167
174
|
...(ruleStore !== undefined ? { ruleStore } : {}),
|
|
168
175
|
tightened: pass.tightenedBy,
|
|
@@ -269,6 +276,7 @@ export async function runGateLanes(pass) {
|
|
|
269
276
|
pass.deniedBy = "persisted_rule";
|
|
270
277
|
}
|
|
271
278
|
if (pass.decision.action === "ask") {
|
|
279
|
+
pass.decision = { ...pass.decision };
|
|
272
280
|
if (pass.policyRewrite !== undefined) {
|
|
273
281
|
pass.currentInput = pass.policyRewrite;
|
|
274
282
|
pass.req.args = pass.policyRewrite;
|
|
@@ -278,18 +286,19 @@ export async function runGateLanes(pass) {
|
|
|
278
286
|
shellGated: input.shellGated,
|
|
279
287
|
irreversibility: input.irreversibility,
|
|
280
288
|
probeMandated: pass.decision.action === "ask" && pass.decision.probeMandated === true,
|
|
289
|
+
probeUnanswered: pass.probeUnanswered === true,
|
|
281
290
|
});
|
|
282
291
|
let personalEvidence = { dotsAbsent: input.persistedRules === undefined ? "not_wired" : "not_adjudicated" };
|
|
283
292
|
if (laneAnswer?.unreadable === true)
|
|
284
293
|
personalEvidence = { dotsAbsent: "unavailable" };
|
|
285
294
|
let laneCoverage;
|
|
286
|
-
|
|
287
|
-
!orgRealApprovalRequired &&
|
|
295
|
+
const allowLayerMayClear = () => !orgRealApprovalRequired &&
|
|
288
296
|
pass.decision.action === "ask" &&
|
|
289
297
|
pass.decision.requiresRealApproval !== true &&
|
|
290
298
|
pass.decision.decisionReason !== "hook" &&
|
|
291
299
|
pass.req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
292
|
-
|
|
300
|
+
!markedUnresolvable();
|
|
301
|
+
if (input.persistedRules && allowLayerMayClear()) {
|
|
293
302
|
const answer = laneAnswer ?? {};
|
|
294
303
|
const hitRules = answer.hit?.behavior === "allow" ? answer.hit.rules : undefined;
|
|
295
304
|
laneCoverage = answer.coverage;
|
|
@@ -328,11 +337,13 @@ export async function runGateLanes(pass) {
|
|
|
328
337
|
const shownRule = disclosedRuleSet(hitRules);
|
|
329
338
|
const mandateNoun = persistedRuleMandate === "probe_mandate"
|
|
330
339
|
? "the reversibility check declared this call structurally gated (the built-in shell check raises this for a read outside the directories allowed for this session) — it is cleared by confirming this call, never by a standing allow rule"
|
|
331
|
-
: persistedRuleMandate === "
|
|
332
|
-
? "this
|
|
333
|
-
: persistedRuleMandate
|
|
334
|
-
? "this
|
|
335
|
-
:
|
|
340
|
+
: persistedRuleMandate === "probe_unanswered"
|
|
341
|
+
? "the reversibility check did not answer for this call (it timed out or failed), so whether the call reads outside the directories allowed for this session is unknown — it is cleared by confirming this call, never by a standing allow rule"
|
|
342
|
+
: persistedRuleMandate === "operator_always"
|
|
343
|
+
? "this deployment mandates per-call confirmation for shell commands (shellGate: always)"
|
|
344
|
+
: persistedRuleMandate !== undefined
|
|
345
|
+
? "this tool carries egress/irreversibility marks (a mandated confirmation a rule cannot clear)"
|
|
346
|
+
: "an explicit ask rule matched this call (a person's ask-me-each-time outranks a standing allow rule)";
|
|
336
347
|
pass.decision = {
|
|
337
348
|
...pass.decision,
|
|
338
349
|
persistedRuleShadowed: shownRule,
|
|
@@ -347,6 +358,21 @@ export async function runGateLanes(pass) {
|
|
|
347
358
|
const stamped = { ...pass.decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
|
|
348
359
|
pass.decision = { ...stamped, origin: askOriginOf(stamped, originFacts) };
|
|
349
360
|
}
|
|
361
|
+
if (allowLayerMayClear() && pass.decision.action === "ask" && persistedRuleMandate === undefined && pass.decision.matchedAskRule === undefined && pass.req.toolName === COMMAND_RULE_TOOL) {
|
|
362
|
+
const commandParam = catalogRuleFaceOf(COMMAND_RULE_TOOL)?.primaryParams[0] ?? "command";
|
|
363
|
+
const args = pass.req.args;
|
|
364
|
+
const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
|
|
365
|
+
const backgrounded = typeof args === "object" && args !== null && args.run_in_background === true;
|
|
366
|
+
if (typeof command === "string" && !backgrounded && readOnlyShellVerdict(command).readOnly) {
|
|
367
|
+
pass.decision = {
|
|
368
|
+
action: "allow",
|
|
369
|
+
message: "the command is read-only (upstream's read-only command tables) — allowed without asking",
|
|
370
|
+
decisionReason: "read_only",
|
|
371
|
+
...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}),
|
|
372
|
+
};
|
|
373
|
+
await notifier.notifyAsync(() => input.onReadOnlyAllowed?.({ toolName: pass.req.toolName, toolCallId, command }), "toolGate.readOnlyAllowed");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
350
376
|
if (pass.decision.action === "ask")
|
|
351
377
|
ledger.supersede();
|
|
352
378
|
if (input.autoMode && pass.decision.action === "ask" && pass.decision.origin !== undefined && classifierMayAnswer(pass.decision.origin)) {
|
|
@@ -634,6 +660,7 @@ export async function runGateLanes(pass) {
|
|
|
634
660
|
editRewrittenSinceHuman = true;
|
|
635
661
|
}
|
|
636
662
|
const editAskSnapshot = { ...recheck, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
|
|
663
|
+
markedThisRound = undefined;
|
|
637
664
|
const editAskDecision = { ...editAskSnapshot, origin: askOriginOf(editAskSnapshot, askOriginFacts(editOrg.org, editRuleStore)) };
|
|
638
665
|
const editAskReq = { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
|
|
639
666
|
const rr = await (callSignal !== undefined ? resolveAsk(editAskDecision, editAskReq, callSignal) : resolveAsk(editAskDecision, editAskReq));
|
|
@@ -12,6 +12,7 @@ export declare const GOVERNANCE_CODES: {
|
|
|
12
12
|
readonly "config.compliance_required": "transient";
|
|
13
13
|
readonly "config.compliance_denied": "terminal";
|
|
14
14
|
readonly "config.locked_key": "terminal";
|
|
15
|
+
readonly "config.tool_mount_denied": "terminal";
|
|
15
16
|
readonly "memory.capture_optout_denied": "terminal";
|
|
16
17
|
};
|
|
17
18
|
export type GovernanceCode = keyof typeof GOVERNANCE_CODES;
|
|
@@ -5,6 +5,7 @@ export const GOVERNANCE_CODES = {
|
|
|
5
5
|
"config.compliance_required": "transient",
|
|
6
6
|
"config.compliance_denied": "terminal",
|
|
7
7
|
"config.locked_key": "terminal",
|
|
8
|
+
"config.tool_mount_denied": "terminal",
|
|
8
9
|
"memory.capture_optout_denied": "terminal",
|
|
9
10
|
};
|
|
10
11
|
export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1511,6 +1511,19 @@ export interface ToolGateInput {
|
|
|
1511
1511
|
rules: readonly string[];
|
|
1512
1512
|
}) => void;
|
|
1513
1513
|
};
|
|
1514
|
+
/**
|
|
1515
|
+
* #619 — observation sink for a shell call the READ-ONLY reader cleared (`readOnlyShellVerdict`, the
|
|
1516
|
+
* allow layer's second member beside the persisted allow rule: consulted for the shell tool only, after
|
|
1517
|
+
* the person's allow rules, under the SAME consumption predicate — never over a mandated ask, a hook's
|
|
1518
|
+
* ask, an explicit ask rule, governance or a marked call). The allow-side disclosure of "why did this run
|
|
1519
|
+
* without asking me?" for that arm; `command` is the FINAL command the gate judged (a policy rewrite
|
|
1520
|
+
* included). Never affects the outcome. Absent ⇒ the arm still runs, silently.
|
|
1521
|
+
*/
|
|
1522
|
+
onReadOnlyAllowed?: (info: {
|
|
1523
|
+
toolName: string;
|
|
1524
|
+
toolCallId: string;
|
|
1525
|
+
command: string;
|
|
1526
|
+
}) => void;
|
|
1514
1527
|
/**
|
|
1515
1528
|
* design/182 §7 — the ORG layer. Present ONLY when the deployment DECLARED org governance (the
|
|
1516
1529
|
* overlay constructor is the boot gate: a governed declaration with no snapshot provider refuses to
|
|
@@ -499,7 +499,7 @@ export declare function formatRuleText(command: string, match: PersistedRuleMatc
|
|
|
499
499
|
/**
|
|
500
500
|
* The closed set of BASES a path-form pattern resolves against — facts of the CALL (its execution
|
|
501
501
|
* environment and its task), never of the rule:
|
|
502
|
-
* - `cwd` — a relative pattern (`dist/**`, `./x`): the live tracked working directory; when no tracker
|
|
502
|
+
* - `cwd` — a relative pattern (`dist/**`, `./x`, a bare `x` read at any depth): the live tracked working directory; when no tracker
|
|
503
503
|
* moved (or the caller keeps none) the task root stands in for it — that stand-in is the WORD's
|
|
504
504
|
* own meaning (the same reading `TOOL_PATH_BASES.cwd` gives a tool's relative slot), not a
|
|
505
505
|
* fallback to a different base;
|
|
@@ -544,7 +544,9 @@ export declare function isUsablePathBase(base: string | undefined): boolean;
|
|
|
544
544
|
* does this deny/ask rule reach the call's target path? The target is the caller's ALREADY-RESOLVED
|
|
545
545
|
* lexical-normal absolute path (the same identity the read/write fences judge with). A `subpath` rule
|
|
546
546
|
* reaches the directory and everything under it ({@link directoryRuleAdmits}); a `path` rule resolves its
|
|
547
|
-
* spelling against the call's bases
|
|
547
|
+
* spelling against the call's bases ({@link resolvePathPattern} — a cwd-relative bare name reads at any
|
|
548
|
+
* depth, a trailing `/**` peeled first and, for that peeled form alone, by the rule's BEHAVIOR:
|
|
549
|
+
* `path_rule.bare_name`) and matches segment-wise (`*` within a segment, `**` across
|
|
548
550
|
* segments), reaching the path it names and everything under a directory it names. A command-family rule
|
|
549
551
|
* reaches no path. `unreadable` (#644): the pattern needs a base the call did not supply (or supplied as
|
|
550
552
|
* a non-absolute spelling) — the rule CANNOT be judged, and the answer says which base; a lane that must
|
|
@@ -553,7 +555,7 @@ export declare function isUsablePathBase(base: string | undefined): boolean;
|
|
|
553
555
|
* defect (a `~/` deny falling back to the engine host's home guarded the wrong directory; a `/…` deny with
|
|
554
556
|
* no root reached nothing at all).
|
|
555
557
|
*/
|
|
556
|
-
export declare function pathRuleReachOf(rule: Pick<PersistedRule, "match" | "command">, target: string, bases: PathRuleBases): ProgramRunReachOutcome;
|
|
558
|
+
export declare function pathRuleReachOf(rule: Pick<PersistedRule, "match" | "command" | "behavior">, target: string, bases: PathRuleBases): ProgramRunReachOutcome;
|
|
557
559
|
/**
|
|
558
560
|
* Does this rule's command pattern admit `command`?
|
|
559
561
|
*
|
|
@@ -363,7 +363,7 @@ function baseValueOf(base, bases) {
|
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
|
-
function resolvePathPattern(pattern, bases) {
|
|
366
|
+
function resolvePathPattern(pattern, bases, behavior) {
|
|
367
367
|
if (pattern.startsWith("//"))
|
|
368
368
|
return { resolved: "/" + pattern.slice(2) };
|
|
369
369
|
const needed = ruleBasesNeeded({ match: "path", command: pattern })[0];
|
|
@@ -371,7 +371,11 @@ function resolvePathPattern(pattern, bases) {
|
|
|
371
371
|
if (normal === undefined)
|
|
372
372
|
return { missingBase: needed };
|
|
373
373
|
const rest = pattern.startsWith("~/") ? pattern.slice(2) : pattern.startsWith("/") ? pattern.slice(1) : pattern.startsWith("./") ? pattern.slice(2) : pattern;
|
|
374
|
-
|
|
374
|
+
const peeled = rest.endsWith("/**") ? rest.slice(0, -3) : rest;
|
|
375
|
+
const bare = needed === "cwd" && peeled !== "" && !peeled.includes("/");
|
|
376
|
+
const anyDepth = bare && (peeled === rest || behavior !== "allow");
|
|
377
|
+
const body = anyDepth ? "**/" + peeled : rest;
|
|
378
|
+
return { resolved: (normal === "/" ? "" : normal) + "/" + body };
|
|
375
379
|
}
|
|
376
380
|
export function isUsablePathBase(base) {
|
|
377
381
|
return base !== undefined && lexicalNormalAbsolutePathOf(base) !== undefined;
|
|
@@ -423,7 +427,7 @@ export function pathRuleReachOf(rule, target, bases) {
|
|
|
423
427
|
return NOT_REACHED;
|
|
424
428
|
if (!isLexicalNormalAbsoluteDir(target) && target !== "/")
|
|
425
429
|
return NOT_REACHED;
|
|
426
|
-
const r = resolvePathPattern(rule.command, bases);
|
|
430
|
+
const r = resolvePathPattern(rule.command, bases, rule.behavior);
|
|
427
431
|
if ("missingBase" in r) {
|
|
428
432
|
return { reach: "unreadable", reason: `the rule is relative to ${PATH_RULE_BASE_LABEL[r.missingBase]} and this call supplies no absolute \`${r.missingBase}\` base to resolve it against` };
|
|
429
433
|
}
|
|
@@ -39,11 +39,12 @@ export function applyPersistedTightening(decision, read) {
|
|
|
39
39
|
tightened: "ask",
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
+
const asked = { ...decision };
|
|
42
43
|
return {
|
|
43
44
|
decision: {
|
|
44
|
-
...
|
|
45
|
-
...(
|
|
46
|
-
message: `${
|
|
45
|
+
...asked,
|
|
46
|
+
...(asked.matchedAskRule === undefined ? { matchedAskRule: shown } : {}),
|
|
47
|
+
message: `${asked.message !== undefined ? `${asked.message} ` : ""}(a persisted ask rule (${shown}) also requires approval for this call)`,
|
|
47
48
|
},
|
|
48
49
|
tightened: "ask",
|
|
49
50
|
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The READ-ONLY shell tables — the closed data the read-only reader (`read-only-shell.ts`) judges a
|
|
3
|
+
* command against, transcribed from upstream's own tables (CC 2.1.250) and kept as DATA so the parity
|
|
4
|
+
* question is answered by diffing two tables, never by reading two programs:
|
|
5
|
+
* - {@link READ_ONLY_COMMAND_TABLE} — the flag-vetted commands: a command (one or more leading words,
|
|
6
|
+
* `git status`, `gh pr view`, `grep`) with the closed set of flags it may carry, each flag's value
|
|
7
|
+
* ARITY, and where upstream carries one, its extra danger predicate over the remaining words
|
|
8
|
+
* (upstream `w3t` + `tqe` + `nqe` + `XGn` + `JGn` + `dmt`);
|
|
9
|
+
* - {@link READ_ONLY_BARE_PROGRAMS} — programs read-only under ANY literal arguments (upstream `jpe`);
|
|
10
|
+
* - {@link READ_ONLY_GLOB_PROGRAMS} — the programs a segment carrying a GLOB may still be read-only
|
|
11
|
+
* under (upstream `B3t`; a glob may expand to any name, so only a program that cannot write however
|
|
12
|
+
* it is named is admitted);
|
|
13
|
+
* - {@link READ_ONLY_EXACT_FORMS} / {@link READ_ONLY_BARE_ONLY} — whole-command forms (`node -v`) and
|
|
14
|
+
* programs admitted only bare (`pwd`) (upstream `N3t` / `D3t`);
|
|
15
|
+
* - {@link FIND_ACTION_PRIMARIES} / {@link FIND_VALUE_PRIMARIES} / {@link FIND_NEWER_PRIMARY} — the
|
|
16
|
+
* `find` primaries that ACT (write, execute) and the ones that take a value (upstream `I3t`/`smt`/`amt`);
|
|
17
|
+
* - {@link READ_ONLY_ENV_NAMES} — the environment variables a read-only command may be prefixed with
|
|
18
|
+
* (upstream `nH`: a variable that cannot change what a program DOES);
|
|
19
|
+
* - {@link XARGS_READ_ONLY_TARGETS} — the programs `xargs` may hand its input to (upstream `v3t`).
|
|
20
|
+
*
|
|
21
|
+
* NOT transcribed, each a stated gap rather than a silent one: `sed` (upstream vets the sed SCRIPT with a
|
|
22
|
+
* dedicated analyzer; without it a `sed` is not read-only here — fail-closed), the Windows PowerShell table
|
|
23
|
+
* and every Windows-only arm (UNC paths), and the two filesystem probes upstream runs beside the tables (a
|
|
24
|
+
* bare-repository indicator check and a `.git` redirection check — a shell tool here has no filesystem seat).
|
|
25
|
+
*
|
|
26
|
+
* Every predicate over the remaining words receives the words AFTER the command's own (upstream's `t`),
|
|
27
|
+
* dequoted. Upstream's substitution placeholders never appear here: a word carrying an expansion has
|
|
28
|
+
* already taken the whole command out of the read-only reading before a table is consulted.
|
|
29
|
+
*/
|
|
30
|
+
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
31
|
+
/** How many words a flag's VALUE takes, and what the value must look like (upstream `ni`): `none` — the flag
|
|
32
|
+
* takes no value (`--flag=x` is refused); `number` — one word of digits; `string` — one word, any text
|
|
33
|
+
* (a value that starts with `-` is refused: it is another flag); `char` — one word of length 1; `{}` and
|
|
34
|
+
* `EOF` — one word spelled exactly so (`xargs -I {}`, `xargs -E EOF`). Registered in docs/CLOSED-SETS.md. */
|
|
35
|
+
export declare const READ_ONLY_FLAG_ARITIES: readonly ["none", "number", "string", "char", "{}", "EOF"];
|
|
36
|
+
export type ReadOnlyFlagArity = (typeof READ_ONLY_FLAG_ARITIES)[number];
|
|
37
|
+
/** The disposition table over the arity set: does `value` satisfy the arity? (`none` accepts NO value.) */
|
|
38
|
+
export declare const FLAG_VALUE_ACCEPTS: {
|
|
39
|
+
readonly none: () => boolean;
|
|
40
|
+
readonly number: (value: string) => boolean;
|
|
41
|
+
readonly string: () => boolean;
|
|
42
|
+
readonly char: (value: string) => boolean;
|
|
43
|
+
readonly "{}": (value: string) => boolean;
|
|
44
|
+
readonly EOF: (value: string) => boolean;
|
|
45
|
+
};
|
|
46
|
+
/** Compile-time fence: `never` while every arity has a row. */
|
|
47
|
+
export type FlagArityTableCoversEveryArity = AssertAllKeysHandled<Exclude<ReadOnlyFlagArity, keyof typeof FLAG_VALUE_ACCEPTS>>;
|
|
48
|
+
/** One row of {@link READ_ONLY_COMMAND_TABLE}. */
|
|
49
|
+
export interface ReadOnlyCommandRow {
|
|
50
|
+
/** Flag → the arity of its value. A flag not in the table refuses the command (a short cluster `-abc`
|
|
51
|
+
* is admitted only when every letter is a `none` flag). */
|
|
52
|
+
readonly safeFlags: Readonly<Record<string, ReadOnlyFlagArity>>;
|
|
53
|
+
/** `false`: a `--` word is skipped like any other rather than ending the flag walk (the words after it
|
|
54
|
+
* keep being vetted). Default: `--` ends the walk and the rest are operands. */
|
|
55
|
+
readonly respectsDoubleDash?: false;
|
|
56
|
+
/** Upstream carries a whole-text regex for this row; expressed here over the remaining words. */
|
|
57
|
+
readonly wordsShape?: (args: readonly string[]) => boolean;
|
|
58
|
+
/** Upstream's `additionalCommandIsDangerousCallback` over the remaining words: `true` refuses. */
|
|
59
|
+
readonly dangerous?: (args: readonly string[]) => boolean;
|
|
60
|
+
}
|
|
61
|
+
export declare const dockerRetargetDanger: (args: readonly string[]) => boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The flag-vetted command table (upstream `w3t` and everything it spreads; `sed` withheld — see the module
|
|
64
|
+
* note). Keys with a space are multi-word commands and are matched on the leading words.
|
|
65
|
+
*/
|
|
66
|
+
export declare const READ_ONLY_COMMAND_TABLE: Readonly<Record<string, ReadOnlyCommandRow>>;
|
|
67
|
+
/** Programs read-only under any LITERAL arguments (upstream `jpe`): `cat x`, `wc -l x`, `diff a b`, `sleep 3`…
|
|
68
|
+
* The two-word `docker ps` / `docker images` are matched on both words. */
|
|
69
|
+
export declare const READ_ONLY_BARE_PROGRAMS: readonly ["docker ps", "docker images", "cal", "uptime", "cat", "head", "tail", "wc", "stat", "strings", "hexdump", "od", "nl", "id", "uname", "free", "df", "du", "locale", "groups", "nproc", "basename", "dirname", "realpath", "cut", "paste", "tr", "column", "tac", "rev", "fold", "expand", "unexpand", "fmt", "comm", "cmp", "numfmt", "readlink", "diff", "true", "false", "sleep", "which", "type", "expr", "seq", "tsort", "pr"];
|
|
70
|
+
/** Programs a segment carrying a GLOB may still be read-only under (upstream `B3t`): the glob may name
|
|
71
|
+
* anything, so only a program that writes nothing whatever it is handed. */
|
|
72
|
+
export declare const READ_ONLY_GLOB_PROGRAMS: readonly ["ls", "cat", "head", "tail", "wc", "stat", "grep", "egrep", "fgrep", "diff", "du", "df", "echo", "strings", "hexdump", "od", "nl", "cut", "column", "tr", "tac", "rev", "cmp", "basename", "dirname", "realpath", "readlink", "sha256sum", "sha1sum", "md5sum", "cd"];
|
|
73
|
+
/** Whole-command forms admitted exactly as spelled (upstream `N3t`). */
|
|
74
|
+
export declare const READ_ONLY_EXACT_FORMS: readonly (readonly string[])[];
|
|
75
|
+
/** Programs admitted only BARE (upstream `D3t`: `pwd`, `whoami`, `alias` — with an operand `alias` DEFINES one). */
|
|
76
|
+
export declare const READ_ONLY_BARE_ONLY: readonly ["pwd", "whoami", "alias"];
|
|
77
|
+
/** `find` primaries that ACT — delete, execute, write a file (upstream `I3t`). */
|
|
78
|
+
export declare const FIND_ACTION_PRIMARIES: readonly ["-delete", "-exec", "-execdir", "-ok", "-okdir", "-fprint", "-fprint0", "-fls", "-fprintf", "-files0-from"];
|
|
79
|
+
/** `find` primaries that take ONE value word (the value is skipped, not vetted) (upstream `smt`). */
|
|
80
|
+
export declare const FIND_VALUE_PRIMARIES: readonly ["-name", "-iname", "-path", "-ipath", "-lname", "-ilname", "-regex", "-iregex", "-wholename", "-iwholename", "-samefile", "-newer", "-anewer", "-cnewer", "-mnewer", "-perm", "-user", "-group", "-uid", "-gid", "-size", "-type", "-xtype", "-fstype", "-inum", "-links", "-used", "-context", "-amin", "-cmin", "-mmin", "-atime", "-ctime", "-mtime", "-mindepth", "-maxdepth", "-printf", "-regextype", "-D", "-f", "-flags", "-Bnewer", "-Btime", "-Bmin", "-files0-from", "-xattrname"];
|
|
81
|
+
/** The `-newerXY` family, a value primary spelled by pattern (upstream `amt`). */
|
|
82
|
+
export declare const FIND_NEWER_PRIMARY: RegExp;
|
|
83
|
+
/** Environment variables a read-only command may be prefixed with (upstream `nH`): locale, colour, terminal
|
|
84
|
+
* and toolchain switches that change how output LOOKS, never what a program does to the filesystem. */
|
|
85
|
+
export declare const READ_ONLY_ENV_NAMES: readonly ["GOEXPERIMENT", "GOOS", "GOARCH", "CGO_ENABLED", "GO111MODULE", "RUST_BACKTRACE", "RUST_LOG", "NODE_ENV", "PYTHONUNBUFFERED", "PYTHONDONTWRITEBYTECODE", "PYTEST_DISABLE_PLUGIN_AUTOLOAD", "PYTEST_DEBUG", "ANTHROPIC_API_KEY", "LANG", "LANGUAGE", "LC_ALL", "LC_CTYPE", "LC_TIME", "CHARSET", "TERM", "COLORTERM", "NO_COLOR", "FORCE_COLOR", "TZ", "LS_COLORS", "LSCOLORS", "GREP_COLOR", "GREP_COLORS", "GCC_COLORS", "TIME_STYLE", "BLOCK_SIZE", "BLOCKSIZE", "COLUMNS", "LINES", "CLICOLOR", "CLICOLOR_FORCE", "CI", "DEBIAN_FRONTEND", "GIT_TERMINAL_PROMPT"];
|
|
86
|
+
/** The programs `xargs` may hand its input to and stay read-only (upstream `v3t`). */
|
|
87
|
+
export declare const XARGS_READ_ONLY_TARGETS: readonly ["echo", "printf", "wc", "grep", "egrep", "fgrep", "head", "tail"];
|